1mod github_context;
7mod spec;
8
9pub use github_context::GhOutput;
10pub use github_context::GhToRust;
11pub use github_context::RustToGh;
12
13use self::steps::ado::AdoRuntimeVar;
14use self::steps::ado::AdoStepServices;
15use self::steps::github::GhStepBuilder;
16use self::steps::rust::RustRuntimeServices;
17use self::user_facing::ClaimedGhParam;
18use self::user_facing::GhPermission;
19use self::user_facing::GhPermissionValue;
20use crate::node::github_context::GhContextVarReader;
21use github_context::state::Root;
22use serde::Deserialize;
23use serde::Serialize;
24use serde::de::DeserializeOwned;
25use std::cell::RefCell;
26use std::collections::BTreeMap;
27use std::path::PathBuf;
28use std::rc::Rc;
29use user_facing::GhParam;
30
31pub mod user_facing {
34 pub use super::ClaimVar;
35 pub use super::ClaimedReadVar;
36 pub use super::ClaimedWriteVar;
37 pub use super::FlowArch;
38 pub use super::FlowBackend;
39 pub use super::FlowNode;
40 pub use super::FlowPlatform;
41 pub use super::FlowPlatformKind;
42 pub use super::GhUserSecretVar;
43 pub use super::ImportCtx;
44 pub use super::IntoRequest;
45 pub use super::NodeCtx;
46 pub use super::ReadVar;
47 pub use super::SideEffect;
48 pub use super::SimpleFlowNode;
49 pub use super::StepCtx;
50 pub use super::VarClaimed;
51 pub use super::VarEqBacking;
52 pub use super::VarNotClaimed;
53 pub use super::WriteVar;
54 pub use super::steps::ado::AdoResourcesRepositoryId;
55 pub use super::steps::ado::AdoRuntimeVar;
56 pub use super::steps::ado::AdoStepServices;
57 pub use super::steps::github::ClaimedGhParam;
58 pub use super::steps::github::GhParam;
59 pub use super::steps::github::GhPermission;
60 pub use super::steps::github::GhPermissionValue;
61 pub use super::steps::rust::RustRuntimeServices;
62 pub use crate::flowey_request;
63 pub use crate::new_flow_node;
64 pub use crate::new_simple_flow_node;
65 pub use crate::node::FlowPlatformLinuxDistro;
66 pub use crate::pipeline::Artifact;
67
68 pub fn same_across_all_reqs<T: PartialEq>(
100 req_name: &str,
101 var: &mut Option<T>,
102 new: T,
103 ) -> anyhow::Result<()> {
104 match (var.as_ref(), new) {
105 (None, v) => *var = Some(v),
106 (Some(old), new) => {
107 if *old != new {
108 anyhow::bail!("`{}` must be consistent across requests", req_name);
109 }
110 }
111 }
112
113 Ok(())
114 }
115
116 pub fn same_across_all_reqs_backing_var<V: VarEqBacking>(
120 req_name: &str,
121 var: &mut Option<V>,
122 new: V,
123 ) -> anyhow::Result<()> {
124 match (var.as_ref(), new) {
125 (None, v) => *var = Some(v),
126 (Some(old), new) => {
127 if !old.eq(&new) {
128 anyhow::bail!("`{}` must be consistent across requests", req_name);
129 }
130 }
131 }
132
133 Ok(())
134 }
135
136 #[macro_export]
140 macro_rules! match_arch {
141 ($host_arch:expr, $match_arch:pat, $expr:expr) => {
142 if matches!($host_arch, $match_arch) {
143 $expr
144 } else {
145 anyhow::bail!("Linux distro not supported on host arch {}", $host_arch);
146 }
147 };
148 }
149}
150
151pub trait VarEqBacking {
175 fn eq(&self, other: &Self) -> bool;
177}
178
179impl<T> VarEqBacking for WriteVar<T>
180where
181 T: Serialize + DeserializeOwned,
182{
183 fn eq(&self, other: &Self) -> bool {
184 self.backing_var == other.backing_var
185 }
186}
187
188impl<T> VarEqBacking for ReadVar<T>
189where
190 T: Serialize + DeserializeOwned + PartialEq + Eq + Clone,
191{
192 fn eq(&self, other: &Self) -> bool {
193 self.backing_var == other.backing_var
194 }
195}
196
197impl<T, U> VarEqBacking for (T, U)
199where
200 T: VarEqBacking,
201 U: VarEqBacking,
202{
203 fn eq(&self, other: &Self) -> bool {
204 (self.0.eq(&other.0)) && (self.1.eq(&other.1))
205 }
206}
207
208pub type SideEffect = ();
215
216#[derive(Clone, Debug, Serialize, Deserialize)]
219pub enum VarNotClaimed {}
220
221#[derive(Clone, Debug, Serialize, Deserialize)]
224pub enum VarClaimed {}
225
226#[derive(Debug, Serialize, Deserialize)]
246pub struct WriteVar<T: Serialize + DeserializeOwned, C = VarNotClaimed> {
247 backing_var: String,
248 is_side_effect: bool,
251
252 #[serde(skip)]
253 _kind: core::marker::PhantomData<(T, C)>,
254}
255
256pub type ClaimedWriteVar<T> = WriteVar<T, VarClaimed>;
259
260impl<T: Serialize + DeserializeOwned> WriteVar<T, VarNotClaimed> {
261 fn into_claimed(self) -> WriteVar<T, VarClaimed> {
263 let Self {
264 backing_var,
265 is_side_effect,
266 _kind,
267 } = self;
268
269 WriteVar {
270 backing_var,
271 is_side_effect,
272 _kind: std::marker::PhantomData,
273 }
274 }
275
276 #[track_caller]
278 pub fn write_static(self, ctx: &mut NodeCtx<'_>, val: T)
279 where
280 T: 'static,
281 {
282 let val = ReadVar::from_static(val);
283 val.write_into(ctx, self, |v| v);
284 }
285
286 pub(crate) fn into_json(self) -> WriteVar<serde_json::Value> {
287 WriteVar {
288 backing_var: self.backing_var,
289 is_side_effect: self.is_side_effect,
290 _kind: std::marker::PhantomData,
291 }
292 }
293}
294
295impl WriteVar<SideEffect, VarNotClaimed> {
296 pub fn discard_result<T: Serialize + DeserializeOwned>(self) -> WriteVar<T> {
301 WriteVar {
302 backing_var: self.backing_var,
303 is_side_effect: true,
304 _kind: std::marker::PhantomData,
305 }
306 }
307}
308
309pub trait ClaimVar {
317 type Claimed;
319 fn claim(self, ctx: &mut StepCtx<'_>) -> Self::Claimed;
321}
322
323pub trait ReadVarValue {
329 type Value;
331 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value;
333}
334
335impl<T: Serialize + DeserializeOwned> ClaimVar for ReadVar<T> {
336 type Claimed = ClaimedReadVar<T>;
337
338 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedReadVar<T> {
339 if let ReadVarBacking::RuntimeVar {
340 var,
341 is_side_effect: _,
342 } = &self.backing_var
343 {
344 ctx.backend.borrow_mut().on_claimed_runtime_var(var, true);
345 }
346 self.into_claimed()
347 }
348}
349
350impl<T: Serialize + DeserializeOwned> ClaimVar for WriteVar<T> {
351 type Claimed = ClaimedWriteVar<T>;
352
353 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedWriteVar<T> {
354 ctx.backend
355 .borrow_mut()
356 .on_claimed_runtime_var(&self.backing_var, false);
357 self.into_claimed()
358 }
359}
360
361impl<T: Serialize + DeserializeOwned> ReadVarValue for ClaimedReadVar<T> {
362 type Value = T;
363
364 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
365 match self.backing_var {
366 ReadVarBacking::RuntimeVar {
367 var,
368 is_side_effect,
369 } => {
370 let data = rt.get_var(&var, is_side_effect);
372 if is_side_effect {
373 serde_json::from_slice(b"null").expect("should be deserializing into ()")
377 } else {
378 serde_json::from_slice(&data).expect("improve this error path")
380 }
381 }
382 ReadVarBacking::Inline(val) => val,
383 }
384 }
385}
386
387impl<T: ClaimVar> ClaimVar for Vec<T> {
388 type Claimed = Vec<T::Claimed>;
389
390 fn claim(self, ctx: &mut StepCtx<'_>) -> Vec<T::Claimed> {
391 self.into_iter().map(|v| v.claim(ctx)).collect()
392 }
393}
394
395impl<T: ReadVarValue> ReadVarValue for Vec<T> {
396 type Value = Vec<T::Value>;
397
398 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
399 self.into_iter().map(|v| v.read_value(rt)).collect()
400 }
401}
402
403impl<T: ClaimVar> ClaimVar for Option<T> {
404 type Claimed = Option<T::Claimed>;
405
406 fn claim(self, ctx: &mut StepCtx<'_>) -> Option<T::Claimed> {
407 self.map(|x| x.claim(ctx))
408 }
409}
410
411impl<T: ReadVarValue> ReadVarValue for Option<T> {
412 type Value = Option<T::Value>;
413
414 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
415 self.map(|x| x.read_value(rt))
416 }
417}
418
419impl<U: Ord, T: ClaimVar> ClaimVar for BTreeMap<U, T> {
420 type Claimed = BTreeMap<U, T::Claimed>;
421
422 fn claim(self, ctx: &mut StepCtx<'_>) -> BTreeMap<U, T::Claimed> {
423 self.into_iter().map(|(k, v)| (k, v.claim(ctx))).collect()
424 }
425}
426
427impl<U: Ord, T: ReadVarValue> ReadVarValue for BTreeMap<U, T> {
428 type Value = BTreeMap<U, T::Value>;
429
430 fn read_value(self, rt: &mut RustRuntimeServices<'_>) -> Self::Value {
431 self.into_iter()
432 .map(|(k, v)| (k, v.read_value(rt)))
433 .collect()
434 }
435}
436
437macro_rules! impl_tuple_claim {
438 ($($T:tt)*) => {
439 impl<$($T,)*> $crate::node::ClaimVar for ($($T,)*)
440 where
441 $($T: $crate::node::ClaimVar,)*
442 {
443 type Claimed = ($($T::Claimed,)*);
444
445 #[expect(non_snake_case)]
446 fn claim(self, ctx: &mut $crate::node::StepCtx<'_>) -> Self::Claimed {
447 let ($($T,)*) = self;
448 ($($T.claim(ctx),)*)
449 }
450 }
451
452 impl<$($T,)*> $crate::node::ReadVarValue for ($($T,)*)
453 where
454 $($T: $crate::node::ReadVarValue,)*
455 {
456 type Value = ($($T::Value,)*);
457
458 #[expect(non_snake_case)]
459 fn read_value(self, rt: &mut $crate::node::RustRuntimeServices<'_>) -> Self::Value {
460 let ($($T,)*) = self;
461 ($($T.read_value(rt),)*)
462 }
463 }
464 };
465}
466
467impl_tuple_claim!(A B C D E F G H I J);
468impl_tuple_claim!(A B C D E F G H I);
469impl_tuple_claim!(A B C D E F G H);
470impl_tuple_claim!(A B C D E F G);
471impl_tuple_claim!(A B C D E F);
472impl_tuple_claim!(A B C D E);
473impl_tuple_claim!(A B C D);
474impl_tuple_claim!(A B C);
475impl_tuple_claim!(A B);
476impl_tuple_claim!(A);
477
478impl ClaimVar for () {
479 type Claimed = ();
480
481 fn claim(self, _ctx: &mut StepCtx<'_>) -> Self::Claimed {}
482}
483
484impl ReadVarValue for () {
485 type Value = ();
486
487 fn read_value(self, _rt: &mut RustRuntimeServices<'_>) -> Self::Value {}
488}
489
490#[derive(Serialize, Deserialize, Clone)]
495pub struct GhUserSecretVar(pub(crate) String);
496
497#[derive(Debug, Serialize, Deserialize)]
516pub struct ReadVar<T, C = VarNotClaimed> {
517 backing_var: ReadVarBacking<T>,
518 #[serde(skip)]
519 _kind: std::marker::PhantomData<C>,
520}
521
522pub type ClaimedReadVar<T> = ReadVar<T, VarClaimed>;
525
526impl<T: Serialize + DeserializeOwned, C> Clone for ReadVar<T, C> {
528 fn clone(&self) -> Self {
529 ReadVar {
530 backing_var: self.backing_var.clone(),
531 _kind: std::marker::PhantomData,
532 }
533 }
534}
535
536#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
537enum ReadVarBacking<T> {
538 RuntimeVar {
539 var: String,
540 is_side_effect: bool,
547 },
548 Inline(T),
549}
550
551impl<T: Serialize + DeserializeOwned> Clone for ReadVarBacking<T> {
553 fn clone(&self) -> Self {
554 match self {
555 Self::RuntimeVar {
556 var,
557 is_side_effect,
558 } => Self::RuntimeVar {
559 var: var.clone(),
560 is_side_effect: *is_side_effect,
561 },
562 Self::Inline(v) => {
563 Self::Inline(serde_json::from_value(serde_json::to_value(v).unwrap()).unwrap())
564 }
565 }
566 }
567}
568
569impl<T: Serialize + DeserializeOwned> ReadVar<T> {
570 fn into_claimed(self) -> ReadVar<T, VarClaimed> {
572 let Self { backing_var, _kind } = self;
573
574 ReadVar {
575 backing_var,
576 _kind: std::marker::PhantomData,
577 }
578 }
579
580 #[must_use]
589 pub fn into_side_effect(self) -> ReadVar<SideEffect> {
590 ReadVar {
591 backing_var: match self.backing_var {
592 ReadVarBacking::RuntimeVar {
593 var,
594 is_side_effect: _,
595 } => ReadVarBacking::RuntimeVar {
596 var,
597 is_side_effect: true,
598 },
599 ReadVarBacking::Inline(_) => ReadVarBacking::Inline(()),
600 },
601 _kind: std::marker::PhantomData,
602 }
603 }
604
605 #[track_caller]
608 #[must_use]
609 pub fn map<F, U>(&self, ctx: &mut NodeCtx<'_>, f: F) -> ReadVar<U>
610 where
611 T: 'static,
612 U: Serialize + DeserializeOwned + 'static,
613 F: FnOnce(T) -> U + 'static,
614 {
615 let (read_from, write_into) = ctx.new_var();
616 self.write_into(ctx, write_into, f);
617 read_from
618 }
619
620 #[track_caller]
623 pub fn write_into<F, U>(&self, ctx: &mut NodeCtx<'_>, write_into: WriteVar<U>, f: F)
624 where
625 T: 'static,
626 U: Serialize + DeserializeOwned + 'static,
627 F: FnOnce(T) -> U + 'static,
628 {
629 let this = self.clone();
630 ctx.emit_minor_rust_step("🌼 write_into Var", move |ctx| {
631 let this = this.claim(ctx);
632 let write_into = write_into.claim(ctx);
633 move |rt| {
634 let this = rt.read(this);
635 rt.write(write_into, &f(this));
636 }
637 });
638 }
639
640 #[track_caller]
643 #[must_use]
644 pub fn zip<U>(&self, ctx: &mut NodeCtx<'_>, other: ReadVar<U>) -> ReadVar<(T, U)>
645 where
646 T: 'static,
647 U: Serialize + DeserializeOwned + 'static,
648 {
649 let (read_from, write_into) = ctx.new_var();
650 let this = self.clone();
651 ctx.emit_minor_rust_step("🌼 Zip Vars", move |ctx| {
652 let this = this.claim(ctx);
653 let other = other.claim(ctx);
654 let write_into = write_into.claim(ctx);
655 move |rt| {
656 let this = rt.read(this);
657 let other = rt.read(other);
658 rt.write(write_into, &(this, other));
659 }
660 });
661 read_from
662 }
663
664 #[track_caller]
669 #[must_use]
670 pub fn from_static(val: T) -> ReadVar<T>
671 where
672 T: 'static,
673 {
674 ReadVar {
675 backing_var: ReadVarBacking::Inline(val),
676 _kind: std::marker::PhantomData,
677 }
678 }
679
680 pub fn get_static(&self) -> Option<T> {
689 match self.clone().backing_var {
690 ReadVarBacking::Inline(v) => Some(v),
691 _ => None,
692 }
693 }
694
695 #[track_caller]
697 #[must_use]
698 pub fn transpose_vec(ctx: &mut NodeCtx<'_>, vec: Vec<ReadVar<T>>) -> ReadVar<Vec<T>>
699 where
700 T: 'static,
701 {
702 let (read_from, write_into) = ctx.new_var();
703 ctx.emit_minor_rust_step("🌼 Transpose Vec<ReadVar<T>>", move |ctx| {
704 let vec = vec.claim(ctx);
705 let write_into = write_into.claim(ctx);
706 move |rt| {
707 let mut v = Vec::new();
708 for var in vec {
709 v.push(rt.read(var));
710 }
711 rt.write(write_into, &v);
712 }
713 });
714 read_from
715 }
716
717 #[must_use]
733 pub fn depending_on<U>(&self, ctx: &mut NodeCtx<'_>, other: &ReadVar<U>) -> Self
734 where
735 T: 'static,
736 U: Serialize + DeserializeOwned + 'static,
737 {
738 ctx.emit_minor_rust_stepv("🌼 Add dependency", |ctx| {
741 let this = self.clone().claim(ctx);
742 other.clone().claim(ctx);
743 move |rt| rt.read(this)
744 })
745 }
746
747 pub fn claim_unused(self, ctx: &mut NodeCtx<'_>) {
750 match self.backing_var {
751 ReadVarBacking::RuntimeVar {
752 var,
753 is_side_effect: _,
754 } => ctx.backend.borrow_mut().on_unused_read_var(&var),
755 ReadVarBacking::Inline(_) => {}
756 }
757 }
758
759 pub(crate) fn into_json(self) -> ReadVar<serde_json::Value> {
760 match self.backing_var {
761 ReadVarBacking::RuntimeVar {
762 var,
763 is_side_effect,
764 } => ReadVar {
765 backing_var: ReadVarBacking::RuntimeVar {
766 var,
767 is_side_effect,
768 },
769 _kind: std::marker::PhantomData,
770 },
771 ReadVarBacking::Inline(v) => ReadVar {
772 backing_var: ReadVarBacking::Inline(serde_json::to_value(v).unwrap()),
773 _kind: std::marker::PhantomData,
774 },
775 }
776 }
777}
778
779#[must_use]
785pub fn thin_air_read_runtime_var<T>(backing_var: String) -> ReadVar<T>
786where
787 T: Serialize + DeserializeOwned,
788{
789 ReadVar {
790 backing_var: ReadVarBacking::RuntimeVar {
791 var: backing_var,
792 is_side_effect: false,
793 },
794 _kind: std::marker::PhantomData,
795 }
796}
797
798#[must_use]
804pub fn thin_air_write_runtime_var<T>(backing_var: String) -> WriteVar<T>
805where
806 T: Serialize + DeserializeOwned,
807{
808 WriteVar {
809 backing_var,
810 is_side_effect: false,
811 _kind: std::marker::PhantomData,
812 }
813}
814
815pub fn read_var_internals<T: Serialize + DeserializeOwned, C>(
821 var: &ReadVar<T, C>,
822) -> (Option<String>, bool) {
823 match var.backing_var {
824 ReadVarBacking::RuntimeVar {
825 var: ref s,
826 is_side_effect,
827 } => (Some(s.clone()), is_side_effect),
828 ReadVarBacking::Inline(_) => (None, false),
829 }
830}
831
832pub trait ImportCtxBackend {
833 fn on_possible_dep(&mut self, node_handle: NodeHandle);
834}
835
836pub struct ImportCtx<'a> {
838 backend: &'a mut dyn ImportCtxBackend,
839}
840
841impl ImportCtx<'_> {
842 pub fn import<N: FlowNodeBase + 'static>(&mut self) {
844 self.backend.on_possible_dep(NodeHandle::from_type::<N>())
845 }
846}
847
848pub fn new_import_ctx(backend: &mut dyn ImportCtxBackend) -> ImportCtx<'_> {
849 ImportCtx { backend }
850}
851
852#[derive(Debug)]
853pub enum CtxAnchor {
854 PostJob,
855}
856
857pub trait NodeCtxBackend {
858 fn current_node(&self) -> NodeHandle;
860
861 fn on_new_var(&mut self) -> String;
866
867 fn on_claimed_runtime_var(&mut self, var: &str, is_read: bool);
869
870 fn on_unused_read_var(&mut self, var: &str);
872
873 fn on_request(&mut self, node_handle: NodeHandle, req: anyhow::Result<Box<[u8]>>);
881
882 fn on_emit_rust_step(
883 &mut self,
884 label: &str,
885 can_merge: bool,
886 code: Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
887 );
888
889 fn on_emit_ado_step(
890 &mut self,
891 label: &str,
892 yaml_snippet: Box<dyn for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String>,
893 inline_script: Option<
894 Box<dyn for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>>,
895 >,
896 condvar: Option<String>,
897 );
898
899 fn on_emit_gh_step(
900 &mut self,
901 label: &str,
902 uses: &str,
903 with: BTreeMap<String, ClaimedGhParam>,
904 condvar: Option<String>,
905 outputs: BTreeMap<String, Vec<GhOutput>>,
906 permissions: BTreeMap<GhPermission, GhPermissionValue>,
907 gh_to_rust: Vec<GhToRust>,
908 rust_to_gh: Vec<RustToGh>,
909 );
910
911 fn on_emit_side_effect_step(&mut self);
912
913 fn backend(&mut self) -> FlowBackend;
914 fn platform(&mut self) -> FlowPlatform;
915 fn arch(&mut self) -> FlowArch;
916
917 fn persistent_dir_path_var(&mut self) -> Option<String>;
921}
922
923pub fn new_node_ctx(backend: &mut dyn NodeCtxBackend) -> NodeCtx<'_> {
924 NodeCtx {
925 backend: Rc::new(RefCell::new(backend)),
926 }
927}
928
929#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
931pub enum FlowBackend {
932 Local,
934 Ado,
936 Github,
938}
939
940#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
942pub enum FlowPlatformKind {
943 Windows,
944 Unix,
945}
946
947#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
949pub enum FlowPlatformLinuxDistro {
950 Fedora,
952 Ubuntu,
954 AzureLinux,
956 Arch,
958 Nix,
960 Unknown,
962}
963
964#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
966#[non_exhaustive]
967pub enum FlowPlatform {
968 Windows,
970 Linux(FlowPlatformLinuxDistro),
972 MacOs,
974}
975
976impl FlowPlatform {
977 pub fn kind(&self) -> FlowPlatformKind {
978 match self {
979 Self::Windows => FlowPlatformKind::Windows,
980 Self::Linux(_) | Self::MacOs => FlowPlatformKind::Unix,
981 }
982 }
983
984 fn as_str(&self) -> &'static str {
985 match self {
986 Self::Windows => "windows",
987 Self::Linux(_) => "linux",
988 Self::MacOs => "macos",
989 }
990 }
991
992 pub fn exe_suffix(&self) -> &'static str {
994 if self == &Self::Windows { ".exe" } else { "" }
995 }
996
997 pub fn binary(&self, name: &str) -> String {
999 format!("{}{}", name, self.exe_suffix())
1000 }
1001}
1002
1003impl std::fmt::Display for FlowPlatform {
1004 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1005 f.pad(self.as_str())
1006 }
1007}
1008
1009#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1011#[non_exhaustive]
1012pub enum FlowArch {
1013 X86_64,
1014 Aarch64,
1015}
1016
1017impl FlowArch {
1018 fn as_str(&self) -> &'static str {
1019 match self {
1020 Self::X86_64 => "x86_64",
1021 Self::Aarch64 => "aarch64",
1022 }
1023 }
1024}
1025
1026impl std::fmt::Display for FlowArch {
1027 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1028 f.pad(self.as_str())
1029 }
1030}
1031
1032pub struct StepCtx<'a> {
1034 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
1035}
1036
1037impl StepCtx<'_> {
1038 pub fn backend(&self) -> FlowBackend {
1041 self.backend.borrow_mut().backend()
1042 }
1043
1044 pub fn platform(&self) -> FlowPlatform {
1047 self.backend.borrow_mut().platform()
1048 }
1049}
1050
1051const NO_ADO_INLINE_SCRIPT: Option<
1052 for<'a> fn(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()>,
1053> = None;
1054
1055pub struct NodeCtx<'a> {
1057 backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>,
1058}
1059
1060impl<'ctx> NodeCtx<'ctx> {
1061 pub fn emit_rust_step<F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<SideEffect>
1067 where
1068 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1069 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1070 {
1071 self.emit_rust_step_inner(label.as_ref(), false, code)
1072 }
1073
1074 pub fn emit_minor_rust_step<F, G>(
1084 &mut self,
1085 label: impl AsRef<str>,
1086 code: F,
1087 ) -> ReadVar<SideEffect>
1088 where
1089 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1090 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) + 'static,
1091 {
1092 self.emit_rust_step_inner(label.as_ref(), true, |ctx| {
1093 let f = code(ctx);
1094 |rt| {
1095 f(rt);
1096 Ok(())
1097 }
1098 })
1099 }
1100
1101 #[must_use]
1122 #[track_caller]
1123 pub fn emit_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1124 where
1125 T: Serialize + DeserializeOwned + 'static,
1126 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1127 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1128 {
1129 self.emit_rust_stepv_inner(label.as_ref(), false, code)
1130 }
1131
1132 #[must_use]
1156 #[track_caller]
1157 pub fn emit_minor_rust_stepv<T, F, G>(&mut self, label: impl AsRef<str>, code: F) -> ReadVar<T>
1158 where
1159 T: Serialize + DeserializeOwned + 'static,
1160 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1161 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> T + 'static,
1162 {
1163 self.emit_rust_stepv_inner(label.as_ref(), true, |ctx| {
1164 let f = code(ctx);
1165 |rt| Ok(f(rt))
1166 })
1167 }
1168
1169 fn emit_rust_step_inner<F, G>(
1170 &mut self,
1171 label: &str,
1172 can_merge: bool,
1173 code: F,
1174 ) -> ReadVar<SideEffect>
1175 where
1176 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1177 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1178 {
1179 let (read, write) = self.new_prefixed_var("auto_se");
1180
1181 let ctx = &mut StepCtx {
1182 backend: self.backend.clone(),
1183 };
1184 write.claim(ctx);
1185
1186 let code = code(ctx);
1187 self.backend
1188 .borrow_mut()
1189 .on_emit_rust_step(label.as_ref(), can_merge, Box::new(code));
1190 read
1191 }
1192
1193 #[must_use]
1194 #[track_caller]
1195 fn emit_rust_stepv_inner<T, F, G>(
1196 &mut self,
1197 label: impl AsRef<str>,
1198 can_merge: bool,
1199 code: F,
1200 ) -> ReadVar<T>
1201 where
1202 T: Serialize + DeserializeOwned + 'static,
1203 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1204 G: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<T> + 'static,
1205 {
1206 let (read, write) = self.new_var();
1207
1208 let ctx = &mut StepCtx {
1209 backend: self.backend.clone(),
1210 };
1211 let write = write.claim(ctx);
1212
1213 let code = code(ctx);
1214 self.backend.borrow_mut().on_emit_rust_step(
1215 label.as_ref(),
1216 can_merge,
1217 Box::new(|rt| {
1218 let val = code(rt)?;
1219 rt.write(write, &val);
1220 Ok(())
1221 }),
1222 );
1223 read
1224 }
1225
1226 #[track_caller]
1228 #[must_use]
1229 pub fn get_ado_variable(&mut self, ado_var: AdoRuntimeVar) -> ReadVar<String> {
1230 let (var, write_var) = self.new_var();
1231 self.emit_ado_step(format!("🌼 read {}", ado_var.as_raw_var_name()), |ctx| {
1232 let write_var = write_var.claim(ctx);
1233 |rt| {
1234 rt.set_var(write_var, ado_var);
1235 "".into()
1236 }
1237 });
1238 var
1239 }
1240
1241 pub fn emit_ado_step<F, G>(&mut self, display_name: impl AsRef<str>, yaml_snippet: F)
1243 where
1244 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1245 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1246 {
1247 self.emit_ado_step_inner(display_name, None, |ctx| {
1248 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1249 })
1250 }
1251
1252 pub fn emit_ado_step_with_condition<F, G>(
1255 &mut self,
1256 display_name: impl AsRef<str>,
1257 cond: ReadVar<bool>,
1258 yaml_snippet: F,
1259 ) where
1260 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1261 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1262 {
1263 self.emit_ado_step_inner(display_name, Some(cond), |ctx| {
1264 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1265 })
1266 }
1267
1268 pub fn emit_ado_step_with_condition_optional<F, G>(
1271 &mut self,
1272 display_name: impl AsRef<str>,
1273 cond: Option<ReadVar<bool>>,
1274 yaml_snippet: F,
1275 ) where
1276 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> G,
1277 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1278 {
1279 self.emit_ado_step_inner(display_name, cond, |ctx| {
1280 (yaml_snippet(ctx), NO_ADO_INLINE_SCRIPT)
1281 })
1282 }
1283
1284 pub fn emit_ado_step_with_inline_script<F, G, H>(
1313 &mut self,
1314 display_name: impl AsRef<str>,
1315 yaml_snippet: F,
1316 ) where
1317 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, H),
1318 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1319 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1320 {
1321 self.emit_ado_step_inner(display_name, None, |ctx| {
1322 let (f, g) = yaml_snippet(ctx);
1323 (f, Some(g))
1324 })
1325 }
1326
1327 fn emit_ado_step_inner<F, G, H>(
1328 &mut self,
1329 display_name: impl AsRef<str>,
1330 cond: Option<ReadVar<bool>>,
1331 yaml_snippet: F,
1332 ) where
1333 F: for<'a> FnOnce(&'a mut StepCtx<'_>) -> (G, Option<H>),
1334 G: for<'a> FnOnce(&'a mut AdoStepServices<'_>) -> String + 'static,
1335 H: for<'a> FnOnce(&'a mut RustRuntimeServices<'_>) -> anyhow::Result<()> + 'static,
1336 {
1337 let condvar = match cond.map(|c| c.backing_var) {
1338 Some(ReadVarBacking::Inline(cond)) => {
1340 if !cond {
1341 return;
1342 } else {
1343 None
1344 }
1345 }
1346 Some(ReadVarBacking::RuntimeVar {
1347 var,
1348 is_side_effect,
1349 }) => {
1350 assert!(!is_side_effect);
1351 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1352 Some(var)
1353 }
1354 None => None,
1355 };
1356
1357 let (yaml_snippet, inline_script) = yaml_snippet(&mut StepCtx {
1358 backend: self.backend.clone(),
1359 });
1360 self.backend.borrow_mut().on_emit_ado_step(
1361 display_name.as_ref(),
1362 Box::new(yaml_snippet),
1363 if let Some(inline_script) = inline_script {
1364 Some(Box::new(inline_script))
1365 } else {
1366 None
1367 },
1368 condvar,
1369 );
1370 }
1371
1372 #[track_caller]
1374 #[must_use]
1375 pub fn get_gh_context_var(&mut self) -> GhContextVarReader<'ctx, Root> {
1376 GhContextVarReader {
1377 ctx: NodeCtx {
1378 backend: self.backend.clone(),
1379 },
1380 _state: std::marker::PhantomData,
1381 }
1382 }
1383
1384 pub fn emit_gh_step(
1386 &mut self,
1387 display_name: impl AsRef<str>,
1388 uses: impl AsRef<str>,
1389 ) -> GhStepBuilder {
1390 GhStepBuilder::new(display_name, uses)
1391 }
1392
1393 fn emit_gh_step_inner(
1394 &mut self,
1395 display_name: impl AsRef<str>,
1396 cond: Option<ReadVar<bool>>,
1397 uses: impl AsRef<str>,
1398 with: Option<BTreeMap<String, GhParam>>,
1399 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1400 run_after: Vec<ReadVar<SideEffect>>,
1401 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1402 ) {
1403 let condvar = match cond.map(|c| c.backing_var) {
1404 Some(ReadVarBacking::Inline(cond)) => {
1406 if !cond {
1407 return;
1408 } else {
1409 None
1410 }
1411 }
1412 Some(ReadVarBacking::RuntimeVar {
1413 var,
1414 is_side_effect,
1415 }) => {
1416 assert!(!is_side_effect);
1417 self.backend.borrow_mut().on_claimed_runtime_var(&var, true);
1418 Some(var)
1419 }
1420 None => None,
1421 };
1422
1423 let with = with
1424 .unwrap_or_default()
1425 .into_iter()
1426 .map(|(k, v)| {
1427 (
1428 k.clone(),
1429 v.claim(&mut StepCtx {
1430 backend: self.backend.clone(),
1431 }),
1432 )
1433 })
1434 .collect();
1435
1436 for var in run_after {
1437 var.claim(&mut StepCtx {
1438 backend: self.backend.clone(),
1439 });
1440 }
1441
1442 let outputvars = outputs
1443 .into_iter()
1444 .map(|(name, vars)| {
1445 (
1446 name,
1447 vars.into_iter()
1448 .map(|var| {
1449 let var = var.claim(&mut StepCtx {
1450 backend: self.backend.clone(),
1451 });
1452 GhOutput {
1453 backing_var: var.backing_var,
1454 is_secret: false,
1455 is_object: false,
1456 }
1457 })
1458 .collect(),
1459 )
1460 })
1461 .collect();
1462
1463 self.backend.borrow_mut().on_emit_gh_step(
1464 display_name.as_ref(),
1465 uses.as_ref(),
1466 with,
1467 condvar,
1468 outputvars,
1469 permissions,
1470 Vec::new(),
1471 Vec::new(),
1472 );
1473 }
1474
1475 pub fn emit_side_effect_step(
1483 &mut self,
1484 use_side_effects: impl IntoIterator<Item = ReadVar<SideEffect>>,
1485 resolve_side_effects: impl IntoIterator<Item = WriteVar<SideEffect>>,
1486 ) {
1487 let mut backend = self.backend.borrow_mut();
1488 for var in use_side_effects.into_iter() {
1489 if let ReadVarBacking::RuntimeVar {
1490 var,
1491 is_side_effect: _,
1492 } = &var.backing_var
1493 {
1494 backend.on_claimed_runtime_var(var, true);
1495 }
1496 }
1497
1498 for var in resolve_side_effects.into_iter() {
1499 backend.on_claimed_runtime_var(&var.backing_var, false);
1500 }
1501
1502 backend.on_emit_side_effect_step();
1503 }
1504
1505 pub fn backend(&self) -> FlowBackend {
1508 self.backend.borrow_mut().backend()
1509 }
1510
1511 pub fn platform(&self) -> FlowPlatform {
1514 self.backend.borrow_mut().platform()
1515 }
1516
1517 pub fn arch(&self) -> FlowArch {
1519 self.backend.borrow_mut().arch()
1520 }
1521
1522 pub fn req<R>(&mut self, req: R)
1524 where
1525 R: IntoRequest + 'static,
1526 {
1527 let mut backend = self.backend.borrow_mut();
1528 backend.on_request(
1529 NodeHandle::from_type::<R::Node>(),
1530 serde_json::to_vec(&req.into_request())
1531 .map(Into::into)
1532 .map_err(Into::into),
1533 );
1534 }
1535
1536 #[track_caller]
1539 #[must_use]
1540 pub fn reqv<T, R>(&mut self, f: impl FnOnce(WriteVar<T>) -> R) -> ReadVar<T>
1541 where
1542 T: Serialize + DeserializeOwned,
1543 R: IntoRequest + 'static,
1544 {
1545 let (read, write) = self.new_var();
1546 self.req::<R>(f(write));
1547 read
1548 }
1549
1550 pub fn requests<N>(&mut self, reqs: impl IntoIterator<Item = N::Request>)
1552 where
1553 N: FlowNodeBase + 'static,
1554 {
1555 let mut backend = self.backend.borrow_mut();
1556 for req in reqs.into_iter() {
1557 backend.on_request(
1558 NodeHandle::from_type::<N>(),
1559 serde_json::to_vec(&req).map(Into::into).map_err(Into::into),
1560 );
1561 }
1562 }
1563
1564 #[track_caller]
1567 #[must_use]
1568 pub fn new_var<T>(&self) -> (ReadVar<T>, WriteVar<T>)
1569 where
1570 T: Serialize + DeserializeOwned,
1571 {
1572 self.new_prefixed_var("")
1573 }
1574
1575 #[track_caller]
1576 #[must_use]
1577 fn new_prefixed_var<T>(&self, prefix: &'static str) -> (ReadVar<T>, WriteVar<T>)
1578 where
1579 T: Serialize + DeserializeOwned,
1580 {
1581 let caller = std::panic::Location::caller()
1583 .to_string()
1584 .replace('\\', "/");
1585
1586 let caller = caller
1602 .split_once("flowey/")
1603 .expect("due to a known limitation with flowey, all flowey code must have an ancestor dir called 'flowey/' somewhere in its full path")
1604 .1;
1605
1606 let colon = if prefix.is_empty() { "" } else { ":" };
1607 let ordinal = self.backend.borrow_mut().on_new_var();
1608 let backing_var = format!("{prefix}{colon}{ordinal}:{caller}");
1609
1610 (
1611 ReadVar {
1612 backing_var: ReadVarBacking::RuntimeVar {
1613 var: backing_var.clone(),
1614 is_side_effect: false,
1615 },
1616 _kind: std::marker::PhantomData,
1617 },
1618 WriteVar {
1619 backing_var,
1620 is_side_effect: false,
1621 _kind: std::marker::PhantomData,
1622 },
1623 )
1624 }
1625
1626 #[track_caller]
1637 #[must_use]
1638 pub fn new_post_job_side_effect(&self) -> (ReadVar<SideEffect>, WriteVar<SideEffect>) {
1639 self.new_prefixed_var("post_job")
1640 }
1641
1642 #[track_caller]
1655 #[must_use]
1656 pub fn persistent_dir(&mut self) -> Option<ReadVar<PathBuf>> {
1657 let path: ReadVar<PathBuf> = ReadVar {
1658 backing_var: ReadVarBacking::RuntimeVar {
1659 var: self.backend.borrow_mut().persistent_dir_path_var()?,
1660 is_side_effect: false,
1661 },
1662 _kind: std::marker::PhantomData,
1663 };
1664
1665 let folder_name = self
1666 .backend
1667 .borrow_mut()
1668 .current_node()
1669 .modpath()
1670 .replace("::", "__");
1671
1672 Some(
1673 self.emit_rust_stepv("🌼 Create persistent store dir", |ctx| {
1674 let path = path.claim(ctx);
1675 |rt| {
1676 let dir = rt.read(path).join(folder_name);
1677 fs_err::create_dir_all(&dir)?;
1678 Ok(dir)
1679 }
1680 }),
1681 )
1682 }
1683
1684 pub fn supports_persistent_dir(&mut self) -> bool {
1686 self.backend
1687 .borrow_mut()
1688 .persistent_dir_path_var()
1689 .is_some()
1690 }
1691}
1692
1693pub trait RuntimeVarDb {
1696 fn get_var(&mut self, var_name: &str) -> (Vec<u8>, bool) {
1697 self.try_get_var(var_name)
1698 .unwrap_or_else(|| panic!("db is missing var {}", var_name))
1699 }
1700
1701 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)>;
1702 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>);
1703}
1704
1705impl RuntimeVarDb for Box<dyn RuntimeVarDb> {
1706 fn try_get_var(&mut self, var_name: &str) -> Option<(Vec<u8>, bool)> {
1707 (**self).try_get_var(var_name)
1708 }
1709
1710 fn set_var(&mut self, var_name: &str, is_secret: bool, value: Vec<u8>) {
1711 (**self).set_var(var_name, is_secret, value)
1712 }
1713}
1714
1715pub mod steps {
1716 pub mod ado {
1717 use crate::node::ClaimedReadVar;
1718 use crate::node::ClaimedWriteVar;
1719 use crate::node::ReadVarBacking;
1720 use serde::Deserialize;
1721 use serde::Serialize;
1722 use std::borrow::Cow;
1723
1724 #[derive(Debug, Clone, Serialize, Deserialize)]
1730 pub struct AdoResourcesRepositoryId {
1731 pub(crate) repo_id: String,
1732 }
1733
1734 impl AdoResourcesRepositoryId {
1735 pub fn new_self() -> Self {
1741 Self {
1742 repo_id: "self".into(),
1743 }
1744 }
1745
1746 pub fn dangerous_get_raw_id(&self) -> &str {
1752 &self.repo_id
1753 }
1754
1755 pub fn dangerous_new(repo_id: &str) -> Self {
1761 Self {
1762 repo_id: repo_id.into(),
1763 }
1764 }
1765 }
1766
1767 #[derive(Clone, Debug, Serialize, Deserialize)]
1772 pub struct AdoRuntimeVar {
1773 is_secret: bool,
1774 ado_var: Cow<'static, str>,
1775 }
1776
1777 #[allow(non_upper_case_globals)]
1778 impl AdoRuntimeVar {
1779 pub const BUILD__SOURCE_BRANCH: AdoRuntimeVar =
1785 AdoRuntimeVar::new("build.SourceBranch");
1786
1787 pub const BUILD__BUILD_NUMBER: AdoRuntimeVar = AdoRuntimeVar::new("build.BuildNumber");
1789
1790 pub const SYSTEM__ACCESS_TOKEN: AdoRuntimeVar =
1792 AdoRuntimeVar::new_secret("System.AccessToken");
1793
1794 pub const SYSTEM__JOB_ATTEMPT: AdoRuntimeVar =
1796 AdoRuntimeVar::new_secret("System.JobAttempt");
1797 }
1798
1799 impl AdoRuntimeVar {
1800 const fn new(s: &'static str) -> Self {
1801 Self {
1802 is_secret: false,
1803 ado_var: Cow::Borrowed(s),
1804 }
1805 }
1806
1807 const fn new_secret(s: &'static str) -> Self {
1808 Self {
1809 is_secret: true,
1810 ado_var: Cow::Borrowed(s),
1811 }
1812 }
1813
1814 pub fn is_secret(&self) -> bool {
1816 self.is_secret
1817 }
1818
1819 pub fn as_raw_var_name(&self) -> String {
1821 self.ado_var.as_ref().into()
1822 }
1823
1824 pub fn dangerous_from_global(ado_var_name: impl AsRef<str>, is_secret: bool) -> Self {
1832 Self {
1833 is_secret,
1834 ado_var: ado_var_name.as_ref().to_owned().into(),
1835 }
1836 }
1837 }
1838
1839 pub fn new_ado_step_services(
1840 fresh_ado_var: &mut dyn FnMut() -> String,
1841 ) -> AdoStepServices<'_> {
1842 AdoStepServices {
1843 fresh_ado_var,
1844 ado_to_rust: Vec::new(),
1845 rust_to_ado: Vec::new(),
1846 }
1847 }
1848
1849 pub struct CompletedAdoStepServices {
1850 pub ado_to_rust: Vec<(String, String, bool)>,
1851 pub rust_to_ado: Vec<(String, String)>,
1852 }
1853
1854 impl CompletedAdoStepServices {
1855 pub fn from_ado_step_services(access: AdoStepServices<'_>) -> Self {
1856 let AdoStepServices {
1857 fresh_ado_var: _,
1858 ado_to_rust,
1859 rust_to_ado,
1860 } = access;
1861
1862 Self {
1863 ado_to_rust,
1864 rust_to_ado,
1865 }
1866 }
1867 }
1868
1869 pub struct AdoStepServices<'a> {
1870 fresh_ado_var: &'a mut dyn FnMut() -> String,
1871 ado_to_rust: Vec<(String, String, bool)>,
1872 rust_to_ado: Vec<(String, String)>,
1873 }
1874
1875 impl AdoStepServices<'_> {
1876 pub fn resolve_repository_id(&self, repo_id: AdoResourcesRepositoryId) -> String {
1879 repo_id.repo_id
1880 }
1881
1882 pub fn set_var(&mut self, var: ClaimedWriteVar<String>, from_ado_var: AdoRuntimeVar) {
1888 self.ado_to_rust.push((
1889 from_ado_var.ado_var.into(),
1890 var.backing_var,
1891 from_ado_var.is_secret,
1892 ))
1893 }
1894
1895 pub fn get_var(&mut self, var: ClaimedReadVar<String>) -> AdoRuntimeVar {
1897 let backing_var = if let ReadVarBacking::RuntimeVar {
1898 var,
1899 is_side_effect,
1900 } = &var.backing_var
1901 {
1902 assert!(!is_side_effect);
1903 var
1904 } else {
1905 todo!("support inline ado read vars")
1906 };
1907
1908 let new_ado_var_name = (self.fresh_ado_var)();
1909
1910 self.rust_to_ado
1911 .push((backing_var.clone(), new_ado_var_name.clone()));
1912 AdoRuntimeVar::dangerous_from_global(new_ado_var_name, false)
1913 }
1914 }
1915 }
1916
1917 pub mod github {
1918 use crate::node::ClaimVar;
1919 use crate::node::NodeCtx;
1920 use crate::node::ReadVar;
1921 use crate::node::ReadVarBacking;
1922 use crate::node::SideEffect;
1923 use crate::node::StepCtx;
1924 use crate::node::VarClaimed;
1925 use crate::node::VarNotClaimed;
1926 use crate::node::WriteVar;
1927 use std::collections::BTreeMap;
1928
1929 pub struct GhStepBuilder {
1930 display_name: String,
1931 cond: Option<ReadVar<bool>>,
1932 uses: String,
1933 with: Option<BTreeMap<String, GhParam>>,
1934 outputs: BTreeMap<String, Vec<WriteVar<String>>>,
1935 run_after: Vec<ReadVar<SideEffect>>,
1936 permissions: BTreeMap<GhPermission, GhPermissionValue>,
1937 }
1938
1939 impl GhStepBuilder {
1940 pub fn new(display_name: impl AsRef<str>, uses: impl AsRef<str>) -> Self {
1955 Self {
1956 display_name: display_name.as_ref().into(),
1957 cond: None,
1958 uses: uses.as_ref().into(),
1959 with: None,
1960 outputs: BTreeMap::new(),
1961 run_after: Vec::new(),
1962 permissions: BTreeMap::new(),
1963 }
1964 }
1965
1966 pub fn condition(mut self, cond: ReadVar<bool>) -> Self {
1973 self.cond = Some(cond);
1974 self
1975 }
1976
1977 pub fn with(mut self, k: impl AsRef<str>, v: impl Into<GhParam>) -> Self {
2003 self.with.get_or_insert_with(BTreeMap::new);
2004 if let Some(with) = &mut self.with {
2005 with.insert(k.as_ref().to_string(), v.into());
2006 }
2007 self
2008 }
2009
2010 pub fn output(mut self, k: impl AsRef<str>, v: WriteVar<String>) -> Self {
2019 self.outputs
2020 .entry(k.as_ref().to_string())
2021 .or_default()
2022 .push(v);
2023 self
2024 }
2025
2026 pub fn run_after(mut self, side_effect: ReadVar<SideEffect>) -> Self {
2028 self.run_after.push(side_effect);
2029 self
2030 }
2031
2032 pub fn requires_permission(
2037 mut self,
2038 perm: GhPermission,
2039 value: GhPermissionValue,
2040 ) -> Self {
2041 self.permissions.insert(perm, value);
2042 self
2043 }
2044
2045 #[track_caller]
2047 pub fn finish(self, ctx: &mut NodeCtx<'_>) -> ReadVar<SideEffect> {
2048 let (side_effect, claim_side_effect) = ctx.new_prefixed_var("auto_se");
2049 ctx.backend
2050 .borrow_mut()
2051 .on_claimed_runtime_var(&claim_side_effect.backing_var, false);
2052
2053 ctx.emit_gh_step_inner(
2054 self.display_name,
2055 self.cond,
2056 self.uses,
2057 self.with,
2058 self.outputs,
2059 self.run_after,
2060 self.permissions,
2061 );
2062
2063 side_effect
2064 }
2065 }
2066
2067 #[derive(Clone, Debug)]
2068 pub enum GhParam<C = VarNotClaimed> {
2069 Static(String),
2070 FloweyVar(ReadVar<String, C>),
2071 }
2072
2073 impl From<String> for GhParam {
2074 fn from(param: String) -> GhParam {
2075 GhParam::Static(param)
2076 }
2077 }
2078
2079 impl From<&str> for GhParam {
2080 fn from(param: &str) -> GhParam {
2081 GhParam::Static(param.to_string())
2082 }
2083 }
2084
2085 impl From<ReadVar<String>> for GhParam {
2086 fn from(param: ReadVar<String>) -> GhParam {
2087 GhParam::FloweyVar(param)
2088 }
2089 }
2090
2091 pub type ClaimedGhParam = GhParam<VarClaimed>;
2092
2093 impl ClaimVar for GhParam {
2094 type Claimed = ClaimedGhParam;
2095
2096 fn claim(self, ctx: &mut StepCtx<'_>) -> ClaimedGhParam {
2097 match self {
2098 GhParam::Static(s) => ClaimedGhParam::Static(s),
2099 GhParam::FloweyVar(var) => match &var.backing_var {
2100 ReadVarBacking::RuntimeVar { is_side_effect, .. } => {
2101 assert!(!is_side_effect);
2102 ClaimedGhParam::FloweyVar(var.claim(ctx))
2103 }
2104 ReadVarBacking::Inline(var) => ClaimedGhParam::Static(var.clone()),
2105 },
2106 }
2107 }
2108 }
2109
2110 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
2115 pub enum GhPermissionValue {
2116 None = 0,
2117 Read = 1,
2118 Write = 2,
2119 }
2120
2121 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
2127 pub enum GhPermission {
2128 Actions,
2129 Attestations,
2130 Checks,
2131 Contents,
2132 Deployments,
2133 Discussions,
2134 IdToken,
2135 Issues,
2136 Packages,
2137 Pages,
2138 PullRequests,
2139 RepositoryProjects,
2140 SecurityEvents,
2141 Statuses,
2142 }
2143 }
2144
2145 pub mod rust {
2146 use crate::node::ClaimedWriteVar;
2147 use crate::node::FlowArch;
2148 use crate::node::FlowBackend;
2149 use crate::node::FlowPlatform;
2150 use crate::node::ReadVarValue;
2151 use crate::node::RuntimeVarDb;
2152 use crate::shell::FloweyShell;
2153 use serde::Serialize;
2154 use serde::de::DeserializeOwned;
2155
2156 pub fn new_rust_runtime_services(
2157 runtime_var_db: &mut dyn RuntimeVarDb,
2158 backend: FlowBackend,
2159 platform: FlowPlatform,
2160 arch: FlowArch,
2161 ) -> anyhow::Result<RustRuntimeServices<'_>> {
2162 Ok(RustRuntimeServices {
2163 runtime_var_db,
2164 backend,
2165 platform,
2166 arch,
2167 has_read_secret: false,
2168 sh: FloweyShell::new()?,
2169 })
2170 }
2171
2172 pub struct RustRuntimeServices<'a> {
2173 runtime_var_db: &'a mut dyn RuntimeVarDb,
2174 backend: FlowBackend,
2175 platform: FlowPlatform,
2176 arch: FlowArch,
2177 has_read_secret: bool,
2178 pub sh: FloweyShell,
2184 }
2185
2186 impl RustRuntimeServices<'_> {
2187 pub fn backend(&self) -> FlowBackend {
2190 self.backend
2191 }
2192
2193 pub fn platform(&self) -> FlowPlatform {
2196 self.platform
2197 }
2198
2199 pub fn arch(&self) -> FlowArch {
2201 self.arch
2202 }
2203
2204 pub fn write<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2212 where
2213 T: Serialize + DeserializeOwned,
2214 {
2215 self.write_maybe_secret(var, val, self.has_read_secret)
2216 }
2217
2218 pub fn write_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2224 where
2225 T: Serialize + DeserializeOwned,
2226 {
2227 self.write_maybe_secret(var, val, true)
2228 }
2229
2230 pub fn write_not_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T)
2237 where
2238 T: Serialize + DeserializeOwned,
2239 {
2240 self.write_maybe_secret(var, val, false)
2241 }
2242
2243 fn write_maybe_secret<T>(&mut self, var: ClaimedWriteVar<T>, val: &T, is_secret: bool)
2244 where
2245 T: Serialize + DeserializeOwned,
2246 {
2247 let val = if var.is_side_effect {
2248 b"null".to_vec()
2249 } else {
2250 serde_json::to_vec(val).expect("improve this error path")
2251 };
2252 self.runtime_var_db
2253 .set_var(&var.backing_var, is_secret, val);
2254 }
2255
2256 pub fn write_all<T>(
2257 &mut self,
2258 vars: impl IntoIterator<Item = ClaimedWriteVar<T>>,
2259 val: &T,
2260 ) where
2261 T: Serialize + DeserializeOwned,
2262 {
2263 for var in vars {
2264 self.write(var, val)
2265 }
2266 }
2267
2268 pub fn read<T: ReadVarValue>(&mut self, var: T) -> T::Value {
2269 var.read_value(self)
2270 }
2271
2272 pub(crate) fn get_var(&mut self, var: &str, is_side_effect: bool) -> Vec<u8> {
2273 let (v, is_secret) = self.runtime_var_db.get_var(var);
2274 self.has_read_secret |= is_secret && !is_side_effect;
2275 v
2276 }
2277
2278 pub fn dangerous_gh_set_global_env_var(
2285 &mut self,
2286 var: String,
2287 gh_env_var: String,
2288 ) -> anyhow::Result<()> {
2289 if !matches!(self.backend, FlowBackend::Github) {
2290 return Err(anyhow::anyhow!(
2291 "dangerous_set_gh_env_var can only be used on GitHub Actions"
2292 ));
2293 }
2294
2295 let gh_env_file_path = std::env::var("GITHUB_ENV")?;
2296 let mut gh_env_file = fs_err::OpenOptions::new()
2297 .append(true)
2298 .open(gh_env_file_path)?;
2299 let gh_env_var_assignment = format!(
2300 r#"{}<<EOF
2301{}
2302EOF
2303"#,
2304 gh_env_var, var
2305 );
2306 std::io::Write::write_all(&mut gh_env_file, gh_env_var_assignment.as_bytes())?;
2307
2308 Ok(())
2309 }
2310 }
2311 }
2312}
2313
2314pub trait FlowNodeBase {
2319 type Request: Serialize + DeserializeOwned;
2320
2321 fn imports(&mut self, ctx: &mut ImportCtx<'_>);
2322 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2323
2324 fn i_know_what_im_doing_with_this_manual_impl(&mut self);
2330}
2331
2332pub mod erased {
2333 use crate::node::FlowNodeBase;
2334 use crate::node::NodeCtx;
2335 use crate::node::user_facing::*;
2336
2337 pub struct ErasedNode<N: FlowNodeBase>(pub N);
2338
2339 impl<N: FlowNodeBase> ErasedNode<N> {
2340 pub fn from_node(node: N) -> Self {
2341 Self(node)
2342 }
2343 }
2344
2345 impl<N> FlowNodeBase for ErasedNode<N>
2346 where
2347 N: FlowNodeBase,
2348 {
2349 type Request = Box<[u8]>;
2351
2352 fn imports(&mut self, ctx: &mut ImportCtx<'_>) {
2353 self.0.imports(ctx)
2354 }
2355
2356 fn emit(&mut self, requests: Vec<Box<[u8]>>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
2357 let mut converted_requests = Vec::new();
2358 for req in requests {
2359 converted_requests.push(serde_json::from_slice(&req)?)
2360 }
2361
2362 self.0.emit(converted_requests, ctx)
2363 }
2364
2365 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2366 }
2367}
2368
2369#[derive(Clone, Copy, PartialEq, Eq, Hash)]
2371pub struct NodeHandle(std::any::TypeId);
2372
2373impl Ord for NodeHandle {
2374 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
2375 self.modpath().cmp(other.modpath())
2376 }
2377}
2378
2379impl PartialOrd for NodeHandle {
2380 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2381 Some(self.cmp(other))
2382 }
2383}
2384
2385impl std::fmt::Debug for NodeHandle {
2386 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2387 std::fmt::Debug::fmt(&self.try_modpath(), f)
2388 }
2389}
2390
2391impl NodeHandle {
2392 pub fn from_type<N: FlowNodeBase + 'static>() -> NodeHandle {
2393 NodeHandle(std::any::TypeId::of::<N>())
2394 }
2395
2396 pub fn from_modpath(modpath: &str) -> NodeHandle {
2397 node_luts::erased_node_by_modpath().get(modpath).unwrap().0
2398 }
2399
2400 pub fn try_from_modpath(modpath: &str) -> Option<NodeHandle> {
2401 node_luts::erased_node_by_modpath()
2402 .get(modpath)
2403 .map(|(s, _)| *s)
2404 }
2405
2406 pub fn new_erased_node(&self) -> Box<dyn FlowNodeBase<Request = Box<[u8]>>> {
2407 let ctor = node_luts::erased_node_by_typeid().get(self).unwrap();
2408 ctor()
2409 }
2410
2411 pub fn modpath(&self) -> &'static str {
2412 node_luts::modpath_by_node_typeid().get(self).unwrap()
2413 }
2414
2415 pub fn try_modpath(&self) -> Option<&'static str> {
2416 node_luts::modpath_by_node_typeid().get(self).cloned()
2417 }
2418
2419 pub fn dummy() -> NodeHandle {
2422 NodeHandle(std::any::TypeId::of::<()>())
2423 }
2424}
2425
2426pub fn list_all_registered_nodes() -> impl Iterator<Item = NodeHandle> {
2427 node_luts::modpath_by_node_typeid().keys().cloned()
2428}
2429
2430mod node_luts {
2445 use super::FlowNodeBase;
2446 use super::NodeHandle;
2447 use std::collections::HashMap;
2448 use std::sync::OnceLock;
2449
2450 pub(super) fn modpath_by_node_typeid() -> &'static HashMap<NodeHandle, &'static str> {
2451 static TYPEID_TO_MODPATH: OnceLock<HashMap<NodeHandle, &'static str>> = OnceLock::new();
2452
2453 TYPEID_TO_MODPATH.get_or_init(|| {
2454 let mut lookup = HashMap::new();
2455 for crate::node::private::FlowNodeMeta {
2456 module_path,
2457 ctor: _,
2458 typeid,
2459 } in crate::node::private::FLOW_NODES
2460 {
2461 let existing = lookup.insert(
2462 NodeHandle(*typeid),
2463 module_path
2464 .strip_suffix("::_only_one_call_to_flowey_node_per_module")
2465 .unwrap(),
2466 );
2467 assert!(existing.is_none())
2470 }
2471
2472 lookup
2473 })
2474 }
2475
2476 pub(super) fn erased_node_by_typeid()
2477 -> &'static HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>> {
2478 static LOOKUP: OnceLock<
2479 HashMap<NodeHandle, fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>>,
2480 > = OnceLock::new();
2481
2482 LOOKUP.get_or_init(|| {
2483 let mut lookup = HashMap::new();
2484 for crate::node::private::FlowNodeMeta {
2485 module_path: _,
2486 ctor,
2487 typeid,
2488 } in crate::node::private::FLOW_NODES
2489 {
2490 let existing = lookup.insert(NodeHandle(*typeid), *ctor);
2491 assert!(existing.is_none())
2494 }
2495
2496 lookup
2497 })
2498 }
2499
2500 pub(super) fn erased_node_by_modpath() -> &'static HashMap<
2501 &'static str,
2502 (
2503 NodeHandle,
2504 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2505 ),
2506 > {
2507 static MODPATH_LOOKUP: OnceLock<
2508 HashMap<
2509 &'static str,
2510 (
2511 NodeHandle,
2512 fn() -> Box<dyn FlowNodeBase<Request = Box<[u8]>>>,
2513 ),
2514 >,
2515 > = OnceLock::new();
2516
2517 MODPATH_LOOKUP.get_or_init(|| {
2518 let mut lookup = HashMap::new();
2519 for crate::node::private::FlowNodeMeta { module_path, ctor, typeid } in crate::node::private::FLOW_NODES {
2520 let existing = lookup.insert(module_path.strip_suffix("::_only_one_call_to_flowey_node_per_module").unwrap(), (NodeHandle(*typeid), *ctor));
2521 if existing.is_some() {
2522 panic!("conflicting node registrations at {module_path}! please ensure there is a single node per module!")
2523 }
2524 }
2525 lookup
2526 })
2527 }
2528}
2529
2530#[doc(hidden)]
2531pub mod private {
2532 pub use linkme;
2533
2534 pub struct FlowNodeMeta {
2535 pub module_path: &'static str,
2536 pub ctor: fn() -> Box<dyn super::FlowNodeBase<Request = Box<[u8]>>>,
2537 pub typeid: std::any::TypeId,
2538 }
2539
2540 #[linkme::distributed_slice]
2541 pub static FLOW_NODES: [FlowNodeMeta] = [..];
2542
2543 #[expect(unsafe_code)]
2545 #[linkme::distributed_slice(FLOW_NODES)]
2546 static DUMMY_FLOW_NODE: FlowNodeMeta = FlowNodeMeta {
2547 module_path: "<dummy>::_only_one_call_to_flowey_node_per_module",
2548 ctor: || unreachable!(),
2549 typeid: std::any::TypeId::of::<()>(),
2550 };
2551}
2552
2553#[doc(hidden)]
2554#[macro_export]
2555macro_rules! new_flow_node_base {
2556 (struct Node) => {
2557 #[non_exhaustive]
2559 pub struct Node;
2560
2561 mod _only_one_call_to_flowey_node_per_module {
2562 const _: () = {
2563 use $crate::node::private::linkme;
2564
2565 fn new_erased() -> Box<dyn $crate::node::FlowNodeBase<Request = Box<[u8]>>> {
2566 Box::new($crate::node::erased::ErasedNode(super::Node))
2567 }
2568
2569 #[linkme::distributed_slice($crate::node::private::FLOW_NODES)]
2570 #[linkme(crate = linkme)]
2571 static FLOW_NODE: $crate::node::private::FlowNodeMeta =
2572 $crate::node::private::FlowNodeMeta {
2573 module_path: module_path!(),
2574 ctor: new_erased,
2575 typeid: std::any::TypeId::of::<super::Node>(),
2576 };
2577 };
2578 }
2579 };
2580}
2581
2582pub trait FlowNode {
2669 type Request: Serialize + DeserializeOwned;
2673
2674 fn imports(ctx: &mut ImportCtx<'_>);
2686
2687 fn emit(requests: Vec<Self::Request>, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2690}
2691
2692#[macro_export]
2693macro_rules! new_flow_node {
2694 (struct Node) => {
2695 $crate::new_flow_node_base!(struct Node);
2696
2697 impl $crate::node::FlowNodeBase for Node
2698 where
2699 Node: FlowNode,
2700 {
2701 type Request = <Node as FlowNode>::Request;
2702
2703 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2704 <Node as FlowNode>::imports(dep)
2705 }
2706
2707 fn emit(
2708 &mut self,
2709 requests: Vec<Self::Request>,
2710 ctx: &mut $crate::node::NodeCtx<'_>,
2711 ) -> anyhow::Result<()> {
2712 <Node as FlowNode>::emit(requests, ctx)
2713 }
2714
2715 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2716 }
2717 };
2718}
2719
2720pub trait SimpleFlowNode {
2741 type Request: Serialize + DeserializeOwned;
2742
2743 fn imports(ctx: &mut ImportCtx<'_>);
2755
2756 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()>;
2758}
2759
2760#[macro_export]
2761macro_rules! new_simple_flow_node {
2762 (struct Node) => {
2763 $crate::new_flow_node_base!(struct Node);
2764
2765 impl $crate::node::FlowNodeBase for Node
2766 where
2767 Node: $crate::node::SimpleFlowNode,
2768 {
2769 type Request = <Node as $crate::node::SimpleFlowNode>::Request;
2770
2771 fn imports(&mut self, dep: &mut $crate::node::ImportCtx<'_>) {
2772 <Node as $crate::node::SimpleFlowNode>::imports(dep)
2773 }
2774
2775 fn emit(&mut self, requests: Vec<Self::Request>, ctx: &mut $crate::node::NodeCtx<'_>) -> anyhow::Result<()> {
2776 for req in requests {
2777 <Node as $crate::node::SimpleFlowNode>::process_request(req, ctx)?
2778 }
2779
2780 Ok(())
2781 }
2782
2783 fn i_know_what_im_doing_with_this_manual_impl(&mut self) {}
2784 }
2785 };
2786}
2787
2788pub trait IntoRequest {
2796 type Node: FlowNodeBase;
2797 fn into_request(self) -> <Self::Node as FlowNodeBase>::Request;
2798
2799 #[doc(hidden)]
2802 #[expect(nonstandard_style)]
2803 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self);
2804}
2805
2806#[doc(hidden)]
2807#[macro_export]
2808macro_rules! __flowey_request_inner {
2809 (@emit_struct [$req:ident]
2813 $(#[$a:meta])*
2814 $variant:ident($($tt:tt)*),
2815 $($rest:tt)*
2816 ) => {
2817 $(#[$a])*
2818 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2819 pub struct $variant($($tt)*);
2820
2821 impl IntoRequest for $variant {
2822 type Node = Node;
2823 fn into_request(self) -> $req {
2824 $req::$variant(self)
2825 }
2826 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2827 }
2828
2829 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2830 };
2831 (@emit_struct [$req:ident]
2832 $(#[$a:meta])*
2833 $variant:ident { $($tt:tt)* },
2834 $($rest:tt)*
2835 ) => {
2836 $(#[$a])*
2837 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2838 pub struct $variant {
2839 $($tt)*
2840 }
2841
2842 impl IntoRequest for $variant {
2843 type Node = Node;
2844 fn into_request(self) -> $req {
2845 $req::$variant(self)
2846 }
2847 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2848 }
2849
2850 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2851 };
2852 (@emit_struct [$req:ident]
2853 $(#[$a:meta])*
2854 $variant:ident,
2855 $($rest:tt)*
2856 ) => {
2857 $(#[$a])*
2858 #[derive(Serialize, Deserialize)]
2859 pub struct $variant;
2860
2861 impl IntoRequest for $variant {
2862 type Node = Node;
2863 fn into_request(self) -> $req {
2864 $req::$variant(self)
2865 }
2866 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2867 }
2868
2869 $crate::__flowey_request_inner!(@emit_struct [$req] $($rest)*);
2870 };
2871 (@emit_struct [$req:ident]
2872 ) => {};
2873
2874 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2878 $(#[$a:meta])*
2879 $variant:ident($($tt:tt)*),
2880 $($rest:tt)*
2881 ) => {
2882 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2883 };
2884 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2885 $(#[$a:meta])*
2886 $variant:ident { $($tt:tt)* },
2887 $($rest:tt)*
2888 ) => {
2889 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2890 };
2891 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2892 $(#[$a:meta])*
2893 $variant:ident,
2894 $($rest:tt)*
2895 ) => {
2896 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*), $($prev[$($prev_a,)*])* $variant[$($a,)*]] $($rest)*);
2897 };
2898 (@emit_req_enum [$req:ident($($root_a:meta,)*), $($prev:ident[$($prev_a:meta,)*])*]
2899 ) => {
2900 #[derive(Serialize, Deserialize)]
2901 pub enum $req {$(
2902 $(#[$prev_a])*
2903 $prev(self::req::$prev),
2904 )*}
2905
2906 impl IntoRequest for $req {
2907 type Node = Node;
2908 fn into_request(self) -> $req {
2909 self
2910 }
2911 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2912 }
2913 };
2914}
2915
2916#[macro_export]
2964macro_rules! flowey_request {
2965 (
2966 $(#[$root_a:meta])*
2967 pub enum_struct $req:ident {
2968 $($tt:tt)*
2969 }
2970 ) => {
2971 $crate::__flowey_request_inner!(@emit_req_enum [$req($($root_a,)*),] $($tt)*);
2972 pub mod req {
2973 use super::*;
2974 $crate::__flowey_request_inner!(@emit_struct [$req] $($tt)*);
2975 }
2976 };
2977
2978 (
2979 $(#[$a:meta])*
2980 pub enum $req:ident {
2981 $($tt:tt)*
2982 }
2983 ) => {
2984 $(#[$a])*
2985 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
2986 pub enum $req {
2987 $($tt)*
2988 }
2989
2990 impl $crate::node::IntoRequest for $req {
2991 type Node = Node;
2992 fn into_request(self) -> $req {
2993 self
2994 }
2995 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
2996 }
2997 };
2998
2999 (
3000 $(#[$a:meta])*
3001 pub struct $req:ident {
3002 $($tt:tt)*
3003 }
3004 ) => {
3005 $(#[$a])*
3006 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
3007 pub struct $req {
3008 $($tt)*
3009 }
3010
3011 impl $crate::node::IntoRequest for $req {
3012 type Node = Node;
3013 fn into_request(self) -> $req {
3014 self
3015 }
3016 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
3017 }
3018 };
3019
3020 (
3021 $(#[$a:meta])*
3022 pub struct $req:ident($($tt:tt)*);
3023 ) => {
3024 $(#[$a])*
3025 #[derive($crate::reexports::Serialize, $crate::reexports::Deserialize)]
3026 pub struct $req($($tt)*);
3027
3028 impl $crate::node::IntoRequest for $req {
3029 type Node = Node;
3030 fn into_request(self) -> $req {
3031 self
3032 }
3033 fn do_not_manually_impl_this_trait__use_the_flowey_request_macro_instead(&mut self) {}
3034 }
3035 };
3036}
3037
3038#[macro_export]
3055macro_rules! shell_cmd {
3056 ($rt:expr, $cmd:literal) => {{
3057 let flowey_sh = &$rt.sh;
3058 #[expect(clippy::disallowed_macros)]
3059 flowey_sh.wrap($crate::reexports::xshell::cmd!(flowey_sh.xshell(), $cmd))
3060 }};
3061}