| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "graph.h" |
| |
| #include <deque> |
| #include <unordered_set> |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include "build_log.h" |
| #include "debug_flags.h" |
| #include "depfile_parser.h" |
| #include "deps_log.h" |
| #include "disk_interface.h" |
| #include "metrics.h" |
| #include "parallel_map.h" |
| #include "state.h" |
| #include "util.h" |
| |
| bool Node::PrecomputeStat(DiskInterface* disk_interface, std::string* err) { |
| if (in_edge() != nullptr) { |
| if (in_edge()->IsPhonyOutput()) { |
| return true; |
| } |
| return (precomputed_mtime_ = disk_interface->LStat(globalPath().h.data(), nullptr, nullptr, err)) != -1; |
| } else { |
| return (precomputed_mtime_ = disk_interface->Stat(globalPath().h.data(), err)) != -1; |
| } |
| } |
| |
| bool Node::Stat(DiskInterface* disk_interface, string* err) { |
| if (in_edge() != nullptr) { |
| assert(!in_edge()->IsPhonyOutput()); |
| return (mtime_ = disk_interface->LStat(globalPath().h.data(), nullptr, nullptr, err)) != -1; |
| } else { |
| return (mtime_ = disk_interface->Stat(globalPath().h.data(), err)) != -1; |
| } |
| } |
| |
| void Node::UpdatePhonyMtime(TimeStamp mtime) { |
| if (!exists()) { |
| mtime_ = std::max(mtime_, mtime); |
| } |
| } |
| |
| bool Node::LStat( |
| DiskInterface* disk_interface, bool* is_dir, bool* is_symlink, string* err) { |
| assert(in_edge() != nullptr); |
| assert(!in_edge()->IsPhonyOutput()); |
| return (mtime_ = disk_interface->LStat(globalPath().h.data(), is_dir, is_symlink, err)) != -1; |
| } |
| |
| bool DependencyScan::RecomputeNodesDirty(const std::vector<Node*>& initial_nodes, |
| std::vector<Node*>* validation_nodes, |
| std::string* err) { |
| METRIC_RECORD("dep scan"); |
| std::vector<Node*> all_nodes; |
| std::vector<Edge*> all_edges; |
| std::unique_ptr<ThreadPool> thread_pool = CreateThreadPool(); |
| |
| { |
| METRIC_RECORD("dep scan : collect nodes+edges"); |
| for (Node* node : initial_nodes) |
| CollectPrecomputeLists(node, &all_nodes, &all_edges); |
| } |
| |
| bool success = true; |
| if (!PrecomputeNodesDirty(all_nodes, all_edges, thread_pool.get(), err)) |
| success = false; |
| |
| |
| std::deque<Node*> nodes(initial_nodes.begin(), initial_nodes.end()); |
| |
| if (success) { |
| METRIC_RECORD("dep scan : main pass"); |
| std::vector<Node*> stack; |
| std::vector<Node*> new_validation_nodes; |
| while (!nodes.empty()) { |
| Node* node = nodes.front(); |
| nodes.pop_front(); |
| |
| stack.clear(); |
| new_validation_nodes.clear(); |
| if (!RecomputeNodeDirty(node, &stack, &new_validation_nodes, err)) { |
| success = false; |
| break; |
| } |
| nodes.insert(nodes.end(), new_validation_nodes.begin(), |
| new_validation_nodes.end()); |
| if (!new_validation_nodes.empty()) { |
| assert(validation_nodes && |
| "validations require RecomputeDirty to be called with validation_nodes"); |
| validation_nodes->insert(validation_nodes->end(), |
| new_validation_nodes.begin(), |
| new_validation_nodes.end()); |
| } |
| } |
| } |
| |
| { |
| // Ensure that the precomputed mtime information can't be used after this |
| // dependency scan finishes. |
| METRIC_RECORD("dep scan : clear pre-stat"); |
| ParallelMap(thread_pool.get(), all_nodes, [](Node* node) { |
| node->ClearPrecomputedStat(); |
| }); |
| } |
| |
| return success; |
| } |
| |
| void DependencyScan::CollectPrecomputeLists(Node* node, |
| std::vector<Node*>* nodes, |
| std::vector<Edge*>* edges) { |
| if (node->precomputed_dirtiness()) |
| return; |
| node->set_precomputed_dirtiness(true); |
| nodes->push_back(node); |
| |
| Edge* edge = node->in_edge(); |
| if (edge && !edge->precomputed_dirtiness_) { |
| edge->precomputed_dirtiness_ = true; |
| edges->push_back(edge); |
| |
| for (Node* node : edge->inputs_) { |
| // Duplicate the dirtiness check here to avoid an unnecessary function |
| // call. (The precomputed_dirtiness() will be inlined, but the recursive |
| // call can't be.) |
| if (!node->precomputed_dirtiness()) |
| CollectPrecomputeLists(node, nodes, edges); |
| } |
| |
| if (!edge->validations_.empty()) { |
| for (Node* node : edge->validations_) { |
| // Duplicate the dirtiness check here to avoid an unnecessary function |
| // call. (The precomputed_dirtiness() will be inlined, but the recursive |
| // call can't be.) |
| if (!node->precomputed_dirtiness()) |
| CollectPrecomputeLists(node, nodes, edges); |
| } |
| } |
| } |
| |
| // Collect dependencies from the deps log. This pass could also examine |
| // depfiles, but it would be a more intrusive design change, because we don't |
| // want to parse a depfile twice. |
| if (DepsLog::Deps* deps = deps_log()->GetDeps(node)) { |
| for (int i = 0; i < deps->node_count; ++i) { |
| Node* node = deps->nodes[i]; |
| // Duplicate the dirtiness check here to avoid an unnecessary function |
| // call. |
| if (!node->precomputed_dirtiness()) { |
| CollectPrecomputeLists(node, nodes, edges); |
| } |
| } |
| } |
| } |
| |
| bool DependencyScan::PrecomputeNodesDirty(const std::vector<Node*>& nodes, |
| const std::vector<Edge*>& edges, |
| ThreadPool* thread_pool, |
| std::string* err) { |
| // Optimize the "null build" case by calling Stat in parallel on every node in |
| // the transitive closure. |
| // |
| // The Windows RealDiskInterface::Stat uses a directory-based cache that isn't |
| // thread-safe. Various tests also uses a non-thread-safe Stat, so disable the |
| // parallelized stat'ing for them as well. |
| if (disk_interface_->IsStatThreadSafe() && |
| GetOptimalThreadPoolJobCount() > 1) { |
| METRIC_RECORD("dep scan : pre-stat nodes"); |
| if (!PropagateError(err, ParallelMap(thread_pool, nodes, |
| [this](Node* node) { |
| // Each node is guaranteed to appear at most once in the collected list |
| // of nodes, so it's safe to modify the nodes from worker threads. |
| std::string err; |
| node->PrecomputeStat(disk_interface_, &err); |
| return err; |
| }))) { |
| return false; |
| } |
| } |
| |
| { |
| METRIC_RECORD("dep scan : precompute edge info"); |
| if (!PropagateError(err, ParallelMap(thread_pool, edges, [](Edge* edge) { |
| // As with the node list, each edge appears at most once in the |
| // collected list, so it's safe to modify the edges from worker threads. |
| std::string err; |
| edge->PrecomputeDepScanInfo(&err); |
| return err; |
| }))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DependencyScan::RecomputeNodeDirty(Node* node, std::vector<Node*>* stack, |
| std::vector<Node*>* validation_nodes, |
| std::string* err) { |
| Edge* edge = node->in_edge(); |
| if (!edge) { |
| // If we already visited this leaf node then we are done. |
| if (node->status_known()) |
| return true; |
| // This node has no in-edge; it is dirty if it is missing. |
| if (!node->StatIfNecessary(disk_interface_, err)) |
| return false; |
| if (!node->exists()) |
| EXPLAIN("%s has no in-edge and is missing", node->globalPath().h.data()); |
| node->set_dirty(!node->exists()); |
| return true; |
| } |
| |
| // If we already finished this edge then we are done. |
| if (edge->mark_ == Edge::VisitDone) |
| return true; |
| |
| // If we encountered this edge earlier in the call stack we have a cycle. |
| if (!VerifyDAG(node, stack, err)) |
| return false; |
| |
| // Mark the edge temporarily while in the call stack. |
| edge->mark_ = Edge::VisitInStack; |
| stack->push_back(node); |
| |
| bool dirty = false; |
| bool phony_output = edge->IsPhonyOutput(); |
| edge->outputs_ready_ = true; |
| edge->deps_missing_ = false; |
| |
| if (phony_output) { |
| EXPLAIN("edge with output %s is a phony output, so is always dirty", |
| node->globalPath().h.data()); |
| dirty = true; |
| |
| if (edge->UsesDepsLog() || edge->UsesDepfile()) { |
| *err = "phony output " + node->globalPath().h.str_view().AsString() + |
| " has deps, which does not make sense."; |
| return false; |
| } |
| } else { |
| if (!edge->deps_loaded_) { |
| // This is our first encounter with this edge. |
| // If there is a pending dyndep file, visit it now: |
| // * If the dyndep file is ready then load it now to get any |
| // additional inputs and outputs for this and other edges. |
| // Once the dyndep file is loaded it will no longer be pending |
| // if any other edges encounter it, but they will already have |
| // been updated. |
| // * If the dyndep file is not ready then since is known to be an |
| // input to this edge, the edge will not be considered ready below. |
| // Later during the build the dyndep file will become ready and be |
| // loaded to update this edge before it can possibly be scheduled. |
| if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) { |
| if (!RecomputeDirty(edge->dyndep_, stack, err)) |
| return false; |
| |
| if (!edge->dyndep_->in_edge() || |
| edge->dyndep_->in_edge()->outputs_ready()) { |
| // The dyndep file is ready, so load it now. |
| if (!LoadDyndeps(edge->dyndep_, err)) |
| return false; |
| } |
| } |
| } |
| |
| // Load output mtimes so we can compare them to the most recent input below. |
| for (vector<Node*>::iterator o = edge->outputs_.begin(); |
| o != edge->outputs_.end(); ++o) { |
| if (!(*o)->StatIfNecessary(disk_interface_, err)) |
| return false; |
| } |
| |
| if (!edge->deps_loaded_) { |
| // This is our first encounter with this edge. Load discovered deps. |
| edge->deps_loaded_ = true; |
| 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; |
| } |
| } |
| } |
| |
| // Store any validation nodes from the edge for adding to the initial |
| // nodes. Don't recurse into them, that would trigger the dependency |
| // cycle detector if the validation node depends on this node. |
| // RecomputeNodesDirty will add the validation nodes to the initial nodes |
| // and recurse into them. |
| validation_nodes->insert(validation_nodes->end(), |
| edge->validations_.begin(), edge->validations_.end()); |
| |
| // Visit all inputs; we're dirty if any of the inputs are dirty. |
| Node* most_recent_input = NULL; |
| for (vector<Node*>::iterator i = edge->inputs_.begin(); |
| i != edge->inputs_.end(); ++i) { |
| // Visit this input. |
| if (!RecomputeNodeDirty(*i, stack, validation_nodes, err)) |
| return false; |
| |
| // If an input is not ready, neither are our outputs. |
| Edge* in_edge = (*i)->in_edge(); |
| if (in_edge != nullptr) { |
| if (!in_edge->outputs_ready_) |
| edge->outputs_ready_ = false; |
| } |
| |
| if (!phony_output && !edge->is_order_only(i - edge->inputs_.begin())) { |
| if (in_edge != nullptr && in_edge->IsPhonyOutput()) { |
| *err = "real file '" + node->globalPath().h.str_view().AsString() + |
| "' depends on phony output '" + (*i)->globalPath().h.str_view().AsString() + "'\n"; |
| return false; |
| } |
| |
| // If a regular input is dirty (or missing), we're dirty. |
| // Otherwise consider mtime. |
| if ((*i)->dirty()) { |
| EXPLAIN("%s is dirty", (*i)->globalPath().h.data()); |
| dirty = true; |
| } else { |
| if (!most_recent_input || (*i)->mtime() > most_recent_input->mtime()) { |
| most_recent_input = *i; |
| } |
| } |
| } |
| } |
| |
| // 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) |
| if (!RecomputeOutputsDirty(edge, most_recent_input, &dirty, err)) |
| return false; |
| |
| // 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) |
| (*o)->MarkDirty(); |
| } |
| |
| // If an edge is dirty, its outputs are normally not ready. (It's |
| // possible to be clean but still not be ready in the presence of |
| // order-only inputs.) |
| // But phony edges with no inputs have nothing to do, so are always |
| // ready. |
| if (dirty && !(edge->is_phony() && edge->inputs_.empty())) |
| edge->outputs_ready_ = false; |
| |
| // Mark the edge as finished during this walk now that it will no longer |
| // be in the call stack. |
| edge->mark_ = Edge::VisitDone; |
| assert(stack->back() == node); |
| stack->pop_back(); |
| |
| return true; |
| } |
| |
| bool DependencyScan::VerifyDAG(Node* node, vector<Node*>* stack, string* err) { |
| Edge* edge = node->in_edge(); |
| assert(edge != NULL); |
| |
| // If we have no temporary mark on the edge then we do not yet have a cycle. |
| if (edge->mark_ != Edge::VisitInStack) |
| return true; |
| |
| // We have this edge earlier in the call stack. Find it. |
| vector<Node*>::iterator start = stack->begin(); |
| while (start != stack->end() && (*start)->in_edge() != edge) |
| ++start; |
| assert(start != stack->end()); |
| |
| // Make the cycle clear by reporting its start as the node at its end |
| // instead of some other output of the starting edge. For example, |
| // running 'ninja b' on |
| // build a b: cat c |
| // build c: cat a |
| // should report a -> c -> a instead of b -> c -> a. |
| *start = node; |
| |
| // Construct the error message rejecting the cycle. |
| *err = "dependency cycle: "; |
| for (vector<Node*>::const_iterator i = start; i != stack->end(); ++i) { |
| err->append((*i)->globalPath().h.data()); |
| err->append(" -> "); |
| } |
| err->append((*start)->globalPath().h.data()); |
| |
| if ((start + 1) == stack->end() && edge->maybe_phonycycle_diagnostic()) { |
| // The manifest parser would have filtered out the self-referencing |
| // input if it were not configured to allow the error. |
| err->append(" [-w phonycycle=err]"); |
| } |
| |
| return false; |
| } |
| |
| bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input, |
| bool* outputs_dirty, string* err) { |
| assert(!edge->IsPhonyOutput()); |
| |
| uint64_t command_hash = edge->GetCommandHash(); |
| for (vector<Node*>::iterator o = edge->outputs_.begin(); |
| o != edge->outputs_.end(); ++o) { |
| 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. |
| if (edge->inputs_.empty() && !(*o)->exists()) { |
| // For phony targets defined in the ninja file, error when using dirty phony edges. |
| // The phony edges automatically created from depfiles still need the old behavior. |
| if (missing_phony_is_err_ && !edge->phony_from_depfile_) { |
| *err = "output " + (*o)->globalPath().h.str_view().AsString() + |
| " of phony edge doesn't exist. Missing 'phony_output = true'?"; |
| return false; |
| } else { |
| EXPLAIN("output %s of phony edge with no inputs doesn't exist", |
| (*o)->globalPath().h.data()); |
| *outputs_dirty = true; |
| return true; |
| } |
| } |
| |
| // Update the mtime with the newest input. Dependents can thus call mtime() |
| // on the fake node and get the latest mtime of the dependencies |
| if (most_recent_input) { |
| (*o)->UpdatePhonyMtime(most_recent_input->mtime()); |
| } |
| continue; |
| } |
| if (RecomputeOutputDirty(edge, most_recent_input, command_hash, *o)) { |
| *outputs_dirty = true; |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| bool DependencyScan::RecomputeOutputDirty(Edge* edge, |
| Node* most_recent_input, |
| uint64_t command_hash, |
| Node* output) { |
| assert(!edge->is_phony()); |
| |
| BuildLog::LogEntry* entry = 0; |
| |
| // Dirty if we're missing the output. |
| if (!output->exists()) { |
| EXPLAIN("output %s doesn't exist", output->globalPath().h.data()); |
| return true; |
| } |
| |
| // Dirty if the output is older than the input. |
| if (most_recent_input && output->mtime() < most_recent_input->mtime()) { |
| TimeStamp output_mtime = output->mtime(); |
| |
| // If this is a restat rule, we may have cleaned the output with a restat |
| // rule in a previous run and stored the most recent input mtime in the |
| // build log. Use that mtime instead, so that the file will only be |
| // considered dirty if an input was modified since the previous run. |
| bool used_restat = false; |
| if (edge->IsRestat() && build_log() && |
| (entry = build_log()->LookupByOutput(output->globalPath()))) { |
| output_mtime = entry->mtime; |
| used_restat = true; |
| output->set_restat_mtime(output_mtime); |
| } |
| |
| if (output_mtime < most_recent_input->mtime()) { |
| EXPLAIN("%soutput %s older than most recent input %s " |
| "(%" PRId64 " vs %" PRId64 ")", |
| used_restat ? "restat of " : "", output->globalPath().h.data(), |
| most_recent_input->globalPath().h.data(), |
| output_mtime, most_recent_input->mtime()); |
| return true; |
| } |
| } |
| |
| if (build_log()) { |
| bool generator = edge->IsGenerator(); |
| if (entry || (entry = build_log()->LookupByOutput(output->globalPath()))) { |
| if (!generator && |
| command_hash != entry->command_hash) { |
| // May also be dirty due to the command changing since the last build. |
| // But if this is a generator rule, the command changing does not make us |
| // dirty. |
| EXPLAIN("command line changed for %s", output->globalPath().h.data()); |
| return true; |
| } |
| if (most_recent_input && entry->mtime < most_recent_input->mtime()) { |
| // May also be dirty due to the mtime in the log being older than the |
| // mtime of the most recent input. This can occur even when the mtime |
| // on disk is newer if a previous run wrote to the output file but |
| // exited with an error or was interrupted. |
| EXPLAIN("recorded mtime of %s older than most recent input %s (%" PRId64 " vs %" PRId64 ")", |
| output->globalPath().h.data(), most_recent_input->globalPath().h.data(), |
| entry->mtime, most_recent_input->mtime()); |
| return true; |
| } |
| } |
| if (!entry && !generator) { |
| EXPLAIN("command line not found in log for %s", output->globalPath().h.data()); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DependencyScan::LoadDyndeps(Node* node, string* err) const { |
| return dyndep_loader_.LoadDyndeps(node, err); |
| } |
| |
| bool DependencyScan::LoadDyndeps(Node* node, DyndepFile* ddf, |
| string* err) const { |
| return dyndep_loader_.LoadDyndeps(node, ddf, err); |
| } |
| |
| bool Edge::AllInputsReady() const { |
| for (vector<Node*>::const_iterator i = inputs_.begin(); |
| i != inputs_.end(); ++i) { |
| if ((*i)->in_edge() && !(*i)->in_edge()->outputs_ready()) |
| return false; |
| } |
| return true; |
| } |
| |
| static const HashedStrView kIn { "in" }; |
| static const HashedStrView kInNewline { "in_newline" }; |
| static const HashedStrView kOut { "out" }; |
| |
| bool EdgeEval::EvaluateVariable(std::string* out_append, |
| const HashedStrView& var, |
| Scope* target, |
| std::string* err) { |
| if (var == kIn || var == kInNewline) { |
| int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ - |
| edge_->order_only_deps_; |
| AppendPathList(out_append, |
| edge_->inputs_.begin(), |
| edge_->inputs_.begin() + explicit_deps_count, |
| var == kIn ? ' ' : '\n', target); |
| return true; |
| } else if (var == kOut) { |
| int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_; |
| AppendPathList(out_append, |
| edge_->outputs_.begin(), |
| edge_->outputs_.begin() + explicit_outs_count, |
| ' ', target); |
| return true; |
| } |
| |
| if (edge_->EvaluateVariableSelfOnly(out_append, var)) |
| return true; |
| |
| // Search for a matching rule binding. |
| if (const std::string* binding_pattern = edge_->rule().GetBinding(var)) { |
| // Detect recursive rule variable usage. |
| if (recursion_count_ == kEvalRecursionLimit) { |
| std::string cycle = recursion_vars_[0].AsString(); |
| for (int i = 1; i < kEvalRecursionLimit; ++i) { |
| cycle += " -> " + recursion_vars_[i].AsString(); |
| if (recursion_vars_[i] == recursion_vars_[0]) |
| break; |
| } |
| *err = "cycle in rule variables: " + cycle; |
| return false; |
| } |
| recursion_vars_[recursion_count_++] = var.str_view(); |
| |
| return EvaluateBindingOnRule(out_append, *binding_pattern, this, target, err); |
| } |
| |
| // Fall back to the edge's enclosing scope. |
| if (eval_phase_ == EdgeEval::kParseTime) { |
| Scope::EvaluateVariableAtPos(out_append, var, edge_->pos_.scope_pos()); |
| } else { |
| Scope::EvaluateVariable(out_append, var, edge_->pos_.scope()); |
| } |
| return true; |
| } |
| |
| void EdgeEval::AppendPathList(std::string* out_append, |
| std::vector<Node*>::iterator begin, |
| std::vector<Node*>::iterator end, |
| char sep, Scope* target) { |
| for (auto it = begin; it != end; ++it) { |
| if (it != begin) |
| out_append->push_back(sep); |
| |
| string path = (*it)->PathDecanonicalized(target); |
| if (escape_in_out_ == kShellEscape) { |
| #if _WIN32 |
| GetWin32EscapedString(path, out_append); |
| #else |
| GetShellEscapedString(path, out_append); |
| #endif |
| } else { |
| out_append->append(path); |
| } |
| } |
| } |
| |
| static const HashedStrView kCommand { "command" }; |
| static const HashedStrView kDepfile { "depfile" }; |
| static const HashedStrView kDyndep { "dyndep" }; |
| static const HashedStrView kRspfile { "rspfile" }; |
| static const HashedStrView kRspFileContent { "rspfile_content" }; |
| |
| bool Edge::EvaluateCommand(std::string* out_append, bool incl_rsp_file, |
| std::string* err) { |
| METRIC_RECORD("eval command"); |
| auto len_pre_chdir = out_append->size(); |
| if (!pos_.scope()->chdir().empty()) { |
| #ifdef _WIN32 |
| out_append->append(NINJA_WIN32_CD_DELIM); |
| out_append->append(pos_.scope()->chdir()); |
| out_append->append(NINJA_WIN32_CD_DELIM); |
| #else |
| out_append->append("cd \""); |
| out_append->append(pos_.scope()->chdir()); |
| out_append->append("\" && "); |
| #endif |
| } |
| auto len_post_chdir = out_append->size(); |
| if (!EvaluateVariable(out_append, kCommand, pos_.scope(), err)) |
| return false; |
| // out_append has the chdir in it, waiting, but the correct output |
| // is the empty string if EvaluateVariable() produced the empty string. |
| // In other words, clean up chdir if it's the only thing in out_append. |
| if (!pos_.scope()->chdir().empty() && out_append->size() == len_post_chdir) { |
| out_append->resize(len_pre_chdir); |
| } |
| |
| if (incl_rsp_file) { |
| std::string rspfile_content; |
| if (!EvaluateVariable(&rspfile_content, kRspFileContent, pos_.scope(), err)) |
| return false; |
| if (!rspfile_content.empty()) { |
| out_append->append(";rspfile="); |
| out_append->append(rspfile_content); |
| } |
| } |
| return true; |
| } |
| |
| void Edge::EvaluateCommand(EdgeCommand* out, bool incl_rsp_file) { |
| std::string err; |
| if (!EvaluateCommand(&out->command, incl_rsp_file, &err)) |
| Fatal("%s", err.c_str()); |
| out->use_console = use_console(); |
| out->env = cmdEnviron; |
| } |
| |
| static const HashedStrView kRestat { "restat" }; |
| static const HashedStrView kGenerator { "generator" }; |
| static const HashedStrView kDeps { "deps" }; |
| static const HashedStrView kPhonyOutput { "phony_output" }; |
| |
| bool Edge::PrecomputeDepScanInfo(std::string* err) { |
| if (dep_scan_info_.valid) |
| return true; |
| |
| // Precompute boolean flags. |
| auto get_bool_var = [this, err](const HashedStrView& var, |
| EdgeEval::EscapeKind escape, bool* out) { |
| std::string value; |
| if (!EvaluateVariable(&value, var, pos_.scope(), err, EdgeEval::kFinalScope, escape)) |
| return false; |
| *out = !value.empty(); |
| return true; |
| }; |
| if (!get_bool_var(kRestat, EdgeEval::kShellEscape, &dep_scan_info_.restat)) return false; |
| if (!get_bool_var(kGenerator, EdgeEval::kShellEscape, &dep_scan_info_.generator)) return false; |
| if (!get_bool_var(kDeps, EdgeEval::kShellEscape, &dep_scan_info_.deps)) return false; |
| if (!get_bool_var(kDepfile, EdgeEval::kDoNotEscape, &dep_scan_info_.depfile)) return false; |
| if (!get_bool_var(kPhonyOutput, EdgeEval::kShellEscape, &dep_scan_info_.phony_output)) return false; |
| |
| // Precompute the command hash. |
| std::string command; |
| if (!EvaluateCommand(&command, /*incl_rsp_file=*/true, err)) |
| return false; |
| dep_scan_info_.command_hash = BuildLog::LogEntry::HashCommand(command); |
| |
| dep_scan_info_.valid = true; |
| return true; |
| } |
| |
| /// Returns dependency-scanning info or exits with a fatal error. |
| const Edge::DepScanInfo& Edge::ComputeDepScanInfo() { |
| std::string err; |
| if (!PrecomputeDepScanInfo(&err)) |
| Fatal("%s", err.c_str()); |
| return dep_scan_info_; |
| } |
| |
| void Edge::SetRestat() { |
| std::string err; |
| if (!PrecomputeDepScanInfo(&err)) |
| Fatal("%s", err.c_str()); |
| dep_scan_info_.restat = true; |
| } |
| |
| bool Edge::EvaluateVariable(std::string* out_append, const HashedStrView& key, |
| Scope* target, std::string* err, |
| EdgeEval::EvalPhase phase, |
| EdgeEval::EscapeKind escape) { |
| EdgeEval eval(this, phase, escape); |
| return eval.EvaluateVariable(out_append, key, target, err); |
| } |
| |
| std::string Edge::GetBindingImpl(const HashedStrView& key, |
| EdgeEval::EvalPhase phase, |
| EdgeEval::EscapeKind escape) { |
| std::string result; |
| std::string err; |
| if (!EvaluateVariable(&result, key, pos_.scope(), &err, phase, escape)) |
| Fatal("%s", err.c_str()); |
| return result; |
| } |
| |
| std::string Edge::GetBinding(const HashedStrView& key) { |
| return GetBindingImpl(key, EdgeEval::kFinalScope, EdgeEval::kShellEscape); |
| } |
| |
| std::string Edge::GetUnescapedDepfile() { |
| return GetBindingImpl(kDepfile, EdgeEval::kFinalScope, EdgeEval::kDoNotEscape); |
| } |
| |
| std::string Edge::GetUnescapedDyndep() { |
| return GetBindingImpl(kDyndep, EdgeEval::kFinalScope, EdgeEval::kDoNotEscape); |
| } |
| |
| std::string Edge::GetUnescapedRspfile() { |
| return GetBindingImpl(kRspfile, EdgeEval::kFinalScope, EdgeEval::kDoNotEscape); |
| } |
| |
| void Edge::Dump(const char* prefix) const { |
| printf("%s[ ", prefix); |
| for (vector<Node*>::const_iterator i = inputs_.begin(); |
| i != inputs_.end() && *i != NULL; ++i) { |
| printf("%s ", (*i)->path().c_str()); |
| } |
| printf("--%s-> ", rule_->name().c_str()); |
| for (vector<Node*>::const_iterator i = outputs_.begin(); |
| i != outputs_.end() && *i != NULL; ++i) { |
| printf("%s ", (*i)->path().c_str()); |
| } |
| if (!validations_.empty()) { |
| printf(" validations "); |
| for (vector<Node*>::const_iterator i = validations_.begin(); |
| i != validations_.end() && *i != NULL; ++i) { |
| printf("%s ", (*i)->path().c_str()); |
| } |
| } |
| if (pool_) { |
| if (!pool_->name().empty()) { |
| printf("(in pool '%s')", pool_->name().c_str()); |
| } |
| } else { |
| printf("(null pool?)"); |
| } |
| printf("] 0x%p\n", this); |
| } |
| |
| bool Edge::is_phony() const { |
| return rule_ == &State::kPhonyRule; |
| } |
| |
| bool Edge::use_console() const { |
| return pool() == &State::kConsolePool; |
| } |
| |
| bool Edge::maybe_phonycycle_diagnostic() const { |
| // CMake 2.8.12.x and 3.0.x produced self-referencing phony rules |
| // of the form "build a: phony ... a ...". Restrict our |
| // "phonycycle" diagnostic option to the form it used. |
| return is_phony() && outputs_.size() == 1 && implicit_outs_ == 0 && |
| implicit_deps_ == 0 && order_only_deps_ == 0; |
| } |
| |
| bool Edge::EvaluateVariableSelfOnly(std::string* out_append, |
| const HashedStrView& var) const { |
| // ninja allows declaring the same binding repeatedly on an edge. Use the |
| // last matching binding. |
| const auto it_end = unevaled_bindings_.rend(); |
| for (auto it = unevaled_bindings_.rbegin(); it != it_end; ++it) { |
| if (var == it->first) { |
| EvaluateBindingInScope(out_append, it->second, pos_.scope_pos()); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // static |
| string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) { |
| string result = path; |
| #ifdef _WIN32 |
| uint64_t mask = 1; |
| for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) { |
| if (slash_bits & mask) |
| *c = '\\'; |
| c++; |
| mask <<= 1; |
| } |
| #endif |
| return result; |
| } |
| |
| Node::~Node() { |
| EdgeList* node = out_edges_.load(); |
| while (node != nullptr) { |
| EdgeList* next = node->next; |
| delete node; |
| node = next; |
| } |
| } |
| |
| // Does the node have at least one out edge? |
| bool Node::has_out_edge() const { |
| return out_edges_.load() != nullptr; |
| } |
| |
| std::vector<Edge*> Node::GetOutEdges() const { |
| // Include out-edges from the manifest. |
| std::vector<Edge*> result; |
| for (EdgeList* node = out_edges_.load(); node != nullptr; node = node->next) { |
| result.push_back(node->edge); |
| } |
| std::sort(result.begin(), result.end(), EdgeCmp()); |
| |
| // Add extra out-edges from depfiles and the deps log. Preserve the order |
| // of these extra edges; don't sort them. |
| std::copy(dep_scan_out_edges_.begin(), dep_scan_out_edges_.end(), |
| std::back_inserter(result)); |
| |
| return result; |
| } |
| |
| std::vector<Edge*> Node::GetValidationOutEdges() const { |
| std::vector<Edge*> result; |
| for (EdgeList* node = validation_out_edges_.load(); node != nullptr; node = node->next) { |
| result.push_back(node->edge); |
| } |
| std::sort(result.begin(), result.end(), EdgeCmp()); |
| |
| return result; |
| } |
| |
| std::vector<Edge*> Node::GetAllOutEdges() const { |
| std::vector<Edge*> result = this->GetOutEdges(); |
| std::vector<Edge*> extra = this->GetValidationOutEdges(); |
| if (!extra.empty()) { |
| result.insert(result.end(), extra.begin(), extra.end()); |
| } |
| return result; |
| } |
| |
| void Node::AddOutEdge(Edge* edge) { |
| EdgeList* new_node = new EdgeList { edge }; |
| while (true) { |
| EdgeList* cur_head = out_edges_.load(); |
| new_node->next = cur_head; |
| if (out_edges_.compare_exchange_weak(cur_head, new_node)) |
| break; |
| } |
| } |
| |
| void Node::AddValidationOutEdge(Edge* edge) { |
| EdgeList* new_node = new EdgeList { edge }; |
| while (true) { |
| EdgeList* cur_head = validation_out_edges_.load(); |
| new_node->next = cur_head; |
| if (validation_out_edges_.compare_exchange_weak(cur_head, new_node)) |
| break; |
| } |
| } |
| |
| void Node::Dump(const char* prefix) const { |
| printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ", |
| prefix, path().c_str(), this, |
| mtime(), mtime() ? "" : " (:missing)", |
| dirty() ? " dirty" : " clean"); |
| if (in_edge()) { |
| in_edge()->Dump("in-edge: "); |
| } else { |
| printf("no in-edge\n"); |
| } |
| printf(" out edges:\n"); |
| const std::vector<Edge*> out_edges = GetOutEdges(); |
| for (vector<Edge*>::const_iterator e = out_edges.begin(); |
| e != out_edges.end() && *e != NULL; ++e) { |
| (*e)->Dump(" +- "); |
| } |
| const std::vector<Edge*> validation_out_edges = GetValidationOutEdges(); |
| if (!validation_out_edges.empty()) { |
| printf(" validation out edges:\n"); |
| for (vector<Edge*>::const_iterator e = validation_out_edges.begin(); |
| e != validation_out_edges.end() && *e != NULL; ++e) { |
| (*e)->Dump(" +- "); |
| } |
| } |
| } |
| |
| GlobalPathStr Node::globalPath() { |
| return scope_->GlobalPath(path_); |
| } |
| |
| static void GetDependencyPathsDfs(Node* node, Node* out, |
| DepPath& path_till_here, |
| std::unordered_set<Node*>& dead_ends, |
| std::vector<DepPath>& output) { |
| if (node == out) { |
| path_till_here.push_back(node); |
| output.push_back(path_till_here); |
| path_till_here.pop_back(); |
| return; |
| } |
| |
| if (dead_ends.find(node) != dead_ends.end()) |
| return; |
| const size_t result_count = output.size(); |
| |
| path_till_here.push_back(node); |
| node->set_dirty(true); |
| for (Edge* edge : node->GetAllOutEdges()) { |
| for (Node* next : edge->outputs_) { |
| if (!next->dirty()) { |
| GetDependencyPathsDfs(next, out, path_till_here, dead_ends, output); |
| } |
| } |
| } |
| node->set_dirty(false); |
| path_till_here.pop_back(); |
| |
| if (output.size() == result_count) { |
| // There are no paths from `node` to `out`, so skip `node` for the rest of |
| // the search. |
| dead_ends.insert(node); |
| } |
| } |
| |
| std::vector<DepPath> GetDependencyPaths(Node* in, Node* out) { |
| assert(in != nullptr && out != nullptr); |
| std::unordered_set<Node*> dead_ends; |
| std::vector<DepPath> result; |
| DepPath path_till_here; |
| GetDependencyPathsDfs(in, out, path_till_here, dead_ends, result); |
| return result; |
| } |
| |
| bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) { |
| if (edge->UsesDepsLog()) |
| return LoadDepsFromLog(edge, err); |
| |
| if (edge->UsesDepfile()) { |
| std::string depfile = edge->GetUnescapedDepfile(); |
| assert(!depfile.empty() && |
| "UsesDepfile was set, so the depfile should be non-empty"); |
| return LoadDepFile(edge, depfile, err); |
| } |
| |
| // No deps to load. |
| return true; |
| } |
| |
| bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path, |
| string* err) { |
| METRIC_RECORD("depfile load"); |
| // Read depfile content. Treat a missing depfile as empty. |
| string content; |
| switch (disk_interface_->ReadFile(path, &content, err)) { |
| case DiskInterface::Okay: |
| break; |
| case DiskInterface::NotFound: |
| err->clear(); |
| break; |
| case DiskInterface::OtherError: |
| *err = "loading '" + path + "': " + *err; |
| return false; |
| } |
| // On a missing depfile: return false and empty *err. |
| if (content.empty()) { |
| EXPLAIN("depfile '%s' is missing", path.c_str()); |
| return false; |
| } |
| |
| DepfileParser depfile(depfile_parser_options_ |
| ? *depfile_parser_options_ |
| : DepfileParserOptions()); |
| string depfile_err; |
| string depfile_warn; |
| if (!depfile.Parse(&content, &depfile_warn, &depfile_err)) { |
| *err = path + ": " + depfile_err; |
| return false; |
| } |
| |
| if (!depfile_warn.empty()) { |
| Warning("%s: %s", path.c_str(), depfile_warn.c_str()); |
| } |
| |
| uint64_t unused; |
| if (!CanonicalizePath(const_cast<char*>(depfile.out_.str_), |
| &depfile.out_.len_, &unused, err)) { |
| *err = path + ": " + *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]; |
| if (first_output->path() != depfile.out_) { |
| EXPLAIN("expected depfile '%s' to mention '%s', got '%s'", path.c_str(), |
| first_output->path().c_str(), depfile.out_.AsString().c_str()); |
| return false; |
| } |
| |
| // Preallocate space in edge->inputs_ to be filled in below. |
| vector<Node*>::iterator implicit_dep = |
| PreallocateSpace(edge, depfile.ins_.size()); |
| |
| // Add all its in-edges. |
| for (vector<StringPiece>::iterator i = depfile.ins_.begin(); |
| i != depfile.ins_.end(); ++i, ++implicit_dep) { |
| uint64_t slash_bits; |
| if (!CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits, |
| err)) |
| return false; |
| |
| Node* node = state_->GetNode(edge->pos_.scope()->GlobalPath(*i), |
| slash_bits); |
| *implicit_dep = node; |
| node->AddOutEdgeDepScan(edge); |
| CreatePhonyInEdge(node); |
| } |
| |
| return true; |
| } |
| |
| bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) { |
| // NOTE: deps are only supported for single-target edges. |
| Node* output = edge->outputs_[0]; |
| DepsLog::Deps* deps = deps_log_->GetDeps(output); |
| if (!deps) { |
| EXPLAIN("deps for '%s' are missing", output->path().c_str()); |
| return false; |
| } |
| |
| // Deps are invalid if the output is newer than the deps. |
| if (output->mtime() > deps->mtime) { |
| EXPLAIN("stored deps info out of date for '%s' (%" PRId64 " vs %" PRId64 ")", |
| output->path().c_str(), deps->mtime, output->mtime()); |
| return false; |
| } |
| |
| vector<Node*>::iterator implicit_dep = |
| PreallocateSpace(edge, deps->node_count); |
| for (int i = 0; i < deps->node_count; ++i, ++implicit_dep) { |
| Node* node = deps->nodes[i]; |
| *implicit_dep = node; |
| node->AddOutEdgeDepScan(edge); |
| CreatePhonyInEdge(node); |
| } |
| return true; |
| } |
| |
| vector<Node*>::iterator ImplicitDepLoader::PreallocateSpace(Edge* edge, |
| int count) { |
| edge->inputs_.insert(edge->inputs_.end() - edge->order_only_deps_, |
| (size_t)count, 0); |
| edge->implicit_deps_ += count; |
| return edge->inputs_.end() - edge->order_only_deps_ - count; |
| } |
| |
| void ImplicitDepLoader::CreatePhonyInEdge(Node* node) { |
| if (node->in_edge()) |
| return; |
| |
| Edge* phony_edge = state_->AddEdge(&State::kPhonyRule); |
| node->set_in_edge(phony_edge); |
| phony_edge->outputs_.push_back(node); |
| ++phony_edge->explicit_outs_; |
| |
| // RecomputeDirty might not be called for phony_edge if a previous call |
| // to RecomputeDirty had caused the file to be stat'ed. Because previous |
| // invocations of RecomputeDirty would have seen this node without an |
| // input edge (and therefore ready), we have to set outputs_ready_ to true |
| // to avoid a potential stuck build. If we do call RecomputeDirty for |
| // this node, it will simply set outputs_ready_ to the correct value. |
| phony_edge->outputs_ready_ = true; |
| |
| phony_edge->phony_from_depfile_ = true; |
| } |