Skip to content

Commit

Permalink
implement -t restat to make cmake happy
Browse files Browse the repository at this point in the history
Fixes #40.
  • Loading branch information
evmar committed Jun 29, 2023
1 parent 31d6c98 commit 055ce72
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 26 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,25 @@ $ cargo install --git https://github.com/evmar/n2
$ n2 -C some/build/dir some-target
```

### Replacing Ninja when using CMake
### Using with CMake

When CMake generates Ninja files it attempts run a program named `ninja` with
some particular Ninja behaviors. If you have Ninja installed already then things
will continue to work as before.
some particular Ninja behaviors. In particular, it attempts to inform Ninja/n2
that its generated build files are up to date so that the build system doesn't
attempt to rebuild them.

If you don't have Ninja installed at all, n2 can emulate the expected CMake
behavior when invoked as `ninja`. To do this you create a symlink named `ninja`
somewhere in your `$PATH`, such that CMake can discover it.
n2 can emulate the expected CMake behavior when invoked as `ninja`. To do this
you create a symlink named `ninja` somewhere in your `$PATH`, such that CMake
can discover it.

- UNIX: `ln -s path/to/n2 ninja`
- Windows(cmd): `mklink ninja.exe path\to\n2`
- Windows(PowerShell): `New-Item -Type Symlink ninja.exe -Target path\to\n2`

> **Warning**\
> If you don't have Ninja installed at all, you must install such a symlink
> because CMake attempts to invoke `ninja` when generating build files!
## The console output

While building, n2 displays build progress like this:
Expand Down
53 changes: 34 additions & 19 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ fn build(
state.pools,
);

let mut tasks_finished = 0;

// Attempt to rebuild build.ninja.
let build_file_target = work.lookup(&build_filename);
if let Some(target) = build_file_target {
Expand All @@ -43,8 +45,9 @@ fn build(
// a step that doesn't touch build.ninja. We should instead
// verify the specific FileId was updated.
}
Some(_) => {
Some(n) => {
// Regenerated build.ninja; start over.
tasks_finished = n;
state = trace::scope("load::read", || load::read(&build_filename))?;
work = work::Work::new(
state.graph,
Expand Down Expand Up @@ -77,8 +80,9 @@ fn build(
anyhow::bail!("no path specified and no default");
}

let ret = trace::scope("work.run", || work.run());
ret
let tasks = trace::scope("work.run", || work.run())?;
// Include any tasks from initial build in final count of steps.
Ok(tasks.map(|n| n + tasks_finished))
}

fn default_parallelism() -> anyhow::Result<usize> {
Expand Down Expand Up @@ -129,7 +133,7 @@ struct Args {
}

fn run_impl() -> anyhow::Result<i32> {
let fake_ninja_compat = Path::new(&std::env::args().nth(0).unwrap())
let mut fake_ninja_compat = Path::new(&std::env::args().nth(0).unwrap())
.file_name()
.unwrap()
== std::ffi::OsStr::new(&format!("ninja{}", std::env::consts::EXE_SUFFIX));
Expand All @@ -143,19 +147,17 @@ fn run_impl() -> anyhow::Result<i32> {
},
failures_left: Some(args.keep_going).filter(|&n| n > 0),
explain: false,
adopt: false,
};

if args.version {
if fake_ninja_compat {
println!("1.10.2");
} else {
println!("{}", env!("CARGO_PKG_VERSION"));
}
return Ok(0);
if let Some(dir) = args.chdir {
let dir = Path::new(&dir);
std::env::set_current_dir(dir).map_err(|err| anyhow!("chdir {:?}: {}", dir, err))?;
}

if let Some(debug) = args.debug {
match debug.as_str() {
"ninja_compat" => fake_ninja_compat = true,
"explain" => options.explain = true,
"list" => {
println!("debug tools:");
Expand All @@ -168,27 +170,40 @@ fn run_impl() -> anyhow::Result<i32> {
}
}

if args.version {
if fake_ninja_compat {
// CMake requires a particular Ninja version.
println!("1.10.2");
return Ok(0);
} else {
println!("{}", env!("CARGO_PKG_VERSION"));
}
return Ok(0);
}

if let Some(tool) = args.tool {
match tool.as_str() {
"list" => {
println!("subcommands:");
println!(" (none yet, but see README if you're looking here trying to get CMake to work)");
return Ok(1);
}
"recompact" if fake_ninja_compat => {
// CMake unconditionally invokes this tool, yuck.
return Ok(0); // do nothing
}
"restat" if fake_ninja_compat => {
// CMake invokes this after generating build files; mark build
// targets as up to date by running the build with "adopt" flag
// on.
options.adopt = true;
}
_ => {
if fake_ninja_compat {
return Ok(0);
}
anyhow::bail!("unknown -t {:?}, use -t list to list", tool);
}
}
}

if let Some(dir) = args.chdir {
let dir = Path::new(&dir);
std::env::set_current_dir(dir).map_err(|err| anyhow!("chdir {:?}: {}", dir, err))?;
}

match build(options, args.build_file, args.targets, args.verbose)? {
None => {
// Don't print any summary, the failing task is enough info.
Expand Down
19 changes: 18 additions & 1 deletion src/work.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ pub struct Options {
pub parallelism: usize,
/// When true, verbosely explain why targets are considered dirty.
pub explain: bool,
/// When true, just mark targets up to date without running anything.
pub adopt: bool,
}

pub struct Work<'a> {
Expand All @@ -294,6 +296,7 @@ pub struct Work<'a> {
progress: &'a mut dyn Progress,
failures_left: Option<usize>,
explain: bool,
adopt: bool,
file_state: FileState,
last_hashes: Hashes,
build_states: BuildStates,
Expand All @@ -317,6 +320,7 @@ impl<'a> Work<'a> {
progress,
failures_left: options.failures_left,
explain: options.explain,
adopt: options.adopt,
file_state,
last_hashes,
build_states: BuildStates::new(build_count, pools),
Expand Down Expand Up @@ -690,7 +694,20 @@ impl<'a> Work<'a> {
// Not dirty; go directly to the Done state.
self.ready_dependents(id);
} else {
self.build_states.enqueue(id, self.graph.build(id))?;
if self.adopt {
// Act as if the target already finished.
self.record_finished(
id,
task::TaskResult {
termination: task::Termination::Success,
output: vec![],
discovered_deps: None,
},
)?;
self.ready_dependents(id);
} else {
self.build_states.enqueue(id, self.graph.build(id))?;
}
}
made_progress = true;
}
Expand Down
30 changes: 30 additions & 0 deletions tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,33 @@ fn explain() -> anyhow::Result<()> {

Ok(())
}

#[test]
fn restat() -> anyhow::Result<()> {
let space = TestSpace::new()?;
space.write(
"build.ninja",
&[TOUCH_RULE, "build build.ninja: touch in", ""].join("\n"),
)?;
space.write("in", "")?;

let out = space.run_expect(&mut n2_command(vec![
"-d",
"ninja_compat",
"-t",
"restat",
"build.ninja",
]))?;
assert_output_not_contains(&out, "touch build.ninja");

// Building the build file should do nothing, because restat marked it up to date.
let out = space.run_expect(&mut n2_command(vec!["build.ninja"]))?;
assert_output_not_contains(&out, "touch build.ninja");

// But modifying the input should cause it to be up to date.
space.write("in", "")?;
let out = space.run_expect(&mut n2_command(vec!["build.ninja"]))?;
assert_output_contains(&out, "touch build.ninja");

Ok(())
}

0 comments on commit 055ce72

Please sign in to comment.