diff options
author | Cole Faust <colefaust@google.com> | 2023-12-20 14:54:40 -0800 |
---|---|---|
committer | Evan Martin <evan.martin@gmail.com> | 2023-12-21 11:07:34 -0800 |
commit | c1fcd4a39620fe68e26f17fb35df66f9807a0984 (patch) | |
tree | 9d42335e7ada8a4489565a13dead3329836d8ed5 | |
parent | 8aaa403e3f1325fed7d8018aa66a2f2dfa555f77 (diff) | |
download | n2-c1fcd4a39620fe68e26f17fb35df66f9807a0984.tar.gz |
Add validation inputs
These were added to regular ninja in:
https://github.com/ninja-build/ninja/commit/04c410b15b70fb321928ffba19d697db15cb0121
Android uses them, so they're needed to port android to n2. (which is
something I'm exploring but not committing to)
-rw-r--r-- | src/graph.rs | 15 | ||||
-rw-r--r-- | src/load.rs | 3 | ||||
-rw-r--r-- | src/parse.rs | 20 | ||||
-rw-r--r-- | src/work.rs | 7 | ||||
-rw-r--r-- | tests/e2e/mod.rs | 5 | ||||
-rw-r--r-- | tests/e2e/validations.rs | 103 |
6 files changed, 146 insertions, 7 deletions
diff --git a/src/graph.rs b/src/graph.rs index f3577c1..245bd72 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -80,8 +80,9 @@ pub struct BuildIns { pub ids: Vec<FileId>, pub explicit: usize, pub implicit: usize, - // order_only count implied by other counts. - // pub order_only: usize, + pub order_only: usize, + // validation count implied by other counts. + // pub validation: usize, } /// Output files from a Build. @@ -204,7 +205,15 @@ impl Build { /// Note that we don't order on discovered_ins, because they're not allowed to /// affect build order. pub fn ordering_ins(&self) -> &[FileId] { - &self.ins.ids + &self.ins.ids[0..(self.ins.order_only + self.ins.explicit + self.ins.implicit)] + } + + /// Inputs that are needed before validating information. + /// Validation inputs will be built whenever this Build is built, but this Build will not + /// wait for them to complete before running. The validation inputs can fail to build, which + /// will cause the overall build to fail. + pub fn validation_ins(&self) -> &[FileId] { + &self.ins.ids[(self.ins.order_only + self.ins.explicit + self.ins.implicit)..] } /// Potentially update discovered_ins with a new set of deps, returning true if they changed. diff --git a/src/load.rs b/src/load.rs index f583309..7029d4c 100644 --- a/src/load.rs +++ b/src/load.rs @@ -82,7 +82,8 @@ impl Loader { ids: b.ins, explicit: b.explicit_ins, implicit: b.implicit_ins, - // order_only is unused + order_only: b.order_only_ins, + // validation is implied by the other counts }; let outs = graph::BuildOuts { ids: b.outs, diff --git a/src/parse.rs b/src/parse.rs index 382f6f0..22ead3c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -30,6 +30,7 @@ pub struct Build<'text, Path> { pub explicit_ins: usize, pub implicit_ins: usize, pub order_only_ins: usize, + pub validation_ins: usize, pub vars: VarList<'text>, } @@ -215,7 +216,8 @@ impl<'text> Parser<'text> { if self.scanner.peek() == '|' { self.scanner.next(); - if self.scanner.peek() == '|' { + let peek = self.scanner.peek(); + if peek == '|' || peek == '@' { self.scanner.back(); } else { self.read_paths_to(loader, &mut ins)?; @@ -225,11 +227,22 @@ impl<'text> Parser<'text> { if self.scanner.peek() == '|' { self.scanner.next(); - self.scanner.expect('|')?; - self.read_paths_to(loader, &mut ins)?; + if self.scanner.peek() == '@' { + self.scanner.back(); + } else { + self.scanner.expect('|')?; + self.read_paths_to(loader, &mut ins)?; + } } let order_only_ins = ins.len() - implicit_ins - explicit_ins; + if self.scanner.peek() == '|' { + self.scanner.next(); + self.scanner.expect('@')?; + self.read_paths_to(loader, &mut ins)?; + } + let validation_ins = ins.len() - order_only_ins - implicit_ins - explicit_ins; + self.scanner.skip('\r'); self.scanner.expect('\n')?; let vars = self.read_scoped_vars()?; @@ -242,6 +255,7 @@ impl<'text> Parser<'text> { explicit_ins, implicit_ins, order_only_ins, + validation_ins, vars, }) } diff --git a/src/work.rs b/src/work.rs index c8d6ca0..afdf2c2 100644 --- a/src/work.rs +++ b/src/work.rs @@ -216,6 +216,13 @@ impl BuildStates { self.want_file(graph, stack, id)?; ready = ready && graph.file(id).input.is_none(); } + for &id in build.validation_ins() { + // This build doesn't technically depend on the validation inputs, so + // allocate a new stack. Validation inputs could in theory depend on this build's + // outputs. + let mut stack = Vec::new(); + self.want_file(graph, &mut stack, id)?; + } if ready { self.set(id, build, BuildState::Ready); diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index b28e45d..a4cea68 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -4,6 +4,7 @@ mod basic; mod discovered; mod missing; mod regen; +mod validations; pub fn n2_binary() -> std::path::PathBuf { std::env::current_exe() @@ -72,6 +73,10 @@ impl TestSpace { std::fs::metadata(self.dir.path().join(path)) } + pub fn path(&self) -> &std::path::Path { + self.dir.path() + } + /// Invoke n2, returning process output. pub fn run(&self, cmd: &mut std::process::Command) -> std::io::Result<std::process::Output> { cmd.current_dir(self.dir.path()).output() diff --git a/tests/e2e/validations.rs b/tests/e2e/validations.rs new file mode 100644 index 0000000..1b1eb98 --- /dev/null +++ b/tests/e2e/validations.rs @@ -0,0 +1,103 @@ +use crate::e2e::*; + +#[test] +fn basic_validation() -> anyhow::Result<()> { + let space = TestSpace::new()?; + space.write( + "build.ninja", + &[ + TOUCH_RULE, + "build my_validation: touch", + "build out: touch |@ my_validation", + "", + ] + .join("\n"), + )?; + space.run_expect(&mut n2_command(vec!["out"]))?; + assert!(space.read("out").is_ok()); + assert!(space.read("my_validation").is_ok()); + Ok(()) +} + +#[cfg(unix)] +#[test] +fn build_starts_before_validation_finishes() -> anyhow::Result<()> { + let space = TestSpace::new()?; + space.write( + "build.ninja", + " +rule build_slow + command = sleep 0.3 && touch $out + +rule build_fast + command = sleep 0.1 && touch $out + +build out: build_fast regular_input |@ validation_input +build regular_input: build_fast +build validation_input: build_slow +", + )?; + let command = n2_command(vec!["out"]) + .current_dir(space.path()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdin(std::process::Stdio::null()) + .spawn()?; + std::thread::sleep(std::time::Duration::from_millis(50)); + assert!(space.read("out").is_err()); + assert!(space.read("regular_input").is_err()); + assert!(space.read("validation_input").is_err()); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(space.read("out").is_err()); + assert!(space.read("regular_input").is_ok()); + assert!(space.read("validation_input").is_err()); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(space.read("out").is_ok()); + assert!(space.read("regular_input").is_ok()); + assert!(space.read("validation_input").is_err()); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert!(space.read("out").is_ok()); + assert!(space.read("regular_input").is_ok()); + assert!(space.read("validation_input").is_ok()); + assert!(command.wait_with_output()?.status.success()); + Ok(()) +} + +#[cfg(unix)] +#[test] +fn build_fails_when_validation_fails() -> anyhow::Result<()> { + let space = TestSpace::new()?; + space.write( + "build.ninja", + " +rule touch + command = touch $out + +rule fail + command = exit 1 + +build out: touch |@ validation_input +build validation_input: fail +", + )?; + let output = space.run(&mut n2_command(vec!["out"]))?; + assert!(!output.status.success()); + Ok(()) +} + +#[test] +fn validation_inputs_break_cycles() -> anyhow::Result<()> { + let space = TestSpace::new()?; + space.write( + "build.ninja", + &[ + TOUCH_RULE, + "build out: touch |@ validation_input", + "build validation_input: touch out", + "", + ] + .join("\n"), + )?; + space.run_expect(&mut n2_command(vec!["out"]))?; + Ok(()) +} |