From 0d578c770a0a34d2d886b83d286ea3ac1498f3cb Mon Sep 17 00:00:00 2001 From: ok <59588824+jku20@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:26:49 -0400 Subject: [PATCH] [fud2] Implement Shell Commands With Dependencies (#2224) Implements shell commands with dependencies. This has been split off from the origin PR for ease of review. --- fud2/fud-core/src/run.rs | 65 +++-- fud2/fud-core/src/script/error.rs | 57 ++++ fud2/fud-core/src/script/plugin.rs | 247 +++++++++++++++--- fud2/tests/scripts/shell_deps/test.rhai | 29 ++ fud2/tests/snapshots/tests__test@s1_s2.snap | 34 +++ .../tests/snapshots/tests__test@s1_to_s2.snap | 34 +++ fud2/tests/snapshots/tests__test@s3_s4.snap | 16 ++ .../tests/snapshots/tests__test@s3_to_s4.snap | 16 ++ fud2/tests/snapshots/tests__test@s5_s6.snap | 28 ++ .../tests/snapshots/tests__test@s5_to_s6.snap | 28 ++ ...state0_state1_state2_to_state3_state4.snap | 15 +- .../tests__test@state0_state1_to_state2.snap | 4 +- ...sts__test@state0_through_t4_to_state1.snap | 6 +- ...sts__test@state0_through_t5_to_state1.snap | 6 +- .../tests__test@state0_to_state1.snap | 4 +- .../tests__test@state0_to_state2_state1.snap | 4 +- fud2/tests/tests.rs | 13 + 17 files changed, 532 insertions(+), 74 deletions(-) create mode 100644 fud2/tests/scripts/shell_deps/test.rhai create mode 100644 fud2/tests/snapshots/tests__test@s1_s2.snap create mode 100644 fud2/tests/snapshots/tests__test@s1_to_s2.snap create mode 100644 fud2/tests/snapshots/tests__test@s3_s4.snap create mode 100644 fud2/tests/snapshots/tests__test@s3_to_s4.snap create mode 100644 fud2/tests/snapshots/tests__test@s5_s6.snap create mode 100644 fud2/tests/snapshots/tests__test@s5_to_s6.snap diff --git a/fud2/fud-core/src/run.rs b/fud2/fud-core/src/run.rs index c0978b9fa8..c30fbdabf1 100644 --- a/fud2/fud-core/src/run.rs +++ b/fud2/fud-core/src/run.rs @@ -92,21 +92,36 @@ impl EmitBuild for EmitBuildFn { pub enum ConfigVar { /// The key for the config variable. Required(String), + /// The key for the config variable followed by the value it should be if the key is not found. Optional(String, String), } -/// The data required to emit a single, simple op whose body is composed of an ordered set of -/// commands. +/// A shell command tagged with required and generated files. +#[derive(Debug, Clone)] +pub struct Rule { + /// A shell command. + pub cmd: String, + + /// Files required by the command. + pub deps: Vec, + + /// Files generated by the command. + pub gens: Vec, +} + +/// The data required to emit a single op. pub struct RulesOp { - /// The name of the rule generated. - pub rule_name: String, + /// The name of the op. + pub op_name: String, - /// The shell commands emitted by to the generated rule. - /// Each of these must be run in order in a context where the variables in each cmd are - /// supplied. In particular, this means that variables of the form "$[i|o]" are in - /// scope. - pub cmds: Vec, + /// The commands run whenever the op run. Each of these must be run in order in a context where + /// the variables in each cmd are supplied. In particular, this means that variables of the + /// form "$[i|o]" are in scope. + /// + /// The second and third element of the tuple is a list of the indices of other commands the + /// command depends on. + pub cmds: Vec, /// Variables to be emitted pub config_vars: Vec, @@ -129,9 +144,7 @@ impl EmitBuild for RulesOp { inputs: &[&str], outputs: &[&str], ) -> EmitResult { - // Write the Ninja file. - let cmd = self.cmds.join(" && "); - emitter.rule(&self.rule_name, &cmd)?; + // Collect variables into ninja variables. let in_vars = inputs .iter() .enumerate() @@ -149,13 +162,27 @@ impl EmitBuild for RulesOp { } } - emitter.build_cmd_with_args( - outputs, - &self.rule_name, - inputs, - &[], - &vars, - )?; + // Write a sequence of rules and build statements for each cmd. + for (i, cmd) in self.cmds.iter().enumerate() { + // Don't need to output commands which never generate any outputs as these commands + // would never be run. + if cmd.gens.is_empty() { + continue; + } + + let rule_name = format!("{}_rule_{}", self.op_name, i + 1); + emitter.rule(&rule_name, &cmd.cmd)?; + let targets = cmd.gens.iter().map(|s| s.as_str()).collect_vec(); + let deps = cmd.deps.iter().map(|s| s.as_str()).collect_vec(); + emitter.build_cmd_with_args( + &targets, + &rule_name, + &deps, + &[], + &vars, + )?; + } + Ok(()) } } diff --git a/fud2/fud-core/src/script/error.rs b/fud2/fud-core/src/script/error.rs index 4bd84f8629..2a803b4e44 100644 --- a/fud2/fud-core/src/script/error.rs +++ b/fud2/fud-core/src/script/error.rs @@ -14,6 +14,13 @@ pub(super) enum RhaiSystemErrorKind { StateRef(String), BeganOp(String, String), NoOp, + NoDep(String), + DupTarget(String), + + /// The string is the type name of non-string value. + ExpectedString(String), + ExpectedShell, + ExpectedShellDeps, } impl RhaiSystemError { @@ -48,6 +55,41 @@ impl RhaiSystemError { } } + pub(super) fn no_dep(dep: &str) -> Self { + Self { + kind: RhaiSystemErrorKind::NoDep(dep.to_string()), + position: rhai::Position::NONE, + } + } + + pub(super) fn expected_string(v: &str) -> Self { + Self { + kind: RhaiSystemErrorKind::ExpectedString(v.to_string()), + position: rhai::Position::NONE, + } + } + + pub(super) fn dup_target(target: &str) -> Self { + Self { + kind: RhaiSystemErrorKind::DupTarget(target.to_string()), + position: rhai::Position::NONE, + } + } + + pub(super) fn expected_shell() -> Self { + Self { + kind: RhaiSystemErrorKind::ExpectedShell, + position: rhai::Position::NONE, + } + } + + pub(super) fn expected_shell_deps() -> Self { + Self { + kind: RhaiSystemErrorKind::ExpectedShellDeps, + position: rhai::Position::NONE, + } + } + pub(super) fn with_pos(mut self, p: rhai::Position) -> Self { self.position = p; self @@ -69,6 +111,21 @@ impl Display for RhaiSystemError { RhaiSystemErrorKind::NoOp => { write!(f, "Unable to find current op being built. Consider calling start_op_stmts earlier in the program.") } + RhaiSystemErrorKind::NoDep(dep) => { + write!(f, "Unable to find dep: `{dep:?}`. A call to `shell` with `{dep:?}` as an output must occur prior to this call.") + } + RhaiSystemErrorKind::ExpectedString(v) => { + write!(f, "Expected string, got: `{v:?}`.") + } + RhaiSystemErrorKind::DupTarget(target) => { + write!(f, "Duplicate target: `{target:?}`. Consider removing a shell command generating `{target:?}`.") + } + RhaiSystemErrorKind::ExpectedShell => { + write!(f, "Expected `shell`, got `shell_deps`. Ops may contain only one of `shell` or `shell_deps` calls, not calls to both") + } + RhaiSystemErrorKind::ExpectedShellDeps => { + write!(f, "Expected `shell_deps`, got shell. Ops may contain only one of `shell` or `shell_deps` calls, not calls to both") + } } } } diff --git a/fud2/fud-core/src/script/plugin.rs b/fud2/fud-core/src/script/plugin.rs index 632e24db80..040e32db35 100644 --- a/fud2/fud-core/src/script/plugin.rs +++ b/fud2/fud-core/src/script/plugin.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::{ cell::{RefCell, RefMut}, - collections::HashMap, + collections::{HashMap, HashSet}, path::{Path, PathBuf}, rc::Rc, }; @@ -18,18 +18,40 @@ use super::{ resolver::Resolver, }; +/// A collection of rules created from Rhai shell commands. +/// +/// This enum enforces if the input and output files of a shell command are unspecified, the +/// dependancy graph for the commands is a linked-list. +enum ShellCommands { + /// This is a collection of rules whose dependency graph is guaranteed to form a sequence. It + /// corresponds to commands added with the function defined by `reg_shell`. + SeqCmds(Vec), + /// This is a collection of rules allowing an arbitrary dependency graph. This corresponds to + /// command added with the function defined by `reg_shell_deps`. + Cmds(Vec), +} + /// The signature and implementation of an operation specified in Rhai. struct RhaiOp { /// Operation name. name: String, + /// Inputs states of the op. input_states: Vec, + /// Output states of the op. output_states: Vec, - /// An ordered list of the commands run when this op is required. - cmds: Vec, + + /// An ordered list of the commands run when this op is required. The second element of the + /// tuple is a list of the indices of commands to be run before the given command. + cmds: Option, + /// A list of the values required from the config. config_vars: Vec, + + /// This is a map from a file name generated or required as input by a call to the Rhai + /// function `shell`. It maps to the index of the command generating this file. + seen_deps: HashSet, } #[derive(Clone)] @@ -119,34 +141,150 @@ impl ScriptContext { let mut cur_op = self.cur_op.borrow_mut(); match *cur_op { None => { + let num_inputs = inputs.len(); *cur_op = Some(RhaiOp { name: name.to_string(), input_states: inputs, output_states: outputs, - cmds: vec![], + cmds: None, config_vars: vec![], + seen_deps: HashSet::from_iter((0..num_inputs).map(|i| { + format!("${}", crate::run::io_file_var_name(i, true)) + })), }); Ok(()) } Some(RhaiOp { - name: ref old_name, - input_states: _, - output_states: _, - cmds: _, - config_vars: _, + name: ref old_name, .. }) => Err(RhaiSystemError::began_op(old_name, name) .with_pos(pos) .into()), } } - /// Adds a shell command to the `cur_op`.Returns and error if `begin_op` has not been called - /// before this `end_op` and after any previous `end_op` - fn add_shell(&self, pos: Position, cmd: String) -> RhaiResult<()> { + /// Returns a fake file name for `cmd` to use as a dependancy by other commands dending on + /// `cmd`. + fn fake_file_name(op_name: &str, rule_idx: usize) -> String { + format!("_{}_rule_{}.fake", op_name, rule_idx + 1) + } + + /// Adds a shell command to the `cur_op`. Returns an error if `begin_op` has not been called + /// before this `end_op` and after any previous `end_op` or a dep cannot be found. The contents + /// of `gens` are also added to `self`. + /// + /// If `dep_on_all` is true, then the command will depend on all previous run commands. + fn add_shell( + &self, + pos: Position, + cmd: String, + deps: rhai::Array, + gens: rhai::Array, + is_shell: bool, + ) -> RhaiResult<()> { let mut cur_op = self.cur_op.borrow_mut(); match *cur_op { - Some(ref mut op_sig) => { - op_sig.cmds.push(cmd); + Some(RhaiOp { + ref mut cmds, + ref name, + ref mut seen_deps, + .. + }) => { + // Guard against mixing `shell` and `shell_deps`. + if matches!(cmds, Some(ShellCommands::SeqCmds(_))) && !is_shell + { + return Err(RhaiSystemError::expected_shell() + .with_pos(pos) + .into()); + } else if matches!(cmds, Some(ShellCommands::Cmds(_))) + && is_shell + { + return Err(RhaiSystemError::expected_shell_deps() + .with_pos(pos) + .into()); + } + + // If cmds has not yet been initialized, initilized it. + let mut cmd_list = match cmds.take() { + Some(ShellCommands::Cmds(cmds)) + | Some(ShellCommands::SeqCmds(cmds)) => cmds, + None => { + vec![] + } + }; + + // Extract dependancies. + let deps = if is_shell { + // Depends on all previously run command in sequence. If none exists, depend on + // nothing. + // + // Because this is a `shell` command, the last element, if it exists, is + // guarenteed to have a single output. + cmd_list + .last() + .map(|v| vec![v.gens[0].clone()]) + .unwrap_or(vec![]) + } else { + // Depends on files specified in `deps`. + deps.into_iter() + .map(|d| { + let type_name = d.type_name(); + let dep = + d.try_cast::().ok_or_else(|| { + RhaiSystemError::expected_string(type_name) + .with_pos(pos) + })?; + seen_deps + .get(&dep) + .ok_or_else(|| { + RhaiSystemError::no_dep(&dep).with_pos(pos) + }) + .map(|_| dep) + }) + .collect::, _>>()? + }; + + // Get generated files. + let mut gens = gens + .into_iter() + .map(|v| { + let type_name = v.type_name(); + v.try_cast::().ok_or_else(|| { + RhaiSystemError::expected_string(type_name) + .with_pos(pos) + }) + }) + .collect::, _>>()?; + + // All shell commands generate a "fake" file for dependancy tracking because + // generated and depended on files aren't tracked. + if is_shell { + gens.push(Self::fake_file_name(name, cmd_list.len())); + } + + // Duplicated targets should be an error. + for target in &gens { + let matched_target = cmd_list + .iter() + .flat_map(|c| &c.gens) + .find(|s| *s == target); + if let Some(t) = matched_target { + return Err(RhaiSystemError::dup_target(t) + .with_pos(pos) + .into()); + } + } + + // Add all generated files to seen dependancies. + for v in &gens { + seen_deps.insert(v.clone()); + } + + cmd_list.push(crate::run::Rule { cmd, deps, gens }); + *cmds = Some(if is_shell { + ShellCommands::SeqCmds(cmd_list) + } else { + ShellCommands::Cmds(cmd_list) + }); Ok(()) } None => Err(RhaiSystemError::no_op().with_pos(pos).into()), @@ -185,13 +323,41 @@ impl ScriptContext { ref output_states, ref cmds, ref config_vars, + seen_deps: _, }) => { // Create the emitter. - let cmds = cmds.clone(); + let cmds = match cmds { + Some(ShellCommands::SeqCmds(c)) => { + // The final command should generate the outputs otherwise none of the + // rules would get called. + let outputs: Vec<_> = (0..output_states.len()) + .map(|i| crate::run::io_file_var_name(i, false)) + .collect(); + let mut c = c.clone(); + if let Some(v) = c.last_mut() { + let outputs = + outputs.iter().map(|v| format!("${}", v)); + v.gens.extend(outputs); + } + + // Similarly, the first command should depend on all inputs. + let inputs: Vec<_> = (0..input_states.len()) + .map(|i| crate::run::io_file_var_name(i, true)) + .collect(); + if let Some(v) = c.first_mut() { + let inputs = + inputs.iter().map(|v| format!("${}", v)); + v.deps.extend(inputs); + } + c + } + Some(ShellCommands::Cmds(c)) => c.clone(), + None => vec![], + }; let op_name = name.clone(); let config_vars = config_vars.clone(); let op_emitter = crate::run::RulesOp { - rule_name: op_name, + op_name, cmds, config_vars, }; @@ -438,27 +604,40 @@ impl ScriptRunner { ); } - /// Registers a Rhai function which starts the parser listening for shell commands, how an op - /// does its transformation. - fn reg_start_op_stmts(&mut self, sctx: ScriptContext) { + /// Registers a Rhai function which adds shell commands to be used by an op based on a given + /// command and specified generated files and dependancies. + fn reg_shell_deps(&mut self, sctx: ScriptContext) { self.engine.register_fn( - "start_op_stmts", + "shell_deps", move |ctx: rhai::NativeCallContext, - name: &str, - inputs: rhai::Array, - outputs: rhai::Array| + cmd: &str, + deps: rhai::Array, + gens: rhai::Array| -> RhaiResult<_> { - sctx.begin_op(ctx.position(), name, inputs, outputs) + sctx.add_shell( + ctx.position(), + cmd.to_string(), + deps, + gens, + false, + ) }, ); } - /// Registers a Rhai function which adds shell commands to be used by an op. + /// Registers a Rhai function which adds shell commands to be used by an op based on a given + /// command. fn reg_shell(&mut self, sctx: ScriptContext) { self.engine.register_fn( "shell", move |ctx: rhai::NativeCallContext, cmd: &str| -> RhaiResult<_> { - sctx.add_shell(ctx.position(), cmd.to_string()) + sctx.add_shell( + ctx.position(), + cmd.to_string(), + rhai::Array::new(), + rhai::Array::new(), + true, + ) }, ); } @@ -498,18 +677,6 @@ impl ScriptRunner { ); } - /// Registers a Rhai function which stops the parser listening to shell commands and adds the - /// created op to `self.builder`. - fn reg_end_op_stmts(&mut self, sctx: ScriptContext) { - let bld = Rc::clone(&self.builder); - self.engine.register_fn( - "end_op_stmts", - move |ctx: rhai::NativeCallContext| -> RhaiResult<_> { - sctx.end_op(ctx.position(), bld.borrow_mut()) - }, - ); - } - /// A parse function to add custom syntax for defining ops to rhai. fn parse_defop( symbols: &[ImmutableString], @@ -610,6 +777,7 @@ impl ScriptRunner { true, move |context, inputs, state| { let state = state.clone_cast::(); + // Collect name of op and input/output states. let op_name = inputs.first().unwrap().get_string_value().unwrap(); @@ -692,9 +860,8 @@ impl ScriptRunner { let sctx = self.script_context(path.to_path_buf()); self.reg_rule(sctx.clone()); self.reg_op(sctx.clone()); - self.reg_start_op_stmts(sctx.clone()); self.reg_shell(sctx.clone()); - self.reg_end_op_stmts(sctx.clone()); + self.reg_shell_deps(sctx.clone()); self.reg_defop_syntax(sctx.clone()); self.reg_config(sctx.clone()); self.reg_config_or(sctx.clone()); diff --git a/fud2/tests/scripts/shell_deps/test.rhai b/fud2/tests/scripts/shell_deps/test.rhai new file mode 100644 index 0000000000..101f147b6d --- /dev/null +++ b/fud2/tests/scripts/shell_deps/test.rhai @@ -0,0 +1,29 @@ +const s1 = state("s1", []); +const s2 = state("s2", []); +const s3 = state("s3", []); +const s4 = state("s4", []); +const s5 = state("s5", []); +const s6 = state("s6", []); + +// Testing all outputs covered by `shell_deps` and general enumerations. +defop t1(a: s1) >> b: s2 { + shell_deps("echo one", [], ["f1"]); + shell_deps("echo two", ["f1"], []); + shell_deps("echo four", [], []); + shell_deps("echo five", ["f1"], ["f2"]); + shell_deps("echo five", [a], []); + shell_deps("echo five", [], [b]); + shell_deps("echo five", [a, "f1", "f2"], ["f4", "f5"]); +} + +// Testing not having all outputs covered by `shell_deps` commands. +defop t2(a: s3) >> b: s4 { + shell_deps("echo five", [a], ["f4", "f5"]); +} + +// Testing `shell`. +defop t3(a: s5) >> b: s6 { + shell("echo Char"); + shell("echo Ray"); + shell("echo Saura"); +} diff --git a/fud2/tests/snapshots/tests__test@s1_s2.snap b/fud2/tests/snapshots/tests__test@s1_s2.snap new file mode 100644 index 0000000000..3dd4f9ad23 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s1_s2.snap @@ -0,0 +1,34 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s1 -> s2" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t1_rule_1 + command = echo one +build f1: t1_rule_1 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_4 + command = echo five +build f2: t1_rule_4 f1 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_6 + command = echo five +build $o0: t1_rule_6 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_7 + command = echo five +build f4 f5: t1_rule_7 $i0 f1 f2 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + + +default _to_stdout_s2 diff --git a/fud2/tests/snapshots/tests__test@s1_to_s2.snap b/fud2/tests/snapshots/tests__test@s1_to_s2.snap new file mode 100644 index 0000000000..3dd4f9ad23 --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s1_to_s2.snap @@ -0,0 +1,34 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s1 -> s2" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t1_rule_1 + command = echo one +build f1: t1_rule_1 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_4 + command = echo five +build f2: t1_rule_4 f1 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_6 + command = echo five +build $o0: t1_rule_6 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + +rule t1_rule_7 + command = echo five +build f4 f5: t1_rule_7 $i0 f1 f2 + i0 = _from_stdin_s1 + o0 = _to_stdout_s2 + + +default _to_stdout_s2 diff --git a/fud2/tests/snapshots/tests__test@s3_s4.snap b/fud2/tests/snapshots/tests__test@s3_s4.snap new file mode 100644 index 0000000000..ca9a75566d --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s3_s4.snap @@ -0,0 +1,16 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s3 -> s4" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t2_rule_1 + command = echo five +build f4 f5: t2_rule_1 $i0 + i0 = _from_stdin_s3 + o0 = _to_stdout_s4 + + +default _to_stdout_s4 diff --git a/fud2/tests/snapshots/tests__test@s3_to_s4.snap b/fud2/tests/snapshots/tests__test@s3_to_s4.snap new file mode 100644 index 0000000000..ca9a75566d --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s3_to_s4.snap @@ -0,0 +1,16 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s3 -> s4" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t2_rule_1 + command = echo five +build f4 f5: t2_rule_1 $i0 + i0 = _from_stdin_s3 + o0 = _to_stdout_s4 + + +default _to_stdout_s4 diff --git a/fud2/tests/snapshots/tests__test@s5_s6.snap b/fud2/tests/snapshots/tests__test@s5_s6.snap new file mode 100644 index 0000000000..59aa2ea31c --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s5_s6.snap @@ -0,0 +1,28 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s5 -> s6" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t3_rule_1 + command = echo Char +build _t3_rule_1.fake: t3_rule_1 + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + +rule t3_rule_2 + command = echo Ray +build _t3_rule_2.fake: t3_rule_2 _t3_rule_1.fake + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + +rule t3_rule_3 + command = echo Saura +build _t3_rule_3.fake $o0: t3_rule_3 _t3_rule_2.fake + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + + +default _to_stdout_s6 diff --git a/fud2/tests/snapshots/tests__test@s5_to_s6.snap b/fud2/tests/snapshots/tests__test@s5_to_s6.snap new file mode 100644 index 0000000000..619f84ad0a --- /dev/null +++ b/fud2/tests/snapshots/tests__test@s5_to_s6.snap @@ -0,0 +1,28 @@ +--- +source: fud2/tests/tests.rs +description: "emit request: s5 -> s6" +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +rule t3_rule_1 + command = echo Char +build _t3_rule_1.fake: t3_rule_1 $i0 + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + +rule t3_rule_2 + command = echo Ray +build _t3_rule_2.fake: t3_rule_2 _t3_rule_1.fake + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + +rule t3_rule_3 + command = echo Saura +build _t3_rule_3.fake $o0: t3_rule_3 _t3_rule_2.fake + i0 = _from_stdin_s5 + o0 = _to_stdout_s6 + + +default _to_stdout_s6 diff --git a/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap b/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap index 01c59e594a..7a0ed935d6 100644 --- a/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap +++ b/fud2/tests/snapshots/tests__test@state0_state1_state2_to_state3_state4.snap @@ -6,9 +6,18 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t3 - command = echo inputs $i0 $i1 $i2 && echo outputs $o0 $o1 -build _to_stdout_state3 _to_stdout_state4: t3 _from_stdin_state0 _from_stdin_state1 _from_stdin_state2 +rule t3_rule_1 + command = echo inputs $i0 $i1 $i2 +build _t3_rule_1.fake: t3_rule_1 $i0 $i1 $i2 + i0 = _from_stdin_state0 + i1 = _from_stdin_state1 + i2 = _from_stdin_state2 + o0 = _to_stdout_state3 + o1 = _to_stdout_state4 + +rule t3_rule_2 + command = echo outputs $o0 $o1 +build _t3_rule_2.fake $o0 $o1: t3_rule_2 _t3_rule_1.fake i0 = _from_stdin_state0 i1 = _from_stdin_state1 i2 = _from_stdin_state2 diff --git a/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap b/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap index 1ca3828b59..99bfdc6cd4 100644 --- a/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap +++ b/fud2/tests/snapshots/tests__test@state0_state1_to_state2.snap @@ -6,9 +6,9 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t1 +rule t1_rule_1 command = echo owo -build _to_stdout_state2: t1 _from_stdin_state0 _from_stdin_state1 +build _t1_rule_1.fake $o0: t1_rule_1 $i0 $i1 i0 = _from_stdin_state0 i1 = _from_stdin_state1 o0 = _to_stdout_state2 diff --git a/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap index 2541b82a8a..411771f11d 100644 --- a/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap +++ b/fud2/tests/snapshots/tests__test@state0_through_t4_to_state1.snap @@ -6,10 +6,10 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t4 - command = echo ${c0} c0 = v1 -build _to_stdout_state1: t4 _from_stdin_state0 +rule t4_rule_1 + command = echo ${c0} +build _t4_rule_1.fake $o0: t4_rule_1 $i0 i0 = _from_stdin_state0 o0 = _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap index b50dc74348..c1f69f46fb 100644 --- a/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap +++ b/fud2/tests/snapshots/tests__test@state0_through_t5_to_state1.snap @@ -6,10 +6,10 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t5 - command = echo ${this-config-better-not-exist} this-config-better-not-exist = gholdengo -build _to_stdout_state1: t5 _from_stdin_state0 +rule t5_rule_1 + command = echo ${this-config-better-not-exist} +build _t5_rule_1.fake $o0: t5_rule_1 $i0 i0 = _from_stdin_state0 o0 = _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_to_state1.snap b/fud2/tests/snapshots/tests__test@state0_to_state1.snap index b2cd496a36..2b98a66b5f 100644 --- a/fud2/tests/snapshots/tests__test@state0_to_state1.snap +++ b/fud2/tests/snapshots/tests__test@state0_to_state1.snap @@ -6,9 +6,9 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t0 +rule t0_rule_1 command = echo uwu -build _to_stdout_state1: t0 _from_stdin_state0 +build _t0_rule_1.fake $o0: t0_rule_1 $i0 i0 = _from_stdin_state0 o0 = _to_stdout_state1 diff --git a/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap b/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap index ff06d4ce3b..d6298053cf 100644 --- a/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap +++ b/fud2/tests/snapshots/tests__test@state0_to_state2_state1.snap @@ -6,9 +6,9 @@ build-tool = fud2 rule get-rsrc command = $build-tool get-rsrc $out -rule t2 +rule t2_rule_1 command = echo -_- -build _to_stdout_state1 _to_stdout_state2: t2 _from_stdin_state0 +build _t2_rule_1.fake $o0 $o1: t2_rule_1 $i0 i0 = _from_stdin_state0 o0 = _to_stdout_state1 o1 = _to_stdout_state2 diff --git a/fud2/tests/tests.rs b/fud2/tests/tests.rs index 5b0a4d9271..a936a3f928 100644 --- a/fud2/tests/tests.rs +++ b/fud2/tests/tests.rs @@ -297,6 +297,19 @@ fn frontend_tests() { } } +#[test] +fn shell_deps_tests() { + let driver = driver_from_path("shell_deps"); + request(&driver, &["s1"], &["s2"], &[]).test(&driver); + request(&driver, &["s3"], &["s4"], &[]).test(&driver); +} + +#[test] +fn shell_tests() { + let driver = driver_from_path("shell_deps"); + request(&driver, &["s5"], &["s6"], &[]).test(&driver); +} + #[test] fn simple_defops() { let driver = driver_from_path("defop");