summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xconfigure.py1
-rw-r--r--src/build.cc2
-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
7 files changed, 73 insertions, 10 deletions
diff --git a/configure.py b/configure.py
index b5c986d..79d6ac9 100755
--- a/configure.py
+++ b/configure.py
@@ -215,6 +215,7 @@ for name in ['build',
'disk_interface',
'edit_distance',
'eval_env',
+ 'explain',
'graph',
'graphviz',
'lexer',
diff --git a/src/build.cc b/src/build.cc
index 3ff90c3..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;
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;