diff options
-rwxr-xr-x | configure.py | 10 | ||||
-rw-r--r-- | src/build.cc | 14 | ||||
-rw-r--r-- | src/clean.cc | 20 | ||||
-rw-r--r-- | src/clean_test.cc | 25 | ||||
-rw-r--r-- | src/explain.cc | 15 | ||||
-rw-r--r-- | src/explain.h | 22 | ||||
-rw-r--r-- | src/graph.cc | 31 | ||||
-rw-r--r-- | src/graph.h | 3 | ||||
-rw-r--r-- | src/metrics.cc | 6 | ||||
-rw-r--r-- | src/ninja.cc | 9 |
10 files changed, 126 insertions, 29 deletions
diff --git a/configure.py b/configure.py index 0637de6..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', @@ -259,11 +260,14 @@ if options.with_gtest: path = options.with_gtest gtest_all_incs = '-I%s -I%s' % (path, os.path.join(path, 'include')) - gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs - objs += n.build(built('gtest-all.o'), 'cxx', + if platform == 'windows': + gtest_cflags = '/nologo /EHsc ' + gtest_all_incs + else: + gtest_cflags = '-fvisibility=hidden ' + gtest_all_incs + objs += n.build(built('gtest-all' + objext), 'cxx', os.path.join(path, 'src/gtest-all.cc'), variables=[('cflags', gtest_cflags)]) - objs += n.build(built('gtest_main.o'), 'cxx', + objs += n.build(built('gtest_main' + objext), 'cxx', os.path.join(path, 'src/gtest_main.cc'), variables=[('cflags', gtest_cflags)]) diff --git a/src/build.cc b/src/build.cc index aa44d8b..bacae6c 100644 --- a/src/build.cc +++ b/src/build.cc @@ -450,7 +450,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; @@ -568,10 +568,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_; } @@ -644,6 +640,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/metrics.cc b/src/metrics.cc index b2433aa..fb44868 100644 --- a/src/metrics.cc +++ b/src/metrics.cc @@ -102,14 +102,14 @@ void Metrics::Report() { width = max((int)(*i)->name.size(), width); } - printf("%-*s\t%-6s\t%9s\t%s\n", width, - "metric", "count", "total (ms)" , "avg (us)"); + printf("%-*s\t%-6s\t%-9s\t%s\n", width, + "metric", "count", "avg (us)", "total (ms)"); for (vector<Metric*>::iterator i = metrics_.begin(); i != metrics_.end(); ++i) { Metric* metric = *i; double total = metric->sum / (double)1000; double avg = metric->sum / (double)metric->count; printf("%-*s\t%-6d\t%-8.1f\t%.1f\n", width, metric->name.c_str(), - metric->count, total, avg); + metric->count, avg, total); } } 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; |