summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/build.cc14
-rw-r--r--src/clean.cc20
-rw-r--r--src/clean_test.cc25
-rw-r--r--src/explain.cc15
-rw-r--r--src/explain.h22
-rw-r--r--src/graph.cc31
-rw-r--r--src/graph.h3
-rw-r--r--src/ninja.cc9
8 files changed, 116 insertions, 23 deletions
diff --git a/src/build.cc b/src/build.cc
index a2e4f64..ac49d27 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -395,7 +395,7 @@ void Plan::CleanNode(BuildLog* build_log, Node* node) {
if (!(*ni)->dirty())
continue;
- if ((*ei)->RecomputeOutputDirty(build_log, most_recent_input, command,
+ if ((*ei)->RecomputeOutputDirty(build_log, most_recent_input, NULL, command,
*ni)) {
(*ni)->MarkDirty();
all_outputs_clean = false;
@@ -513,10 +513,6 @@ struct DryRunCommandRunner : public CommandRunner {
Builder::Builder(State* state, const BuildConfig& config)
: state_(state), config_(config) {
disk_interface_ = new RealDiskInterface;
- if (config.dry_run)
- command_runner_.reset(new DryRunCommandRunner);
- else
- command_runner_.reset(new RealCommandRunner(config));
status_ = new BuildStatus(config);
log_ = state->build_log_;
}
@@ -589,6 +585,14 @@ bool Builder::Build(string* err) {
int pending_commands = 0;
int failures_allowed = config_.failures_allowed;
+ // Set up the command runner if we haven't done so already.
+ if (!command_runner_.get()) {
+ if (config_.dry_run)
+ command_runner_.reset(new DryRunCommandRunner);
+ else
+ command_runner_.reset(new RealCommandRunner(config_));
+ }
+
// This main loop runs the entire build process.
// It is structured like this:
// First, we attempt to start as many commands as allowed by the
diff --git a/src/clean.cc b/src/clean.cc
index c6a294f..3fe23ec 100644
--- a/src/clean.cc
+++ b/src/clean.cc
@@ -102,7 +102,7 @@ int Cleaner::CleanAll(bool generator) {
for (vector<Edge*>::iterator e = state_->edges_.begin();
e != state_->edges_.end(); ++e) {
// Do not try to remove phony targets
- if ((*e)->rule_ == &State::kPhonyRule)
+ if ((*e)->is_phony())
continue;
// Do not remove generator's files unless generator specified.
if (!generator && (*e)->rule().generator())
@@ -123,14 +123,16 @@ int Cleaner::CleanAll(bool generator) {
}
void Cleaner::DoCleanTarget(Node* target) {
- if (target->in_edge()) {
- Remove(target->path());
- if (!target->in_edge()->rule().depfile().empty())
- Remove(target->in_edge()->EvaluateDepFile());
- if (target->in_edge()->HasRspFile())
- Remove(target->in_edge()->GetRspFile());
- for (vector<Node*>::iterator n = target->in_edge()->inputs_.begin();
- n != target->in_edge()->inputs_.end();
+ if (Edge* e = target->in_edge()) {
+ // Do not try to remove phony targets
+ if (!e->is_phony()) {
+ Remove(target->path());
+ if (!target->in_edge()->rule().depfile().empty())
+ Remove(target->in_edge()->EvaluateDepFile());
+ if (e->HasRspFile())
+ Remove(e->GetRspFile());
+ }
+ for (vector<Node*>::iterator n = e->inputs_.begin(); n != e->inputs_.end();
++n) {
DoCleanTarget(*n);
}
diff --git a/src/clean_test.cc b/src/clean_test.cc
index 2e3a6b0..5ed48da 100644
--- a/src/clean_test.cc
+++ b/src/clean_test.cc
@@ -347,3 +347,28 @@ TEST_F(CleanTest, CleanFailure) {
Cleaner cleaner(&state_, config_, &fs_);
EXPECT_NE(0, cleaner.CleanAll());
}
+
+TEST_F(CleanTest, CleanPhony) {
+ ASSERT_NO_FATAL_FAILURE(AssertParse(&state_,
+"build phony: phony t1 t2\n"
+"build t1: cat\n"
+"build t2: cat\n"));
+
+ fs_.Create("phony", 1, "");
+ fs_.Create("t1", 1, "");
+ fs_.Create("t2", 1, "");
+
+ // Check that CleanAll does not remove "phony".
+ Cleaner cleaner(&state_, config_, &fs_);
+ EXPECT_EQ(0, cleaner.CleanAll());
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ EXPECT_NE(0, fs_.Stat("phony"));
+
+ fs_.Create("t1", 1, "");
+ fs_.Create("t2", 1, "");
+
+ // Check that CleanTarget does not remove "phony".
+ EXPECT_EQ(0, cleaner.CleanTarget("phony"));
+ EXPECT_EQ(2, cleaner.cleaned_files_count());
+ EXPECT_NE(0, fs_.Stat("phony"));
+}
diff --git a/src/explain.cc b/src/explain.cc
new file mode 100644
index 0000000..4e14c25
--- /dev/null
+++ b/src/explain.cc
@@ -0,0 +1,15 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+bool g_explaining = false;
diff --git a/src/explain.h b/src/explain.h
new file mode 100644
index 0000000..021f0d4
--- /dev/null
+++ b/src/explain.h
@@ -0,0 +1,22 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+
+#define EXPLAIN(fmt, ...) { \
+ if (g_explaining) \
+ fprintf(stderr, "ninja explain: " fmt "\n", __VA_ARGS__); \
+}
+
+extern bool g_explaining;
diff --git a/src/graph.cc b/src/graph.cc
index 9d45ce1..1d6d8c7 100644
--- a/src/graph.cc
+++ b/src/graph.cc
@@ -20,6 +20,7 @@
#include "build_log.h"
#include "depfile_parser.h"
#include "disk_interface.h"
+#include "explain.h"
#include "metrics.h"
#include "parsers.h"
#include "state.h"
@@ -43,6 +44,7 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
// Visit all inputs; we're dirty if any of the inputs are dirty.
TimeStamp most_recent_input = 1;
+ Node* most_recent_node = NULL;
for (vector<Node*>::iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
if ((*i)->StatIfNecessary(disk_interface)) {
if (Edge* edge = (*i)->in_edge()) {
@@ -50,6 +52,8 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
return false;
} else {
// This input has no in-edge; it is dirty if it is missing.
+ if (!(*i)->exists())
+ EXPLAIN("%s has no in-edge and is missing", (*i)->path().c_str());
(*i)->set_dirty(!(*i)->exists());
}
}
@@ -64,10 +68,13 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
// If a regular input is dirty (or missing), we're dirty.
// Otherwise consider mtime.
if ((*i)->dirty()) {
+ EXPLAIN("%s is dirty", (*i)->path().c_str());
dirty = true;
} else {
- if ((*i)->mtime() > most_recent_input)
+ if ((*i)->mtime() > most_recent_input) {
most_recent_input = (*i)->mtime();
+ most_recent_node = *i;
+ }
}
}
}
@@ -81,7 +88,7 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
for (vector<Node*>::iterator i = outputs_.begin();
i != outputs_.end(); ++i) {
(*i)->StatIfNecessary(disk_interface);
- if (RecomputeOutputDirty(build_log, most_recent_input, command, *i)) {
+ if (RecomputeOutputDirty(build_log, most_recent_input, most_recent_node, command, *i)) {
dirty = true;
break;
}
@@ -107,7 +114,9 @@ bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
bool Edge::RecomputeOutputDirty(BuildLog* build_log,
TimeStamp most_recent_input,
- const string& command, Node* output) {
+ Node* most_recent_node,
+ const string& command,
+ Node* output) {
if (is_phony()) {
// Phony edges don't write any output. Outputs are only dirty if
// there are no inputs and we're missing the output.
@@ -117,8 +126,10 @@ bool Edge::RecomputeOutputDirty(BuildLog* build_log,
BuildLog::LogEntry* entry = 0;
// Dirty if we're missing the output.
- if (!output->exists())
+ if (!output->exists()) {
+ EXPLAIN("output %s doesn't exist", output->path().c_str());
return true;
+ }
// Dirty if the output is older than the input.
if (output->mtime() < most_recent_input) {
@@ -128,9 +139,15 @@ bool Edge::RecomputeOutputDirty(BuildLog* build_log,
// considered dirty if an input was modified since the previous run.
if (rule_->restat() && build_log &&
(entry = build_log->LookupByOutput(output->path()))) {
- if (entry->restat_mtime < most_recent_input)
+ if (entry->restat_mtime < most_recent_input) {
+ EXPLAIN("restat of output %s older than inputs", output->path().c_str());
return true;
+ }
} else {
+ EXPLAIN("output %s older than most recent input %s (%d vs %d)",
+ output->path().c_str(),
+ most_recent_node ? most_recent_node->path().c_str() : "",
+ output->mtime(), most_recent_input);
return true;
}
}
@@ -140,8 +157,10 @@ bool Edge::RecomputeOutputDirty(BuildLog* build_log,
// dirty.
if (!rule_->generator() && build_log &&
(entry || (entry = build_log->LookupByOutput(output->path())))) {
- if (command != entry->command)
+ if (command != entry->command) {
+ EXPLAIN("command line changed for %s", output->path().c_str());
return true;
+ }
}
return false;
diff --git a/src/graph.h b/src/graph.h
index aa1bf49..afe4daf 100644
--- a/src/graph.h
+++ b/src/graph.h
@@ -145,7 +145,8 @@ struct Edge {
/// Recompute whether a given single output should be marked dirty.
/// Returns true if so.
bool RecomputeOutputDirty(BuildLog* build_log, TimeStamp most_recent_input,
- const string& command, Node* output);
+ Node* most_recent_node, const string& command,
+ Node* output);
/// Return true if all inputs' in-edges are ready.
bool AllInputsReady() const;
diff --git a/src/ninja.cc b/src/ninja.cc
index 7d020db..c2426c4 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -39,6 +39,7 @@
#include "build_log.h"
#include "clean.h"
#include "edit_distance.h"
+#include "explain.h"
#include "graph.h"
#include "graphviz.h"
#include "metrics.h"
@@ -567,12 +568,16 @@ int RunTool(const string& tool, Globals* globals, int argc, char** argv) {
bool DebugEnable(const string& name, Globals* globals) {
if (name == "list") {
printf("debugging modes:\n"
-" stats print operation counts/timing info\n");
-//"multiple modes can be enabled via -d FOO -d BAR\n");
+" stats print operation counts/timing info\n"
+" explain explain what caused a command to execute\n"
+"multiple modes can be enabled via -d FOO -d BAR\n");
return false;
} else if (name == "stats") {
g_metrics = new Metrics;
return true;
+ } else if (name == "explain") {
+ g_explaining = true;
+ return true;
} else {
printf("ninja: unknown debug setting '%s'\n", name.c_str());
return false;