Merge "Add a debug tool to show all (recursive) inputs to a given target."
diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc
index c2b00db..7d36701 100644
--- a/doc/manual.asciidoc
+++ b/doc/manual.asciidoc
@@ -830,11 +830,11 @@
`phony_output`, so that it's not possible to accidentally cause everything to
rebuild on every run.
+
-When `-w usesphonyoutputs=yes` is set on the ninja command line, it becomes an
+When `-o usesphonyoutputs=yes` is set on the ninja command line, it becomes an
error for a `phony` rule to cause rebuilds, so that users can be found and
migrated.
+
-Properly using `phony_output` and turning on `-w usesphonyoutputs=yes` allows
+Properly using `phony_output` and turning on `-o usesphonyoutputs=yes` allows
the `-w outputdir={err,warn}` (consider output files that are directories as
errors/warnings), `-w missingoutfile={err,warn}` (error/warn when an output file
does not exist after a successful rule execution), and `-w oldoutput={err,warn}`
diff --git a/src/build.cc b/src/build.cc
index 5a7221d..19121ee 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -535,12 +535,22 @@
status_->BuildEdgeStarted(edge, start_time_millis);
if (!edge->IsPhonyOutput()) {
- // Create directories necessary for outputs.
- // XXX: this will block; do we care?
for (vector<Node*>::iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o) {
+ // Create directories necessary for outputs.
+ // XXX: this will block; do we care?
if (!disk_interface_->MakeDirs((*o)->path()))
return false;
+
+ if (!(*o)->exists())
+ continue;
+
+ // Remove existing outputs for non-restat rules.
+ // XXX: this will block; do we care?
+ if (config_.pre_remove_output_files && !edge->IsRestat() && !config_.dry_run) {
+ if (disk_interface_->RemoveFile((*o)->path()) < 0)
+ return false;
+ }
}
}
@@ -603,13 +613,16 @@
vector<Node*> nodes_cleaned;
TimeStamp newest_input = 0;
+ Node* newest_input_node = nullptr;
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end() - edge->order_only_deps_; ++i) {
TimeStamp input_mtime = (*i)->mtime();
if (input_mtime == -1)
return false;
- if (input_mtime > newest_input)
+ if (input_mtime > newest_input) {
newest_input = input_mtime;
+ newest_input_node = (*i);
+ }
}
for (vector<Node*>::iterator o = edge->outputs_.begin();
@@ -631,8 +644,10 @@
} else if (!restat && new_mtime < newest_input) {
if (!result->output.empty())
result->output.append("\n");
- result->output.append("ninja: Missing `restat`? An output file is older than the most recent input: ");
+ result->output.append("ninja: Missing `restat`? An output file is older than the most recent input:\n output: ");
result->output.append((*o)->path());
+ result->output.append("\n input: ");
+ result->output.append(newest_input_node->path());
if (config_.old_output_should_err) {
result->status = ExitFailure;
}
diff --git a/src/build.h b/src/build.h
index fa11442..d5d7bc9 100644
--- a/src/build.h
+++ b/src/build.h
@@ -147,7 +147,8 @@
uses_phony_outputs(false),
output_directory_should_err(false),
missing_output_file_should_err(false),
- old_output_should_err(false) {}
+ old_output_should_err(false),
+ pre_remove_output_files(false) {}
enum Verbosity {
NORMAL,
@@ -184,6 +185,9 @@
/// Whether an output with an older timestamp than the inputs should
/// warn or print an error.
bool old_output_should_err;
+
+ /// Whether to remove outputs before executing rule commands
+ bool pre_remove_output_files;
};
/// Builder wraps the build process: starting commands, updating status.
diff --git a/src/build_test.cc b/src/build_test.cc
index 2400f18..7571e3f 100644
--- a/src/build_test.cc
+++ b/src/build_test.cc
@@ -920,21 +920,15 @@
"rule cc\n command = cc $in\n depfile = $out.d\n"
"build foo.o: cc foo.c\n"));
- BuildConfig config(config_);
- config.uses_phony_outputs = true;
- Builder builder(&state_, config, nullptr, nullptr, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
- command_runner_.commands_ran_.clear();
+ config_.uses_phony_outputs = true;
fs_.Create("foo.c", "");
GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing.
fs_.Create("foo.o.d", "foo.o: blah.h bar.h\n");
- EXPECT_TRUE(builder.AddTarget("foo.o", &err));
+ EXPECT_TRUE(builder_.AddTarget("foo.o", &err));
ASSERT_EQ("", err);
ASSERT_TRUE(GetNode("bar.h")->in_edge()->phony_from_depfile_);
-
- builder.command_runner_.release();
}
TEST_F(BuildTest, DepFileParseError) {
@@ -2400,18 +2394,13 @@
config_.uses_phony_outputs = true;
- Builder builder(&state_, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
string err;
- EXPECT_TRUE(builder.AddTarget("outdir", &err));
+ EXPECT_TRUE(builder_.AddTarget("outdir", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
EXPECT_EQ("ninja: outputs should be files, not directories: outdir", status_.last_output_);
-
- builder.command_runner_.release();
}
TEST_F(BuildTest, OutputDirectoryError) {
@@ -2423,18 +2412,13 @@
config_.uses_phony_outputs = true;
config_.output_directory_should_err = true;
- Builder builder(&state_, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
string err;
- EXPECT_TRUE(builder.AddTarget("outdir", &err));
+ EXPECT_TRUE(builder_.AddTarget("outdir", &err));
EXPECT_EQ("", err);
- EXPECT_FALSE(builder.Build(&err));
+ EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("subcommand failed", err);
EXPECT_EQ("ninja: outputs should be files, not directories: outdir", status_.last_output_);
-
- builder.command_runner_.release();
}
TEST_F(BuildTest, OutputFileMissingIgnore) {
@@ -2458,18 +2442,13 @@
config_.uses_phony_outputs = true;
- Builder builder(&state_, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
string err;
- EXPECT_TRUE(builder.AddTarget("outfile", &err));
+ EXPECT_TRUE(builder_.AddTarget("outfile", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
EXPECT_EQ("ninja: output file missing after successful execution: outfile", status_.last_output_);
-
- builder.command_runner_.release();
}
TEST_F(BuildTest, OutputFileMissingError) {
@@ -2480,18 +2459,13 @@
config_.uses_phony_outputs = true;
config_.missing_output_file_should_err = true;
- Builder builder(&state_, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
string err;
- EXPECT_TRUE(builder.AddTarget("outfile", &err));
+ EXPECT_TRUE(builder_.AddTarget("outfile", &err));
EXPECT_EQ("", err);
- EXPECT_FALSE(builder.Build(&err));
+ EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("subcommand failed", err);
EXPECT_EQ("ninja: output file missing after successful execution: outfile", status_.last_output_);
-
- builder.command_runner_.release();
}
TEST_F(BuildTest, OutputFileNotNeeded) {
@@ -2504,16 +2478,11 @@
config_.uses_phony_outputs = true;
config_.missing_output_file_should_err = true;
- Builder builder(&state_, config_, NULL, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
string err;
- EXPECT_TRUE(builder.AddTarget("outphony", &err));
+ EXPECT_TRUE(builder_.AddTarget("outphony", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
-
- builder.command_runner_.release();
}
TEST_F(BuildWithLogTest, OldOutputFileIgnored) {
@@ -2550,17 +2519,14 @@
config_.uses_phony_outputs = true;
- Builder builder(&state_, config_, &build_log_, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
fs_.Create("in", "");
fs_.Tick();
fs_.Create("out", "");
string err;
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
fs_.Tick();
@@ -2568,14 +2534,12 @@
command_runner_.commands_ran_.clear();
state_.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
- EXPECT_EQ("ninja: Missing `restat`? An output file is older than the most recent input: out", status_.last_output_);
-
- builder.command_runner_.release();
+ EXPECT_EQ("ninja: Missing `restat`? An output file is older than the most recent input:\n output: out\n input: in", status_.last_output_);
}
TEST_F(BuildWithLogTest, OldOutputFileError) {
@@ -2586,17 +2550,14 @@
config_.uses_phony_outputs = true;
config_.old_output_should_err = true;
- Builder builder(&state_, config_, &build_log_, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
fs_.Create("in", "");
fs_.Tick();
fs_.Create("out", "");
string err;
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
fs_.Tick();
@@ -2604,14 +2565,12 @@
command_runner_.commands_ran_.clear();
state_.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_FALSE(builder.Build(&err));
+ EXPECT_FALSE(builder_.Build(&err));
EXPECT_EQ("subcommand failed", err);
- EXPECT_EQ("ninja: Missing `restat`? An output file is older than the most recent input: out", status_.last_output_);
-
- builder.command_runner_.release();
+ EXPECT_EQ("ninja: Missing `restat`? An output file is older than the most recent input:\n output: out\n input: in", status_.last_output_);
}
TEST_F(BuildWithLogTest, OutputFileUpdated) {
@@ -2622,17 +2581,14 @@
config_.uses_phony_outputs = true;
config_.old_output_should_err = true;
- Builder builder(&state_, config_, &build_log_, NULL, &fs_, &status_, 0);
- builder.command_runner_.reset(&command_runner_);
-
fs_.Create("in", "");
fs_.Tick();
fs_.Create("out", "");
string err;
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
fs_.Tick();
@@ -2640,14 +2596,12 @@
command_runner_.commands_ran_.clear();
state_.Reset();
- EXPECT_TRUE(builder.AddTarget("out", &err));
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
EXPECT_EQ("", err);
- EXPECT_TRUE(builder.Build(&err));
+ EXPECT_TRUE(builder_.Build(&err));
EXPECT_EQ("", err);
EXPECT_EQ("", status_.last_output_);
-
- builder.command_runner_.release();
}
TEST_F(BuildWithDepsLogTest, MissingDepfileWarning) {
@@ -2700,4 +2654,57 @@
EXPECT_EQ("depfile is missing", status_.last_output_);
builder.command_runner_.release();
+}
+
+TEST_F(BuildTest, PreRemoveOutputs) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule touch\n command = touch ${out}\n"
+"build out: touch in\n"
+"build out2: touch out\n"));
+
+ config_.uses_phony_outputs = true;
+ config_.pre_remove_output_files = true;
+
+ fs_.Create("out", "");
+ fs_.Tick();
+ fs_.Create("in", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out2", &err));
+ EXPECT_EQ("", err);
+
+ fs_.files_created_.clear();
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(2u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(2u, fs_.files_created_.size());
+ EXPECT_EQ(1u, fs_.files_removed_.size());
+}
+
+TEST_F(BuildTest, PreRemoveOutputsWithPhonyOutputs) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"rule phony_out\n"
+" command = echo ${out}\n"
+" phony_output = true\n"
+"build out: phony_out\n"));
+
+ config_.uses_phony_outputs = true;
+ config_.pre_remove_output_files = true;
+
+ fs_.Create("out", "");
+
+ string err;
+ EXPECT_TRUE(builder_.AddTarget("out", &err));
+ EXPECT_EQ("", err);
+
+ fs_.files_created_.clear();
+
+ EXPECT_TRUE(builder_.Build(&err));
+ EXPECT_EQ("", err);
+
+ EXPECT_EQ(1u, command_runner_.commands_ran_.size());
+ EXPECT_EQ(0u, fs_.files_created_.size());
+ EXPECT_EQ(0u, fs_.files_removed_.size());
}
\ No newline at end of file
diff --git a/src/ninja.cc b/src/ninja.cc
index d350544..c84342d 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -233,6 +233,7 @@
" -d MODE enable debugging (use '-d list' to list modes)\n"
" -t TOOL run a subtool (use '-t list' to list subtools)\n"
" terminates toplevel options; further flags are passed to the tool\n"
+" -o FLAG adjust options (use '-o list' to list options)\n"
" -w FLAG adjust warnings (use '-w list' to list warnings)\n"
#ifndef _WIN32
"\n"
@@ -1091,8 +1092,7 @@
" phonycycle={err,warn} phony build statement references itself\n"
" missingdepfile={err,warn} how to treat missing depfiles\n"
"\n"
-" usesphonyoutputs={yes,no} whether the generate uses 'phony_output's so \n"
-" that the following warnings work\n"
+" requires -o usesphonyoutputs=yes\n"
" outputdir={err,warn} how to treat outputs that are directories\n"
" missingoutfile={err,warn} how to treat missing output files\n"
" oldoutput={err,warn} how to treat output files older than their inputs\n");
@@ -1115,12 +1115,6 @@
} else if (name == "missingdepfile=warn") {
config->missing_depfile_should_err = false;
return true;
- } else if (name == "usesphonyoutputs=yes") {
- config->uses_phony_outputs = true;
- return true;
- } else if (name == "usesphonyoutputs=no") {
- config->uses_phony_outputs = false;
- return true;
} else if (name == "outputdir=err") {
config->output_directory_should_err = true;
return true;
@@ -1144,7 +1138,6 @@
SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
"phonycycle=err", "phonycycle=warn",
"missingdepfile=err", "missingdepfile=warn",
- "usesphonyoutputs=yes", "usesphonyoutputs=no",
"outputdir=err", "outputdir=warn",
"missingoutfile=err", "missingoutfile=warn",
"oldoutput=err", "oldoutput=warn", NULL);
@@ -1158,6 +1151,46 @@
}
}
+/// Set an option flag. Returns false if Ninja should exit instead of
+/// continuing.
+bool OptionEnable(const string& name, Options* options, BuildConfig* config) {
+ if (name == "list") {
+ printf("option flags:\n"
+" usesphonyoutputs={yes,no} whether the generate uses 'phony_output's so \n"
+" that these warnings work:\n"
+" outputdir\n"
+" missingoutfile\n"
+" oldoutput\n"
+" preremoveoutputs={yes,no} whether to remove outputs before running rule\n");
+ return false;
+ } else if (name == "usesphonyoutputs=yes") {
+ config->uses_phony_outputs = true;
+ return true;
+ } else if (name == "usesphonyoutputs=no") {
+ config->uses_phony_outputs = false;
+ return true;
+ } else if (name == "preremoveoutputs=yes") {
+ config->pre_remove_output_files = true;
+ return true;
+ } else if (name == "preremoveoutputs=no") {
+ config->pre_remove_output_files = false;
+ return true;
+ } else {
+ const char* suggestion =
+ SpellcheckString(name.c_str(),
+ "usesphonyoutputs=yes", "usesphonyoutputs=no",
+ "preremoveoutputs=yes", "preremoveoutputs=no",
+ NULL);
+ if (suggestion) {
+ Error("unknown option flag '%s', did you mean '%s'?",
+ name.c_str(), suggestion);
+ } else {
+ Error("unknown option flag '%s'", name.c_str());
+ }
+ return false;
+ }
+}
+
bool NinjaMain::OpenBuildLog(bool recompact_only) {
string log_path = ".ninja_log";
if (!build_dir_.empty())
@@ -1332,7 +1365,7 @@
int opt;
while (!options->tool &&
- (opt = getopt_long(*argc, *argv, "d:f:j:k:l:mnt:vw:C:ph", kLongOptions,
+ (opt = getopt_long(*argc, *argv, "d:f:j:k:l:mnt:vw:o:C:ph", kLongOptions,
NULL)) != -1) {
switch (opt) {
case 'd':
@@ -1388,6 +1421,10 @@
if (!WarningEnable(optarg, options, config))
return 1;
break;
+ case 'o':
+ if (!OptionEnable(optarg, options, config))
+ return 1;
+ break;
case 'C':
options->working_dir = optarg;
break;
@@ -1416,6 +1453,10 @@
Fatal("only one of --frontend or --frontend_file may be specified.");
}
+ if (config->pre_remove_output_files && !config->uses_phony_outputs) {
+ Fatal("preremoveoutputs=yes requires usesphonyoutputs=yes.");
+ }
+
return -1;
}