diff options
Diffstat (limited to 'src')
62 files changed, 2651 insertions, 1032 deletions
diff --git a/src/browse.cc b/src/browse.cc index 83bfe43..8673919 100644 --- a/src/browse.cc +++ b/src/browse.cc @@ -18,7 +18,7 @@ #include <stdlib.h> #include <unistd.h> -#include "../build/browse_py.h" +#include "build/browse_py.h" void RunBrowsePython(State* state, const char* ninja_command, const char* initial_target) { diff --git a/src/build.cc b/src/build.cc index 64bcea3..b8c2560 100644 --- a/src/build.cc +++ b/src/build.cc @@ -278,7 +278,7 @@ bool Plan::AddSubTarget(Node* node, vector<Node*>* stack, string* err) { return false; } - if (CheckDependencyCycle(node, stack, err)) + if (CheckDependencyCycle(node, *stack, err)) return false; if (edge->outputs_ready()) @@ -316,20 +316,32 @@ bool Plan::AddSubTarget(Node* node, vector<Node*>* stack, string* err) { return true; } -bool Plan::CheckDependencyCycle(Node* node, vector<Node*>* stack, string* err) { - vector<Node*>::reverse_iterator ri = - find(stack->rbegin(), stack->rend(), node); - if (ri == stack->rend()) +bool Plan::CheckDependencyCycle(Node* node, const vector<Node*>& stack, + string* err) { + vector<Node*>::const_iterator start = stack.begin(); + while (start != stack.end() && (*start)->in_edge() != node->in_edge()) + ++start; + if (start == stack.end()) return false; - // Add this node onto the stack to make it clearer where the loop - // is. - stack->push_back(node); + // Build error string for the cycle. + vector<Node*> cycle(start, stack.end()); + cycle.push_back(node); + + if (cycle.front() != cycle.back()) { + // Consider + // build a b: cat c + // build c: cat a + // stack will contain [b, c], node will be a. To not print b -> c -> a, + // shift by one to get c -> a -> c which makes the cycle clear. + cycle.erase(cycle.begin()); + cycle.push_back(cycle.front()); + assert(cycle.front() == cycle.back()); + } - vector<Node*>::iterator start = find(stack->begin(), stack->end(), node); *err = "dependency cycle: "; - for (vector<Node*>::iterator i = start; i != stack->end(); ++i) { - if (i != start) + for (vector<Node*>::const_iterator i = cycle.begin(); i != cycle.end(); ++i) { + if (i != cycle.begin()) err->append(" -> "); err->append((*i)->path()); } @@ -339,9 +351,9 @@ bool Plan::CheckDependencyCycle(Node* node, vector<Node*>* stack, string* err) { Edge* Plan::FindWork() { if (ready_.empty()) return NULL; - set<Edge*>::iterator i = ready_.begin(); - Edge* edge = *i; - ready_.erase(i); + set<Edge*>::iterator e = ready_.begin(); + Edge* edge = *e; + ready_.erase(e); return edge; } @@ -350,7 +362,7 @@ void Plan::ScheduleWork(Edge* edge) { if (pool->ShouldDelayEdge()) { // The graph is not completely clean. Some Nodes have duplicate Out edges. // We need to explicitly ignore these here, otherwise their work will get - // scheduled twice (see https://github.com/martine/ninja/pull/519) + // scheduled twice (see https://github.com/ninja-build/ninja/pull/519) if (ready_.count(edge)) { return; } @@ -362,101 +374,106 @@ void Plan::ScheduleWork(Edge* edge) { } } -void Plan::ResumeDelayedJobs(Edge* edge) { - edge->pool()->EdgeFinished(*edge); - edge->pool()->RetrieveReadyEdges(&ready_); -} - void Plan::EdgeFinished(Edge* edge) { - map<Edge*, bool>::iterator i = want_.find(edge); - assert(i != want_.end()); - if (i->second) + map<Edge*, bool>::iterator e = want_.find(edge); + assert(e != want_.end()); + bool directly_wanted = e->second; + if (directly_wanted) --wanted_edges_; - want_.erase(i); + want_.erase(e); edge->outputs_ready_ = true; - // See if this job frees up any delayed jobs - ResumeDelayedJobs(edge); + // See if this job frees up any delayed jobs. + if (directly_wanted) + edge->pool()->EdgeFinished(*edge); + edge->pool()->RetrieveReadyEdges(&ready_); // Check off any nodes we were waiting for with this edge. - for (vector<Node*>::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - NodeFinished(*i); + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { + NodeFinished(*o); } } void Plan::NodeFinished(Node* node) { // See if we we want any edges from this node. - for (vector<Edge*>::const_iterator i = node->out_edges().begin(); - i != node->out_edges().end(); ++i) { - map<Edge*, bool>::iterator want_i = want_.find(*i); - if (want_i == want_.end()) + for (vector<Edge*>::const_iterator oe = node->out_edges().begin(); + oe != node->out_edges().end(); ++oe) { + map<Edge*, bool>::iterator want_e = want_.find(*oe); + if (want_e == want_.end()) continue; // See if the edge is now ready. - if ((*i)->AllInputsReady()) { - if (want_i->second) { - ScheduleWork(*i); + if ((*oe)->AllInputsReady()) { + if (want_e->second) { + ScheduleWork(*oe); } else { // We do not need to build this edge, but we might need to build one of // its dependents. - EdgeFinished(*i); + EdgeFinished(*oe); } } } } -void Plan::CleanNode(DependencyScan* scan, Node* node) { +bool Plan::CleanNode(DependencyScan* scan, Node* node, string* err) { node->set_dirty(false); - for (vector<Edge*>::const_iterator ei = node->out_edges().begin(); - ei != node->out_edges().end(); ++ei) { + for (vector<Edge*>::const_iterator oe = node->out_edges().begin(); + oe != node->out_edges().end(); ++oe) { // Don't process edges that we don't actually want. - map<Edge*, bool>::iterator want_i = want_.find(*ei); - if (want_i == want_.end() || !want_i->second) + map<Edge*, bool>::iterator want_e = want_.find(*oe); + if (want_e == want_.end() || !want_e->second) continue; // Don't attempt to clean an edge if it failed to load deps. - if ((*ei)->deps_missing_) + if ((*oe)->deps_missing_) continue; // If all non-order-only inputs for this edge are now clean, // we might have changed the dirty state of the outputs. vector<Node*>::iterator - begin = (*ei)->inputs_.begin(), - end = (*ei)->inputs_.end() - (*ei)->order_only_deps_; + begin = (*oe)->inputs_.begin(), + end = (*oe)->inputs_.end() - (*oe)->order_only_deps_; if (find_if(begin, end, mem_fun(&Node::dirty)) == end) { // Recompute most_recent_input. Node* most_recent_input = NULL; - for (vector<Node*>::iterator ni = begin; ni != end; ++ni) { - if (!most_recent_input || (*ni)->mtime() > most_recent_input->mtime()) - most_recent_input = *ni; + for (vector<Node*>::iterator i = begin; i != end; ++i) { + if (!most_recent_input || (*i)->mtime() > most_recent_input->mtime()) + most_recent_input = *i; } // Now, this edge is dirty if any of the outputs are dirty. // If the edge isn't dirty, clean the outputs and mark the edge as not // wanted. - if (!scan->RecomputeOutputsDirty(*ei, most_recent_input)) { - for (vector<Node*>::iterator ni = (*ei)->outputs_.begin(); - ni != (*ei)->outputs_.end(); ++ni) { - CleanNode(scan, *ni); + bool outputs_dirty = false; + if (!scan->RecomputeOutputsDirty(*oe, most_recent_input, + &outputs_dirty, err)) { + return false; + } + if (!outputs_dirty) { + for (vector<Node*>::iterator o = (*oe)->outputs_.begin(); + o != (*oe)->outputs_.end(); ++o) { + if (!CleanNode(scan, *o, err)) + return false; } - want_i->second = false; + want_e->second = false; --wanted_edges_; - if (!(*ei)->is_phony()) + if (!(*oe)->is_phony()) --command_edges_; } } } + return true; } void Plan::Dump() { printf("pending: %d\n", (int)want_.size()); - for (map<Edge*, bool>::iterator i = want_.begin(); i != want_.end(); ++i) { - if (i->second) + for (map<Edge*, bool>::iterator e = want_.begin(); e != want_.end(); ++e) { + if (e->second) printf("want "); - i->first->Dump(); + e->first->Dump(); } printf("ready: %d\n", (int)ready_.size()); } @@ -477,9 +494,9 @@ struct RealCommandRunner : public CommandRunner { vector<Edge*> RealCommandRunner::GetActiveEdges() { vector<Edge*> edges; - for (map<Subprocess*, Edge*>::iterator i = subproc_to_edge_.begin(); - i != subproc_to_edge_.end(); ++i) - edges.push_back(i->second); + for (map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.begin(); + e != subproc_to_edge_.end(); ++e) + edges.push_back(e->second); return edges; } @@ -488,7 +505,9 @@ void RealCommandRunner::Abort() { } bool RealCommandRunner::CanRunMore() { - return ((int)subprocs_.running_.size()) < config_.parallelism + size_t subproc_number = + subprocs_.running_.size() + subprocs_.finished_.size(); + return (int)subproc_number < config_.parallelism && ((subprocs_.running_.empty() || config_.max_load_average <= 0.0f) || GetLoadAverage() < config_.max_load_average); } @@ -514,9 +533,9 @@ bool RealCommandRunner::WaitForCommand(Result* result) { result->status = subproc->Finish(); result->output = subproc->GetOutput(); - map<Subprocess*, Edge*>::iterator i = subproc_to_edge_.find(subproc); - result->edge = i->second; - subproc_to_edge_.erase(i); + map<Subprocess*, Edge*>::iterator e = subproc_to_edge_.find(subproc); + result->edge = e->second; + subproc_to_edge_.erase(e); delete subproc; return true; @@ -539,11 +558,11 @@ void Builder::Cleanup() { vector<Edge*> active_edges = command_runner_->GetActiveEdges(); command_runner_->Abort(); - for (vector<Edge*>::iterator i = active_edges.begin(); - i != active_edges.end(); ++i) { - string depfile = (*i)->GetUnescapedDepfile(); - for (vector<Node*>::iterator ni = (*i)->outputs_.begin(); - ni != (*i)->outputs_.end(); ++ni) { + for (vector<Edge*>::iterator e = active_edges.begin(); + e != active_edges.end(); ++e) { + string depfile = (*e)->GetUnescapedDepfile(); + for (vector<Node*>::iterator o = (*e)->outputs_.begin(); + o != (*e)->outputs_.end(); ++o) { // Only delete this output if it was actually modified. This is // important for things like the generator where we don't want to // delete the manifest file if we can avoid it. But if the rule @@ -551,10 +570,12 @@ void Builder::Cleanup() { // need to rebuild an output because of a modified header file // mentioned in a depfile, and the command touches its depfile // but is interrupted before it touches its output file.) - if (!depfile.empty() || - (*ni)->mtime() != disk_interface_->Stat((*ni)->path())) { - disk_interface_->RemoveFile((*ni)->path()); - } + string err; + TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), &err); + if (new_mtime == -1) // Log and ignore Stat() errors. + Error("%s", err.c_str()); + if (!depfile.empty() || (*o)->mtime() != new_mtime) + disk_interface_->RemoveFile((*o)->path()); } if (!depfile.empty()) disk_interface_->RemoveFile(depfile); @@ -574,7 +595,6 @@ Node* Builder::AddTarget(const string& name, string* err) { } bool Builder::AddTarget(Node* node, string* err) { - node->StatIfNecessary(disk_interface_); if (Edge* in_edge = node->in_edge()) { if (!scan_.RecomputeDirty(in_edge, err)) return false; @@ -688,9 +708,9 @@ bool Builder::StartEdge(Edge* edge, string* err) { // Create directories necessary for outputs. // XXX: this will block; do we care? - for (vector<Node*>::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - if (!disk_interface_->MakeDirs((*i)->path())) + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { + if (!disk_interface_->MakeDirs((*o)->path())) return false; } @@ -750,14 +770,17 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) { if (edge->GetBindingBool("restat") && !config_.dry_run) { bool node_cleaned = false; - for (vector<Node*>::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - TimeStamp new_mtime = disk_interface_->Stat((*i)->path()); - if ((*i)->mtime() == new_mtime) { + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { + TimeStamp new_mtime = disk_interface_->Stat((*o)->path(), err); + if (new_mtime == -1) + return false; + if ((*o)->mtime() == new_mtime) { // The rule command did not change the output. Propagate the clean // state through the build graph. // Note that this also applies to nonexistent outputs (mtime == 0). - plan_.CleanNode(&scan_, *i); + if (!plan_.CleanNode(&scan_, *o, err)) + return false; node_cleaned = true; } } @@ -767,14 +790,18 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) { // (existing) non-order-only input or the depfile. for (vector<Node*>::iterator i = edge->inputs_.begin(); i != edge->inputs_.end() - edge->order_only_deps_; ++i) { - TimeStamp input_mtime = disk_interface_->Stat((*i)->path()); + TimeStamp input_mtime = disk_interface_->Stat((*i)->path(), err); + if (input_mtime == -1) + return false; if (input_mtime > restat_mtime) restat_mtime = input_mtime; } string depfile = edge->GetUnescapedDepfile(); if (restat_mtime != 0 && deps_type.empty() && !depfile.empty()) { - TimeStamp depfile_mtime = disk_interface_->Stat(depfile); + TimeStamp depfile_mtime = disk_interface_->Stat(depfile, err); + if (depfile_mtime == -1) + return false; if (depfile_mtime > restat_mtime) restat_mtime = depfile_mtime; } @@ -803,7 +830,9 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) { if (!deps_type.empty() && !config_.dry_run) { assert(edge->outputs_.size() == 1 && "should have been rejected by parser"); Node* out = edge->outputs_[0]; - TimeStamp deps_mtime = disk_interface_->Stat(out->path()); + TimeStamp deps_mtime = disk_interface_->Stat(out->path(), err); + if (deps_mtime == -1) + return false; if (!scan_.deps_log()->RecordDeps(out, deps_mtime, deps_nodes)) { *err = string("Error writing to deps log: ") + strerror(errno); return false; @@ -820,10 +849,17 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, #ifdef _WIN32 if (deps_type == "msvc") { CLParser parser; - result->output = parser.Parse(result->output, deps_prefix); + string output; + if (!parser.Parse(result->output, deps_prefix, &output, err)) + return false; + result->output = output; for (set<string>::iterator i = parser.includes_.begin(); i != parser.includes_.end(); ++i) { - deps_nodes->push_back(state_->GetNode(*i)); + // ~0 is assuming that with MSVC-parsed headers, it's ok to always make + // all backslashes (as some of the slashes will certainly be backslashes + // anyway). This could be fixed if necessary with some additional + // complexity in IncludesNormalize::Relativize. + deps_nodes->push_back(state_->GetNode(*i, ~0u)); } } else #endif @@ -848,9 +884,11 @@ bool Builder::ExtractDeps(CommandRunner::Result* result, deps_nodes->reserve(deps.ins_.size()); for (vector<StringPiece>::iterator i = deps.ins_.begin(); i != deps.ins_.end(); ++i) { - if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, err)) + unsigned int slash_bits; + if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits, + err)) return false; - deps_nodes->push_back(state_->GetNode(*i)); + deps_nodes->push_back(state_->GetNode(*i, slash_bits)); } if (disk_interface_->RemoveFile(depfile) < 0) { diff --git a/src/build.h b/src/build.h index eb3636a..8106faa 100644 --- a/src/build.h +++ b/src/build.h @@ -61,14 +61,16 @@ struct Plan { void EdgeFinished(Edge* edge); /// Clean the given node during the build. - void CleanNode(DependencyScan* scan, Node* node); + /// Return false on error. + bool CleanNode(DependencyScan* scan, Node* node, string* err); /// Number of edges with commands to run. int command_edge_count() const { return command_edges_; } private: bool AddSubTarget(Node* node, vector<Node*>* stack, string* err); - bool CheckDependencyCycle(Node* node, vector<Node*>* stack, string* err); + bool CheckDependencyCycle(Node* node, const vector<Node*>& stack, + string* err); void NodeFinished(Node* node); /// Submits a ready edge as a candidate for execution. @@ -76,11 +78,6 @@ private: /// currently-full pool. void ScheduleWork(Edge* edge); - /// Allows jobs blocking on |edge| to potentially resume. - /// For example, if |edge| is a member of a pool, calling this may schedule - /// previously pending jobs in that pool. - void ResumeDelayedJobs(Edge* edge); - /// Keep track of which edges we want to build in this plan. If this map does /// not contain an entry for an edge, we do not want to build the entry or its /// dependents. If an entry maps to false, we do not want to build it, but we diff --git a/src/build_log.cc b/src/build_log.cc index 3f24c16..589c6da 100644 --- a/src/build_log.cc +++ b/src/build_log.cc @@ -19,7 +19,9 @@ #include <string.h> #ifndef _WIN32 +#ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS +#endif #include <inttypes.h> #include <unistd.h> #endif @@ -354,7 +356,6 @@ bool BuildLog::WriteEntry(FILE* f, const LogEntry& entry) { bool BuildLog::Recompact(const string& path, const BuildLogUser& user, string* err) { METRIC_RECORD(".ninja_log recompact"); - printf("Recompacting log...\n"); Close(); string temp_path = path + ".recompact"; diff --git a/src/build_log.h b/src/build_log.h index fe81a85..785961e 100644 --- a/src/build_log.h +++ b/src/build_log.h @@ -27,7 +27,7 @@ struct Edge; /// Can answer questions about the manifest for the BuildLog. struct BuildLogUser { - /// Return if a given output no longer part of the build manifest. + /// Return if a given output is no longer part of the build manifest. /// This is only called during recompaction and doesn't have to be fast. virtual bool IsPathDead(StringPiece s) const = 0; }; diff --git a/src/build_log_test.cc b/src/build_log_test.cc index 6738c7b..2c41ba6 100644 --- a/src/build_log_test.cc +++ b/src/build_log_test.cc @@ -17,12 +17,12 @@ #include "util.h" #include "test.h" +#include <sys/stat.h> #ifdef _WIN32 #include <fcntl.h> #include <share.h> #else #include <sys/types.h> -#include <sys/stat.h> #include <unistd.h> #endif diff --git a/src/build_test.cc b/src/build_test.cc index dad69dc..20fb664 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -14,6 +14,8 @@ #include "build.h" +#include <assert.h> + #include "build_log.h" #include "deps_log.h" #include "graph.h" @@ -49,9 +51,9 @@ struct PlanTest : public StateTestWithBuiltinRules { }; TEST_F(PlanTest, Basic) { - AssertParse(&state_, + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat mid\n" -"build mid: cat in\n"); +"build mid: cat in\n")); GetNode("mid")->MarkDirty(); GetNode("out")->MarkDirty(); string err; @@ -82,9 +84,9 @@ TEST_F(PlanTest, Basic) { // Test that two outputs from one rule can be handled as inputs to the next. TEST_F(PlanTest, DoubleOutputDirect) { - AssertParse(&state_, + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat mid1 mid2\n" -"build mid1 mid2: cat in\n"); +"build mid1 mid2: cat in\n")); GetNode("mid1")->MarkDirty(); GetNode("mid2")->MarkDirty(); GetNode("out")->MarkDirty(); @@ -109,11 +111,11 @@ TEST_F(PlanTest, DoubleOutputDirect) { // Test that two outputs from one rule can eventually be routed to another. TEST_F(PlanTest, DoubleOutputIndirect) { - AssertParse(&state_, + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat b1 b2\n" "build b1: cat a1\n" "build b2: cat a2\n" -"build a1 a2: cat in\n"); +"build a1 a2: cat in\n")); GetNode("a1")->MarkDirty(); GetNode("a2")->MarkDirty(); GetNode("b1")->MarkDirty(); @@ -147,11 +149,11 @@ TEST_F(PlanTest, DoubleOutputIndirect) { // Test that two edges from one output can both execute. TEST_F(PlanTest, DoubleDependent) { - AssertParse(&state_, + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat a1 a2\n" "build a1: cat mid\n" "build a2: cat mid\n" -"build mid: cat in\n"); +"build mid: cat in\n")); GetNode("mid")->MarkDirty(); GetNode("a1")->MarkDirty(); GetNode("a2")->MarkDirty(); @@ -184,11 +186,11 @@ TEST_F(PlanTest, DoubleDependent) { } TEST_F(PlanTest, DependencyCycle) { - AssertParse(&state_, + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat mid\n" "build mid: cat in\n" "build in: cat pre\n" -"build pre: cat out\n"); +"build pre: cat out\n")); GetNode("out")->MarkDirty(); GetNode("mid")->MarkDirty(); GetNode("in")->MarkDirty(); @@ -199,6 +201,43 @@ TEST_F(PlanTest, DependencyCycle) { ASSERT_EQ("dependency cycle: out -> mid -> in -> pre -> out", err); } +TEST_F(PlanTest, CycleInEdgesButNotInNodes1) { + string err; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build a b: cat a\n")); + EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err)); + ASSERT_EQ("dependency cycle: a -> a", err); +} + +TEST_F(PlanTest, CycleInEdgesButNotInNodes2) { + string err; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build b a: cat a\n")); + EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err)); + ASSERT_EQ("dependency cycle: a -> a", err); +} + +TEST_F(PlanTest, CycleInEdgesButNotInNodes3) { + string err; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build a b: cat c\n" +"build c: cat a\n")); + EXPECT_FALSE(plan_.AddTarget(GetNode("b"), &err)); + ASSERT_EQ("dependency cycle: c -> a -> c", err); +} + +TEST_F(PlanTest, CycleInEdgesButNotInNodes4) { + string err; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build d: cat c\n" +"build c: cat b\n" +"build b: cat a\n" +"build a e: cat d\n" +"build f: cat e\n")); + EXPECT_FALSE(plan_.AddTarget(GetNode("f"), &err)); + ASSERT_EQ("dependency cycle: d -> c -> b -> a -> d", err); +} + void PlanTest::TestPoolWithDepthOne(const char* test_case) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, test_case)); GetNode("out1")->MarkDirty(); @@ -456,8 +495,8 @@ struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser { /// State of command_runner_ and logs contents (if specified) ARE MODIFIED. /// Handy to check for NOOP builds, and higher-level rebuild tests. void RebuildTarget(const string& target, const char* manifest, - const char* log_path = NULL, - const char* deps_path = NULL); + const char* log_path = NULL, const char* deps_path = NULL, + State* state = NULL); // Mark a path dirty. void Dirty(const string& path); @@ -477,10 +516,13 @@ struct BuildTest : public StateTestWithBuiltinRules, public BuildLogUser { }; void BuildTest::RebuildTarget(const string& target, const char* manifest, - const char* log_path, const char* deps_path) { - State state; - ASSERT_NO_FATAL_FAILURE(AddCatRule(&state)); - AssertParse(&state, manifest); + const char* log_path, const char* deps_path, + State* state) { + State local_state, *pstate = &local_state; + if (state) + pstate = state; + ASSERT_NO_FATAL_FAILURE(AddCatRule(pstate)); + AssertParse(pstate, manifest); string err; BuildLog build_log, *pbuild_log = NULL; @@ -493,20 +535,20 @@ void BuildTest::RebuildTarget(const string& target, const char* manifest, DepsLog deps_log, *pdeps_log = NULL; if (deps_path) { - ASSERT_TRUE(deps_log.Load(deps_path, &state, &err)); + ASSERT_TRUE(deps_log.Load(deps_path, pstate, &err)); ASSERT_TRUE(deps_log.OpenForWrite(deps_path, &err)); ASSERT_EQ("", err); pdeps_log = &deps_log; } - Builder builder(&state, config_, pbuild_log, pdeps_log, &fs_); + Builder builder(pstate, config_, pbuild_log, pdeps_log, &fs_); EXPECT_TRUE(builder.AddTarget(target, &err)); command_runner_.commands_ran_.clear(); builder.command_runner_.reset(&command_runner_); if (!builder.AlreadyUpToDate()) { bool build_res = builder.Build(&err); - EXPECT_TRUE(build_res) << "builder.Build(&err)"; + EXPECT_TRUE(build_res); } builder.command_runner_.release(); } @@ -676,7 +718,7 @@ TEST_F(BuildTest, TwoOutputs) { } // Test case from -// https://github.com/martine/ninja/issues/148 +// https://github.com/ninja-build/ninja/issues/148 TEST_F(BuildTest, MultiOutIn) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule touch\n" @@ -753,23 +795,18 @@ TEST_F(BuildTest, MakeDirs) { #ifdef _WIN32 ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir\\dir2\\file: cat in1\n")); - EXPECT_TRUE(builder_.AddTarget("subdir\\dir2\\file", &err)); #else ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build subdir/dir2/file: cat in1\n")); - EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err)); #endif + EXPECT_TRUE(builder_.AddTarget("subdir/dir2/file", &err)); EXPECT_EQ("", err); EXPECT_TRUE(builder_.Build(&err)); ASSERT_EQ("", err); ASSERT_EQ(2u, fs_.directories_made_.size()); EXPECT_EQ("subdir", fs_.directories_made_[0]); -#ifdef _WIN32 - EXPECT_EQ("subdir\\dir2", fs_.directories_made_[1]); -#else EXPECT_EQ("subdir/dir2", fs_.directories_made_[1]); -#endif } TEST_F(BuildTest, DepFileMissing) { @@ -819,8 +856,7 @@ TEST_F(BuildTest, DepFileParseError) { fs_.Create("foo.c", ""); fs_.Create("foo.o.d", "randomtext\n"); EXPECT_FALSE(builder_.AddTarget("foo.o", &err)); - EXPECT_EQ("expected depfile 'foo.o.d' to mention 'foo.o', got 'randomtext'", - err); + EXPECT_EQ("foo.o.d: expected ':' in depfile", err); } TEST_F(BuildTest, OrderOnlyDeps) { @@ -940,6 +976,38 @@ TEST_F(BuildTest, RebuildOrderOnlyDeps) { ASSERT_EQ("cc oo.h.in", command_runner_.commands_ran_[0]); } +#ifdef _WIN32 +TEST_F(BuildTest, DepFileCanonicalize) { + string err; + int orig_edges = state_.edges_.size(); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"rule cc\n command = cc $in\n depfile = $out.d\n" +"build gen/stuff\\things/foo.o: cc x\\y/z\\foo.c\n")); + Edge* edge = state_.edges_.back(); + + fs_.Create("x/y/z/foo.c", ""); + GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing. + // Note, different slashes from manifest. + fs_.Create("gen/stuff\\things/foo.o.d", + "gen\\stuff\\things\\foo.o: blah.h bar.h\n"); + EXPECT_TRUE(builder_.AddTarget("gen/stuff/things/foo.o", &err)); + ASSERT_EQ("", err); + ASSERT_EQ(1u, fs_.files_read_.size()); + // The depfile path does not get Canonicalize as it seems unnecessary. + EXPECT_EQ("gen/stuff\\things/foo.o.d", fs_.files_read_[0]); + + // Expect three new edges: one generating foo.o, and two more from + // loading the depfile. + ASSERT_EQ(orig_edges + 3, (int)state_.edges_.size()); + // Expect our edge to now have three inputs: foo.c and two headers. + ASSERT_EQ(3u, edge->inputs_.size()); + + // Expect the command line we generate to only use the original input, and + // using the slashes from the manifest. + ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); +} +#endif + TEST_F(BuildTest, Phony) { string err; ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, @@ -1027,6 +1095,31 @@ TEST_F(BuildTest, SwallowFailuresLimit) { ASSERT_EQ("cannot make progress due to previous errors", err); } +TEST_F(BuildTest, PoolEdgesReadyButNotWanted) { + fs_.Create("x", ""); + + const char* manifest = + "pool some_pool\n" + " depth = 4\n" + "rule touch\n" + " command = touch $out\n" + " pool = some_pool\n" + "rule cc\n" + " command = touch grit\n" + "\n" + "build B.d.stamp: cc | x\n" + "build C.stamp: touch B.d.stamp\n" + "build final.stamp: touch || C.stamp\n"; + + RebuildTarget("final.stamp", manifest); + + fs_.RemoveFile("B.d.stamp"); + + State save_state; + RebuildTarget("final.stamp", manifest, NULL, NULL, &save_state); + EXPECT_GE(save_state.LookupPool("some_pool")->current_use(), 0); +} + struct BuildWithLogTest : public BuildTest { BuildWithLogTest() { builder_.SetBuildLog(&build_log_); @@ -1206,7 +1299,7 @@ TEST_F(BuildWithLogTest, RestatSingleDependentOutputDirty) { } // Test scenario, in which an input file is removed, but output isn't changed -// https://github.com/martine/ninja/issues/295 +// https://github.com/ninja-build/ninja/issues/295 TEST_F(BuildWithLogTest, RestatMissingInput) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule true\n" @@ -1385,7 +1478,7 @@ TEST_F(BuildTest, RspFileFailure) { ASSERT_EQ("Another very long command", fs_.files_["out.rsp"].contents); } -// Test that contens of the RSP file behaves like a regular part of +// Test that contents of the RSP file behaves like a regular part of // command line, i.e. triggers a rebuild if changed TEST_F(BuildWithLogTest, RspFileCmdLineChange) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, @@ -1455,7 +1548,7 @@ TEST_F(BuildTest, InterruptCleanup) { EXPECT_FALSE(builder_.Build(&err)); EXPECT_EQ("interrupted by user", err); builder_.Cleanup(); - EXPECT_GT(fs_.Stat("out1"), 0); + EXPECT_GT(fs_.Stat("out1", &err), 0); err = ""; // A touched output of an interrupted command should be deleted. @@ -1464,7 +1557,22 @@ TEST_F(BuildTest, InterruptCleanup) { EXPECT_FALSE(builder_.Build(&err)); EXPECT_EQ("interrupted by user", err); builder_.Cleanup(); - EXPECT_EQ(0, fs_.Stat("out2")); + EXPECT_EQ(0, fs_.Stat("out2", &err)); +} + +TEST_F(BuildTest, StatFailureAbortsBuild) { + const string kTooLongToStat(400, 'i'); + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +("build " + kTooLongToStat + ": cat " + kTooLongToStat + "\n").c_str())); + // Also cyclic, for good measure. + + // This simulates a stat failure: + fs_.files_[kTooLongToStat].mtime = -1; + fs_.files_[kTooLongToStat].stat_error = "stat failed"; + + string err; + EXPECT_FALSE(builder_.AddTarget(kTooLongToStat, &err)); + EXPECT_EQ("stat failed", err); } TEST_F(BuildTest, PhonyWithNoInputs) { @@ -1584,7 +1692,7 @@ TEST_F(BuildWithDepsLogTest, Straightforward) { EXPECT_EQ("", err); // The deps file should have been removed. - EXPECT_EQ(0, fs_.Stat("in1.d")); + EXPECT_EQ(0, fs_.Stat("in1.d", &err)); // Recreate it for the next step. fs_.Create("in1.d", "out: in2"); deps_log.Close(); @@ -1664,7 +1772,7 @@ TEST_F(BuildWithDepsLogTest, ObsoleteDeps) { fs_.Create("out", ""); // The deps file should have been removed, so no need to timestamp it. - EXPECT_EQ(0, fs_.Stat("in1.d")); + EXPECT_EQ(0, fs_.Stat("in1.d", &err)); { State state; @@ -1854,7 +1962,7 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) { Edge* edge = state.edges_.back(); - state.GetNode("bar.h")->MarkDirty(); // Mark bar.h as missing. + state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing. EXPECT_TRUE(builder.AddTarget("fo o.o", &err)); ASSERT_EQ("", err); @@ -1872,8 +1980,74 @@ TEST_F(BuildWithDepsLogTest, DepFileOKDepsLog) { } } +#ifdef _WIN32 +TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) { + string err; + const char* manifest = + "rule cc\n command = cc $in\n depfile = $out.d\n deps = gcc\n" + "build a/b\\c\\d/e/fo$ o.o: cc x\\y/z\\foo.c\n"; + + fs_.Create("x/y/z/foo.c", ""); + + { + State state; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + // Run the build once, everything should be ok. + DepsLog deps_log; + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err)); + ASSERT_EQ("", err); + // Note, different slashes from manifest. + fs_.Create("a/b\\c\\d/e/fo o.o.d", + "a\\b\\c\\d\\e\\fo\\ o.o: blah.h bar.h\n"); + EXPECT_TRUE(builder.Build(&err)); + EXPECT_EQ("", err); + + deps_log.Close(); + builder.command_runner_.release(); + } + + { + State state; + ASSERT_NO_FATAL_FAILURE(AssertParse(&state, manifest)); + + DepsLog deps_log; + ASSERT_TRUE(deps_log.Load("ninja_deps", &state, &err)); + ASSERT_TRUE(deps_log.OpenForWrite("ninja_deps", &err)); + ASSERT_EQ("", err); + + Builder builder(&state, config_, NULL, &deps_log, &fs_); + builder.command_runner_.reset(&command_runner_); + + Edge* edge = state.edges_.back(); + + state.GetNode("bar.h", 0)->MarkDirty(); // Mark bar.h as missing. + EXPECT_TRUE(builder.AddTarget("a/b/c/d/e/fo o.o", &err)); + ASSERT_EQ("", err); + + // Expect three new edges: one generating fo o.o, and two more from + // loading the depfile. + ASSERT_EQ(3u, state.edges_.size()); + // Expect our edge to now have three inputs: foo.c and two headers. + ASSERT_EQ(3u, edge->inputs_.size()); + + // Expect the command line we generate to only use the original input. + // Note, slashes from manifest, not .d. + ASSERT_EQ("cc x\\y/z\\foo.c", edge->EvaluateCommand()); + + deps_log.Close(); + builder.command_runner_.release(); + } +} +#endif + /// Check that a restat rule doesn't clear an edge if the depfile is missing. -/// Follows from: https://github.com/martine/ninja/issues/603 +/// Follows from: https://github.com/ninja-build/ninja/issues/603 TEST_F(BuildTest, RestatMissingDepfile) { const char* manifest = "rule true\n" @@ -1897,7 +2071,7 @@ const char* manifest = } /// Check that a restat rule doesn't clear an edge if the deps are missing. -/// https://github.com/martine/ninja/issues/603 +/// https://github.com/ninja-build/ninja/issues/603 TEST_F(BuildWithDepsLogTest, RestatMissingDepfileDepslog) { string err; const char* manifest = @@ -1948,6 +2122,23 @@ TEST_F(BuildWithDepsLogTest, RestatMissingDepfileDepslog) { ASSERT_EQ(0u, command_runner_.commands_ran_.size()); } +TEST_F(BuildTest, WrongOutputInDepfileCausesRebuild) { + string err; + const char* manifest = +"rule cc\n" +" command = cc $in\n" +" depfile = $out.d\n" +"build foo.o: cc foo.c\n"; + + fs_.Create("foo.c", ""); + fs_.Create("foo.o", ""); + fs_.Create("header.h", ""); + fs_.Create("foo.o.d", "bar.o.d: header.h\n"); + + RebuildTarget("foo.o", manifest, "build_log", "ninja_deps"); + ASSERT_EQ(1u, command_runner_.commands_ran_.size()); +} + TEST_F(BuildTest, Console) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule console\n" diff --git a/src/canon_perftest.cc b/src/canon_perftest.cc index 59bd18f..389ac24 100644 --- a/src/canon_perftest.cc +++ b/src/canon_perftest.cc @@ -33,8 +33,9 @@ int main() { for (int j = 0; j < 5; ++j) { const int kNumRepetitions = 2000000; int64_t start = GetTimeMillis(); + unsigned int slash_bits; for (int i = 0; i < kNumRepetitions; ++i) { - CanonicalizePath(buf, &len, &err); + CanonicalizePath(buf, &len, &slash_bits, &err); } int delta = (int)(GetTimeMillis() - start); times.push_back(delta); diff --git a/src/clean.cc b/src/clean.cc index 98c638c..1d6ba9e 100644 --- a/src/clean.cc +++ b/src/clean.cc @@ -49,7 +49,11 @@ int Cleaner::RemoveFile(const string& path) { } bool Cleaner::FileExists(const string& path) { - return disk_interface_->Stat(path) > 0; + string err; + TimeStamp mtime = disk_interface_->Stat(path, &err); + if (mtime == -1) + Error("%s", err.c_str()); + return mtime > 0; // Treat Stat() errors as "file does not exist". } void Cleaner::Report(const string& path) { @@ -220,7 +224,7 @@ int Cleaner::CleanRule(const char* rule) { assert(rule); Reset(); - const Rule* r = state_->LookupRule(rule); + const Rule* r = state_->bindings_.LookupRule(rule); if (r) { CleanRule(r); } else { @@ -237,7 +241,7 @@ int Cleaner::CleanRules(int rule_count, char* rules[]) { PrintHeader(); for (int i = 0; i < rule_count; ++i) { const char* rule_name = rules[i]; - const Rule* rule = state_->LookupRule(rule_name); + const Rule* rule = state_->bindings_.LookupRule(rule_name); if (rule) { if (IsVerbose()) printf("Rule %s\n", rule_name); diff --git a/src/clean_test.cc b/src/clean_test.cc index 5869bbb..395343b 100644 --- a/src/clean_test.cc +++ b/src/clean_test.cc @@ -44,10 +44,11 @@ TEST_F(CleanTest, CleanAll) { EXPECT_EQ(4u, fs_.files_removed_.size()); // Check they are removed. - EXPECT_EQ(0, fs_.Stat("in1")); - EXPECT_EQ(0, fs_.Stat("out1")); - EXPECT_EQ(0, fs_.Stat("in2")); - EXPECT_EQ(0, fs_.Stat("out2")); + string err; + EXPECT_EQ(0, fs_.Stat("in1", &err)); + EXPECT_EQ(0, fs_.Stat("out1", &err)); + EXPECT_EQ(0, fs_.Stat("in2", &err)); + EXPECT_EQ(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); EXPECT_EQ(0, cleaner.CleanAll()); @@ -75,10 +76,11 @@ TEST_F(CleanTest, CleanAllDryRun) { EXPECT_EQ(0u, fs_.files_removed_.size()); // Check they are not removed. - EXPECT_NE(0, fs_.Stat("in1")); - EXPECT_NE(0, fs_.Stat("out1")); - EXPECT_NE(0, fs_.Stat("in2")); - EXPECT_NE(0, fs_.Stat("out2")); + string err; + EXPECT_LT(0, fs_.Stat("in1", &err)); + EXPECT_LT(0, fs_.Stat("out1", &err)); + EXPECT_LT(0, fs_.Stat("in2", &err)); + EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); EXPECT_EQ(0, cleaner.CleanAll()); @@ -105,10 +107,11 @@ TEST_F(CleanTest, CleanTarget) { EXPECT_EQ(2u, fs_.files_removed_.size()); // Check they are removed. - EXPECT_EQ(0, fs_.Stat("in1")); - EXPECT_EQ(0, fs_.Stat("out1")); - EXPECT_NE(0, fs_.Stat("in2")); - EXPECT_NE(0, fs_.Stat("out2")); + string err; + EXPECT_EQ(0, fs_.Stat("in1", &err)); + EXPECT_EQ(0, fs_.Stat("out1", &err)); + EXPECT_LT(0, fs_.Stat("in2", &err)); + EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanTarget("out1")); @@ -135,11 +138,12 @@ TEST_F(CleanTest, CleanTargetDryRun) { EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); - // Check they are removed. - EXPECT_NE(0, fs_.Stat("in1")); - EXPECT_NE(0, fs_.Stat("out1")); - EXPECT_NE(0, fs_.Stat("in2")); - EXPECT_NE(0, fs_.Stat("out2")); + // Check they are not removed. + string err; + EXPECT_LT(0, fs_.Stat("in1", &err)); + EXPECT_LT(0, fs_.Stat("out1", &err)); + EXPECT_LT(0, fs_.Stat("in2", &err)); + EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanTarget("out1")); @@ -168,10 +172,11 @@ TEST_F(CleanTest, CleanRule) { EXPECT_EQ(2u, fs_.files_removed_.size()); // Check they are removed. - EXPECT_EQ(0, fs_.Stat("in1")); - EXPECT_NE(0, fs_.Stat("out1")); - EXPECT_EQ(0, fs_.Stat("in2")); - EXPECT_NE(0, fs_.Stat("out2")); + string err; + EXPECT_EQ(0, fs_.Stat("in1", &err)); + EXPECT_LT(0, fs_.Stat("out1", &err)); + EXPECT_EQ(0, fs_.Stat("in2", &err)); + EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); @@ -200,11 +205,12 @@ TEST_F(CleanTest, CleanRuleDryRun) { EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); - // Check they are removed. - EXPECT_NE(0, fs_.Stat("in1")); - EXPECT_NE(0, fs_.Stat("out1")); - EXPECT_NE(0, fs_.Stat("in2")); - EXPECT_NE(0, fs_.Stat("out2")); + // Check they are not removed. + string err; + EXPECT_LT(0, fs_.Stat("in1", &err)); + EXPECT_LT(0, fs_.Stat("out1", &err)); + EXPECT_LT(0, fs_.Stat("in2", &err)); + EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); @@ -328,12 +334,13 @@ TEST_F(CleanTest, CleanRsp) { EXPECT_EQ(6u, fs_.files_removed_.size()); // Check they are removed. - EXPECT_EQ(0, fs_.Stat("in1")); - EXPECT_EQ(0, fs_.Stat("out1")); - EXPECT_EQ(0, fs_.Stat("in2")); - EXPECT_EQ(0, fs_.Stat("out2")); - EXPECT_EQ(0, fs_.Stat("in2.rsp")); - EXPECT_EQ(0, fs_.Stat("out2.rsp")); + string err; + EXPECT_EQ(0, fs_.Stat("in1", &err)); + EXPECT_EQ(0, fs_.Stat("out1", &err)); + EXPECT_EQ(0, fs_.Stat("in2", &err)); + EXPECT_EQ(0, fs_.Stat("out2", &err)); + EXPECT_EQ(0, fs_.Stat("in2.rsp", &err)); + EXPECT_EQ(0, fs_.Stat("out2.rsp", &err)); } TEST_F(CleanTest, CleanFailure) { @@ -345,6 +352,7 @@ TEST_F(CleanTest, CleanFailure) { } TEST_F(CleanTest, CleanPhony) { + string err; ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build phony: phony t1 t2\n" "build t1: cat\n" @@ -358,7 +366,7 @@ TEST_F(CleanTest, CleanPhony) { Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(2, cleaner.cleaned_files_count()); - EXPECT_NE(0, fs_.Stat("phony")); + EXPECT_LT(0, fs_.Stat("phony", &err)); fs_.Create("t1", ""); fs_.Create("t2", ""); @@ -366,7 +374,7 @@ TEST_F(CleanTest, CleanPhony) { // 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")); + EXPECT_LT(0, fs_.Stat("phony", &err)); } TEST_F(CleanTest, CleanDepFileAndRspFileWithSpaces) { @@ -391,8 +399,9 @@ TEST_F(CleanTest, CleanDepFileAndRspFileWithSpaces) { EXPECT_EQ(4, cleaner.cleaned_files_count()); EXPECT_EQ(4u, fs_.files_removed_.size()); - EXPECT_EQ(0, fs_.Stat("out 1")); - EXPECT_EQ(0, fs_.Stat("out 2")); - EXPECT_EQ(0, fs_.Stat("out 1.d")); - EXPECT_EQ(0, fs_.Stat("out 2.rsp")); + string err; + EXPECT_EQ(0, fs_.Stat("out 1", &err)); + EXPECT_EQ(0, fs_.Stat("out 2", &err)); + EXPECT_EQ(0, fs_.Stat("out 1.d", &err)); + EXPECT_EQ(0, fs_.Stat("out 2.rsp", &err)); } diff --git a/src/debug_flags.cc b/src/debug_flags.cc index 75f1ea5..8065001 100644 --- a/src/debug_flags.cc +++ b/src/debug_flags.cc @@ -15,3 +15,5 @@ bool g_explaining = false; bool g_keep_rsp = false; + +bool g_experimental_statcache = true; diff --git a/src/debug_flags.h b/src/debug_flags.h index ba3ebf3..7965585 100644 --- a/src/debug_flags.h +++ b/src/debug_flags.h @@ -26,4 +26,6 @@ extern bool g_explaining; extern bool g_keep_rsp; +extern bool g_experimental_statcache; + #endif // NINJA_EXPLAIN_H_ diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc index dc53d4f..7cee892 100644 --- a/src/depfile_parser.cc +++ b/src/depfile_parser.cc @@ -234,5 +234,9 @@ yy16: return false; } } + if (parsing_targets) { + *err = "expected ':' in depfile"; + return false; + } return true; } diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc index 24744c7..98c1621 100644 --- a/src/depfile_parser.in.cc +++ b/src/depfile_parser.in.cc @@ -112,5 +112,9 @@ bool DepfileParser::Parse(string* content, string* err) { return false; } } + if (parsing_targets) { + *err = "expected ':' in depfile"; + return false; + } return true; } diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc index c7a163c..ee798f8 100644 --- a/src/depfile_parser_test.cc +++ b/src/depfile_parser_test.cc @@ -14,7 +14,7 @@ #include "depfile_parser.h" -#include <gtest/gtest.h> +#include "test.h" struct DepfileParserTest : public testing::Test { bool Parse(const char* input, string* err); @@ -106,7 +106,7 @@ TEST_F(DepfileParserTest, Escapes) { // it through. string err; EXPECT_TRUE(Parse( -"\\!\\@\\#$$\\%\\^\\&\\\\", +"\\!\\@\\#$$\\%\\^\\&\\\\:", &err)); ASSERT_EQ("", err); EXPECT_EQ("\\!\\@#$\\%\\^\\&\\", @@ -142,8 +142,8 @@ TEST_F(DepfileParserTest, UnifyMultipleOutputs) { // check that multiple duplicate targets are properly unified string err; EXPECT_TRUE(Parse("foo foo: x y z", &err)); - ASSERT_EQ(parser_.out_.AsString(), "foo"); - ASSERT_EQ(parser_.ins_.size(), 3u); + ASSERT_EQ("foo", parser_.out_.AsString()); + ASSERT_EQ(3u, parser_.ins_.size()); EXPECT_EQ("x", parser_.ins_[0].AsString()); EXPECT_EQ("y", parser_.ins_[1].AsString()); EXPECT_EQ("z", parser_.ins_[2].AsString()); diff --git a/src/deps_log.cc b/src/deps_log.cc index 61df387..89c6023 100644 --- a/src/deps_log.cc +++ b/src/deps_log.cc @@ -239,8 +239,13 @@ bool DepsLog::Load(const string& path, State* state, string* err) { if (buf[path_size - 1] == '\0') --path_size; if (buf[path_size - 1] == '\0') --path_size; if (buf[path_size - 1] == '\0') --path_size; - StringPiece path(buf, path_size); - Node* node = state->GetNode(path); + StringPiece subpath(buf, path_size); + // It is not necessary to pass in a correct slash_bits here. It will + // either be a Node that's in the manifest (in which case it will already + // have a correct slash_bits that GetNode will look up), or it is an + // implicit dependency from a .d which does not affect the build command + // (and so need not have its slashes maintained). + Node* node = state->GetNode(subpath, 0); // Check that the expected index matches the actual index. This can only // happen if two ninja processes write to the same deps log concurrently. @@ -270,7 +275,7 @@ bool DepsLog::Load(const string& path, State* state, string* err) { } fclose(f); - if (!Truncate(path.c_str(), offset, err)) + if (!Truncate(path, offset, err)) return false; // The truncate succeeded; we'll just report the load error as a @@ -302,7 +307,6 @@ DepsLog::Deps* DepsLog::GetDeps(Node* node) { bool DepsLog::Recompact(const string& path, string* err) { METRIC_RECORD(".ninja_deps recompact"); - printf("Recompacting deps...\n"); Close(); string temp_path = path + ".recompact"; diff --git a/src/deps_log_test.cc b/src/deps_log_test.cc index e8e5138..cab06fb 100644 --- a/src/deps_log_test.cc +++ b/src/deps_log_test.cc @@ -14,6 +14,11 @@ #include "deps_log.h" +#include <sys/stat.h> +#ifndef _WIN32 +#include <unistd.h> +#endif + #include "graph.h" #include "util.h" #include "test.h" @@ -41,16 +46,16 @@ TEST_F(DepsLogTest, WriteRead) { { vector<Node*> deps; - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar.h")); - log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + deps.push_back(state1.GetNode("foo.h", 0)); + deps.push_back(state1.GetNode("bar.h", 0)); + log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state1.GetNode("foo.h")); - deps.push_back(state1.GetNode("bar2.h")); - log1.RecordDeps(state1.GetNode("out2.o"), 2, deps); + deps.push_back(state1.GetNode("foo.h", 0)); + deps.push_back(state1.GetNode("bar2.h", 0)); + log1.RecordDeps(state1.GetNode("out2.o", 0), 2, deps); - DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o")); + DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0)); ASSERT_TRUE(log_deps); ASSERT_EQ(1, log_deps->mtime); ASSERT_EQ(2, log_deps->node_count); @@ -74,7 +79,7 @@ TEST_F(DepsLogTest, WriteRead) { } // Spot-check the entries in log2. - DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o")); + DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o", 0)); ASSERT_TRUE(log_deps); ASSERT_EQ(2, log_deps->mtime); ASSERT_EQ(2, log_deps->node_count); @@ -96,11 +101,11 @@ TEST_F(DepsLogTest, LotsOfDeps) { for (int i = 0; i < kNumDeps; ++i) { char buf[32]; sprintf(buf, "file%d.h", i); - deps.push_back(state1.GetNode(buf)); + deps.push_back(state1.GetNode(buf, 0)); } - log1.RecordDeps(state1.GetNode("out.o"), 1, deps); + log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps); - DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o")); + DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0)); ASSERT_EQ(kNumDeps, log_deps->node_count); } @@ -111,7 +116,7 @@ TEST_F(DepsLogTest, LotsOfDeps) { EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err)); ASSERT_EQ("", err); - DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o")); + DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o", 0)); ASSERT_EQ(kNumDeps, log_deps->node_count); } @@ -127,9 +132,9 @@ TEST_F(DepsLogTest, DoubleEntry) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -149,9 +154,9 @@ TEST_F(DepsLogTest, DoubleEntry) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -181,14 +186,14 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("baz.h")); - log.RecordDeps(state.GetNode("other_out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("baz.h", 0)); + log.RecordDeps(state.GetNode("other_out.o", 0), 1, deps); log.Close(); @@ -211,8 +216,8 @@ TEST_F(DepsLogTest, Recompact) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); log.Close(); struct stat st; @@ -232,14 +237,14 @@ TEST_F(DepsLogTest, Recompact) { string err; ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); - Node* out = state.GetNode("out.o"); + Node* out = state.GetNode("out.o", 0); DepsLog::Deps* deps = log.GetDeps(out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); ASSERT_EQ(1, deps->node_count); ASSERT_EQ("foo.h", deps->nodes[0]->path()); - Node* other_out = state.GetNode("other_out.o"); + Node* other_out = state.GetNode("other_out.o", 0); deps = log.GetDeps(other_out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); @@ -281,14 +286,14 @@ TEST_F(DepsLogTest, Recompact) { string err; ASSERT_TRUE(log.Load(kTestFilename, &state, &err)); - Node* out = state.GetNode("out.o"); + Node* out = state.GetNode("out.o", 0); DepsLog::Deps* deps = log.GetDeps(out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); ASSERT_EQ(1, deps->node_count); ASSERT_EQ("foo.h", deps->nodes[0]->path()); - Node* other_out = state.GetNode("other_out.o"); + Node* other_out = state.GetNode("other_out.o", 0); deps = log.GetDeps(other_out); ASSERT_TRUE(deps); ASSERT_EQ(1, deps->mtime); @@ -354,14 +359,14 @@ TEST_F(DepsLogTest, Truncated) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 2, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 2, deps); log.Close(); } @@ -413,14 +418,14 @@ TEST_F(DepsLogTest, TruncatedRecovery) { ASSERT_EQ("", err); vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar.h")); - log.RecordDeps(state.GetNode("out.o"), 1, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar.h", 0)); + log.RecordDeps(state.GetNode("out.o", 0), 1, deps); deps.clear(); - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 2, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 2, deps); log.Close(); } @@ -441,16 +446,16 @@ TEST_F(DepsLogTest, TruncatedRecovery) { err.clear(); // The truncated entry should've been discarded. - EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o"))); + EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o", 0))); EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err)); ASSERT_EQ("", err); // Add a new entry. vector<Node*> deps; - deps.push_back(state.GetNode("foo.h")); - deps.push_back(state.GetNode("bar2.h")); - log.RecordDeps(state.GetNode("out2.o"), 3, deps); + deps.push_back(state.GetNode("foo.h", 0)); + deps.push_back(state.GetNode("bar2.h", 0)); + log.RecordDeps(state.GetNode("out2.o", 0), 3, deps); log.Close(); } @@ -464,7 +469,7 @@ TEST_F(DepsLogTest, TruncatedRecovery) { EXPECT_TRUE(log.Load(kTestFilename, &state, &err)); // The truncated entry should exist. - DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o")); + DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o", 0)); ASSERT_TRUE(deps); } } diff --git a/src/disk_interface.cc b/src/disk_interface.cc index 4dfae1a..70282c0 100644 --- a/src/disk_interface.cc +++ b/src/disk_interface.cc @@ -23,6 +23,7 @@ #include <sys/types.h> #ifdef _WIN32 +#include <sstream> #include <windows.h> #include <direct.h> // _mkdir #endif @@ -55,6 +56,76 @@ int MakeDir(const string& path) { #endif } +#ifdef _WIN32 +TimeStamp TimeStampFromFileTime(const FILETIME& filetime) { + // FILETIME is in 100-nanosecond increments since the Windows epoch. + // We don't much care about epoch correctness but we do want the + // resulting value to fit in an integer. + uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) | + ((uint64_t)filetime.dwLowDateTime); + mtime /= 1000000000LL / 100; // 100ns -> s. + mtime -= 12622770400LL; // 1600 epoch -> 2000 epoch (subtract 400 years). + return (TimeStamp)mtime; +} + +TimeStamp StatSingleFile(const string& path, string* err) { + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &attrs)) { + DWORD win_err = GetLastError(); + if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) + return 0; + *err = "GetFileAttributesEx(" + path + "): " + GetLastErrorString(); + return -1; + } + return TimeStampFromFileTime(attrs.ftLastWriteTime); +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4996) // GetVersionExA is deprecated post SDK 8.1. +#endif +bool IsWindows7OrLater() { + OSVERSIONINFO version_info = { sizeof(version_info) }; + if (!GetVersionEx(&version_info)) + Fatal("GetVersionEx: %s", GetLastErrorString().c_str()); + return version_info.dwMajorVersion > 6 || + (version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 1); +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps, + string* err) { + // FindExInfoBasic is 30% faster than FindExInfoStandard. + static bool can_use_basic_info = IsWindows7OrLater(); + // This is not in earlier SDKs. + const FINDEX_INFO_LEVELS kFindExInfoBasic = + static_cast<FINDEX_INFO_LEVELS>(1); + FINDEX_INFO_LEVELS level = + can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard; + WIN32_FIND_DATAA ffd; + HANDLE find_handle = FindFirstFileExA((dir + "\\*").c_str(), level, &ffd, + FindExSearchNameMatch, NULL, 0); + + if (find_handle == INVALID_HANDLE_VALUE) { + DWORD win_err = GetLastError(); + if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) + return true; + *err = "FindFirstFileExA(" + dir + "): " + GetLastErrorString(); + return false; + } + do { + string lowername = ffd.cFileName; + transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower); + stamps->insert(make_pair(lowername, + TimeStampFromFileTime(ffd.ftLastWriteTime))); + } while (FindNextFileA(find_handle, &ffd)); + FindClose(find_handle); + return true; +} +#endif // _WIN32 + } // namespace // DiskInterface --------------------------------------------------------------- @@ -63,9 +134,12 @@ bool DiskInterface::MakeDirs(const string& path) { string dir = DirName(path); if (dir.empty()) return true; // Reached root; assume it's there. - TimeStamp mtime = Stat(dir); - if (mtime < 0) - return false; // Error. + string err; + TimeStamp mtime = Stat(dir, &err); + if (mtime < 0) { + Error("%s", err.c_str()); + return false; + } if (mtime > 0) return true; // Exists already; we're done. @@ -78,45 +152,42 @@ bool DiskInterface::MakeDirs(const string& path) { // RealDiskInterface ----------------------------------------------------------- -TimeStamp RealDiskInterface::Stat(const string& path) { +TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { #ifdef _WIN32 // MSDN: "Naming Files, Paths, and Namespaces" // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) { - if (!quiet_) { - Error("Stat(%s): Filename longer than %i characters", - path.c_str(), MAX_PATH); - } + ostringstream err_stream; + err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH + << " characters"; + *err = err_stream.str(); return -1; } - WIN32_FILE_ATTRIBUTE_DATA attrs; - if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &attrs)) { - DWORD err = GetLastError(); - if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) - return 0; - if (!quiet_) { - Error("GetFileAttributesEx(%s): %s", path.c_str(), - GetLastErrorString().c_str()); + if (!use_cache_) + return StatSingleFile(path, err); + + string dir = DirName(path); + string base(path.substr(dir.size() ? dir.size() + 1 : 0)); + + transform(dir.begin(), dir.end(), dir.begin(), ::tolower); + transform(base.begin(), base.end(), base.begin(), ::tolower); + + Cache::iterator ci = cache_.find(dir); + if (ci == cache_.end()) { + ci = cache_.insert(make_pair(dir, DirCache())).first; + if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) { + cache_.erase(ci); + return -1; } - return -1; } - const FILETIME& filetime = attrs.ftLastWriteTime; - // FILETIME is in 100-nanosecond increments since the Windows epoch. - // We don't much care about epoch correctness but we do want the - // resulting value to fit in an integer. - uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) | - ((uint64_t)filetime.dwLowDateTime); - mtime /= 1000000000LL / 100; // 100ns -> s. - mtime -= 12622770400LL; // 1600 epoch -> 2000 epoch (subtract 400 years). - return (TimeStamp)mtime; + DirCache::iterator di = ci->second.find(base); + return di != ci->second.end() ? di->second : 0; #else struct stat st; if (stat(path.c_str(), &st) < 0) { if (errno == ENOENT || errno == ENOTDIR) return 0; - if (!quiet_) { - Error("stat(%s): %s", path.c_str(), strerror(errno)); - } + *err = "stat(" + path + "): " + strerror(errno); return -1; } return st.st_mtime; @@ -181,3 +252,11 @@ int RealDiskInterface::RemoveFile(const string& path) { return 0; } } + +void RealDiskInterface::AllowStatCache(bool allow) { +#ifdef _WIN32 + use_cache_ = allow; + if (!use_cache_) + cache_.clear(); +#endif +} diff --git a/src/disk_interface.h b/src/disk_interface.h index ff1e21c..b61d192 100644 --- a/src/disk_interface.h +++ b/src/disk_interface.h @@ -15,6 +15,7 @@ #ifndef NINJA_DISK_INTERFACE_H_ #define NINJA_DISK_INTERFACE_H_ +#include <map> #include <string> using namespace std; @@ -29,7 +30,7 @@ struct DiskInterface { /// stat() a file, returning the mtime, or 0 if missing and -1 on /// other errors. - virtual TimeStamp Stat(const string& path) = 0; + virtual TimeStamp Stat(const string& path, string* err) const = 0; /// Create a directory, returning false on failure. virtual bool MakeDir(const string& path) = 0; @@ -55,16 +56,32 @@ struct DiskInterface { /// Implementation of DiskInterface that actually hits the disk. struct RealDiskInterface : public DiskInterface { - RealDiskInterface() : quiet_(false) {} + RealDiskInterface() +#ifdef _WIN32 + : use_cache_(false) +#endif + {} virtual ~RealDiskInterface() {} - virtual TimeStamp Stat(const string& path); + virtual TimeStamp Stat(const string& path, string* err) const; virtual bool MakeDir(const string& path); virtual bool WriteFile(const string& path, const string& contents); virtual string ReadFile(const string& path, string* err); virtual int RemoveFile(const string& path); - /// Whether to print on errors. Used to make a test quieter. - bool quiet_; + /// Whether stat information can be cached. Only has an effect on Windows. + void AllowStatCache(bool allow); + + private: +#ifdef _WIN32 + /// Whether stat information can be cached. + bool use_cache_; + + typedef map<string, TimeStamp> DirCache; + // TODO: Neither a map nor a hashmap seems ideal here. If the statcache + // works out, come up with a better data structure. + typedef map<string, DirCache> Cache; + mutable Cache cache_; +#endif }; #endif // NINJA_DISK_INTERFACE_H_ diff --git a/src/disk_interface_test.cc b/src/disk_interface_test.cc index 51a1d14..9d210b4 100644 --- a/src/disk_interface_test.cc +++ b/src/disk_interface_test.cc @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <gtest/gtest.h> - +#include <assert.h> +#include <stdio.h> #ifdef _WIN32 #include <io.h> #include <windows.h> @@ -47,35 +47,114 @@ struct DiskInterfaceTest : public testing::Test { }; TEST_F(DiskInterfaceTest, StatMissingFile) { - EXPECT_EQ(0, disk_.Stat("nosuchfile")); + string err; + EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); + EXPECT_EQ("", err); // On Windows, the errno for a file in a nonexistent directory // is different. - EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile")); + EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); + EXPECT_EQ("", err); // On POSIX systems, the errno is different if a component of the // path prefix is not a directory. ASSERT_TRUE(Touch("notadir")); - EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile")); + EXPECT_EQ(0, disk_.Stat("notadir/nosuchfile", &err)); + EXPECT_EQ("", err); } TEST_F(DiskInterfaceTest, StatBadPath) { - disk_.quiet_ = true; + string err; #ifdef _WIN32 string bad_path("cc:\\foo"); - EXPECT_EQ(-1, disk_.Stat(bad_path)); + EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); + EXPECT_NE("", err); #else string too_long_name(512, 'x'); - EXPECT_EQ(-1, disk_.Stat(too_long_name)); + EXPECT_EQ(-1, disk_.Stat(too_long_name, &err)); + EXPECT_NE("", err); #endif - disk_.quiet_ = false; } TEST_F(DiskInterfaceTest, StatExistingFile) { + string err; ASSERT_TRUE(Touch("file")); - EXPECT_GT(disk_.Stat("file"), 1); + EXPECT_GT(disk_.Stat("file", &err), 1); + EXPECT_EQ("", err); +} + +TEST_F(DiskInterfaceTest, StatExistingDir) { + string err; + ASSERT_TRUE(disk_.MakeDir("subdir")); + ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); + EXPECT_GT(disk_.Stat(".", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("subdir", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); + EXPECT_EQ("", err); + + EXPECT_EQ(disk_.Stat("subdir", &err), + disk_.Stat("subdir/.", &err)); + EXPECT_EQ(disk_.Stat("subdir", &err), + disk_.Stat("subdir/subsubdir/..", &err)); + EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), + disk_.Stat("subdir/subsubdir/.", &err)); } +#ifdef _WIN32 +TEST_F(DiskInterfaceTest, StatCache) { + string err; + disk_.AllowStatCache(true); + + ASSERT_TRUE(Touch("file1")); + ASSERT_TRUE(Touch("fiLE2")); + ASSERT_TRUE(disk_.MakeDir("subdir")); + ASSERT_TRUE(disk_.MakeDir("subdir/subsubdir")); + ASSERT_TRUE(Touch("subdir\\subfile1")); + ASSERT_TRUE(Touch("subdir\\SUBFILE2")); + ASSERT_TRUE(Touch("subdir\\SUBFILE3")); + + EXPECT_GT(disk_.Stat("FIle1", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("file1", &err), 1); + EXPECT_EQ("", err); + + EXPECT_GT(disk_.Stat("subdir/subfile2", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("sUbdir\\suBFile1", &err), 1); + EXPECT_EQ("", err); + + EXPECT_GT(disk_.Stat(".", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("subdir", &err), 1); + EXPECT_EQ("", err); + EXPECT_GT(disk_.Stat("subdir/subsubdir", &err), 1); + EXPECT_EQ("", err); + + EXPECT_EQ(disk_.Stat("subdir", &err), + disk_.Stat("subdir/.", &err)); + EXPECT_EQ("", err); + EXPECT_EQ(disk_.Stat("subdir", &err), + disk_.Stat("subdir/subsubdir/..", &err)); + EXPECT_EQ("", err); + EXPECT_EQ(disk_.Stat("subdir/subsubdir", &err), + disk_.Stat("subdir/subsubdir/.", &err)); + EXPECT_EQ("", err); + + // Test error cases. + string bad_path("cc:\\foo"); + EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); + EXPECT_NE("", err); err.clear(); + EXPECT_EQ(-1, disk_.Stat(bad_path, &err)); + EXPECT_NE("", err); err.clear(); + EXPECT_EQ(0, disk_.Stat("nosuchfile", &err)); + EXPECT_EQ("", err); + EXPECT_EQ(0, disk_.Stat("nosuchdir/nosuchfile", &err)); + EXPECT_EQ("", err); +} +#endif + TEST_F(DiskInterfaceTest, ReadFile) { string err; EXPECT_EQ("", disk_.ReadFile("foobar", &err)); @@ -120,7 +199,7 @@ struct StatTest : public StateTestWithBuiltinRules, StatTest() : scan_(&state_, NULL, NULL, this) {} // DiskInterface implementation. - virtual TimeStamp Stat(const string& path); + virtual TimeStamp Stat(const string& path, string* err) const; virtual bool WriteFile(const string& path, const string& contents) { assert(false); return true; @@ -140,12 +219,12 @@ struct StatTest : public StateTestWithBuiltinRules, DependencyScan scan_; map<string, TimeStamp> mtimes_; - vector<string> stats_; + mutable vector<string> stats_; }; -TimeStamp StatTest::Stat(const string& path) { +TimeStamp StatTest::Stat(const string& path, string* err) const { stats_.push_back(path); - map<string, TimeStamp>::iterator i = mtimes_.find(path); + map<string, TimeStamp>::const_iterator i = mtimes_.find(path); if (i == mtimes_.end()) return 0; // File not found. return i->second; @@ -156,7 +235,9 @@ TEST_F(StatTest, Simple) { "build out: cat in\n")); Node* out = GetNode("out"); - out->Stat(this); + string err; + EXPECT_TRUE(out->Stat(this, &err)); + EXPECT_EQ("", err); ASSERT_EQ(1u, stats_.size()); scan_.RecomputeDirty(out->in_edge(), NULL); ASSERT_EQ(2u, stats_.size()); @@ -170,7 +251,9 @@ TEST_F(StatTest, TwoStep) { "build mid: cat in\n")); Node* out = GetNode("out"); - out->Stat(this); + string err; + EXPECT_TRUE(out->Stat(this, &err)); + EXPECT_EQ("", err); ASSERT_EQ(1u, stats_.size()); scan_.RecomputeDirty(out->in_edge(), NULL); ASSERT_EQ(3u, stats_.size()); @@ -188,7 +271,9 @@ TEST_F(StatTest, Tree) { "build mid2: cat in21 in22\n")); Node* out = GetNode("out"); - out->Stat(this); + string err; + EXPECT_TRUE(out->Stat(this, &err)); + EXPECT_EQ("", err); ASSERT_EQ(1u, stats_.size()); scan_.RecomputeDirty(out->in_edge(), NULL); ASSERT_EQ(1u + 6u, stats_.size()); @@ -207,7 +292,9 @@ TEST_F(StatTest, Middle) { mtimes_["out"] = 1; Node* out = GetNode("out"); - out->Stat(this); + string err; + EXPECT_TRUE(out->Stat(this, &err)); + EXPECT_EQ("", err); ASSERT_EQ(1u, stats_.size()); scan_.RecomputeDirty(out->in_edge(), NULL); ASSERT_FALSE(GetNode("in")->dirty()); diff --git a/src/edit_distance.cc b/src/edit_distance.cc index 9553c6e..3bb62b8 100644 --- a/src/edit_distance.cc +++ b/src/edit_distance.cc @@ -28,40 +28,42 @@ int EditDistance(const StringPiece& s1, // http://en.wikipedia.org/wiki/Levenshtein_distance // // Although the algorithm is typically described using an m x n - // array, only two rows are used at a time, so this implemenation - // just keeps two separate vectors for those two rows. + // array, only one row plus one element are used at a time, so this + // implementation just keeps one vector for the row. To update one entry, + // only the entries to the left, top, and top-left are needed. The left + // entry is in row[x-1], the top entry is what's in row[x] from the last + // iteration, and the top-left entry is stored in previous. int m = s1.len_; int n = s2.len_; - vector<int> previous(n + 1); - vector<int> current(n + 1); - - for (int i = 0; i <= n; ++i) - previous[i] = i; + vector<int> row(n + 1); + for (int i = 1; i <= n; ++i) + row[i] = i; for (int y = 1; y <= m; ++y) { - current[0] = y; - int best_this_row = current[0]; + row[0] = y; + int best_this_row = row[0]; + int previous = y - 1; for (int x = 1; x <= n; ++x) { + int old_row = row[x]; if (allow_replacements) { - current[x] = min(previous[x-1] + (s1.str_[y-1] == s2.str_[x-1] ? 0 : 1), - min(current[x-1], previous[x])+1); + row[x] = min(previous + (s1.str_[y - 1] == s2.str_[x - 1] ? 0 : 1), + min(row[x - 1], row[x]) + 1); } else { - if (s1.str_[y-1] == s2.str_[x-1]) - current[x] = previous[x-1]; + if (s1.str_[y - 1] == s2.str_[x - 1]) + row[x] = previous; else - current[x] = min(current[x-1], previous[x]) + 1; + row[x] = min(row[x - 1], row[x]) + 1; } - best_this_row = min(best_this_row, current[x]); + previous = old_row; + best_this_row = min(best_this_row, row[x]); } if (max_edit_distance && best_this_row > max_edit_distance) return max_edit_distance + 1; - - current.swap(previous); } - return previous[n]; + return row[n]; } diff --git a/src/eval_env.cc b/src/eval_env.cc index 834b7e1..e991d21 100644 --- a/src/eval_env.cc +++ b/src/eval_env.cc @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <assert.h> + #include "eval_env.h" string BindingEnv::LookupVariable(const string& var) { @@ -27,6 +29,55 @@ void BindingEnv::AddBinding(const string& key, const string& val) { bindings_[key] = val; } +void BindingEnv::AddRule(const Rule* rule) { + assert(LookupRuleCurrentScope(rule->name()) == NULL); + rules_[rule->name()] = rule; +} + +const Rule* BindingEnv::LookupRuleCurrentScope(const string& rule_name) { + map<string, const Rule*>::iterator i = rules_.find(rule_name); + if (i == rules_.end()) + return NULL; + return i->second; +} + +const Rule* BindingEnv::LookupRule(const string& rule_name) { + map<string, const Rule*>::iterator i = rules_.find(rule_name); + if (i != rules_.end()) + return i->second; + if (parent_) + return parent_->LookupRule(rule_name); + return NULL; +} + +void Rule::AddBinding(const string& key, const EvalString& val) { + bindings_[key] = val; +} + +const EvalString* Rule::GetBinding(const string& key) const { + map<string, EvalString>::const_iterator i = bindings_.find(key); + if (i == bindings_.end()) + return NULL; + return &i->second; +} + +// static +bool Rule::IsReservedBinding(const string& var) { + return var == "command" || + var == "depfile" || + var == "description" || + var == "deps" || + var == "generator" || + var == "pool" || + var == "restat" || + var == "rspfile" || + var == "rspfile_content"; +} + +const map<string, const Rule*>& BindingEnv::GetRules() const { + return rules_; +} + string BindingEnv::LookupWithFallback(const string& var, const EvalString* eval, Env* env) { diff --git a/src/eval_env.h b/src/eval_env.h index f3c959a..28c4d16 100644 --- a/src/eval_env.h +++ b/src/eval_env.h @@ -22,7 +22,7 @@ using namespace std; #include "string_piece.h" -struct EvalString; +struct Rule; /// An interface for a scope for variable (e.g. "$foo") lookups. struct Env { @@ -30,15 +30,62 @@ struct Env { virtual string LookupVariable(const string& var) = 0; }; +/// A tokenized string that contains variable references. +/// Can be evaluated relative to an Env. +struct EvalString { + string Evaluate(Env* env) const; + + void Clear() { parsed_.clear(); } + bool empty() const { return parsed_.empty(); } + + void AddText(StringPiece text); + void AddSpecial(StringPiece text); + + /// Construct a human-readable representation of the parsed state, + /// for use in tests. + string Serialize() const; + +private: + enum TokenType { RAW, SPECIAL }; + typedef vector<pair<string, TokenType> > TokenList; + TokenList parsed_; +}; + +/// An invokable build command and associated metadata (description, etc.). +struct Rule { + explicit Rule(const string& name) : name_(name) {} + + const string& name() const { return name_; } + + typedef map<string, EvalString> Bindings; + void AddBinding(const string& key, const EvalString& val); + + static bool IsReservedBinding(const string& var); + + const EvalString* GetBinding(const string& key) const; + + private: + // Allow the parsers to reach into this object and fill out its fields. + friend struct ManifestParser; + + string name_; + map<string, EvalString> bindings_; +}; + /// An Env which contains a mapping of variables to values /// as well as a pointer to a parent scope. struct BindingEnv : public Env { BindingEnv() : parent_(NULL) {} - explicit BindingEnv(Env* parent) : parent_(parent) {} + explicit BindingEnv(BindingEnv* parent) : parent_(parent) {} virtual ~BindingEnv() {} virtual string LookupVariable(const string& var); + void AddRule(const Rule* rule); + const Rule* LookupRule(const string& rule_name); + const Rule* LookupRuleCurrentScope(const string& rule_name); + const map<string, const Rule*>& GetRules() const; + void AddBinding(const string& key, const string& val); /// This is tricky. Edges want lookup scope to go in this order: @@ -51,28 +98,8 @@ struct BindingEnv : public Env { private: map<string, string> bindings_; - Env* parent_; -}; - -/// A tokenized string that contains variable references. -/// Can be evaluated relative to an Env. -struct EvalString { - string Evaluate(Env* env) const; - - void Clear() { parsed_.clear(); } - bool empty() const { return parsed_.empty(); } - - void AddText(StringPiece text); - void AddSpecial(StringPiece text); - - /// Construct a human-readable representation of the parsed state, - /// for use in tests. - string Serialize() const; - -private: - enum TokenType { RAW, SPECIAL }; - typedef vector<pair<string, TokenType> > TokenList; - TokenList parsed_; + map<string, const Rule*> rules_; + BindingEnv* parent_; }; #endif // NINJA_EVAL_ENV_H_ diff --git a/src/getopt.c b/src/getopt.c index 75ef99c..3350fb9 100644 --- a/src/getopt.c +++ b/src/getopt.c @@ -299,7 +299,7 @@ getopt_internal (int argc, char **argv, char *shortopts, return (optopt = '?'); } has_arg = ((cp[1] == ':') - ? ((cp[2] == ':') ? OPTIONAL_ARG : REQUIRED_ARG) : no_argument); + ? ((cp[2] == ':') ? OPTIONAL_ARG : required_argument) : no_argument); possible_arg = argv[optind] + optwhere + 1; optopt = *cp; } @@ -318,7 +318,7 @@ getopt_internal (int argc, char **argv, char *shortopts, else optarg = NULL; break; - case REQUIRED_ARG: + case required_argument: if (*possible_arg == '=') possible_arg++; if (*possible_arg != '\0') diff --git a/src/getopt.h b/src/getopt.h index ead9878..b4247fb 100644 --- a/src/getopt.h +++ b/src/getopt.h @@ -4,9 +4,9 @@ /* include files needed by this include file */ /* macros defined by this include file */ -#define no_argument 0 -#define REQUIRED_ARG 1 -#define OPTIONAL_ARG 2 +#define no_argument 0 +#define required_argument 1 +#define OPTIONAL_ARG 2 /* types defined by this include file */ diff --git a/src/graph.cc b/src/graph.cc index aa9c0e8..9e65675 100644 --- a/src/graph.cc +++ b/src/graph.cc @@ -27,34 +27,9 @@ #include "state.h" #include "util.h" -bool Node::Stat(DiskInterface* disk_interface) { +bool Node::Stat(DiskInterface* disk_interface, string* err) { METRIC_RECORD("node stat"); - mtime_ = disk_interface->Stat(path_); - return mtime_ > 0; -} - -void Rule::AddBinding(const string& key, const EvalString& val) { - bindings_[key] = val; -} - -const EvalString* Rule::GetBinding(const string& key) const { - map<string, EvalString>::const_iterator i = bindings_.find(key); - if (i == bindings_.end()) - return NULL; - return &i->second; -} - -// static -bool Rule::IsReservedBinding(const string& var) { - return var == "command" || - var == "depfile" || - var == "description" || - var == "deps" || - var == "generator" || - var == "pool" || - var == "restat" || - var == "rspfile" || - var == "rspfile_content"; + return (mtime_ = disk_interface->Stat(path_, err)) != -1; } bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { @@ -62,10 +37,25 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { edge->outputs_ready_ = true; edge->deps_missing_ = false; + // RecomputeDirty() recursively walks the graph following the input nodes + // of |edge| and the in_edges of these nodes. It uses the stat state of each + // node to mark nodes as visited and doesn't traverse across nodes that have + // been visited already. To make sure that every edge is visited only once + // (important because an edge's deps are loaded every time it's visited), mark + // all outputs of |edge| visited as a first step. This ensures that edges + // with multiple inputs and outputs are visited only once, even in cyclic + // graphs. + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { + if (!(*o)->StatIfNecessary(disk_interface_, err)) + return false; + } + if (!dep_loader_.LoadDeps(edge, err)) { if (!err->empty()) return false; // Failed to load dependency info: rebuild to regenerate it. + // LoadDeps() did EXPLAIN() already, no need to do it here. dirty = edge->deps_missing_ = true; } @@ -73,7 +63,9 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { Node* most_recent_input = NULL; for (vector<Node*>::iterator i = edge->inputs_.begin(); i != edge->inputs_.end(); ++i) { - if ((*i)->StatIfNecessary(disk_interface_)) { + if (!(*i)->status_known()) { + if (!(*i)->StatIfNecessary(disk_interface_, err)) + return false; if (Edge* in_edge = (*i)->in_edge()) { if (!RecomputeDirty(in_edge, err)) return false; @@ -108,15 +100,14 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { // We may also be dirty due to output state: missing outputs, out of // date outputs, etc. Visit all outputs and determine whether they're dirty. if (!dirty) - dirty = RecomputeOutputsDirty(edge, most_recent_input); + if (!RecomputeOutputsDirty(edge, most_recent_input, &dirty, err)) + return false; - // Finally, visit each output to mark off that we've visited it, and update - // their dirty state if necessary. - for (vector<Node*>::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - (*i)->StatIfNecessary(disk_interface_); + // Finally, visit each output and update their dirty state if necessary. + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { if (dirty) - (*i)->MarkDirty(); + (*o)->MarkDirty(); } // If an edge is dirty, its outputs are normally not ready. (It's @@ -130,16 +121,19 @@ bool DependencyScan::RecomputeDirty(Edge* edge, string* err) { return true; } -bool DependencyScan::RecomputeOutputsDirty(Edge* edge, - Node* most_recent_input) { - string command = edge->EvaluateCommand(true); - for (vector<Node*>::iterator i = edge->outputs_.begin(); - i != edge->outputs_.end(); ++i) { - (*i)->StatIfNecessary(disk_interface_); - if (RecomputeOutputDirty(edge, most_recent_input, command, *i)) +bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input, + bool* outputs_dirty, string* err) { + string command = edge->EvaluateCommand(/*incl_rsp_file=*/true); + for (vector<Node*>::iterator o = edge->outputs_.begin(); + o != edge->outputs_.end(); ++o) { + if (!(*o)->StatIfNecessary(disk_interface_, err)) + return false; + if (RecomputeOutputDirty(edge, most_recent_input, command, *o)) { + *outputs_dirty = true; return true; + } } - return false; + return true; } bool DependencyScan::RecomputeOutputDirty(Edge* edge, @@ -149,7 +143,12 @@ bool DependencyScan::RecomputeOutputDirty(Edge* edge, if (edge->is_phony()) { // Phony edges don't write any output. Outputs are only dirty if // there are no inputs and we're missing the output. - return edge->inputs_.empty() && !output->exists(); + if (edge->inputs_.empty() && !output->exists()) { + EXPLAIN("output %s of phony edge with no inputs doesn't exist", + output->path().c_str()); + return true; + } + return false; } BuildLog::LogEntry* entry = 0; @@ -217,8 +216,8 @@ bool Edge::AllInputsReady() const { struct EdgeEnv : public Env { enum EscapeKind { kShellEscape, kDoNotEscape }; - explicit EdgeEnv(Edge* edge, EscapeKind escape) - : edge_(edge), escape_in_out_(escape) {} + EdgeEnv(Edge* edge, EscapeKind escape) + : edge_(edge), escape_in_out_(escape), recursive_(false) {} virtual string LookupVariable(const string& var); /// Given a span of Nodes, construct a list of paths suitable for a command @@ -227,8 +226,11 @@ struct EdgeEnv : public Env { vector<Node*>::iterator end, char sep); + private: + vector<string> lookups_; Edge* edge_; EscapeKind escape_in_out_; + bool recursive_; }; string EdgeEnv::LookupVariable(const string& var) { @@ -244,8 +246,25 @@ string EdgeEnv::LookupVariable(const string& var) { ' '); } + if (recursive_) { + vector<string>::const_iterator it; + if ((it = find(lookups_.begin(), lookups_.end(), var)) != lookups_.end()) { + string cycle; + for (; it != lookups_.end(); ++it) + cycle.append(*it + " -> "); + cycle.append(var); + Fatal(("cycle in rule variables: " + cycle).c_str()); + } + } + // See notes on BindingEnv::LookupWithFallback. const EvalString* eval = edge_->rule_->GetBinding(var); + if (recursive_ && eval) + lookups_.push_back(var); + + // In practice, variables defined on rules never use another rule variable. + // For performance, only start checking for cycles after the first lookup. + recursive_ = true; return edge_->env_->LookupWithFallback(var, eval, this); } @@ -256,7 +275,7 @@ string EdgeEnv::MakePathList(vector<Node*>::iterator begin, for (vector<Node*>::iterator i = begin; i != end; ++i) { if (!result.empty()) result.push_back(sep); - const string& path = (*i)->path(); + const string& path = (*i)->PathDecanonicalized(); if (escape_in_out_ == kShellEscape) { #if _WIN32 GetWin32EscapedString(path, &result); @@ -328,6 +347,21 @@ bool Edge::use_console() const { return pool() == &State::kConsolePool; } +// static +string Node::PathDecanonicalized(const string& path, unsigned int slash_bits) { + string result = path; +#ifdef _WIN32 + unsigned int mask = 1; + for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) { + if (slash_bits & mask) + *c = '\\'; + c++; + mask <<= 1; + } +#endif + return result; +} + void Node::Dump(const char* prefix) const { printf("%s <%s 0x%p> mtime: %d%s, (:%s), ", prefix, path().c_str(), this, @@ -379,12 +413,18 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, return false; } - // Check that this depfile matches the edge's output. + unsigned int unused; + if (!CanonicalizePath(const_cast<char*>(depfile.out_.str_), + &depfile.out_.len_, &unused, err)) + return false; + + // Check that this depfile matches the edge's output, if not return false to + // mark the edge as dirty. Node* first_output = edge->outputs_[0]; StringPiece opath = StringPiece(first_output->path()); if (opath != depfile.out_) { - *err = "expected depfile '" + path + "' to mention '" + - first_output->path() + "', got '" + depfile.out_.AsString() + "'"; + EXPLAIN("expected depfile '%s' to mention '%s', got '%s'", path.c_str(), + first_output->path().c_str(), depfile.out_.AsString().c_str()); return false; } @@ -395,10 +435,12 @@ bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, // Add all its in-edges. for (vector<StringPiece>::iterator i = depfile.ins_.begin(); i != depfile.ins_.end(); ++i, ++implicit_dep) { - if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, err)) + unsigned int slash_bits; + if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits, + err)) return false; - Node* node = state_->GetNode(*i); + Node* node = state_->GetNode(*i, slash_bits); *implicit_dep = node; node->AddOutEdge(edge); CreatePhonyInEdge(node); @@ -418,7 +460,7 @@ bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) { // Deps are invalid if the output is newer than the deps. if (output->mtime() > deps->mtime) { - EXPLAIN("stored deps info out of date for for '%s' (%d vs %d)", + EXPLAIN("stored deps info out of date for '%s' (%d vs %d)", output->path().c_str(), deps->mtime, output->mtime()); return false; } diff --git a/src/graph.h b/src/graph.h index 66e31b5..cf15123 100644 --- a/src/graph.h +++ b/src/graph.h @@ -33,22 +33,22 @@ struct State; /// Information about a node in the dependency graph: the file, whether /// it's dirty, mtime, etc. struct Node { - explicit Node(const string& path) + Node(const string& path, unsigned int slash_bits) : path_(path), + slash_bits_(slash_bits), mtime_(-1), dirty_(false), in_edge_(NULL), id_(-1) {} - /// Return true if the file exists (mtime_ got a value). - bool Stat(DiskInterface* disk_interface); + /// Return false on error. + bool Stat(DiskInterface* disk_interface, string* err); - /// Return true if we needed to stat. - bool StatIfNecessary(DiskInterface* disk_interface) { + /// Return false on error. + bool StatIfNecessary(DiskInterface* disk_interface, string* err) { if (status_known()) - return false; - Stat(disk_interface); - return true; + return true; + return Stat(disk_interface, err); } /// Mark as not-yet-stat()ed and not dirty. @@ -71,6 +71,14 @@ struct Node { } const string& path() const { return path_; } + /// Get |path()| but use slash_bits to convert back to original slash styles. + string PathDecanonicalized() const { + return PathDecanonicalized(path_, slash_bits_); + } + static string PathDecanonicalized(const string& path, + unsigned int slash_bits); + unsigned int slash_bits() const { return slash_bits_; } + TimeStamp mtime() const { return mtime_; } bool dirty() const { return dirty_; } @@ -90,6 +98,11 @@ struct Node { private: string path_; + + /// Set bits starting from lowest for backslashes that were normalized to + /// forward slashes by CanonicalizePath. See |PathDecanonicalized|. + unsigned int slash_bits_; + /// Possible values of mtime_: /// -1: file hasn't been examined /// 0: we looked, and file doesn't exist @@ -112,30 +125,10 @@ private: int id_; }; -/// An invokable build command and associated metadata (description, etc.). -struct Rule { - explicit Rule(const string& name) : name_(name) {} - - const string& name() const { return name_; } - - typedef map<string, EvalString> Bindings; - void AddBinding(const string& key, const EvalString& val); - - static bool IsReservedBinding(const string& var); - - const EvalString* GetBinding(const string& key) const; - - private: - // Allow the parsers to reach into this object and fill out its fields. - friend struct ManifestParser; - - string name_; - map<string, EvalString> bindings_; -}; - /// An edge in the dependency graph; links between Nodes using Rules. struct Edge { - Edge() : rule_(NULL), env_(NULL), outputs_ready_(false), deps_missing_(false), + Edge() : rule_(NULL), pool_(NULL), env_(NULL), + outputs_ready_(false), deps_missing_(false), implicit_deps_(0), order_only_deps_(0) {} /// Return true if all inputs' in-edges are ready. @@ -248,9 +241,10 @@ struct DependencyScan { /// Returns false on failure. bool RecomputeDirty(Edge* edge, string* err); - /// Recompute whether any output of the edge is dirty. - /// Returns true if so. - bool RecomputeOutputsDirty(Edge* edge, Node* most_recent_input); + /// Recompute whether any output of the edge is dirty, if so sets |*dirty|. + /// Returns false on failure. + bool RecomputeOutputsDirty(Edge* edge, Node* most_recent_input, + bool* dirty, string* err); BuildLog* build_log() const { return build_log_; diff --git a/src/graph_test.cc b/src/graph_test.cc index 14dc678..44be8a5 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -153,7 +153,7 @@ TEST_F(GraphTest, VarInOutPathEscaping) { #endif } -// Regression test for https://github.com/martine/ninja/issues/380 +// Regression test for https://github.com/ninja-build/ninja/issues/380 TEST_F(GraphTest, DepfileWithCanonicalizablePath) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" @@ -172,7 +172,7 @@ TEST_F(GraphTest, DepfileWithCanonicalizablePath) { EXPECT_FALSE(GetNode("out.o")->dirty()); } -// Regression test for https://github.com/martine/ninja/issues/404 +// Regression test for https://github.com/ninja-build/ninja/issues/404 TEST_F(GraphTest, DepfileRemoved) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" @@ -251,3 +251,99 @@ TEST_F(GraphTest, NestedPhonyPrintsDone) { EXPECT_EQ(0, plan_.command_edge_count()); ASSERT_FALSE(plan_.more_to_do()); } + +// Verify that cycles in graphs with multiple outputs are handled correctly +// in RecomputeDirty() and don't cause deps to be loaded multiple times. +TEST_F(GraphTest, CycleWithLengthZeroFromDepfile) { + AssertParse(&state_, +"rule deprule\n" +" depfile = dep.d\n" +" command = unused\n" +"build a b: deprule\n" + ); + fs_.Create("dep.d", "a: b\n"); + + string err; + Edge* edge = GetNode("a")->in_edge(); + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + // Despite the depfile causing edge to be a cycle (it has outputs a and b, + // but the depfile also adds b as an input), the deps should have been loaded + // only once: + EXPECT_EQ(1, edge->inputs_.size()); + EXPECT_EQ("b", edge->inputs_[0]->path()); +} + +// Like CycleWithLengthZeroFromDepfile but with a higher cycle length. +TEST_F(GraphTest, CycleWithLengthOneFromDepfile) { + AssertParse(&state_, +"rule deprule\n" +" depfile = dep.d\n" +" command = unused\n" +"rule r\n" +" command = unused\n" +"build a b: deprule\n" +"build c: r b\n" + ); + fs_.Create("dep.d", "a: c\n"); + + string err; + Edge* edge = GetNode("a")->in_edge(); + EXPECT_TRUE(scan_.RecomputeDirty(edge, &err)); + ASSERT_EQ("", err); + + // Despite the depfile causing edge to be a cycle (|edge| has outputs a and b, + // but c's in_edge has b as input but the depfile also adds |edge| as + // output)), the deps should have been loaded only once: + EXPECT_EQ(1, edge->inputs_.size()); + EXPECT_EQ("c", edge->inputs_[0]->path()); +} + +// Like CycleWithLengthOneFromDepfile but building a node one hop away from +// the cycle. +TEST_F(GraphTest, CycleWithLengthOneFromDepfileOneHopAway) { + AssertParse(&state_, +"rule deprule\n" +" depfile = dep.d\n" +" command = unused\n" +"rule r\n" +" command = unused\n" +"build a b: deprule\n" +"build c: r b\n" +"build d: r a\n" + ); + fs_.Create("dep.d", "a: c\n"); + + string err; + EXPECT_TRUE(scan_.RecomputeDirty(GetNode("d")->in_edge(), &err)); + ASSERT_EQ("", err); + + // Despite the depfile causing edge to be a cycle (|edge| has outputs a and b, + // but c's in_edge has b as input but the depfile also adds |edge| as + // output)), the deps should have been loaded only once: + Edge* edge = GetNode("a")->in_edge(); + EXPECT_EQ(1, edge->inputs_.size()); + EXPECT_EQ("c", edge->inputs_[0]->path()); +} + +#ifdef _WIN32 +TEST_F(GraphTest, Decanonicalize) { + ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, +"build out\\out1: cat src\\in1\n" +"build out\\out2/out3\\out4: cat mid1\n" +"build out3 out4\\foo: cat mid1\n")); + + string err; + vector<Node*> root_nodes = state_.RootNodes(&err); + EXPECT_EQ(4u, root_nodes.size()); + EXPECT_EQ(root_nodes[0]->path(), "out/out1"); + EXPECT_EQ(root_nodes[1]->path(), "out/out2/out3/out4"); + EXPECT_EQ(root_nodes[2]->path(), "out3"); + EXPECT_EQ(root_nodes[3]->path(), "out4/foo"); + EXPECT_EQ(root_nodes[0]->PathDecanonicalized(), "out\\out1"); + EXPECT_EQ(root_nodes[1]->PathDecanonicalized(), "out\\out2/out3\\out4"); + EXPECT_EQ(root_nodes[2]->PathDecanonicalized(), "out3"); + EXPECT_EQ(root_nodes[3]->PathDecanonicalized(), "out4\\foo"); +} +#endif diff --git a/src/graphviz.cc b/src/graphviz.cc index 8354a22..dce8b32 100644 --- a/src/graphviz.cc +++ b/src/graphviz.cc @@ -15,6 +15,7 @@ #include "graphviz.h" #include <stdio.h> +#include <algorithm> #include "graph.h" @@ -22,7 +23,9 @@ void GraphViz::AddTarget(Node* node) { if (visited_nodes_.find(node) != visited_nodes_.end()) return; - printf("\"%p\" [label=\"%s\"]\n", node, node->path().c_str()); + string pathstr = node->path(); + replace(pathstr.begin(), pathstr.end(), '\\', '/'); + printf("\"%p\" [label=\"%s\"]\n", node, pathstr.c_str()); visited_nodes_.insert(node); Edge* edge = node->in_edge(); diff --git a/src/graphviz.h b/src/graphviz.h index 1e2a29d..408496d 100644 --- a/src/graphviz.h +++ b/src/graphviz.h @@ -16,7 +16,6 @@ #define NINJA_GRAPHVIZ_H_ #include <set> -using namespace std; struct Node; struct Edge; @@ -27,8 +26,8 @@ struct GraphViz { void AddTarget(Node* node); void Finish(); - set<Node*> visited_nodes_; - set<Edge*> visited_edges_; + std::set<Node*> visited_nodes_; + std::set<Edge*> visited_edges_; }; #endif // NINJA_GRAPHVIZ_H_ diff --git a/src/hash_collision_bench.cc b/src/hash_collision_bench.cc index d0eabde..5be0531 100644 --- a/src/hash_collision_bench.cc +++ b/src/hash_collision_bench.cc @@ -46,7 +46,7 @@ int main() { sort(hashes, hashes + N); - int num_collisions = 0; + int collision_count = 0; for (int i = 1; i < N; ++i) { if (hashes[i - 1].first == hashes[i].first) { if (strcmp(commands[hashes[i - 1].second], @@ -54,9 +54,9 @@ int main() { printf("collision!\n string 1: '%s'\n string 2: '%s'\n", commands[hashes[i - 1].second], commands[hashes[i].second]); - num_collisions++; + collision_count++; } } } - printf("\n\n%d collisions after %d runs\n", num_collisions, N); + printf("\n\n%d collisions after %d runs\n", collision_count, N); } diff --git a/src/hash_map.h b/src/hash_map.h index 77e7586..a91aeb9 100644 --- a/src/hash_map.h +++ b/src/hash_map.h @@ -50,7 +50,22 @@ unsigned int MurmurHash2(const void* key, size_t len) { return h; } -#ifdef _MSC_VER +#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) +#include <unordered_map> + +namespace std { +template<> +struct hash<StringPiece> { + typedef StringPiece argument_type; + typedef size_t result_type; + + size_t operator()(StringPiece key) const { + return MurmurHash2(key.str_, key.len_); + } +}; +} + +#elif defined(_MSC_VER) #include <hash_map> using stdext::hash_map; @@ -61,7 +76,7 @@ struct StringPieceCmp : public hash_compare<StringPiece> { return MurmurHash2(key.str_, key.len_); } bool operator()(const StringPiece& a, const StringPiece& b) const { - int cmp = strncmp(a.str_, b.str_, min(a.len_, b.len_)); + int cmp = memcmp(a.str_, b.str_, min(a.len_, b.len_)); if (cmp < 0) { return true; } else if (cmp > 0) { @@ -73,26 +88,17 @@ struct StringPieceCmp : public hash_compare<StringPiece> { }; #else - #include <ext/hash_map> using __gnu_cxx::hash_map; namespace __gnu_cxx { template<> -struct hash<std::string> { - size_t operator()(const std::string& s) const { - return hash<const char*>()(s.c_str()); - } -}; - -template<> struct hash<StringPiece> { size_t operator()(StringPiece key) const { return MurmurHash2(key.str_, key.len_); } }; - } #endif @@ -102,7 +108,9 @@ struct hash<StringPiece> { /// mapping StringPiece => Foo*. template<typename V> struct ExternalStringHashMap { -#ifdef _MSC_VER +#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) + typedef std::unordered_map<StringPiece, V> Type; +#elif defined(_MSC_VER) typedef hash_map<StringPiece, V, StringPieceCmp> Type; #else typedef hash_map<StringPiece, V> Type; diff --git a/src/includes_normalize-win32.cc b/src/includes_normalize-win32.cc index 05ce75d..ca35012 100644 --- a/src/includes_normalize-win32.cc +++ b/src/includes_normalize-win32.cc @@ -68,12 +68,15 @@ string IncludesNormalize::ToLower(const string& s) { string IncludesNormalize::AbsPath(StringPiece s) { char result[_MAX_PATH]; GetFullPathName(s.AsString().c_str(), sizeof(result), result, NULL); + for (char* c = result; *c; ++c) + if (*c == '\\') + *c = '/'; return result; } string IncludesNormalize::Relativize(StringPiece path, const string& start) { - vector<string> start_list = Split(AbsPath(start), '\\'); - vector<string> path_list = Split(AbsPath(path), '\\'); + vector<string> start_list = Split(AbsPath(start), '/'); + vector<string> path_list = Split(AbsPath(path), '/'); int i; for (i = 0; i < static_cast<int>(min(start_list.size(), path_list.size())); ++i) { @@ -88,28 +91,32 @@ string IncludesNormalize::Relativize(StringPiece path, const string& start) { rel_list.push_back(path_list[j]); if (rel_list.size() == 0) return "."; - return Join(rel_list, '\\'); + return Join(rel_list, '/'); } -string IncludesNormalize::Normalize(const string& input, - const char* relative_to) { - char copy[_MAX_PATH]; +bool IncludesNormalize::Normalize(const string& input, const char* relative_to, + string* result, string* err) { + char copy[_MAX_PATH + 1]; size_t len = input.size(); - strncpy(copy, input.c_str(), input.size() + 1); - for (size_t j = 0; j < len; ++j) - if (copy[j] == '/') - copy[j] = '\\'; - string err; - if (!CanonicalizePath(copy, &len, &err)) { - Warning("couldn't canonicalize '%s: %s\n", input.c_str(), err.c_str()); + if (len > _MAX_PATH) { + *err = "path too long"; + return false; } + strncpy(copy, input.c_str(), input.size() + 1); + unsigned int slash_bits; + if (!CanonicalizePath(copy, &len, &slash_bits, err)) + return false; + StringPiece partially_fixed(copy, len); + string curdir; if (!relative_to) { curdir = AbsPath("."); relative_to = curdir.c_str(); } - StringPiece partially_fixed(copy, len); - if (!SameDrive(partially_fixed, relative_to)) - return partially_fixed.AsString(); - return Relativize(partially_fixed, relative_to); + if (!SameDrive(partially_fixed, relative_to)) { + *result = partially_fixed.AsString(); + return true; + } + *result = Relativize(partially_fixed, relative_to); + return true; } diff --git a/src/includes_normalize.h b/src/includes_normalize.h index 43527af..98e912f 100644 --- a/src/includes_normalize.h +++ b/src/includes_normalize.h @@ -29,7 +29,7 @@ struct IncludesNormalize { static string Relativize(StringPiece path, const string& start); /// Normalize by fixing slashes style, fixing redundant .. and . and makes the - /// path relative to |relative_to|. Case is normalized to lowercase on - /// Windows too. - static string Normalize(const string& input, const char* relative_to); + /// path relative to |relative_to|. + static bool Normalize(const string& input, const char* relative_to, + string* result, string* err); }; diff --git a/src/includes_normalize_test.cc b/src/includes_normalize_test.cc index 419996f..aba25d0 100644 --- a/src/includes_normalize_test.cc +++ b/src/includes_normalize_test.cc @@ -14,18 +14,13 @@ #include "includes_normalize.h" -#include <gtest/gtest.h> +#include <algorithm> + +#include <direct.h> #include "test.h" #include "util.h" -TEST(IncludesNormalize, Simple) { - EXPECT_EQ("b", IncludesNormalize::Normalize("a\\..\\b", NULL)); - EXPECT_EQ("b", IncludesNormalize::Normalize("a\\../b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\.\\b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL)); -} - namespace { string GetCurDir() { @@ -35,28 +30,50 @@ string GetCurDir() { return parts[parts.size() - 1]; } +string NormalizeAndCheckNoError(const std::string& input) { + string result, err; + EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), NULL, &result, &err)); + EXPECT_EQ("", err); + return result; +} + +string NormalizeRelativeAndCheckNoError(const std::string& input, + const std::string& relative_to) { + string result, err; + EXPECT_TRUE(IncludesNormalize::Normalize(input.c_str(), relative_to.c_str(), + &result, &err)); + EXPECT_EQ("", err); + return result; +} + } // namespace +TEST(IncludesNormalize, Simple) { + EXPECT_EQ("b", NormalizeAndCheckNoError("a\\..\\b")); + EXPECT_EQ("b", NormalizeAndCheckNoError("a\\../b")); + EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\.\\b")); + EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b")); +} + TEST(IncludesNormalize, WithRelative) { string currentdir = GetCurDir(); - EXPECT_EQ("c", IncludesNormalize::Normalize("a/b/c", "a/b")); - EXPECT_EQ("a", IncludesNormalize::Normalize(IncludesNormalize::AbsPath("a"), - NULL)); - EXPECT_EQ(string("..\\") + currentdir + string("\\a"), - IncludesNormalize::Normalize("a", "../b")); - EXPECT_EQ(string("..\\") + currentdir + string("\\a\\b"), - IncludesNormalize::Normalize("a/b", "../c")); - EXPECT_EQ("..\\..\\a", IncludesNormalize::Normalize("a", "b/c")); - EXPECT_EQ(".", IncludesNormalize::Normalize("a", "a")); + EXPECT_EQ("c", NormalizeRelativeAndCheckNoError("a/b/c", "a/b")); + EXPECT_EQ("a", NormalizeAndCheckNoError(IncludesNormalize::AbsPath("a"))); + EXPECT_EQ(string("../") + currentdir + string("/a"), + NormalizeRelativeAndCheckNoError("a", "../b")); + EXPECT_EQ(string("../") + currentdir + string("/a/b"), + NormalizeRelativeAndCheckNoError("a/b", "../c")); + EXPECT_EQ("../../a", NormalizeRelativeAndCheckNoError("a", "b/c")); + EXPECT_EQ(".", NormalizeRelativeAndCheckNoError("a", "a")); } TEST(IncludesNormalize, Case) { - EXPECT_EQ("b", IncludesNormalize::Normalize("Abc\\..\\b", NULL)); - EXPECT_EQ("BdEf", IncludesNormalize::Normalize("Abc\\..\\BdEf", NULL)); - EXPECT_EQ("A\\b", IncludesNormalize::Normalize("A\\.\\b", NULL)); - EXPECT_EQ("a\\b", IncludesNormalize::Normalize("a\\./b", NULL)); - EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\.\\B", NULL)); - EXPECT_EQ("A\\B", IncludesNormalize::Normalize("A\\./B", NULL)); + EXPECT_EQ("b", NormalizeAndCheckNoError("Abc\\..\\b")); + EXPECT_EQ("BdEf", NormalizeAndCheckNoError("Abc\\..\\BdEf")); + EXPECT_EQ("A/b", NormalizeAndCheckNoError("A\\.\\b")); + EXPECT_EQ("a/b", NormalizeAndCheckNoError("a\\./b")); + EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\.\\B")); + EXPECT_EQ("A/B", NormalizeAndCheckNoError("A\\./B")); } TEST(IncludesNormalize, Join) { @@ -89,16 +106,63 @@ TEST(IncludesNormalize, ToLower) { TEST(IncludesNormalize, DifferentDrive) { EXPECT_EQ("stuff.h", - IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "p:\\vs08")); + NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "p:\\vs08")); EXPECT_EQ("stuff.h", - IncludesNormalize::Normalize("P:\\Vs08\\stuff.h", "p:\\vs08")); - EXPECT_EQ("p:\\vs08\\stuff.h", - IncludesNormalize::Normalize("p:\\vs08\\stuff.h", "c:\\vs08")); - EXPECT_EQ("P:\\vs08\\stufF.h", - IncludesNormalize::Normalize("P:\\vs08\\stufF.h", "D:\\stuff/things")); - EXPECT_EQ("P:\\vs08\\stuff.h", - IncludesNormalize::Normalize("P:/vs08\\stuff.h", "D:\\stuff/things")); - // TODO: this fails; fix it. - //EXPECT_EQ("P:\\wee\\stuff.h", - // IncludesNormalize::Normalize("P:/vs08\\../wee\\stuff.h", "D:\\stuff/things")); + NormalizeRelativeAndCheckNoError("P:\\Vs08\\stuff.h", "p:\\vs08")); + EXPECT_EQ("p:/vs08/stuff.h", + NormalizeRelativeAndCheckNoError("p:\\vs08\\stuff.h", "c:\\vs08")); + EXPECT_EQ("P:/vs08/stufF.h", NormalizeRelativeAndCheckNoError( + "P:\\vs08\\stufF.h", "D:\\stuff/things")); + EXPECT_EQ("P:/vs08/stuff.h", NormalizeRelativeAndCheckNoError( + "P:/vs08\\stuff.h", "D:\\stuff/things")); + EXPECT_EQ("P:/wee/stuff.h", + NormalizeRelativeAndCheckNoError("P:/vs08\\../wee\\stuff.h", + "D:\\stuff/things")); +} + +TEST(IncludesNormalize, LongInvalidPath) { + const char kLongInputString[] = + "C:\\Program Files (x86)\\Microsoft Visual Studio " + "12.0\\VC\\INCLUDEwarning #31001: The dll for reading and writing the " + "pdb (for example, mspdb110.dll) could not be found on your path. This " + "is usually a configuration error. Compilation will continue using /Z7 " + "instead of /Zi, but expect a similar error when you link your program."; + // Too long, won't be canonicalized. Ensure doesn't crash. + string result, err; + EXPECT_FALSE( + IncludesNormalize::Normalize(kLongInputString, NULL, &result, &err)); + EXPECT_EQ("path too long", err); + + const char kExactlyMaxPath[] = + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "012345678\\" + "0123456789"; + std::string forward_slashes(kExactlyMaxPath); + std::replace(forward_slashes.begin(), forward_slashes.end(), '\\', '/'); + // Make sure a path that's exactly _MAX_PATH long is canonicalized. + EXPECT_EQ(forward_slashes, + NormalizeAndCheckNoError(kExactlyMaxPath)); } diff --git a/src/lexer.cc b/src/lexer.cc index 685fe81..37b8678 100644 --- a/src/lexer.cc +++ b/src/lexer.cc @@ -103,8 +103,6 @@ const char* Lexer::TokenErrorHint(Token expected) { string Lexer::DescribeLastError() { if (last_token_) { switch (last_token_[0]) { - case '\r': - return "carriage returns are not allowed, use newlines"; case '\t': return "tabs are not allowed, use spaces"; } @@ -129,7 +127,7 @@ Lexer::Token Lexer::ReadToken() { unsigned int yyaccept = 0; static const unsigned char yybm[] = { 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 0, 64, 64, + 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 64, 64, 64, 64, 64, 64, 64, @@ -163,56 +161,66 @@ Lexer::Token Lexer::ReadToken() { }; yych = *p; - if (yych <= '^') { - if (yych <= ',') { - if (yych <= 0x1F) { - if (yych <= 0x00) goto yy22; - if (yych == '\n') goto yy6; - goto yy24; + if (yych <= 'Z') { + if (yych <= '#') { + if (yych <= '\f') { + if (yych <= 0x00) goto yy23; + if (yych == '\n') goto yy7; + goto yy25; } else { - if (yych <= ' ') goto yy2; - if (yych == '#') goto yy4; - goto yy24; + if (yych <= 0x1F) { + if (yych <= '\r') goto yy6; + goto yy25; + } else { + if (yych <= ' ') goto yy2; + if (yych <= '"') goto yy25; + goto yy4; + } } } else { - if (yych <= ':') { - if (yych == '/') goto yy24; - if (yych <= '9') goto yy21; - goto yy15; + if (yych <= '9') { + if (yych <= ',') goto yy25; + if (yych == '/') goto yy25; + goto yy22; } else { - if (yych <= '=') { - if (yych <= '<') goto yy24; - goto yy13; + if (yych <= '<') { + if (yych <= ':') goto yy16; + goto yy25; } else { - if (yych <= '@') goto yy24; - if (yych <= 'Z') goto yy21; - goto yy24; + if (yych <= '=') goto yy14; + if (yych <= '@') goto yy25; + goto yy22; } } } } else { if (yych <= 'i') { - if (yych <= 'b') { - if (yych == '`') goto yy24; - if (yych <= 'a') goto yy21; - goto yy8; + if (yych <= 'a') { + if (yych == '_') goto yy22; + if (yych <= '`') goto yy25; + goto yy22; } else { - if (yych == 'd') goto yy12; - if (yych <= 'h') goto yy21; - goto yy19; + if (yych <= 'c') { + if (yych <= 'b') goto yy9; + goto yy22; + } else { + if (yych <= 'd') goto yy13; + if (yych <= 'h') goto yy22; + goto yy20; + } } } else { if (yych <= 'r') { - if (yych == 'p') goto yy10; - if (yych <= 'q') goto yy21; - goto yy11; + if (yych == 'p') goto yy11; + if (yych <= 'q') goto yy22; + goto yy12; } else { if (yych <= 'z') { - if (yych <= 's') goto yy20; - goto yy21; + if (yych <= 's') goto yy21; + goto yy22; } else { - if (yych == '|') goto yy17; - goto yy24; + if (yych == '|') goto yy18; + goto yy25; } } } @@ -220,192 +228,203 @@ Lexer::Token Lexer::ReadToken() { yy2: yyaccept = 0; yych = *(q = ++p); - goto yy70; + goto yy73; yy3: { token = INDENT; break; } yy4: yyaccept = 1; yych = *(q = ++p); - if (yych <= 0x00) goto yy5; - if (yych != '\r') goto yy65; + if (yych >= 0x01) goto yy68; yy5: { token = ERROR; break; } yy6: - ++p; + yych = *++p; + if (yych == '\n') goto yy65; + goto yy5; yy7: - { token = NEWLINE; break; } -yy8: ++p; - if ((yych = *p) == 'u') goto yy59; - goto yy26; +yy8: + { token = NEWLINE; break; } yy9: - { token = IDENT; break; } + ++p; + if ((yych = *p) == 'u') goto yy60; + goto yy27; yy10: - yych = *++p; - if (yych == 'o') goto yy55; - goto yy26; + { token = IDENT; break; } yy11: yych = *++p; - if (yych == 'u') goto yy51; - goto yy26; + if (yych == 'o') goto yy56; + goto yy27; yy12: yych = *++p; - if (yych == 'e') goto yy44; - goto yy26; + if (yych == 'u') goto yy52; + goto yy27; yy13: + yych = *++p; + if (yych == 'e') goto yy45; + goto yy27; +yy14: ++p; { token = EQUALS; break; } -yy15: +yy16: ++p; { token = COLON; break; } -yy17: +yy18: ++p; - if ((yych = *p) == '|') goto yy42; + if ((yych = *p) == '|') goto yy43; { token = PIPE; break; } -yy19: - yych = *++p; - if (yych == 'n') goto yy35; - goto yy26; yy20: yych = *++p; - if (yych == 'u') goto yy27; - goto yy26; + if (yych == 'n') goto yy36; + goto yy27; yy21: yych = *++p; - goto yy26; + if (yych == 'u') goto yy28; + goto yy27; yy22: + yych = *++p; + goto yy27; +yy23: ++p; { token = TEOF; break; } -yy24: +yy25: yych = *++p; goto yy5; -yy25: +yy26: ++p; yych = *p; -yy26: +yy27: if (yybm[0+yych] & 32) { - goto yy25; + goto yy26; } - goto yy9; -yy27: + goto yy10; +yy28: yych = *++p; - if (yych != 'b') goto yy26; + if (yych != 'b') goto yy27; yych = *++p; - if (yych != 'n') goto yy26; + if (yych != 'n') goto yy27; yych = *++p; - if (yych != 'i') goto yy26; + if (yych != 'i') goto yy27; yych = *++p; - if (yych != 'n') goto yy26; + if (yych != 'n') goto yy27; yych = *++p; - if (yych != 'j') goto yy26; + if (yych != 'j') goto yy27; yych = *++p; - if (yych != 'a') goto yy26; + if (yych != 'a') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = SUBNINJA; break; } -yy35: +yy36: yych = *++p; - if (yych != 'c') goto yy26; + if (yych != 'c') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'u') goto yy26; + if (yych != 'u') goto yy27; yych = *++p; - if (yych != 'd') goto yy26; + if (yych != 'd') goto yy27; yych = *++p; - if (yych != 'e') goto yy26; + if (yych != 'e') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = INCLUDE; break; } -yy42: +yy43: ++p; { token = PIPE2; break; } -yy44: +yy45: yych = *++p; - if (yych != 'f') goto yy26; + if (yych != 'f') goto yy27; yych = *++p; - if (yych != 'a') goto yy26; + if (yych != 'a') goto yy27; yych = *++p; - if (yych != 'u') goto yy26; + if (yych != 'u') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 't') goto yy26; + if (yych != 't') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = DEFAULT; break; } -yy51: +yy52: yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'e') goto yy26; + if (yych != 'e') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = RULE; break; } -yy55: +yy56: yych = *++p; - if (yych != 'o') goto yy26; + if (yych != 'o') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = POOL; break; } -yy59: +yy60: yych = *++p; - if (yych != 'i') goto yy26; + if (yych != 'i') goto yy27; yych = *++p; - if (yych != 'l') goto yy26; + if (yych != 'l') goto yy27; yych = *++p; - if (yych != 'd') goto yy26; + if (yych != 'd') goto yy27; ++p; if (yybm[0+(yych = *p)] & 32) { - goto yy25; + goto yy26; } { token = BUILD; break; } -yy64: +yy65: + ++p; + { token = NEWLINE; break; } +yy67: ++p; yych = *p; -yy65: +yy68: if (yybm[0+yych] & 64) { - goto yy64; + goto yy67; } - if (yych <= 0x00) goto yy66; - if (yych <= '\f') goto yy67; -yy66: + if (yych >= 0x01) goto yy70; +yy69: p = q; if (yyaccept <= 0) { goto yy3; } else { goto yy5; } -yy67: +yy70: ++p; { continue; } -yy69: +yy72: yyaccept = 0; q = ++p; yych = *p; -yy70: +yy73: if (yybm[0+yych] & 128) { - goto yy69; + goto yy72; + } + if (yych <= '\f') { + if (yych != '\n') goto yy3; + } else { + if (yych <= '\r') goto yy75; + if (yych == '#') goto yy67; + goto yy3; } - if (yych == '\n') goto yy71; - if (yych == '#') goto yy64; - goto yy3; -yy71: + yych = *++p; + goto yy8; +yy75: ++p; - yych = *p; - goto yy7; + if ((yych = *p) == '\n') goto yy65; + goto yy69; } } @@ -427,6 +446,7 @@ bool Lexer::PeekToken(Token token) { void Lexer::EatWhitespace() { const char* p = ofs_; + const char* q; for (;;) { ofs_ = p; @@ -468,39 +488,48 @@ void Lexer::EatWhitespace() { }; yych = *p; if (yych <= ' ') { - if (yych <= 0x00) goto yy78; - if (yych <= 0x1F) goto yy80; + if (yych <= 0x00) goto yy82; + if (yych <= 0x1F) goto yy84; } else { - if (yych == '$') goto yy76; - goto yy80; + if (yych == '$') goto yy80; + goto yy84; } ++p; yych = *p; - goto yy84; -yy75: + goto yy92; +yy79: { continue; } -yy76: - ++p; - if ((yych = *p) == '\n') goto yy81; -yy77: +yy80: + yych = *(q = ++p); + if (yych == '\n') goto yy85; + if (yych == '\r') goto yy87; +yy81: { break; } -yy78: +yy82: ++p; { break; } -yy80: +yy84: yych = *++p; - goto yy77; -yy81: + goto yy81; +yy85: ++p; { continue; } -yy83: +yy87: + yych = *++p; + if (yych == '\n') goto yy89; + p = q; + goto yy81; +yy89: + ++p; + { continue; } +yy91: ++p; yych = *p; -yy84: +yy92: if (yybm[0+yych] & 128) { - goto yy83; + goto yy91; } - goto yy75; + goto yy79; } } @@ -550,40 +579,40 @@ bool Lexer::ReadIdent(string* out) { yych = *p; if (yych <= '@') { if (yych <= '.') { - if (yych <= ',') goto yy89; + if (yych <= ',') goto yy97; } else { - if (yych <= '/') goto yy89; - if (yych >= ':') goto yy89; + if (yych <= '/') goto yy97; + if (yych >= ':') goto yy97; } } else { if (yych <= '_') { - if (yych <= 'Z') goto yy87; - if (yych <= '^') goto yy89; + if (yych <= 'Z') goto yy95; + if (yych <= '^') goto yy97; } else { - if (yych <= '`') goto yy89; - if (yych >= '{') goto yy89; + if (yych <= '`') goto yy97; + if (yych >= '{') goto yy97; } } -yy87: +yy95: ++p; yych = *p; - goto yy92; -yy88: + goto yy100; +yy96: { out->assign(start, p - start); break; } -yy89: +yy97: ++p; { return false; } -yy91: +yy99: ++p; yych = *p; -yy92: +yy100: if (yybm[0+yych] & 128) { - goto yy91; + goto yy99; } - goto yy88; + goto yy96; } } @@ -638,29 +667,36 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { yych = *p; if (yych <= ' ') { if (yych <= '\n') { - if (yych <= 0x00) goto yy101; - if (yych >= '\n') goto yy97; + if (yych <= 0x00) goto yy110; + if (yych >= '\n') goto yy107; } else { - if (yych == '\r') goto yy103; - if (yych >= ' ') goto yy97; + if (yych == '\r') goto yy105; + if (yych >= ' ') goto yy107; } } else { if (yych <= '9') { - if (yych == '$') goto yy99; + if (yych == '$') goto yy109; } else { - if (yych <= ':') goto yy97; - if (yych == '|') goto yy97; + if (yych <= ':') goto yy107; + if (yych == '|') goto yy107; } } ++p; yych = *p; - goto yy126; -yy96: + goto yy140; +yy104: { eval->AddText(StringPiece(start, p - start)); continue; } -yy97: +yy105: + ++p; + if ((yych = *p) == '\n') goto yy137; + { + last_token_ = start; + return Error(DescribeLastError(), err); + } +yy107: ++p; { if (path) { @@ -673,137 +709,152 @@ yy97: continue; } } -yy99: - ++p; - if ((yych = *p) <= '/') { - if (yych <= ' ') { - if (yych == '\n') goto yy115; - if (yych <= 0x1F) goto yy104; - goto yy106; +yy109: + yych = *++p; + if (yych <= '-') { + if (yych <= 0x1F) { + if (yych <= '\n') { + if (yych <= '\t') goto yy112; + goto yy124; + } else { + if (yych == '\r') goto yy114; + goto yy112; + } } else { - if (yych <= '$') { - if (yych <= '#') goto yy104; - goto yy108; + if (yych <= '#') { + if (yych <= ' ') goto yy115; + goto yy112; } else { - if (yych == '-') goto yy110; - goto yy104; + if (yych <= '$') goto yy117; + if (yych <= ',') goto yy112; + goto yy119; } } } else { - if (yych <= '^') { - if (yych <= ':') { - if (yych <= '9') goto yy110; - goto yy112; + if (yych <= 'Z') { + if (yych <= '9') { + if (yych <= '/') goto yy112; + goto yy119; } else { - if (yych <= '@') goto yy104; - if (yych <= 'Z') goto yy110; - goto yy104; + if (yych <= ':') goto yy121; + if (yych <= '@') goto yy112; + goto yy119; } } else { if (yych <= '`') { - if (yych <= '_') goto yy110; - goto yy104; + if (yych == '_') goto yy119; + goto yy112; } else { - if (yych <= 'z') goto yy110; - if (yych <= '{') goto yy114; - goto yy104; + if (yych <= 'z') goto yy119; + if (yych <= '{') goto yy123; + goto yy112; } } } -yy100: - { - last_token_ = start; - return Error(DescribeLastError(), err); - } -yy101: +yy110: ++p; { last_token_ = start; return Error("unexpected EOF", err); } -yy103: - yych = *++p; - goto yy100; -yy104: +yy112: ++p; -yy105: +yy113: { last_token_ = start; return Error("bad $-escape (literal $ must be written as $$)", err); } -yy106: +yy114: + yych = *++p; + if (yych == '\n') goto yy134; + goto yy113; +yy115: ++p; { eval->AddText(StringPiece(" ", 1)); continue; } -yy108: +yy117: ++p; { eval->AddText(StringPiece("$", 1)); continue; } -yy110: +yy119: ++p; yych = *p; - goto yy124; -yy111: + goto yy133; +yy120: { eval->AddSpecial(StringPiece(start + 1, p - start - 1)); continue; } -yy112: +yy121: ++p; { eval->AddText(StringPiece(":", 1)); continue; } -yy114: +yy123: yych = *(q = ++p); if (yybm[0+yych] & 32) { - goto yy118; + goto yy127; } - goto yy105; -yy115: + goto yy113; +yy124: ++p; yych = *p; if (yybm[0+yych] & 16) { - goto yy115; + goto yy124; } { continue; } -yy118: +yy127: ++p; yych = *p; if (yybm[0+yych] & 32) { - goto yy118; + goto yy127; } - if (yych == '}') goto yy121; + if (yych == '}') goto yy130; p = q; - goto yy105; -yy121: + goto yy113; +yy130: ++p; { eval->AddSpecial(StringPiece(start + 2, p - start - 3)); continue; } -yy123: +yy132: ++p; yych = *p; -yy124: +yy133: if (yybm[0+yych] & 64) { - goto yy123; + goto yy132; } - goto yy111; -yy125: + goto yy120; +yy134: ++p; yych = *p; -yy126: + if (yych == ' ') goto yy134; + { + continue; + } +yy137: + ++p; + { + if (path) + p = start; + break; + } +yy139: + ++p; + yych = *p; +yy140: if (yybm[0+yych] & 128) { - goto yy125; + goto yy139; } - goto yy96; + goto yy104; } } diff --git a/src/lexer.in.cc b/src/lexer.in.cc index 93d5540..f861239 100644 --- a/src/lexer.in.cc +++ b/src/lexer.in.cc @@ -102,8 +102,6 @@ const char* Lexer::TokenErrorHint(Token expected) { string Lexer::DescribeLastError() { if (last_token_) { switch (last_token_[0]) { - case '\r': - return "carriage returns are not allowed, use newlines"; case '\t': return "tabs are not allowed, use spaces"; } @@ -132,8 +130,9 @@ Lexer::Token Lexer::ReadToken() { simple_varname = [a-zA-Z0-9_-]+; varname = [a-zA-Z0-9_.-]+; - [ ]*"#"[^\000\r\n]*"\n" { continue; } - [ ]*[\n] { token = NEWLINE; break; } + [ ]*"#"[^\000\n]*"\n" { continue; } + [ ]*"\r\n" { token = NEWLINE; break; } + [ ]*"\n" { token = NEWLINE; break; } [ ]+ { token = INDENT; break; } "build" { token = BUILD; break; } "pool" { token = POOL; break; } @@ -168,13 +167,15 @@ bool Lexer::PeekToken(Token token) { void Lexer::EatWhitespace() { const char* p = ofs_; + const char* q; for (;;) { ofs_ = p; /*!re2c - [ ]+ { continue; } - "$\n" { continue; } - nul { break; } - [^] { break; } + [ ]+ { continue; } + "$\r\n" { continue; } + "$\n" { continue; } + nul { break; } + [^] { break; } */ } } @@ -207,6 +208,11 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { eval->AddText(StringPiece(start, p - start)); continue; } + "\r\n" { + if (path) + p = start; + break; + } [ :|\n] { if (path) { p = start; @@ -226,6 +232,9 @@ bool Lexer::ReadEvalString(EvalString* eval, bool path, string* err) { eval->AddText(StringPiece(" ", 1)); continue; } + "$\r\n"[ ]* { + continue; + } "$\n"[ ]* { continue; } diff --git a/src/lexer_test.cc b/src/lexer_test.cc index e8a1642..331d8e1 100644 --- a/src/lexer_test.cc +++ b/src/lexer_test.cc @@ -14,9 +14,8 @@ #include "lexer.h" -#include <gtest/gtest.h> - #include "eval_env.h" +#include "test.h" TEST(Lexer, ReadVarValue) { Lexer lexer("plain text $var $VaR ${x}\n"); diff --git a/src/line_printer.cc b/src/line_printer.cc index ef1609c..2cd3e17 100644 --- a/src/line_printer.cc +++ b/src/line_printer.cc @@ -21,6 +21,7 @@ #else #include <unistd.h> #include <sys/ioctl.h> +#include <termios.h> #include <sys/time.h> #endif @@ -49,29 +50,21 @@ void LinePrinter::Print(string to_print, LineType type) { return; } -#ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO csbi; - GetConsoleScreenBufferInfo(console_, &csbi); -#endif - if (smart_terminal_) { -#ifndef _WIN32 printf("\r"); // Print over previous line, if any. -#else - csbi.dwCursorPosition.X = 0; - SetConsoleCursorPosition(console_, csbi.dwCursorPosition); -#endif + // On Windows, calling a C library function writing to stdout also handles + // pausing the executable when the "Pause" key or Ctrl-S is pressed. } if (smart_terminal_ && type == ELIDE) { #ifdef _WIN32 - // Don't use the full width or console will move to next line. - size_t width = static_cast<size_t>(csbi.dwSize.X) - 1; - to_print = ElideMiddle(to_print, width); - // We don't want to have the cursor spamming back and forth, so - // use WriteConsoleOutput instead which updates the contents of - // the buffer, but doesn't move the cursor position. + CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(console_, &csbi); + + to_print = ElideMiddle(to_print, static_cast<size_t>(csbi.dwSize.X)); + // We don't want to have the cursor spamming back and forth, so instead of + // printf use WriteConsoleOutput which updates the contents of the buffer, + // but doesn't move the cursor position. COORD buf_size = { csbi.dwSize.X, 1 }; COORD zero_zero = { 0, 0 }; SMALL_RECT target = { @@ -79,16 +72,12 @@ void LinePrinter::Print(string to_print, LineType type) { static_cast<SHORT>(csbi.dwCursorPosition.X + csbi.dwSize.X - 1), csbi.dwCursorPosition.Y }; - CHAR_INFO* char_data = new CHAR_INFO[csbi.dwSize.X]; - memset(char_data, 0, sizeof(CHAR_INFO) * csbi.dwSize.X); - for (int i = 0; i < csbi.dwSize.X; ++i) { - char_data[i].Char.AsciiChar = ' '; + vector<CHAR_INFO> char_data(csbi.dwSize.X); + for (size_t i = 0; i < static_cast<size_t>(csbi.dwSize.X); ++i) { + char_data[i].Char.AsciiChar = i < to_print.size() ? to_print[i] : ' '; char_data[i].Attributes = csbi.wAttributes; } - for (size_t i = 0; i < to_print.size(); ++i) - char_data[i].Char.AsciiChar = to_print[i]; - WriteConsoleOutput(console_, char_data, buf_size, zero_zero, &target); - delete[] char_data; + WriteConsoleOutput(console_, &char_data[0], buf_size, zero_zero, &target); #else // Limit output to width of the terminal if provided so we don't cause // line-wrapping. diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc index 6fa4f7c..e8c0436 100644 --- a/src/manifest_parser.cc +++ b/src/manifest_parser.cc @@ -24,8 +24,10 @@ #include "util.h" #include "version.h" -ManifestParser::ManifestParser(State* state, FileReader* file_reader) - : state_(state), file_reader_(file_reader) { +ManifestParser::ManifestParser(State* state, FileReader* file_reader, + bool dupe_edge_should_err) + : state_(state), file_reader_(file_reader), + dupe_edge_should_err_(dupe_edge_should_err), quiet_(false) { env_ = &state->bindings_; } @@ -156,7 +158,7 @@ bool ManifestParser::ParseRule(string* err) { if (!ExpectToken(Lexer::NEWLINE, err)) return false; - if (state_->LookupRule(name) != NULL) + if (env_->LookupRuleCurrentScope(name) != NULL) return lexer_.Error("duplicate rule '" + name + "'", err); Rule* rule = new Rule(name); // XXX scoped_ptr @@ -185,13 +187,13 @@ bool ManifestParser::ParseRule(string* err) { if (rule->bindings_["command"].empty()) return lexer_.Error("expected 'command =' line", err); - state_->AddRule(rule); + env_->AddRule(rule); return true; } bool ManifestParser::ParseLet(string* key, EvalString* value, string* err) { if (!lexer_.ReadIdent(key)) - return false; + return lexer_.Error("expected variable name", err); if (!ExpectToken(Lexer::EQUALS, err)) return false; if (!lexer_.ReadVarValue(value, err)) @@ -209,7 +211,8 @@ bool ManifestParser::ParseDefault(string* err) { do { string path = eval.Evaluate(env_); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int slash_bits; // Unused because this only does lookup. + if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); if (!state_->AddDefault(path, &path_err)) return lexer_.Error(path_err, err); @@ -251,7 +254,7 @@ bool ManifestParser::ParseEdge(string* err) { if (!lexer_.ReadIdent(&rule_name)) return lexer_.Error("expected build command name", err); - const Rule* rule = state_->LookupRule(rule_name); + const Rule* rule = env_->LookupRule(rule_name); if (!rule) return lexer_.Error("unknown build rule '" + rule_name + "'", err); @@ -297,16 +300,16 @@ bool ManifestParser::ParseEdge(string* err) { return false; // Bindings on edges are rare, so allocate per-edge envs only when needed. - bool hasIdent = lexer_.PeekToken(Lexer::INDENT); - BindingEnv* env = hasIdent ? new BindingEnv(env_) : env_; - while (hasIdent) { + bool has_indent_token = lexer_.PeekToken(Lexer::INDENT); + BindingEnv* env = has_indent_token ? new BindingEnv(env_) : env_; + while (has_indent_token) { string key; EvalString val; if (!ParseLet(&key, &val, err)) return false; env->AddBinding(key, val.Evaluate(env_)); - hasIdent = lexer_.PeekToken(Lexer::INDENT); + has_indent_token = lexer_.PeekToken(Lexer::INDENT); } Edge* edge = state_->AddEdge(rule); @@ -320,19 +323,42 @@ bool ManifestParser::ParseEdge(string* err) { edge->pool_ = pool; } - for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) { + edge->outputs_.reserve(outs.size()); + for (vector<EvalString>::iterator i = outs.begin(); i != outs.end(); ++i) { string path = i->Evaluate(env); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int slash_bits; + if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); - state_->AddIn(edge, path); + if (!state_->AddOut(edge, path, slash_bits)) { + if (dupe_edge_should_err_) { + lexer_.Error("multiple rules generate " + path + " [-w dupbuild=err]", + err); + return false; + } else if (!quiet_) { + Warning("multiple rules generate %s. " + "builds involving this target will not be correct; " + "continuing anyway [-w dupbuild=warn]", + path.c_str()); + } + } } - for (vector<EvalString>::iterator i = outs.begin(); i != outs.end(); ++i) { + if (edge->outputs_.empty()) { + // All outputs of the edge are already created by other edges. Don't add + // this edge. Do this check before input nodes are connected to the edge. + state_->edges_.pop_back(); + delete edge; + return true; + } + + edge->inputs_.reserve(ins.size()); + for (vector<EvalString>::iterator i = ins.begin(); i != ins.end(); ++i) { string path = i->Evaluate(env); string path_err; - if (!CanonicalizePath(&path, &path_err)) + unsigned int slash_bits; + if (!CanonicalizePath(&path, &slash_bits, &path_err)) return lexer_.Error(path_err, err); - state_->AddOut(edge, path); + state_->AddIn(edge, path, slash_bits); } edge->implicit_deps_ = implicit; edge->order_only_deps_ = order_only; diff --git a/src/manifest_parser.h b/src/manifest_parser.h index 5212f72..f72cd6f 100644 --- a/src/manifest_parser.h +++ b/src/manifest_parser.h @@ -32,13 +32,15 @@ struct ManifestParser { virtual bool ReadFile(const string& path, string* content, string* err) = 0; }; - ManifestParser(State* state, FileReader* file_reader); + ManifestParser(State* state, FileReader* file_reader, + bool dupe_edge_should_err = false); /// Load and parse a file. - bool Load(const string& filename, string* err, Lexer* parent=NULL); + bool Load(const string& filename, string* err, Lexer* parent = NULL); /// Parse a text string of input. Used by tests. bool ParseTest(const string& input, string* err) { + quiet_ = true; return Parse("input", input, err); } @@ -64,6 +66,8 @@ private: BindingEnv* env_; FileReader* file_reader_; Lexer lexer_; + bool dupe_edge_should_err_; + bool quiet_; }; #endif // NINJA_MANIFEST_PARSER_H_ diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc index e40468f..6b56ab0 100644 --- a/src/manifest_parser_perftest.cc +++ b/src/manifest_parser_perftest.cc @@ -16,7 +16,10 @@ // directory. #include <numeric> + +#include <errno.h> #include <stdio.h> +#include <string.h> #ifdef _WIN32 #include "getopt.h" @@ -39,15 +42,19 @@ struct RealFileReader : public ManifestParser::FileReader { } }; -bool WriteFakeManifests(const string& dir) { +bool WriteFakeManifests(const string& dir, string* err) { RealDiskInterface disk_interface; - if (disk_interface.Stat(dir + "/build.ninja") > 0) - return true; + TimeStamp mtime = disk_interface.Stat(dir + "/build.ninja", err); + if (mtime != 0) // 0 means that the file doesn't exist yet. + return mtime != -1; + string command = "python misc/write_fake_manifests.py " + dir; printf("Creating manifest data..."); fflush(stdout); - int err = system(("python misc/write_fake_manifests.py " + dir).c_str()); + int exit_code = system(command.c_str()); printf("done.\n"); - return err == 0; + if (exit_code != 0) + *err = "Failed to run " + command; + return exit_code == 0; } int LoadManifests(bool measure_command_evaluation) { @@ -90,12 +97,14 @@ int main(int argc, char* argv[]) { const char kManifestDir[] = "build/manifest_perftest"; - if (!WriteFakeManifests(kManifestDir)) { - fprintf(stderr, "Failed to write test data\n"); + string err; + if (!WriteFakeManifests(kManifestDir, &err)) { + fprintf(stderr, "Failed to write test data: %s\n", err.c_str()); return 1; } - chdir(kManifestDir); + if (chdir(kManifestDir) < 0) + Fatal("chdir: %s", strerror(errno)); const int kNumRepetitions = 5; vector<int> times; diff --git a/src/manifest_parser_test.cc b/src/manifest_parser_test.cc index 152b965..8f7b575 100644 --- a/src/manifest_parser_test.cc +++ b/src/manifest_parser_test.cc @@ -17,18 +17,18 @@ #include <map> #include <vector> -#include <gtest/gtest.h> - #include "graph.h" #include "state.h" +#include "test.h" struct ParserTest : public testing::Test, public ManifestParser::FileReader { void AssertParse(const char* input) { ManifestParser parser(&state, this); string err; - ASSERT_TRUE(parser.ParseTest(input, &err)) << err; + EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); + VerifyGraph(state); } virtual bool ReadFile(const string& path, string* content, string* err) { @@ -61,8 +61,8 @@ TEST_F(ParserTest, Rules) { "\n" "build result: cat in_1.cc in-2.O\n")); - ASSERT_EQ(3u, state.rules_.size()); - const Rule* rule = state.rules_.begin()->second; + ASSERT_EQ(3u, state.bindings_.GetRules().size()); + const Rule* rule = state.bindings_.GetRules().begin()->second; EXPECT_EQ("cat", rule->name()); EXPECT_EQ("[cat ][$in][ > ][$out]", rule->GetBinding("command")->Serialize()); @@ -94,10 +94,10 @@ TEST_F(ParserTest, IgnoreIndentedComments) { "build result: cat in_1.cc in-2.O\n" " #comment\n")); - ASSERT_EQ(2u, state.rules_.size()); - const Rule* rule = state.rules_.begin()->second; + ASSERT_EQ(2u, state.bindings_.GetRules().size()); + const Rule* rule = state.bindings_.GetRules().begin()->second; EXPECT_EQ("cat", rule->name()); - Edge* edge = state.GetNode("result")->in_edge(); + Edge* edge = state.GetNode("result", 0)->in_edge(); EXPECT_TRUE(edge->GetBindingBool("restat")); EXPECT_FALSE(edge->GetBindingBool("generator")); } @@ -127,8 +127,8 @@ TEST_F(ParserTest, ResponseFiles) { "build out: cat_rsp in\n" " rspfile=out.rsp\n")); - ASSERT_EQ(2u, state.rules_.size()); - const Rule* rule = state.rules_.begin()->second; + ASSERT_EQ(2u, state.bindings_.GetRules().size()); + const Rule* rule = state.bindings_.GetRules().begin()->second; EXPECT_EQ("cat_rsp", rule->name()); EXPECT_EQ("[cat ][$rspfile][ > ][$out]", rule->GetBinding("command")->Serialize()); @@ -144,8 +144,8 @@ TEST_F(ParserTest, InNewline) { "build out: cat_rsp in in2\n" " rspfile=out.rsp\n")); - ASSERT_EQ(2u, state.rules_.size()); - const Rule* rule = state.rules_.begin()->second; + ASSERT_EQ(2u, state.bindings_.GetRules().size()); + const Rule* rule = state.bindings_.GetRules().begin()->second; EXPECT_EQ("cat_rsp", rule->name()); EXPECT_EQ("[cat ][$in_newline][ > ][$out]", rule->GetBinding("command")->Serialize()); @@ -205,8 +205,8 @@ TEST_F(ParserTest, Continuation) { "build a: link c $\n" " d e f\n")); - ASSERT_EQ(2u, state.rules_.size()); - const Rule* rule = state.rules_.begin()->second; + ASSERT_EQ(2u, state.bindings_.GetRules().size()); + const Rule* rule = state.bindings_.GetRules().begin()->second; EXPECT_EQ("link", rule->name()); EXPECT_EQ("[foo bar baz]", rule->GetBinding("command")->Serialize()); } @@ -270,6 +270,26 @@ TEST_F(ParserTest, CanonicalizeFile) { EXPECT_FALSE(state.LookupNode("in//2")); } +#ifdef _WIN32 +TEST_F(ParserTest, CanonicalizeFileBackslashes) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build out: cat in\\1 in\\\\2\n" +"build in\\1: cat\n" +"build in\\2: cat\n")); + + Node* node = state.LookupNode("in/1");; + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); + node = state.LookupNode("in/2"); + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); + EXPECT_FALSE(state.LookupNode("in//1")); + EXPECT_FALSE(state.LookupNode("in//2")); +} +#endif + TEST_F(ParserTest, PathVariables) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n" @@ -293,6 +313,70 @@ TEST_F(ParserTest, CanonicalizePaths) { EXPECT_TRUE(state.LookupNode("bar/foo.cc")); } +#ifdef _WIN32 +TEST_F(ParserTest, CanonicalizePathsBackslashes) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build ./out.o: cat ./bar/baz/../foo.cc\n" +"build .\\out2.o: cat .\\bar/baz\\..\\foo.cc\n" +"build .\\out3.o: cat .\\bar\\baz\\..\\foo3.cc\n" +)); + + EXPECT_FALSE(state.LookupNode("./out.o")); + EXPECT_FALSE(state.LookupNode(".\\out2.o")); + EXPECT_FALSE(state.LookupNode(".\\out3.o")); + EXPECT_TRUE(state.LookupNode("out.o")); + EXPECT_TRUE(state.LookupNode("out2.o")); + EXPECT_TRUE(state.LookupNode("out3.o")); + EXPECT_FALSE(state.LookupNode("./bar/baz/../foo.cc")); + EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo.cc")); + EXPECT_FALSE(state.LookupNode(".\\bar/baz\\..\\foo3.cc")); + Node* node = state.LookupNode("bar/foo.cc"); + EXPECT_TRUE(node); + EXPECT_EQ(0, node->slash_bits()); + node = state.LookupNode("bar/foo3.cc"); + EXPECT_TRUE(node); + EXPECT_EQ(1, node->slash_bits()); +} +#endif + +TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputs) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build out1 out2: cat in1\n" +"build out1: cat in2\n" +"build final: cat out1\n" +)); + // AssertParse() checks that the generated build graph is self-consistent. + // That's all the checking that this test needs. +} + +TEST_F(ParserTest, NoDeadPointerFromDuplicateEdge) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n" +" command = cat $in > $out\n" +"build out: cat in\n" +"build out: cat in\n" +)); + // AssertParse() checks that the generated build graph is self-consistent. + // That's all the checking that this test needs. +} + +TEST_F(ParserTest, DuplicateEdgeWithMultipleOutputsError) { + const char kInput[] = +"rule cat\n" +" command = cat $in > $out\n" +"build out1 out2: cat in1\n" +"build out1: cat in2\n" +"build final: cat out1\n"; + ManifestParser parser(&state, this, /*dupe_edges_should_err=*/true); + string err; + EXPECT_FALSE(parser.ParseTest(kInput, &err)); + EXPECT_EQ("input:5: multiple rules generate out1 [-w dupbuild=err]\n", err); +} + TEST_F(ParserTest, ReservedWords) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule build\n" @@ -553,6 +637,15 @@ TEST_F(ParserTest, Errors) { State state; ManifestParser parser(&state, NULL); string err; + EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n && bar", + &err)); + EXPECT_EQ("input:3: expected variable name\n", err); + } + + { + State state; + ManifestParser parser(&state, NULL); + string err; EXPECT_FALSE(parser.ParseTest("rule cc\n command = foo\n" "build $: cc bar.cc\n", &err)); @@ -767,18 +860,27 @@ TEST_F(ParserTest, MissingSubNinja) { } TEST_F(ParserTest, DuplicateRuleInDifferentSubninjas) { - // Test that rules live in a global namespace and aren't scoped to subninjas. + // Test that rules are scoped to subninjas. files_["test.ninja"] = "rule cat\n" " command = cat\n"; ManifestParser parser(&state, this); string err; - EXPECT_FALSE(parser.ParseTest("rule cat\n" + EXPECT_TRUE(parser.ParseTest("rule cat\n" " command = cat\n" "subninja test.ninja\n", &err)); - EXPECT_EQ("test.ninja:1: duplicate rule 'cat'\n" - "rule cat\n" - " ^ near here" - , err); +} + +TEST_F(ParserTest, DuplicateRuleInDifferentSubninjasWithInclude) { + // Test that rules are scoped to subninjas even with includes. + files_["rules.ninja"] = "rule cat\n" + " command = cat\n"; + files_["test.ninja"] = "include rules.ninja\n" + "build x : cat\n"; + ManifestParser parser(&state, this); + string err; + EXPECT_TRUE(parser.ParseTest("include rules.ninja\n" + "subninja test.ninja\n" + "build y : cat\n", &err)); } TEST_F(ParserTest, Include) { @@ -835,6 +937,16 @@ TEST_F(ParserTest, DefaultDefault) { EXPECT_EQ("", err); } +TEST_F(ParserTest, DefaultDefaultCycle) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule cat\n command = cat $in > $out\n" +"build a: cat a\n")); + + string err; + EXPECT_EQ(0u, state.DefaultNodes(&err).size()); + EXPECT_EQ("could not determine root nodes of build graph", err); +} + TEST_F(ParserTest, DefaultStatements) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n command = cat $in > $out\n" @@ -862,22 +974,18 @@ TEST_F(ParserTest, UTF8) { " description = compilaci\xC3\xB3\n")); } -// We might want to eventually allow CRLF to be nice to Windows developers, -// but for now just verify we error out with a nice message. TEST_F(ParserTest, CRLF) { State state; ManifestParser parser(&state, NULL); string err; - EXPECT_FALSE(parser.ParseTest("# comment with crlf\r\n", - &err)); - EXPECT_EQ("input:1: lexing error\n", - err); - - EXPECT_FALSE(parser.ParseTest("foo = foo\nbar = bar\r\n", - &err)); - EXPECT_EQ("input:2: carriage returns are not allowed, use newlines\n" - "bar = bar\r\n" - " ^ near here", - err); + EXPECT_TRUE(parser.ParseTest("# comment with crlf\r\n", &err)); + EXPECT_TRUE(parser.ParseTest("foo = foo\nbar = bar\r\n", &err)); + EXPECT_TRUE(parser.ParseTest( + "pool link_pool\r\n" + " depth = 15\r\n\r\n" + "rule xyz\r\n" + " command = something$expand \r\n" + " description = YAY!\r\n", + &err)); } diff --git a/src/minidump-win32.cc b/src/minidump-win32.cc index c79ec0e..c611919 100644 --- a/src/minidump-win32.cc +++ b/src/minidump-win32.cc @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NINJA_BOOTSTRAP +#ifdef _MSC_VER #include <windows.h> #include <DbgHelp.h> - #include "util.h" typedef BOOL (WINAPI *MiniDumpWriteDumpFunc) ( @@ -85,4 +84,4 @@ void CreateWin32MiniDump(_EXCEPTION_POINTERS* pep) { Warning("minidump created: %s", temp_file); } -#endif // NINJA_BOOTSTRAP +#endif // _MSC_VER diff --git a/src/msvc_helper-win32.cc b/src/msvc_helper-win32.cc index e465279..d516240 100644 --- a/src/msvc_helper-win32.cc +++ b/src/msvc_helper-win32.cc @@ -15,6 +15,7 @@ #include "msvc_helper.h" #include <algorithm> +#include <assert.h> #include <stdio.h> #include <string.h> #include <windows.h> @@ -82,10 +83,10 @@ bool CLParser::FilterInputFilename(string line) { EndsWith(line, ".cpp"); } -string CLParser::Parse(const string& output, const string& deps_prefix) { - string filtered_output; - +bool CLParser::Parse(const string& output, const string& deps_prefix, + string* filtered_output, string* err) { // Loop over all lines in the output to process them. + assert(&output != filtered_output); size_t start = 0; while (start < output.size()) { size_t end = output.find_first_of("\r\n", start); @@ -95,16 +96,18 @@ string CLParser::Parse(const string& output, const string& deps_prefix) { string include = FilterShowIncludes(line, deps_prefix); if (!include.empty()) { - include = IncludesNormalize::Normalize(include, NULL); - if (!IsSystemInclude(include)) - includes_.insert(include); + string normalized; + if (!IncludesNormalize::Normalize(include, NULL, &normalized, err)) + return false; + if (!IsSystemInclude(normalized)) + includes_.insert(normalized); } else if (FilterInputFilename(line)) { // Drop it. // TODO: if we support compiling multiple output files in a single // cl.exe invocation, we should stash the filename. } else { - filtered_output.append(line); - filtered_output.append("\n"); + filtered_output->append(line); + filtered_output->append("\n"); } if (end < output.size() && output[end] == '\r') @@ -114,7 +117,7 @@ string CLParser::Parse(const string& output, const string& deps_prefix) { start = end; } - return filtered_output; + return true; } int CLWrapper::Run(const string& command, string* output) { diff --git a/src/msvc_helper.h b/src/msvc_helper.h index 5d7dcb0..30f87f3 100644 --- a/src/msvc_helper.h +++ b/src/msvc_helper.h @@ -40,9 +40,11 @@ struct CLParser { /// Exposed for testing. static bool FilterInputFilename(string line); - /// Parse the full output of cl, returning the output (if any) that - /// should printed. - string Parse(const string& output, const string& deps_prefix); + /// Parse the full output of cl, filling filtered_output with the text that + /// should be printed (if any). Returns true on success, or false with err + /// filled. output must not be the same object as filtered_object. + bool Parse(const string& output, const string& deps_prefix, + string* filtered_output, string* err); set<string> includes_; }; diff --git a/src/msvc_helper_main-win32.cc b/src/msvc_helper_main-win32.cc index 58bc797..680aaad 100644 --- a/src/msvc_helper_main-win32.cc +++ b/src/msvc_helper_main-win32.cc @@ -127,7 +127,9 @@ int MSVCHelperMain(int argc, char** argv) { if (output_filename) { CLParser parser; - output = parser.Parse(output, deps_prefix); + string err; + if (!parser.Parse(output, deps_prefix, &output, &err)) + Fatal("%s\n", err.c_str()); WriteDepFileOrDie(output_filename, parser); } diff --git a/src/msvc_helper_test.cc b/src/msvc_helper_test.cc index 391c045..49d27c1 100644 --- a/src/msvc_helper_test.cc +++ b/src/msvc_helper_test.cc @@ -14,8 +14,6 @@ #include "msvc_helper.h" -#include <gtest/gtest.h> - #include "test.h" #include "util.h" @@ -48,11 +46,12 @@ TEST(CLParserTest, FilterInputFilename) { TEST(CLParserTest, ParseSimple) { CLParser parser; - string output = parser.Parse( + string output, err; + ASSERT_TRUE(parser.Parse( "foo\r\n" "Note: inc file prefix: foo.h\r\n" "bar\r\n", - "Note: inc file prefix:"); + "Note: inc file prefix:", &output, &err)); ASSERT_EQ("foo\nbar\n", output); ASSERT_EQ(1u, parser.includes_.size()); @@ -61,20 +60,22 @@ TEST(CLParserTest, ParseSimple) { TEST(CLParserTest, ParseFilenameFilter) { CLParser parser; - string output = parser.Parse( + string output, err; + ASSERT_TRUE(parser.Parse( "foo.cc\r\n" "cl: warning\r\n", - ""); + "", &output, &err)); ASSERT_EQ("cl: warning\n", output); } TEST(CLParserTest, ParseSystemInclude) { CLParser parser; - string output = parser.Parse( + string output, err; + ASSERT_TRUE(parser.Parse( "Note: including file: c:\\Program Files\\foo.h\r\n" "Note: including file: d:\\Microsoft Visual Studio\\bar.h\r\n" "Note: including file: path.h\r\n", - ""); + "", &output, &err)); // We should have dropped the first two includes because they look like // system headers. ASSERT_EQ("", output); @@ -84,11 +85,12 @@ TEST(CLParserTest, ParseSystemInclude) { TEST(CLParserTest, DuplicatedHeader) { CLParser parser; - string output = parser.Parse( + string output, err; + ASSERT_TRUE(parser.Parse( "Note: including file: foo.h\r\n" "Note: including file: bar.h\r\n" "Note: including file: foo.h\r\n", - ""); + "", &output, &err)); // We should have dropped one copy of foo.h. ASSERT_EQ("", output); ASSERT_EQ(2u, parser.includes_.size()); @@ -96,11 +98,12 @@ TEST(CLParserTest, DuplicatedHeader) { TEST(CLParserTest, DuplicatedHeaderPathConverted) { CLParser parser; - string output = parser.Parse( + string output, err; + ASSERT_TRUE(parser.Parse( "Note: including file: sub/foo.h\r\n" "Note: including file: bar.h\r\n" "Note: including file: sub\\foo.h\r\n", - ""); + "", &output, &err)); // We should have dropped one copy of foo.h. ASSERT_EQ("", output); ASSERT_EQ(2u, parser.includes_.size()); diff --git a/src/ninja.cc b/src/ninja.cc index 50de43e..fe4a580 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -64,6 +64,9 @@ struct Options { /// Tool to run rather than building. const Tool* tool; + + /// Whether duplicate rules for one target should warn or print an error. + bool dupe_edges_should_err; }; /// The Ninja main() loads up a series of data structures; various tools need @@ -140,6 +143,8 @@ struct NinjaMain : public BuildLogUser { virtual bool IsPathDead(StringPiece s) const { Node* n = state_.LookupNode(s); + if (!n || !n->in_edge()) + return false; // Just checking n isn't enough: If an old output is both in the build log // and in the deps log, it will have a Node object in state_. (It will also // have an in edge if one of its inputs is another output that's in the deps @@ -147,7 +152,13 @@ struct NinjaMain : public BuildLogUser { // edge is rare, and the first recompaction will delete all old outputs from // the deps log, and then a second recompaction will clear the build log, // which seems good enough for this corner case.) - return !n || !n->in_edge(); + // Do keep entries around for files which still exist on disk, for + // generators that want to use this information. + string err; + TimeStamp mtime = disk_interface_.Stat(s.AsString(), &err); + if (mtime == -1) + Error("%s", err.c_str()); // Log and ignore Stat() errors. + return mtime == 0; } }; @@ -161,7 +172,8 @@ struct Tool { /// When to run the tool. enum { - /// Run after parsing the command-line flags (as early as possible). + /// Run after parsing the command-line flags and potentially changing + /// the current working directory (as early as possible). RUN_AFTER_FLAGS, /// Run after loading build.ninja. @@ -189,17 +201,15 @@ void Usage(const BuildConfig& config) { " -f FILE specify input build file [default=build.ninja]\n" "\n" " -j N run N jobs in parallel [default=%d, derived from CPUs available]\n" -" -l N do not start new jobs if the load average is greater than N\n" -#ifdef _WIN32 -" (not yet implemented on Windows)\n" -#endif " -k N keep going until N jobs fail [default=1]\n" +" -l N do not start new jobs if the load average is greater than N\n" " -n dry run (don't run commands but act like they succeeded)\n" " -v show all command lines while building\n" "\n" " -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", +" terminates toplevel options; further flags are passed to the tool\n" +" -w FLAG adjust warnings (use -w list to list warnings)\n", kNinjaVersion, config.parallelism); } @@ -228,7 +238,8 @@ struct RealFileReader : public ManifestParser::FileReader { /// Returns true if the manifest was rebuilt. bool NinjaMain::RebuildManifest(const char* input_file, string* err) { string path = input_file; - if (!CanonicalizePath(&path, err)) + unsigned int slash_bits; // Unused because this path is only used for lookup. + if (!CanonicalizePath(&path, &slash_bits, err)) return false; Node* node = state_.LookupNode(path); if (!node) @@ -240,17 +251,17 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) { if (builder.AlreadyUpToDate()) return false; // Not an error, but we didn't rebuild. - if (!builder.Build(err)) - return false; - // The manifest was only rebuilt if it is now dirty (it may have been cleaned - // by a restat). - return node->dirty(); + // Even if the manifest was cleaned by a restat rule, claim that it was + // rebuilt. Not doing so can lead to crashes, see + // https://github.com/ninja-build/ninja/issues/874 + return builder.Build(err); } Node* NinjaMain::CollectTarget(const char* cpath, string* err) { string path = cpath; - if (!CanonicalizePath(&path, err)) + unsigned int slash_bits; + if (!CanonicalizePath(&path, &slash_bits, err)) return NULL; // Special syntax: "foo.cc^" means "the first output of foo.cc". @@ -276,8 +287,8 @@ Node* NinjaMain::CollectTarget(const char* cpath, string* err) { } return node; } else { - *err = "unknown target '" + path + "'"; - + *err = + "unknown target '" + Node::PathDecanonicalized(path, slash_bits) + "'"; if (path == "clean") { *err += ", did you mean 'ninja -t clean'?"; } else if (path == "help") { @@ -363,7 +374,7 @@ int NinjaMain::ToolQuery(int argc, char* argv[]) { return 0; } -#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) +#if defined(NINJA_HAVE_BROWSE) int NinjaMain::ToolBrowse(int argc, char* argv[]) { if (argc < 1) { Error("expected a target to browse"); @@ -476,7 +487,10 @@ int NinjaMain::ToolDeps(int argc, char** argv) { continue; } - TimeStamp mtime = disk_interface.Stat((*it)->path()); + string err; + TimeStamp mtime = disk_interface.Stat((*it)->path(), &err); + if (mtime == -1) + Error("%s", err.c_str()); // Log and ignore Stat() errors; printf("%s: #deps %d, deps mtime %d (%s)\n", (*it)->path().c_str(), deps->node_count, deps->mtime, (!mtime || mtime > deps->mtime ? "STALE":"VALID")); @@ -696,7 +710,7 @@ int NinjaMain::ToolUrtle(int argc, char** argv) { /// Returns a Tool, or NULL if Ninja should exit. const Tool* ChooseTool(const string& tool_name) { static const Tool kTools[] = { -#if !defined(_WIN32) && !defined(NINJA_BOOTSTRAP) +#if defined(NINJA_HAVE_BROWSE) { "browse", "browse dependency graph in a web browser", Tool::RUN_AFTER_LOAD, &NinjaMain::ToolBrowse }, #endif @@ -760,6 +774,9 @@ bool DebugEnable(const string& name) { " stats print operation counts/timing info\n" " explain explain what caused a command to execute\n" " keeprsp don't delete @response files on success\n" +#ifdef _WIN32 +" nostatcache don't batch stat() calls per directory and cache them\n" +#endif "multiple modes can be enabled via -d FOO -d BAR\n"); return false; } else if (name == "stats") { @@ -771,9 +788,13 @@ bool DebugEnable(const string& name) { } else if (name == "keeprsp") { g_keep_rsp = true; return true; + } else if (name == "nostatcache") { + g_experimental_statcache = false; + return true; } else { const char* suggestion = - SpellcheckString(name.c_str(), "stats", "explain", NULL); + SpellcheckString(name.c_str(), "stats", "explain", "keeprsp", + "nostatcache", NULL); if (suggestion) { Error("unknown debug setting '%s', did you mean '%s'?", name.c_str(), suggestion); @@ -784,6 +805,32 @@ bool DebugEnable(const string& name) { } } +/// Set a warning flag. Returns false if Ninja should exit instead of +/// continuing. +bool WarningEnable(const string& name, Options* options) { + if (name == "list") { + printf("warning flags:\n" +" dupbuild={err,warn} multiple build lines for one target\n"); + return false; + } else if (name == "dupbuild=err") { + options->dupe_edges_should_err = true; + return true; + } else if (name == "dupbuild=warn") { + options->dupe_edges_should_err = false; + return true; + } else { + const char* suggestion = + SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn", NULL); + if (suggestion) { + Error("unknown warning flag '%s', did you mean '%s'?", + name.c_str(), suggestion); + } else { + Error("unknown warning flag '%s'", name.c_str()); + } + return false; + } +} + bool NinjaMain::OpenBuildLog(bool recompact_only) { string log_path = ".ninja_log"; if (!build_dir_.empty()) @@ -882,6 +929,8 @@ int NinjaMain::RunBuild(int argc, char** argv) { return 1; } + disk_interface_.AllowStatCache(g_experimental_statcache); + Builder builder(&state_, config_, &build_log_, &deps_log_, &disk_interface_); for (size_t i = 0; i < targets.size(); ++i) { if (!builder.AddTarget(targets[i], &err)) { @@ -895,6 +944,9 @@ int NinjaMain::RunBuild(int argc, char** argv) { } } + // Make sure restat rules do not see stale timestamps. + disk_interface_.AllowStatCache(false); + if (builder.AlreadyUpToDate()) { printf("ninja: no work to do.\n"); return 0; @@ -949,7 +1001,7 @@ int ReadFlags(int* argc, char*** argv, int opt; while (!options->tool && - (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vC:h", kLongOptions, + (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions, NULL)) != -1) { switch (opt) { case 'd': @@ -998,6 +1050,10 @@ int ReadFlags(int* argc, char*** argv, case 'v': config->verbosity = BuildConfig::VERBOSE; break; + case 'w': + if (!WarningEnable(optarg, options)) + return 1; + break; case 'C': options->working_dir = optarg; break; @@ -1028,13 +1084,6 @@ int real_main(int argc, char** argv) { if (exit_code >= 0) return exit_code; - if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) { - // None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed - // by other tools. - NinjaMain ninja(ninja_command, config); - return (ninja.*options.tool->func)(argc, argv); - } - if (options.working_dir) { // The formatting of this string, complete with funny quotes, is // so Emacs can properly identify that the cwd has changed for @@ -1048,13 +1097,21 @@ int real_main(int argc, char** argv) { } } - // The build can take up to 2 passes: one to rebuild the manifest, then - // another to build the desired target. - for (int cycle = 0; cycle < 2; ++cycle) { + if (options.tool && options.tool->when == Tool::RUN_AFTER_FLAGS) { + // None of the RUN_AFTER_FLAGS actually use a NinjaMain, but it's needed + // by other tools. + NinjaMain ninja(ninja_command, config); + return (ninja.*options.tool->func)(argc, argv); + } + + // Limit number of rebuilds, to prevent infinite loops. + const int kCycleLimit = 100; + for (int cycle = 1; cycle <= kCycleLimit; ++cycle) { NinjaMain ninja(ninja_command, config); RealFileReader file_reader; - ManifestParser parser(&ninja.state_, &file_reader); + ManifestParser parser(&ninja.state_, &file_reader, + options.dupe_edges_should_err); string err; if (!parser.Load(options.input_file, &err)) { Error("%s", err.c_str()); @@ -1073,16 +1130,13 @@ int real_main(int argc, char** argv) { if (options.tool && options.tool->when == Tool::RUN_AFTER_LOGS) return (ninja.*options.tool->func)(argc, argv); - // The first time through, attempt to rebuild the manifest before - // building anything else. - if (cycle == 0) { - if (ninja.RebuildManifest(options.input_file, &err)) { - // Start the build over with the new manifest. - continue; - } else if (!err.empty()) { - Error("rebuilding '%s': %s", options.input_file, err.c_str()); - return 1; - } + // Attempt to rebuild the manifest before building anything else + if (ninja.RebuildManifest(options.input_file, &err)) { + // Start the build over with the new manifest. + continue; + } else if (!err.empty()) { + Error("rebuilding '%s': %s", options.input_file, err.c_str()); + return 1; } int result = ninja.RunBuild(argc, argv); @@ -1091,16 +1145,18 @@ int real_main(int argc, char** argv) { return result; } - return 1; // Shouldn't be reached. + Error("manifest '%s' still dirty after %d tries\n", + options.input_file, kCycleLimit); + return 1; } } // anonymous namespace int main(int argc, char** argv) { -#if !defined(NINJA_BOOTSTRAP) && defined(_MSC_VER) +#if defined(_MSC_VER) // Set a handler to catch crashes not caught by the __try..__except // block (e.g. an exception in a stack-unwind-block). - set_terminate(TerminateHandler); + std::set_terminate(TerminateHandler); __try { // Running inside __try ... __except suppresses any Windows error // dialogs for errors such as bad_alloc. diff --git a/src/ninja_test.cc b/src/ninja_test.cc index 989ea5c..54d8784 100644 --- a/src/ninja_test.cc +++ b/src/ninja_test.cc @@ -15,9 +15,35 @@ #include <stdarg.h> #include <stdio.h> -#include "gtest/gtest.h" +#ifdef _WIN32 +#include "getopt.h" +#else +#include <getopt.h> +#endif + +#include "test.h" #include "line_printer.h" +struct RegisteredTest { + testing::Test* (*factory)(); + const char *name; + bool should_run; +}; +// This can't be a vector because tests call RegisterTest from static +// initializers and the order static initializers run it isn't specified. So +// the vector constructor isn't guaranteed to run before all of the +// RegisterTest() calls. +static RegisteredTest tests[10000]; +testing::Test* g_current_test; +static int ntests; +static LinePrinter printer; + +void RegisterTest(testing::Test* (*factory)(), const char* name) { + tests[ntests].factory = factory; + tests[ntests++].name = name; +} + +namespace { string StringPrintf(const char* format, ...) { const int N = 1024; char buf[N]; @@ -30,59 +56,101 @@ string StringPrintf(const char* format, ...) { return buf; } -/// A test result printer that's less wordy than gtest's default. -struct LaconicPrinter : public testing::EmptyTestEventListener { - LaconicPrinter() : tests_started_(0), test_count_(0), iteration_(0) {} - virtual void OnTestProgramStart(const testing::UnitTest& unit_test) { - test_count_ = unit_test.test_to_run_count(); - } +void Usage() { + fprintf(stderr, +"usage: ninja_tests [options]\n" +"\n" +"options:\n" +" --gtest_filter=POSTIVE_PATTERN[-NEGATIVE_PATTERN]\n" +" Run tests whose names match the positive but not the negative pattern.\n" +" '*' matches any substring. (gtest's ':', '?' are not implemented).\n"); +} - virtual void OnTestIterationStart(const testing::UnitTest& test_info, - int iteration) { - tests_started_ = 0; - iteration_ = iteration; +bool PatternMatchesString(const char* pattern, const char* str) { + switch (*pattern) { + case '\0': + case '-': return *str == '\0'; + case '*': return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || + PatternMatchesString(pattern + 1, str); + default: return *pattern == *str && + PatternMatchesString(pattern + 1, str + 1); } +} - virtual void OnTestStart(const testing::TestInfo& test_info) { - ++tests_started_; - printer_.Print( - StringPrintf("[%d/%d%s] %s.%s", - tests_started_, - test_count_, - iteration_ ? StringPrintf(" iter %d", iteration_).c_str() - : "", - test_info.test_case_name(), - test_info.name()), - LinePrinter::ELIDE); - } +bool TestMatchesFilter(const char* test, const char* filter) { + // Split --gtest_filter at '-' into positive and negative filters. + const char* const dash = strchr(filter, '-'); + const char* pos = dash == filter ? "*" : filter; //Treat '-test1' as '*-test1' + const char* neg = dash ? dash + 1 : ""; + return PatternMatchesString(pos, test) && !PatternMatchesString(neg, test); +} - virtual void OnTestPartResult( - const testing::TestPartResult& test_part_result) { - if (!test_part_result.failed()) - return; - printer_.PrintOnNewLine(StringPrintf( - "*** Failure in %s:%d\n%s\n", test_part_result.file_name(), - test_part_result.line_number(), test_part_result.summary())); - } +bool ReadFlags(int* argc, char*** argv, const char** test_filter) { + enum { OPT_GTEST_FILTER = 1 }; + const option kLongOptions[] = { + { "gtest_filter", required_argument, NULL, OPT_GTEST_FILTER }, + { NULL, 0, NULL, 0 } + }; - virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { - printer_.PrintOnNewLine(unit_test.Passed() ? "passed\n" : "failed\n"); + int opt; + while ((opt = getopt_long(*argc, *argv, "h", kLongOptions, NULL)) != -1) { + switch (opt) { + case OPT_GTEST_FILTER: + if (strchr(optarg, '?') == NULL && strchr(optarg, ':') == NULL) { + *test_filter = optarg; + break; + } // else fall through. + default: + Usage(); + return false; + } } + *argv += optind; + *argc -= optind; + return true; +} - private: - LinePrinter printer_; - int tests_started_; - int test_count_; - int iteration_; -}; +} // namespace + +bool testing::Test::Check(bool condition, const char* file, int line, + const char* error) { + if (!condition) { + printer.PrintOnNewLine( + StringPrintf("*** Failure in %s:%d\n%s\n", file, line, error)); + failed_ = true; + } + return condition; +} int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); + int tests_started = 0; - testing::TestEventListeners& listeners = - testing::UnitTest::GetInstance()->listeners(); - delete listeners.Release(listeners.default_result_printer()); - listeners.Append(new LaconicPrinter); + const char* test_filter = "*"; + if (!ReadFlags(&argc, &argv, &test_filter)) + return 1; + + int nactivetests = 0; + for (int i = 0; i < ntests; i++) + if ((tests[i].should_run = TestMatchesFilter(tests[i].name, test_filter))) + ++nactivetests; + + bool passed = true; + for (int i = 0; i < ntests; i++) { + if (!tests[i].should_run) continue; + + ++tests_started; + testing::Test* test = tests[i].factory(); + printer.Print( + StringPrintf("[%d/%d] %s", tests_started, nactivetests, tests[i].name), + LinePrinter::ELIDE); + test->SetUp(); + test->Run(); + test->TearDown(); + if (test->Failed()) + passed = false; + delete test; + } - return RUN_ALL_TESTS(); + printer.PrintOnNewLine(passed ? "passed\n" : "failed\n"); + return passed ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/state.cc b/src/state.cc index 7258272..a70f211 100644 --- a/src/state.cc +++ b/src/state.cc @@ -61,6 +61,7 @@ void Pool::Dump() const { } } +// static bool Pool::WeightedEdgeCmp(const Edge* a, const Edge* b) { if (!a) return b; if (!b) return false; @@ -73,23 +74,11 @@ Pool State::kConsolePool("console", 1); const Rule State::kPhonyRule("phony"); State::State() { - AddRule(&kPhonyRule); + bindings_.AddRule(&kPhonyRule); AddPool(&kDefaultPool); AddPool(&kConsolePool); } -void State::AddRule(const Rule* rule) { - assert(LookupRule(rule->name()) == NULL); - rules_[rule->name()] = rule; -} - -const Rule* State::LookupRule(const string& rule_name) { - map<string, const Rule*>::iterator i = rules_.find(rule_name); - if (i == rules_.end()) - return NULL; - return i->second; -} - void State::AddPool(Pool* pool) { assert(LookupPool(pool->name()) == NULL); pools_[pool->name()] = pool; @@ -111,11 +100,11 @@ Edge* State::AddEdge(const Rule* rule) { return edge; } -Node* State::GetNode(StringPiece path) { +Node* State::GetNode(StringPiece path, unsigned int slash_bits) { Node* node = LookupNode(path); if (node) return node; - node = new Node(path.AsString()); + node = new Node(path.AsString(), slash_bits); paths_[node->path()] = node; return node; } @@ -145,22 +134,19 @@ Node* State::SpellcheckNode(const string& path) { return result; } -void State::AddIn(Edge* edge, StringPiece path) { - Node* node = GetNode(path); +void State::AddIn(Edge* edge, StringPiece path, unsigned int slash_bits) { + Node* node = GetNode(path, slash_bits); edge->inputs_.push_back(node); node->AddOutEdge(edge); } -void State::AddOut(Edge* edge, StringPiece path) { - Node* node = GetNode(path); +bool State::AddOut(Edge* edge, StringPiece path, unsigned int slash_bits) { + Node* node = GetNode(path, slash_bits); + if (node->in_edge()) + return false; edge->outputs_.push_back(node); - if (node->in_edge()) { - Warning("multiple rules generate %s. " - "builds involving this target will not be correct; " - "continuing anyway", - path.AsString().c_str()); - } node->set_in_edge(edge); + return true; } bool State::AddDefault(StringPiece path, string* err) { @@ -187,7 +173,6 @@ vector<Node*> State::RootNodes(string* err) { if (!edges_.empty() && root_nodes.empty()) *err = "could not determine root nodes of build graph"; - assert(edges_.empty() || !root_nodes.empty()); return root_nodes; } diff --git a/src/state.h b/src/state.h index c382dc0..d7987ba 100644 --- a/src/state.h +++ b/src/state.h @@ -37,13 +37,14 @@ struct Rule; /// the total scheduled weight diminishes enough (i.e. when a scheduled edge /// completes). struct Pool { - explicit Pool(const string& name, int depth) - : name_(name), current_use_(0), depth_(depth), delayed_(&WeightedEdgeCmp) { } + Pool(const string& name, int depth) + : name_(name), current_use_(0), depth_(depth), delayed_(&WeightedEdgeCmp) {} // A depth of 0 is infinite bool is_valid() const { return depth_ >= 0; } int depth() const { return depth_; } const string& name() const { return name_; } + int current_use() const { return current_use_; } /// true if the Pool might delay this edge bool ShouldDelayEdge() const { return depth_ != 0; } @@ -79,7 +80,7 @@ struct Pool { DelayedEdges delayed_; }; -/// Global state (file status, loaded rules) for a single run. +/// Global state (file status) for a single run. struct State { static Pool kDefaultPool; static Pool kConsolePool; @@ -87,20 +88,17 @@ struct State { State(); - void AddRule(const Rule* rule); - const Rule* LookupRule(const string& rule_name); - void AddPool(Pool* pool); Pool* LookupPool(const string& pool_name); Edge* AddEdge(const Rule* rule); - Node* GetNode(StringPiece path); + Node* GetNode(StringPiece path, unsigned int slash_bits); Node* LookupNode(StringPiece path) const; Node* SpellcheckNode(const string& path); - void AddIn(Edge* edge, StringPiece path); - void AddOut(Edge* edge, StringPiece path); + void AddIn(Edge* edge, StringPiece path, unsigned int slash_bits); + bool AddOut(Edge* edge, StringPiece path, unsigned int slash_bits); bool AddDefault(StringPiece path, string* error); /// Reset state. Keeps all nodes and edges, but restores them to the @@ -119,9 +117,6 @@ struct State { typedef ExternalStringHashMap<Node*>::Type Paths; Paths paths_; - /// All the rules used in the graph. - map<string, const Rule*> rules_; - /// All the pools used in the graph. map<string, Pool*> pools_; diff --git a/src/state_test.cc b/src/state_test.cc index af2bff1..458b519 100644 --- a/src/state_test.cc +++ b/src/state_test.cc @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <gtest/gtest.h> - #include "graph.h" #include "state.h" +#include "test.h" namespace { @@ -30,18 +29,18 @@ TEST(State, Basic) { Rule* rule = new Rule("cat"); rule->AddBinding("command", command); - state.AddRule(rule); + state.bindings_.AddRule(rule); Edge* edge = state.AddEdge(rule); - state.AddIn(edge, "in1"); - state.AddIn(edge, "in2"); - state.AddOut(edge, "out"); + state.AddIn(edge, "in1", 0); + state.AddIn(edge, "in2", 0); + state.AddOut(edge, "out", 0); EXPECT_EQ("cat in1 in2 > out", edge->EvaluateCommand()); - EXPECT_FALSE(state.GetNode("in1")->dirty()); - EXPECT_FALSE(state.GetNode("in2")->dirty()); - EXPECT_FALSE(state.GetNode("out")->dirty()); + EXPECT_FALSE(state.GetNode("in1", 0)->dirty()); + EXPECT_FALSE(state.GetNode("in2", 0)->dirty()); + EXPECT_FALSE(state.GetNode("out", 0)->dirty()); } } // namespace diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index 743e406..f3baec2 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -28,6 +28,7 @@ Subprocess::Subprocess(bool use_console) : fd_(-1), pid_(-1), use_console_(use_console) { } + Subprocess::~Subprocess() { if (fd_ >= 0) close(fd_); @@ -59,14 +60,19 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { // Track which fd we use to report errors on. int error_pipe = output_pipe[1]; do { - if (sigaction(SIGINT, &set->old_act_, 0) < 0) + if (sigaction(SIGINT, &set->old_int_act_, 0) < 0) + break; + if (sigaction(SIGTERM, &set->old_term_act_, 0) < 0) break; if (sigprocmask(SIG_SETMASK, &set->old_mask_, 0) < 0) break; if (!use_console_) { - // Put the child in its own process group, so ctrl-c won't reach it. - if (setpgid(0, 0) < 0) + // Put the child in its own session and process group. It will be + // detached from the current terminal and ctrl-c won't reach it. + // Since this process was just forked, it is not a process group leader + // and setsid() will succeed. + if (setsid() < 0) break; // Open /dev/null over stdin. @@ -130,7 +136,7 @@ ExitStatus Subprocess::Finish() { if (exit == 0) return ExitSuccess; } else if (WIFSIGNALED(status)) { - if (WTERMSIG(status) == SIGINT) + if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM) return ExitInterrupted; } return ExitFailure; @@ -144,31 +150,48 @@ const string& Subprocess::GetOutput() const { return buf_; } -bool SubprocessSet::interrupted_; +int SubprocessSet::interrupted_; void SubprocessSet::SetInterruptedFlag(int signum) { - (void) signum; - interrupted_ = true; + interrupted_ = signum; +} + +void SubprocessSet::HandlePendingInterruption() { + sigset_t pending; + sigemptyset(&pending); + if (sigpending(&pending) == -1) { + perror("ninja: sigpending"); + return; + } + if (sigismember(&pending, SIGINT)) + interrupted_ = SIGINT; + else if (sigismember(&pending, SIGTERM)) + interrupted_ = SIGTERM; } SubprocessSet::SubprocessSet() { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); + sigaddset(&set, SIGTERM); if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0) Fatal("sigprocmask: %s", strerror(errno)); struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_handler = SetInterruptedFlag; - if (sigaction(SIGINT, &act, &old_act_) < 0) + if (sigaction(SIGINT, &act, &old_int_act_) < 0) + Fatal("sigaction: %s", strerror(errno)); + if (sigaction(SIGTERM, &act, &old_term_act_) < 0) Fatal("sigaction: %s", strerror(errno)); } SubprocessSet::~SubprocessSet() { Clear(); - if (sigaction(SIGINT, &old_act_, 0) < 0) + if (sigaction(SIGINT, &old_int_act_, 0) < 0) + Fatal("sigaction: %s", strerror(errno)); + if (sigaction(SIGTERM, &old_term_act_, 0) < 0) Fatal("sigaction: %s", strerror(errno)); if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0) Fatal("sigprocmask: %s", strerror(errno)); @@ -199,16 +222,20 @@ bool SubprocessSet::DoWork() { ++nfds; } - interrupted_ = false; + interrupted_ = 0; int ret = ppoll(&fds.front(), nfds, NULL, &old_mask_); if (ret == -1) { if (errno != EINTR) { perror("ninja: ppoll"); return false; } - return interrupted_; + return IsInterrupted(); } + HandlePendingInterruption(); + if (IsInterrupted()) + return true; + nfds_t cur_nfd = 0; for (vector<Subprocess*>::iterator i = running_.begin(); i != running_.end(); ) { @@ -227,7 +254,7 @@ bool SubprocessSet::DoWork() { ++i; } - return interrupted_; + return IsInterrupted(); } #else // !defined(USE_PPOLL) @@ -246,16 +273,20 @@ bool SubprocessSet::DoWork() { } } - interrupted_ = false; + interrupted_ = 0; int ret = pselect(nfds, &set, 0, 0, 0, &old_mask_); if (ret == -1) { if (errno != EINTR) { perror("ninja: pselect"); return false; } - return interrupted_; + return IsInterrupted(); } + HandlePendingInterruption(); + if (IsInterrupted()) + return true; + for (vector<Subprocess*>::iterator i = running_.begin(); i != running_.end(); ) { int fd = (*i)->fd_; @@ -270,7 +301,7 @@ bool SubprocessSet::DoWork() { ++i; } - return interrupted_; + return IsInterrupted(); } #endif // !defined(USE_PPOLL) @@ -285,10 +316,10 @@ Subprocess* SubprocessSet::NextFinished() { void SubprocessSet::Clear() { for (vector<Subprocess*>::iterator i = running_.begin(); i != running_.end(); ++i) - // Since the foreground process is in our process group, it will receive a - // SIGINT at the same time as us. + // Since the foreground process is in our process group, it will receive + // the interruption signal (i.e. SIGINT or SIGTERM) at the same time as us. if (!(*i)->use_console_) - kill(-(*i)->pid_, SIGINT); + kill(-(*i)->pid_, interrupted_); for (vector<Subprocess*>::iterator i = running_.begin(); i != running_.end(); ++i) delete *i; diff --git a/src/subprocess.h b/src/subprocess.h index b7a1a4c..a001fc9 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -89,9 +89,15 @@ struct SubprocessSet { static HANDLE ioport_; #else static void SetInterruptedFlag(int signum); - static bool interrupted_; + static void HandlePendingInterruption(); + /// Store the signal number that causes the interruption. + /// 0 if not interruption. + static int interrupted_; - struct sigaction old_act_; + static bool IsInterrupted() { return interrupted_ != 0; } + + struct sigaction old_int_act_; + struct sigaction old_term_act_; sigset_t old_mask_; #endif }; diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 775a13a..07cc52f 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -16,8 +16,11 @@ #include "test.h" +#include <string> + #ifndef _WIN32 // SetWithLots need setrlimit. +#include <stdio.h> #include <sys/time.h> #include <sys/resource.h> #include <unistd.h> @@ -92,15 +95,50 @@ TEST_F(SubprocessTest, InterruptParent) { return; } - ADD_FAILURE() << "We should have been interrupted"; + ASSERT_FALSE("We should have been interrupted"); +} + +TEST_F(SubprocessTest, InterruptChildWithSigTerm) { + Subprocess* subproc = subprocs_.Add("kill -TERM $$"); + ASSERT_NE((Subprocess *) 0, subproc); + + while (!subproc->Done()) { + subprocs_.DoWork(); + } + + EXPECT_EQ(ExitInterrupted, subproc->Finish()); +} + +TEST_F(SubprocessTest, InterruptParentWithSigTerm) { + Subprocess* subproc = subprocs_.Add("kill -TERM $PPID ; sleep 1"); + ASSERT_NE((Subprocess *) 0, subproc); + + while (!subproc->Done()) { + bool interrupted = subprocs_.DoWork(); + if (interrupted) + return; + } + + ASSERT_FALSE("We should have been interrupted"); } +// A shell command to check if the current process is connected to a terminal. +// This is different from having stdin/stdout/stderr be a terminal. (For +// instance consider the command "yes < /dev/null > /dev/null 2>&1". +// As "ps" will confirm, "yes" could still be connected to a terminal, despite +// not having any of the standard file descriptors be a terminal. +static const char kIsConnectedToTerminal[] = "tty < /dev/tty > /dev/null"; + TEST_F(SubprocessTest, Console) { // Skip test if we don't have the console ourselves. if (isatty(0) && isatty(1) && isatty(2)) { - Subprocess* subproc = subprocs_.Add("test -t 0 -a -t 1 -a -t 2", - /*use_console=*/true); - ASSERT_NE((Subprocess *) 0, subproc); + // Test that stdin, stdout and stderr are a terminal. + // Also check that the current process is connected to a terminal. + Subprocess* subproc = + subprocs_.Add(std::string("test -t 0 -a -t 1 -a -t 2 && ") + + std::string(kIsConnectedToTerminal), + /*use_console=*/true); + ASSERT_NE((Subprocess*)0, subproc); while (!subproc->Done()) { subprocs_.DoWork(); @@ -110,6 +148,18 @@ TEST_F(SubprocessTest, Console) { } } +TEST_F(SubprocessTest, NoConsole) { + Subprocess* subproc = + subprocs_.Add(kIsConnectedToTerminal, /*use_console=*/false); + ASSERT_NE((Subprocess*)0, subproc); + + while (!subproc->Done()) { + subprocs_.DoWork(); + } + + EXPECT_NE(ExitSuccess, subproc->Finish()); +} + #endif TEST_F(SubprocessTest, SetWithSingle) { @@ -171,14 +221,15 @@ TEST_F(SubprocessTest, SetWithMulti) { TEST_F(SubprocessTest, SetWithLots) { // Arbitrary big number; needs to be over 1024 to confirm we're no longer // hostage to pselect. - const size_t kNumProcs = 1025; + const unsigned kNumProcs = 1025; // Make sure [ulimit -n] isn't going to stop us from working. rlimit rlim; ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlim)); - ASSERT_GT(rlim.rlim_cur, kNumProcs) - << "Raise [ulimit -n] well above " << kNumProcs - << " to make this test go"; + if (rlim.rlim_cur < kNumProcs) { + printf("Raise [ulimit -n] well above %u (currently %lu) to make this test go\n", kNumProcs, rlim.rlim_cur); + return; + } vector<Subprocess*> procs; for (size_t i = 0; i < kNumProcs; ++i) { @@ -194,7 +245,7 @@ TEST_F(SubprocessTest, SetWithLots) { } ASSERT_EQ(kNumProcs, subprocs_.finished_.size()); } -#endif // !__APPLE__ && !_WIN32 +#endif // !__APPLE__ && !_WIN32 // TODO: this test could work on Windows, just not sure how to simply // read stdin. diff --git a/src/test.cc b/src/test.cc index 45a9226..aed8db7 100644 --- a/src/test.cc +++ b/src/test.cc @@ -12,20 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifdef _WIN32 +#include <direct.h> // Has to be before util.h is included. +#endif + #include "test.h" #include <algorithm> #include <errno.h> +#ifdef _WIN32 +#include <windows.h> +#else +#include <unistd.h> +#endif #include "build_log.h" +#include "graph.h" #include "manifest_parser.h" #include "util.h" -#ifdef _WIN32 -#include <windows.h> -#endif - namespace { #ifdef _WIN32 @@ -84,20 +90,54 @@ void StateTestWithBuiltinRules::AddCatRule(State* state) { } Node* StateTestWithBuiltinRules::GetNode(const string& path) { - return state_.GetNode(path); + EXPECT_FALSE(strpbrk(path.c_str(), "/\\")); + return state_.GetNode(path, 0); } void AssertParse(State* state, const char* input) { ManifestParser parser(state, NULL); string err; - ASSERT_TRUE(parser.ParseTest(input, &err)) << err; + EXPECT_TRUE(parser.ParseTest(input, &err)); ASSERT_EQ("", err); + VerifyGraph(*state); } void AssertHash(const char* expected, uint64_t actual) { ASSERT_EQ(BuildLog::LogEntry::HashCommand(expected), actual); } +void VerifyGraph(const State& state) { + for (vector<Edge*>::const_iterator e = state.edges_.begin(); + e != state.edges_.end(); ++e) { + // All edges need at least one output. + EXPECT_FALSE((*e)->outputs_.empty()); + // Check that the edge's inputs have the edge as out-edge. + for (vector<Node*>::const_iterator in_node = (*e)->inputs_.begin(); + in_node != (*e)->inputs_.end(); ++in_node) { + const vector<Edge*>& out_edges = (*in_node)->out_edges(); + EXPECT_NE(std::find(out_edges.begin(), out_edges.end(), *e), + out_edges.end()); + } + // Check that the edge's outputs have the edge as in-edge. + for (vector<Node*>::const_iterator out_node = (*e)->outputs_.begin(); + out_node != (*e)->outputs_.end(); ++out_node) { + EXPECT_EQ((*out_node)->in_edge(), *e); + } + } + + // The union of all in- and out-edges of each nodes should be exactly edges_. + set<const Edge*> node_edge_set; + for (State::Paths::const_iterator p = state.paths_.begin(); + p != state.paths_.end(); ++p) { + const Node* n = p->second; + if (n->in_edge()) + node_edge_set.insert(n->in_edge()); + node_edge_set.insert(n->out_edges().begin(), n->out_edges().end()); + } + set<const Edge*> edge_set(state.edges_.begin(), state.edges_.end()); + EXPECT_EQ(node_edge_set, edge_set); +} + void VirtualFileSystem::Create(const string& path, const string& contents) { files_[path].mtime = now_; @@ -105,10 +145,12 @@ void VirtualFileSystem::Create(const string& path, files_created_.insert(path); } -TimeStamp VirtualFileSystem::Stat(const string& path) { - FileMap::iterator i = files_.find(path); - if (i != files_.end()) +TimeStamp VirtualFileSystem::Stat(const string& path, string* err) const { + FileMap::const_iterator i = files_.find(path); + if (i != files_.end()) { + *err = i->second.stat_error; return i->second.mtime; + } return 0; } @@ -15,12 +15,94 @@ #ifndef NINJA_TEST_H_ #define NINJA_TEST_H_ -#include <gtest/gtest.h> - #include "disk_interface.h" #include "state.h" #include "util.h" +// A tiny testing framework inspired by googletest, but much simpler and +// faster to compile. It supports most things commonly used from googltest. The +// most noticeable things missing: EXPECT_* and ASSERT_* don't support +// streaming notes to them with operator<<, and for failing tests the lhs and +// rhs are not printed. That's so that this header does not have to include +// sstream, which slows down building ninja_test almost 20%. +namespace testing { +class Test { + bool failed_; + int assertion_failures_; + public: + Test() : failed_(false), assertion_failures_(0) {} + virtual ~Test() {} + virtual void SetUp() {} + virtual void TearDown() {} + virtual void Run() = 0; + + bool Failed() const { return failed_; } + int AssertionFailures() const { return assertion_failures_; } + void AddAssertionFailure() { assertion_failures_++; } + bool Check(bool condition, const char* file, int line, const char* error); +}; +} + +void RegisterTest(testing::Test* (*)(), const char*); + +extern testing::Test* g_current_test; +#define TEST_F_(x, y, name) \ + struct y : public x { \ + static testing::Test* Create() { return g_current_test = new y; } \ + virtual void Run(); \ + }; \ + struct Register##y { \ + Register##y() { RegisterTest(y::Create, name); } \ + }; \ + Register##y g_register_##y; \ + void y::Run() + +#define TEST_F(x, y) TEST_F_(x, x##y, #x "." #y) +#define TEST(x, y) TEST_F_(testing::Test, x##y, #x "." #y) + +#define EXPECT_EQ(a, b) \ + g_current_test->Check(a == b, __FILE__, __LINE__, #a " == " #b) +#define EXPECT_NE(a, b) \ + g_current_test->Check(a != b, __FILE__, __LINE__, #a " != " #b) +#define EXPECT_GT(a, b) \ + g_current_test->Check(a > b, __FILE__, __LINE__, #a " > " #b) +#define EXPECT_LT(a, b) \ + g_current_test->Check(a < b, __FILE__, __LINE__, #a " < " #b) +#define EXPECT_GE(a, b) \ + g_current_test->Check(a >= b, __FILE__, __LINE__, #a " >= " #b) +#define EXPECT_LE(a, b) \ + g_current_test->Check(a <= b, __FILE__, __LINE__, #a " <= " #b) +#define EXPECT_TRUE(a) \ + g_current_test->Check(static_cast<bool>(a), __FILE__, __LINE__, #a) +#define EXPECT_FALSE(a) \ + g_current_test->Check(!static_cast<bool>(a), __FILE__, __LINE__, #a) + +#define ASSERT_EQ(a, b) \ + if (!EXPECT_EQ(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_NE(a, b) \ + if (!EXPECT_NE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_GT(a, b) \ + if (!EXPECT_GT(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_LT(a, b) \ + if (!EXPECT_LT(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_GE(a, b) \ + if (!EXPECT_GE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_LE(a, b) \ + if (!EXPECT_LE(a, b)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_TRUE(a) \ + if (!EXPECT_TRUE(a)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_FALSE(a) \ + if (!EXPECT_FALSE(a)) { g_current_test->AddAssertionFailure(); return; } +#define ASSERT_NO_FATAL_FAILURE(a) \ + { \ + int f = g_current_test->AssertionFailures(); \ + a; \ + if (f != g_current_test->AssertionFailures()) { \ + g_current_test->AddAssertionFailure(); \ + return; \ + } \ + } + // Support utilites for tests. struct Node; @@ -42,6 +124,7 @@ struct StateTestWithBuiltinRules : public testing::Test { void AssertParse(State* state, const char* input); void AssertHash(const char* expected, uint64_t actual); +void VerifyGraph(const State& state); /// An implementation of DiskInterface that uses an in-memory representation /// of disk state. It also logs file accesses and directory creations @@ -59,7 +142,7 @@ struct VirtualFileSystem : public DiskInterface { } // DiskInterface - virtual TimeStamp Stat(const string& path); + virtual TimeStamp Stat(const string& path, string* err) const; virtual bool WriteFile(const string& path, const string& contents); virtual bool MakeDir(const string& path); virtual string ReadFile(const string& path, string* err); @@ -68,6 +151,7 @@ struct VirtualFileSystem : public DiskInterface { /// An entry for a single in-memory file. struct Entry { int mtime; + string stat_error; // If mtime is -1. string contents; }; diff --git a/src/util.cc b/src/util.cc index 484b0c1..aa47f2f 100644 --- a/src/util.cc +++ b/src/util.cc @@ -14,7 +14,10 @@ #include "util.h" -#ifdef _WIN32 +#ifdef __CYGWIN__ +#include <windows.h> +#include <io.h> +#elif defined( _WIN32) #include <windows.h> #include <io.h> #include <share.h> @@ -85,19 +88,33 @@ void Error(const char* msg, ...) { fprintf(stderr, "\n"); } -bool CanonicalizePath(string* path, string* err) { +bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err) { METRIC_RECORD("canonicalize str"); size_t len = path->size(); char* str = 0; if (len > 0) str = &(*path)[0]; - if (!CanonicalizePath(str, &len, err)) + if (!CanonicalizePath(str, &len, slash_bits, err)) return false; path->resize(len); return true; } -bool CanonicalizePath(char* path, size_t* len, string* err) { +#ifdef _WIN32 +static unsigned int ShiftOverBit(int offset, unsigned int bits) { + // e.g. for |offset| == 2: + // | ... 9 8 7 6 5 4 3 2 1 0 | + // \_________________/ \_/ + // above below + // So we drop the bit at offset and move above "down" into its place. + unsigned int above = bits & ~((1 << (offset + 1)) - 1); + unsigned int below = bits & ((1 << offset) - 1); + return (above >> 1) | below; +} +#endif + +bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits, + string* err) { // WARNING: this function is performance-critical; please benchmark // any changes you make to it. METRIC_RECORD("canonicalize path"); @@ -115,12 +132,37 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { const char* src = start; const char* end = start + *len; +#ifdef _WIN32 + unsigned int bits = 0; + unsigned int bits_mask = 1; + int bits_offset = 0; + // Convert \ to /, setting a bit in |bits| for each \ encountered. + for (char* c = path; c < end; ++c) { + switch (*c) { + case '\\': + bits |= bits_mask; + *c = '/'; + // Intentional fallthrough. + case '/': + bits_mask <<= 1; + bits_offset++; + } + } + if (bits_offset > 32) { + *err = "too many path components"; + return false; + } + bits_offset = 0; +#endif + if (*src == '/') { #ifdef _WIN32 + bits_offset++; // network path starts with // if (*len > 1 && *(src + 1) == '/') { src += 2; dst += 2; + bits_offset++; } else { ++src; ++dst; @@ -136,6 +178,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { if (src + 1 == end || src[1] == '/') { // '.' component; eliminate. src += 2; +#ifdef _WIN32 + bits = ShiftOverBit(bits_offset, bits); +#endif continue; } else if (src[1] == '.' && (src + 2 == end || src[2] == '/')) { // '..' component. Back up if possible. @@ -143,6 +188,11 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { dst = components[component_count - 1]; src += 3; --component_count; +#ifdef _WIN32 + bits = ShiftOverBit(bits_offset, bits); + bits_offset--; + bits = ShiftOverBit(bits_offset, bits); +#endif } else { *dst++ = *src++; *dst++ = *src++; @@ -154,6 +204,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { if (*src == '/') { src++; +#ifdef _WIN32 + bits = ShiftOverBit(bits_offset, bits); +#endif continue; } @@ -164,6 +217,9 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { while (*src != '/' && src != end) *dst++ = *src++; +#ifdef _WIN32 + bits_offset++; +#endif *dst++ = *src++; // Copy '/' or final \0 character as well. } @@ -173,6 +229,11 @@ bool CanonicalizePath(char* path, size_t* len, string* err) { } *len = dst - start - 1; +#ifdef _WIN32 + *slash_bits = bits; +#else + *slash_bits = 0; +#endif return true; } @@ -280,7 +341,38 @@ void GetWin32EscapedString(const string& input, string* result) { } int ReadFile(const string& path, string* contents, string* err) { - FILE* f = fopen(path.c_str(), "r"); +#ifdef _WIN32 + // This makes a ninja run on a set of 1500 manifest files about 4% faster + // than using the generic fopen code below. + err->clear(); + HANDLE f = ::CreateFile(path.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (f == INVALID_HANDLE_VALUE) { + err->assign(GetLastErrorString()); + return -ENOENT; + } + + for (;;) { + DWORD len; + char buf[64 << 10]; + if (!::ReadFile(f, buf, sizeof(buf), &len, NULL)) { + err->assign(GetLastErrorString()); + contents->clear(); + return -1; + } + if (len == 0) + break; + contents->append(buf, len); + } + ::CloseHandle(f); + return 0; +#else + FILE* f = fopen(path.c_str(), "rb"); if (!f) { err->assign(strerror(errno)); return -errno; @@ -299,6 +391,7 @@ int ReadFile(const string& path, string* contents, string* err) { } fclose(f); return 0; +#endif } void SetCloseOnExec(int fd) { @@ -407,7 +500,7 @@ string StripAnsiEscapeCodes(const string& in) { int GetProcessorCount() { #ifdef _WIN32 SYSTEM_INFO info; - GetSystemInfo(&info); + GetNativeSystemInfo(&info); return info.dwNumberOfProcessors; #else return sysconf(_SC_NPROCESSORS_ONLN); @@ -415,10 +508,70 @@ int GetProcessorCount() { } #if defined(_WIN32) || defined(__CYGWIN__) +static double CalculateProcessorLoad(uint64_t idle_ticks, uint64_t total_ticks) +{ + static uint64_t previous_idle_ticks = 0; + static uint64_t previous_total_ticks = 0; + static double previous_load = -0.0; + + uint64_t idle_ticks_since_last_time = idle_ticks - previous_idle_ticks; + uint64_t total_ticks_since_last_time = total_ticks - previous_total_ticks; + + bool first_call = (previous_total_ticks == 0); + bool ticks_not_updated_since_last_call = (total_ticks_since_last_time == 0); + + double load; + if (first_call || ticks_not_updated_since_last_call) { + load = previous_load; + } else { + // Calculate load. + double idle_to_total_ratio = + ((double)idle_ticks_since_last_time) / total_ticks_since_last_time; + double load_since_last_call = 1.0 - idle_to_total_ratio; + + // Filter/smooth result when possible. + if(previous_load > 0) { + load = 0.9 * previous_load + 0.1 * load_since_last_call; + } else { + load = load_since_last_call; + } + } + + previous_load = load; + previous_total_ticks = total_ticks; + previous_idle_ticks = idle_ticks; + + return load; +} + +static uint64_t FileTimeToTickCount(const FILETIME & ft) +{ + uint64_t high = (((uint64_t)(ft.dwHighDateTime)) << 32); + uint64_t low = ft.dwLowDateTime; + return (high | low); +} + double GetLoadAverage() { - // TODO(nicolas.despres@gmail.com): Find a way to implement it on Windows. - // Remember to also update Usage() when this is fixed. - return -0.0f; + FILETIME idle_time, kernel_time, user_time; + BOOL get_system_time_succeeded = + GetSystemTimes(&idle_time, &kernel_time, &user_time); + + double posix_compatible_load; + if (get_system_time_succeeded) { + uint64_t idle_ticks = FileTimeToTickCount(idle_time); + + // kernel_time from GetSystemTimes already includes idle_time. + uint64_t total_ticks = + FileTimeToTickCount(kernel_time) + FileTimeToTickCount(user_time); + + double processor_load = CalculateProcessorLoad(idle_ticks, total_ticks); + posix_compatible_load = processor_load * GetProcessorCount(); + + } else { + posix_compatible_load = -0.0; + } + + return posix_compatible_load; } #else double GetLoadAverage() { @@ -41,9 +41,11 @@ void Warning(const char* msg, ...); void Error(const char* msg, ...); /// Canonicalize a path like "foo/../bar.h" into just "bar.h". -bool CanonicalizePath(string* path, string* err); - -bool CanonicalizePath(char* path, size_t* len, string* err); +/// |slash_bits| has bits set starting from lowest for a backslash that was +/// normalized to a forward slash. (only used on Windows) +bool CanonicalizePath(string* path, unsigned int* slash_bits, string* err); +bool CanonicalizePath(char* path, size_t* len, unsigned int* slash_bits, + string* err); /// Appends |input| to |*result|, escaping according to the whims of either /// Bash, or Win32's CommandLineToArgvW(). diff --git a/src/util_test.cc b/src/util_test.cc index b58d15e..8ca7f56 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -16,6 +16,15 @@ #include "test.h" +namespace { + +bool CanonicalizePath(string* path, string* err) { + unsigned int unused; + return ::CanonicalizePath(path, &unused, err); +} + +} // namespace + TEST(CanonicalizePath, PathSamples) { string path; string err; @@ -84,6 +93,201 @@ TEST(CanonicalizePath, PathSamples) { EXPECT_EQ("", path); } +#ifdef _WIN32 +TEST(CanonicalizePath, PathSamplesWindows) { + string path; + string err; + + EXPECT_FALSE(CanonicalizePath(&path, &err)); + EXPECT_EQ("empty path", err); + + path = "foo.h"; err = ""; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo.h", path); + + path = ".\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo.h", path); + + path = ".\\foo\\.\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/bar.h", path); + + path = ".\\x\\foo\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("x/bar.h", path); + + path = ".\\x\\foo\\..\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("bar.h", path); + + path = "foo\\\\bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/bar", path); + + path = "foo\\\\.\\\\..\\\\\\bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("bar", path); + + path = ".\\x\\..\\foo\\..\\..\\bar.h"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("../bar.h", path); + + path = "foo\\.\\."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo", path); + + path = "foo\\bar\\.."; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo", path); + + path = "foo\\.hidden_bar"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("foo/.hidden_bar", path); + + path = "\\foo"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("/foo", path); + + path = "\\\\foo"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("//foo", path); + + path = "\\"; + EXPECT_TRUE(CanonicalizePath(&path, &err)); + EXPECT_EQ("", path); +} + +TEST(CanonicalizePath, SlashTracking) { + string path; + string err; + unsigned int slash_bits; + + path = "foo.h"; err = ""; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a/bcd/efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(4, slash_bits); + + path = "a\\bcd/efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(5, slash_bits); + + path = "a\\bcd\\efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(7, slash_bits); + + path = "a/bcd/efh/foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/bcd/efh/foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\./efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/efh/foo.h", path); + EXPECT_EQ(3, slash_bits); + + path = "a\\../efh\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("efh/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b\\c\\d\\e\\f\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/b/c/d/e/f/g/foo.h", path); + EXPECT_EQ(127, slash_bits); + + path = "a\\b\\c\\..\\..\\..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b/c\\../../..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\b/c\\./../..\\g\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/g/foo.h", path); + EXPECT_EQ(3, slash_bits); + + path = "a\\b/c\\./../..\\g/foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/g/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a\\\\\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); + + path = "a/\\\\foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(0, slash_bits); + + path = "a\\//foo.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ("a/foo.h", path); + EXPECT_EQ(1, slash_bits); +} + +TEST(CanonicalizePath, CanonicalizeNotExceedingLen) { + // Make sure searching \/ doesn't go past supplied len. + char buf[] = "foo/bar\\baz.h\\"; // Last \ past end. + unsigned int slash_bits; + string err; + size_t size = 13; + EXPECT_TRUE(::CanonicalizePath(buf, &size, &slash_bits, &err)); + EXPECT_EQ(0, strncmp("foo/bar/baz.h", buf, size)); + EXPECT_EQ(2, slash_bits); // Not including the trailing one. +} + +TEST(CanonicalizePath, TooManyComponents) { + string path; + string err; + unsigned int slash_bits; + + // 32 is OK. + path = "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + + // Backslashes version. + path = + "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\." + "\\a\\.\\a\\.\\a\\.\\a\\.\\x.h"; + EXPECT_TRUE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ(slash_bits, 0xffff); + + // 33 is not. + err = ""; + path = + "a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/x.h"; + EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ(err, "too many path components"); + + // Backslashes version. + err = ""; + path = + "a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\." + "\\a\\.\\a\\.\\a\\.\\a\\.\\a\\x.h"; + EXPECT_FALSE(CanonicalizePath(&path, &slash_bits, &err)); + EXPECT_EQ(err, "too many path components"); +} +#endif + TEST(CanonicalizePath, EmptyResult) { string path; string err; @@ -122,26 +326,27 @@ TEST(CanonicalizePath, NotNullTerminated) { string path; string err; size_t len; + unsigned int unused; path = "foo/. bar/."; len = strlen("foo/."); // Canonicalize only the part before the space. - EXPECT_TRUE(CanonicalizePath(&path[0], &len, &err)); + EXPECT_TRUE(CanonicalizePath(&path[0], &len, &unused, &err)); EXPECT_EQ(strlen("foo"), len); EXPECT_EQ("foo/. bar/.", string(path)); path = "foo/../file bar/."; len = strlen("foo/../file"); - EXPECT_TRUE(CanonicalizePath(&path[0], &len, &err)); + EXPECT_TRUE(CanonicalizePath(&path[0], &len, &unused, &err)); EXPECT_EQ(strlen("file"), len); EXPECT_EQ("file ./file bar/.", string(path)); } TEST(PathEscaping, TortureTest) { string result; - + GetWin32EscapedString("foo bar\\\"'$@d!st!c'\\path'\\", &result); EXPECT_EQ("\"foo bar\\\\\\\"'$@d!st!c'\\path'\\\\\"", result); - result.clear(); + result.clear(); GetShellEscapedString("foo bar\"/'$@d!st!c'/path'", &result); EXPECT_EQ("'foo bar\"/'\\''$@d!st!c'\\''/path'\\'''", result); diff --git a/src/version.cc b/src/version.cc index 1406d91..4c2aea1 100644 --- a/src/version.cc +++ b/src/version.cc @@ -18,7 +18,7 @@ #include "util.h" -const char* kNinjaVersion = "1.4.0.git"; +const char* kNinjaVersion = "1.6.0.git"; void ParseVersion(const string& version, int* major, int* minor) { size_t end = version.find('.'); |