summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCole Faust <colefaust@google.com>2023-12-20 14:54:40 -0800
committerEvan Martin <evan.martin@gmail.com>2023-12-21 11:07:34 -0800
commitc1fcd4a39620fe68e26f17fb35df66f9807a0984 (patch)
tree9d42335e7ada8a4489565a13dead3329836d8ed5
parent8aaa403e3f1325fed7d8018aa66a2f2dfa555f77 (diff)
downloadn2-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.rs15
-rw-r--r--src/load.rs3
-rw-r--r--src/parse.rs20
-rw-r--r--src/work.rs7
-rw-r--r--tests/e2e/mod.rs5
-rw-r--r--tests/e2e/validations.rs103
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(())
+}