diff --git a/doc/missing.md b/doc/missing.md index 87c0152..03f6cc1 100644 --- a/doc/missing.md +++ b/doc/missing.md @@ -10,7 +10,6 @@ ## Missing flags - `-l`, load average throttling -- `-n`, dry run ### Missing subcommands diff --git a/src/run.rs b/src/run.rs index 7dfce83..f20e8b1 100644 --- a/src/run.rs +++ b/src/run.rs @@ -19,6 +19,7 @@ enum BuildResult { struct BuildParams<'a> { parallelism: usize, regen: bool, + dry_run: bool, keep_going: usize, target_names: &'a [String], build_filename: &'a String, @@ -36,6 +37,7 @@ fn build(progress: &mut ConsoleProgress, params: &BuildParams) -> anyhow::Result &state.hashes, &mut state.db, progress, + params.dry_run, params.keep_going, state.pools, params.parallelism, @@ -125,6 +127,7 @@ fn run_impl() -> anyhow::Result { "N", ); opts.optflag("h", "help", ""); + opts.optflag("n", "", "dry run"); opts.optflag("v", "verbose", "print executed command lines"); if fake_ninja_compat { opts.optflag("", "version", "print fake ninja version"); @@ -195,6 +198,8 @@ fn run_impl() -> anyhow::Result { build_filename = name; } + let dry_run = matches.opt_present("n"); + let mut progress = ConsoleProgress::new(matches.opt_present("v"), use_fancy_terminal()); // Build once with regen=true, and if the result says we regenerated the @@ -203,6 +208,7 @@ fn run_impl() -> anyhow::Result { let mut params = BuildParams { parallelism, regen: true, + dry_run, keep_going, target_names: &matches.free, build_filename: &build_filename, diff --git a/src/task.rs b/src/task.rs index 7e0d267..9f71c32 100644 --- a/src/task.rs +++ b/src/task.rs @@ -80,10 +80,18 @@ fn run_task( cmdline: &str, depfile: Option<&str>, rspfile: Option<&RspFile>, + dry_run: bool, ) -> anyhow::Result { if let Some(rspfile) = rspfile { write_rspfile(rspfile)?; } + if dry_run { + return Ok(TaskResult { + termination: Termination::Success, + output: vec![], + discovered_deps: None, + }); + } let mut result = run_command(cmdline)?; if result.termination == Termination::Success { if let Some(depfile) = depfile { @@ -308,18 +316,17 @@ impl Runner { cmdline: String, depfile: Option, rspfile: Option, + dry_run: bool, ) { let tid = self.tids.claim(); let tx = self.finished_send.clone(); std::thread::spawn(move || { let start = Instant::now(); - let result = - run_task(&cmdline, depfile.as_deref(), rspfile.as_ref()).unwrap_or_else(|err| { - TaskResult { - termination: Termination::Failure, - output: err.to_string().into_bytes(), - discovered_deps: None, - } + let result = run_task(&cmdline, depfile.as_deref(), rspfile.as_ref(), dry_run) + .unwrap_or_else(|err| TaskResult { + termination: Termination::Failure, + output: err.to_string().into_bytes(), + discovered_deps: None, }); let finish = Instant::now(); diff --git a/src/work.rs b/src/work.rs index fc0c68d..0560765 100644 --- a/src/work.rs +++ b/src/work.rs @@ -308,6 +308,7 @@ pub struct Work<'a> { file_state: FileState, last_hashes: &'a Hashes, build_states: BuildStates, + dry_run: bool, runner: task::Runner, } @@ -317,6 +318,7 @@ impl<'a> Work<'a> { last_hashes: &'a Hashes, db: &'a mut db::Writer, progress: &'a mut dyn Progress, + dry_run: bool, keep_going: usize, pools: Vec<(String, usize)>, parallelism: usize, @@ -327,6 +329,7 @@ impl<'a> Work<'a> { graph, db, progress, + dry_run, keep_going, file_state, last_hashes, @@ -445,6 +448,12 @@ impl<'a> Work<'a> { } } + if self.dry_run { + // In dry run mode we didn't actually run the task so we don't + // expect the output files to change. + return Ok(()); + } + // Stat all the outputs. This step just finished, so we need to update // any cached state of the output files to reflect their new state. let build = self.graph.build(id); @@ -517,7 +526,7 @@ impl<'a> Work<'a> { // stat any non-generated inputs if needed. // Note that generated inputs should already have been stat()ed when - // they were visited as outputs. + // they were visited as outputs unless we're doing a dry run. // For dirtying_ins, ensure we both have mtimes and that the files are present. for &id in build.dirtying_ins() { @@ -525,7 +534,7 @@ impl<'a> Work<'a> { let mtime = match self.file_state.get(id) { Some(mtime) => mtime, None => { - if file.input.is_some() { + if file.input.is_some() && !self.dry_run { // This is a logic error in ninja; any generated file should // already have been visited by this point. panic!( @@ -540,6 +549,9 @@ impl<'a> Work<'a> { if workaround_missing_phony_deps { continue; } + if self.dry_run { + return Ok(true); + } anyhow::bail!("{}: input {} missing", build.location, file.name); } } @@ -670,6 +682,7 @@ impl<'a> Work<'a> { build.cmdline.clone().unwrap(), build.depfile.clone(), build.rspfile.clone(), + self.dry_run, ); self.progress.task_state(id, build, BuildState::Running); made_progress = true;