#include "graph.h"
#include <algorithm>
#include <deque>
#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 "manifest_parser.h"
#include "metrics.h"
#include "state.h"
#include "util.h"
using namespace std;
bool Node::Stat(DiskInterface* disk_interface, string* err) {
METRIC_RECORD("node stat");
mtime_ = disk_interface->Stat(path_, err);
if (mtime_ == -1) {
return false;
}
exists_ = (mtime_ != 0) ? ExistenceStatusExists : ExistenceStatusMissing;
return true;
}
void Node::UpdatePhonyMtime(TimeStamp mtime) {
if (!exists()) {
mtime_ = std::max(mtime_, mtime);
}
}
bool DependencyScan::RecomputeDirty(Node* initial_node,
std::vector<Node*>* validation_nodes,
string* err) {
std::vector<Node*> stack;
std::vector<Node*> new_validation_nodes;
std::deque<Node*> nodes(1, initial_node);
while (!nodes.empty()) {
Node* node = nodes.front();
nodes.pop_front();
stack.clear();
new_validation_nodes.clear();
if (!RecomputeNodeDirty(node, &stack, &new_validation_nodes, err))
return false;
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());
}
}
return true;
}
bool DependencyScan::RecomputeNodeDirty(Node* node, std::vector<Node*>* stack,
std::vector<Node*>* validation_nodes,
string* err) {
Edge* edge = node->in_edge();
if (!edge) {
if (node->status_known())
return true;
if (!node->StatIfNecessary(disk_interface_, err))
return false;
if (!node->exists())
EXPLAIN("%s has no in-edge and is missing", node->path().c_str());
node->set_dirty(!node->exists());
return true;
}
if (edge->mark_ == Edge::VisitDone)
return true;
if (!VerifyDAG(node, stack, err))
return false;
edge->mark_ = Edge::VisitInStack;
stack->push_back(node);
bool dirty = false;
edge->outputs_ready_ = true;
edge->deps_missing_ = false;
if (!edge->deps_loaded_) {
if (edge->dyndep_ && edge->dyndep_->dyndep_pending()) {
if (!RecomputeNodeDirty(edge->dyndep_, stack, validation_nodes, err))
return false;
if (!edge->dyndep_->in_edge() ||
edge->dyndep_->in_edge()->outputs_ready()) {
if (!LoadDyndeps(edge->dyndep_, err))
return false;
}
}
}
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_) {
edge->deps_loaded_ = true;
if (!dep_loader_.LoadDeps(edge, err)) {
if (!err->empty())
return false;
dirty = edge->deps_missing_ = true;
}
}
validation_nodes->insert(validation_nodes->end(),
edge->validations_.begin(), edge->validations_.end());
Node* most_recent_input = NULL;
for (vector<Node*>::iterator i = edge->inputs_.begin();
i != edge->inputs_.end(); ++i) {
if (!RecomputeNodeDirty(*i, stack, validation_nodes, err))
return false;
if (Edge* in_edge = (*i)->in_edge()) {
if (!in_edge->outputs_ready_)
edge->outputs_ready_ = false;
}
if (!edge->is_order_only(i - edge->inputs_.begin())) {
if ((*i)->dirty()) {
EXPLAIN("%s is dirty", (*i)->path().c_str());
dirty = true;
} else {
if (!most_recent_input || (*i)->mtime() > most_recent_input->mtime()) {
most_recent_input = *i;
}
}
}
}
if (!dirty)
if (!RecomputeOutputsDirty(edge, most_recent_input, &dirty, err))
return false;
for (vector<Node*>::iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o) {
if (dirty)
(*o)->MarkDirty();
}
if (dirty && !(edge->is_phony() && edge->inputs_.empty()))
edge->outputs_ready_ = false;
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 (edge->mark_ != Edge::VisitInStack)
return true;
vector<Node*>::iterator start = stack->begin();
while (start != stack->end() && (*start)->in_edge() != edge)
++start;
assert(start != stack->end());
*start = node;
*err = "dependency cycle: ";
for (vector<Node*>::const_iterator i = start; i != stack->end(); ++i) {
err->append((*i)->path());
err->append(" -> ");
}
err->append((*start)->path());
if ((start + 1) == stack->end() && edge->maybe_phonycycle_diagnostic()) {
err->append(" [-w phonycycle=err]");
}
return false;
}
bool DependencyScan::RecomputeOutputsDirty(Edge* edge, Node* most_recent_input,
bool* outputs_dirty, string* err) {
string command = edge->EvaluateCommand(true);
for (vector<Node*>::iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o) {
if (RecomputeOutputDirty(edge, most_recent_input, command, *o)) {
*outputs_dirty = true;
return true;
}
}
return true;
}
bool DependencyScan::RecomputeOutputDirty(const Edge* edge,
const Node* most_recent_input,
const string& command,
Node* output) {
if (edge->is_phony()) {
if (edge->inputs_.empty() && !output->exists()) {
EXPLAIN("output %s of phony edge with no inputs doesn't exist",
output->path().c_str());
return true;
}
if (most_recent_input) {
output->UpdatePhonyMtime(most_recent_input->mtime());
}
return false;
}
BuildLog::LogEntry* entry = 0;
if (!output->exists()) {
EXPLAIN("output %s doesn't exist", output->path().c_str());
return true;
}
if (most_recent_input && output->mtime() < most_recent_input->mtime()) {
TimeStamp output_mtime = output->mtime();
bool used_restat = false;
if (edge->GetBindingBool("restat") && build_log() &&
(entry = build_log()->LookupByOutput(output->path()))) {
output_mtime = entry->mtime;
used_restat = true;
}
if (output_mtime < most_recent_input->mtime()) {
EXPLAIN("%soutput %s older than most recent input %s "
"(%" PRId64 " vs %" PRId64 ")",
used_restat ? "restat of " : "", output->path().c_str(),
most_recent_input->path().c_str(),
output_mtime, most_recent_input->mtime());
return true;
}
}
if (build_log()) {
bool generator = edge->GetBindingBool("generator");
if (entry || (entry = build_log()->LookupByOutput(output->path()))) {
if (!generator &&
BuildLog::LogEntry::HashCommand(command) != entry->command_hash) {
EXPLAIN("command line changed for %s", output->path().c_str());
return true;
}
if (most_recent_input && entry->mtime < most_recent_input->mtime()) {
EXPLAIN("recorded mtime of %s older than most recent input %s (%" PRId64 " vs %" PRId64 ")",
output->path().c_str(), most_recent_input->path().c_str(),
entry->mtime, most_recent_input->mtime());
return true;
}
}
if (!entry && !generator) {
EXPLAIN("command line not found in log for %s", output->path().c_str());
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;
}
struct EdgeEnv : public Env {
enum EscapeKind { kShellEscape, kDoNotEscape };
EdgeEnv(const Edge* const edge, const EscapeKind escape)
: edge_(edge), escape_in_out_(escape), recursive_(false) {}
virtual string LookupVariable(const string& var);
std::string MakePathList(const Node* const* span, size_t size, char sep) const;
private:
vector<string> lookups_;
const Edge* const edge_;
EscapeKind escape_in_out_;
bool recursive_;
};
string EdgeEnv::LookupVariable(const string& var) {
if (var == "in" || var == "in_newline") {
int explicit_deps_count = edge_->inputs_.size() - edge_->implicit_deps_ -
edge_->order_only_deps_;
#if __cplusplus >= 201103L
return MakePathList(edge_->inputs_.data(), explicit_deps_count,
#else
return MakePathList(&edge_->inputs_[0], explicit_deps_count,
#endif
var == "in" ? ' ' : '\n');
} else if (var == "out") {
int explicit_outs_count = edge_->outputs_.size() - edge_->implicit_outs_;
return MakePathList(&edge_->outputs_[0], explicit_outs_count, ' ');
}
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());
}
}
const EvalString* eval = edge_->rule_->GetBinding(var);
if (recursive_ && eval)
lookups_.push_back(var);
recursive_ = true;
return edge_->env_->LookupWithFallback(var, eval, this);
}
std::string EdgeEnv::MakePathList(const Node* const* const span,
const size_t size, const char sep) const {
string result;
for (const Node* const* i = span; i != span + size; ++i) {
if (!result.empty())
result.push_back(sep);
const string& path = (*i)->PathDecanonicalized();
if (escape_in_out_ == kShellEscape) {
#ifdef _WIN32
GetWin32EscapedString(path, &result);
#else
GetShellEscapedString(path, &result);
#endif
} else {
result.append(path);
}
}
return result;
}
void Edge::CollectInputs(bool shell_escape,
std::vector<std::string>* out) const {
for (std::vector<Node*>::const_iterator it = inputs_.begin();
it != inputs_.end(); ++it) {
std::string path = (*it)->PathDecanonicalized();
if (shell_escape) {
std::string unescaped;
unescaped.swap(path);
#ifdef _WIN32
GetWin32EscapedString(unescaped, &path);
#else
GetShellEscapedString(unescaped, &path);
#endif
}
#if __cplusplus >= 201103L
out->push_back(std::move(path));
#else
out->push_back(path);
#endif
}
}
std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
string command = GetBinding("command");
if (incl_rsp_file) {
string rspfile_content = GetBinding("rspfile_content");
if (!rspfile_content.empty())
command += ";rspfile=" + rspfile_content;
}
return command;
}
std::string Edge::GetBinding(const std::string& key) const {
EdgeEnv env(this, EdgeEnv::kShellEscape);
return env.LookupVariable(key);
}
bool Edge::GetBindingBool(const string& key) const {
return !GetBinding(key).empty();
}
string Edge::GetUnescapedDepfile() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("depfile");
}
string Edge::GetUnescapedDyndep() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("dyndep");
}
std::string Edge::GetUnescapedRspfile() const {
EdgeEnv env(this, EdgeEnv::kDoNotEscape);
return env.LookupVariable("rspfile");
}
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 (std::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 {
return is_phony() && outputs_.size() == 1 && implicit_outs_ == 0 &&
implicit_deps_ == 0;
}
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;
}
void Node::Dump(const char* prefix) const {
printf("%s <%s 0x%p> mtime: %" PRId64 "%s, (:%s), ",
prefix, path().c_str(), this,
mtime(), exists() ? "" : " (:missing)",
dirty() ? " dirty" : " clean");
if (in_edge()) {
in_edge()->Dump("in-edge: ");
} else {
printf("no in-edge\n");
}
printf(" out edges:\n");
for (vector<Edge*>::const_iterator e = out_edges().begin();
e != out_edges().end() && *e != NULL; ++e) {
(*e)->Dump(" +- ");
}
if (!validation_out_edges().empty()) {
printf(" validation out edges:\n");
for (std::vector<Edge*>::const_iterator e = validation_out_edges().begin();
e != validation_out_edges().end() && *e != NULL; ++e) {
(*e)->Dump(" +- ");
}
}
}
bool ImplicitDepLoader::LoadDeps(Edge* edge, string* err) {
string deps_type = edge->GetBinding("deps");
if (!deps_type.empty())
return LoadDepsFromLog(edge, err);
string depfile = edge->GetUnescapedDepfile();
if (!depfile.empty())
return LoadDepFile(edge, depfile, err);
return true;
}
struct matches {
explicit matches(std::vector<StringPiece>::iterator i) : i_(i) {}
bool operator()(const Node* node) const {
StringPiece opath = StringPiece(node->path());
return *i_ == opath;
}
std::vector<StringPiece>::iterator i_;
};
bool ImplicitDepLoader::LoadDepFile(Edge* edge, const string& path,
string* err) {
METRIC_RECORD("depfile load");
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;
}
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;
if (!depfile.Parse(&content, &depfile_err)) {
*err = path + ": " + depfile_err;
return false;
}
if (depfile.outs_.empty()) {
*err = path + ": no outputs declared";
return false;
}
uint64_t unused;
std::vector<StringPiece>::iterator primary_out = depfile.outs_.begin();
CanonicalizePath(const_cast<char*>(primary_out->str_), &primary_out->len_,
&unused);
Node* first_output = edge->outputs_[0];
StringPiece opath = StringPiece(first_output->path());
if (opath != *primary_out) {
EXPLAIN("expected depfile '%s' to mention '%s', got '%s'", path.c_str(),
first_output->path().c_str(), primary_out->AsString().c_str());
return false;
}
for (std::vector<StringPiece>::iterator o = depfile.outs_.begin();
o != depfile.outs_.end(); ++o) {
matches m(o);
if (std::find_if(edge->outputs_.begin(), edge->outputs_.end(), m) == edge->outputs_.end()) {
*err = path + ": depfile mentions '" + o->AsString() + "' as an output, but no such output was declared";
return false;
}
}
return ProcessDepfileDeps(edge, &depfile.ins_, err);
}
bool ImplicitDepLoader::ProcessDepfileDeps(
Edge* edge, std::vector<StringPiece>* depfile_ins, std::string* err) {
vector<Node*>::iterator implicit_dep =
PreallocateSpace(edge, depfile_ins->size());
for (std::vector<StringPiece>::iterator i = depfile_ins->begin();
i != depfile_ins->end(); ++i, ++implicit_dep) {
uint64_t slash_bits;
CanonicalizePath(const_cast<char*>(i->str_), &i->len_, &slash_bits);
Node* node = state_->GetNode(*i, slash_bits);
*implicit_dep = node;
node->AddOutEdge(edge);
CreatePhonyInEdge(node);
}
return true;
}
bool ImplicitDepLoader::LoadDepsFromLog(Edge* edge, string* err) {
Node* output = edge->outputs_[0];
DepsLog::Deps* deps = deps_log_ ? deps_log_->GetDeps(output) : NULL;
if (!deps) {
EXPLAIN("deps for '%s' are missing", output->path().c_str());
return false;
}
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->AddOutEdge(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);
phony_edge->generated_by_dep_loader_ = true;
node->set_in_edge(phony_edge);
phony_edge->outputs_.push_back(node);
phony_edge->outputs_ready_ = true;
}