From 1aae1bc765acf6a21b2fcb088f44bba8f3badec7 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Fri, 13 Apr 2012 16:55:37 -0700 Subject: add support for -d explain to help debug why rules are running --- configure.py | 1 + src/build.cc | 2 +- src/explain.cc | 15 +++++++++++++++ src/explain.h | 22 ++++++++++++++++++++++ src/graph.cc | 31 +++++++++++++++++++++++++------ src/graph.h | 3 ++- src/ninja.cc | 9 +++++++-- 7 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 src/explain.cc create mode 100644 src/explain.h diff --git a/configure.py b/configure.py index 0637de6..e498059 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 a2e4f64..74d310d 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 + +#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::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::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; -- cgit v0.12