mirror of
https://github.com/reactos/ninja.git
synced 2025-02-23 13:10:58 +00:00
442 lines
10 KiB
C++
442 lines
10 KiB
C++
#include <algorithm>
|
|
#include <map>
|
|
#include <queue>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <assert.h>
|
|
|
|
using namespace std;
|
|
|
|
#include "eval_env.h"
|
|
|
|
struct StatHelper {
|
|
virtual int Stat(const string& path);
|
|
};
|
|
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
int StatHelper::Stat(const string& path) {
|
|
struct stat st;
|
|
if (stat(path.c_str(), &st) < 0) {
|
|
if (errno == ENOENT) {
|
|
return 0;
|
|
} else {
|
|
fprintf(stderr, "stat(%s): %s\n", path.c_str(), strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return st.st_mtime;
|
|
return true;
|
|
}
|
|
|
|
struct Node;
|
|
struct FileStat {
|
|
FileStat(const string& path) : path_(path), mtime_(-1), node_(NULL) {}
|
|
void Touch(int mtime);
|
|
// Return true if the file exists (mtime_ got a value).
|
|
bool Stat(StatHelper* stat_helper);
|
|
|
|
// Return true if we needed to stat.
|
|
bool StatIfNecessary(StatHelper* stat_helper) {
|
|
if (status_known())
|
|
return false;
|
|
Stat(stat_helper);
|
|
return true;
|
|
}
|
|
|
|
bool exists() const {
|
|
assert(status_known());
|
|
return mtime_ != 0;
|
|
}
|
|
|
|
bool status_known() const {
|
|
return mtime_ != -1;
|
|
}
|
|
|
|
string path_;
|
|
// Possible values of mtime_:
|
|
// -1: file hasn't been examined
|
|
// 0: we looked, and file doesn't exist
|
|
// >0: actual file's mtime
|
|
time_t mtime_;
|
|
Node* node_;
|
|
};
|
|
|
|
struct Edge;
|
|
struct Node {
|
|
Node(FileStat* file) : file_(file), dirty_(false), in_edge_(NULL) {}
|
|
|
|
bool dirty() const { return dirty_; }
|
|
void MarkDirty();
|
|
|
|
FileStat* file_;
|
|
bool dirty_;
|
|
Edge* in_edge_;
|
|
vector<Edge*> out_edges_;
|
|
};
|
|
|
|
struct Rule {
|
|
Rule(const string& name, const string& command) :
|
|
name_(name) {
|
|
assert(command_.Parse(command)); // XXX
|
|
}
|
|
string name_;
|
|
EvalString command_;
|
|
};
|
|
|
|
struct Edge {
|
|
Edge() : rule_(NULL), env_(NULL) {}
|
|
|
|
void MarkDirty(Node* node);
|
|
void RecomputeDirty(StatHelper* stat_helper);
|
|
string EvaluateCommand(); // XXX move to env, take env ptr
|
|
|
|
Rule* rule_;
|
|
enum InOut { IN, OUT };
|
|
vector<Node*> inputs_;
|
|
vector<Node*> outputs_;
|
|
EvalString::Env* env_;
|
|
};
|
|
|
|
void FileStat::Touch(int mtime) {
|
|
mtime_ = mtime;
|
|
if (node_)
|
|
node_->MarkDirty();
|
|
}
|
|
|
|
bool FileStat::Stat(StatHelper* stat_helper) {
|
|
mtime_ = stat_helper->Stat(path_);
|
|
return mtime_ > 0;
|
|
}
|
|
|
|
void Node::MarkDirty() {
|
|
if (dirty_)
|
|
return; // We already know.
|
|
|
|
if (in_edge_) // No input edges means never dirty.
|
|
dirty_ = true;
|
|
for (vector<Edge*>::iterator i = out_edges_.begin(); i != out_edges_.end(); ++i)
|
|
(*i)->MarkDirty(this);
|
|
}
|
|
|
|
void Edge::RecomputeDirty(StatHelper* stat_helper) {
|
|
bool missing_input = false;
|
|
|
|
time_t most_recent_input = 1;
|
|
for (vector<Node*>::iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
|
|
if ((*i)->file_->StatIfNecessary(stat_helper)) {
|
|
if (Edge* edge = (*i)->in_edge_)
|
|
edge->RecomputeDirty(stat_helper);
|
|
}
|
|
if (!(*i)->file_->exists())
|
|
missing_input = true;
|
|
else if ((*i)->file_->mtime_ > most_recent_input)
|
|
most_recent_input = (*i)->file_->mtime_;
|
|
}
|
|
|
|
assert(!outputs_.empty());
|
|
for (vector<Node*>::iterator i = outputs_.begin(); i != outputs_.end(); ++i) {
|
|
assert((*i)->file_->status_known());
|
|
if (missing_input || (*i)->file_->mtime_ < most_recent_input) {
|
|
(*i)->dirty_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Edge::MarkDirty(Node* node) {
|
|
vector<Node*>::iterator i = find(inputs_.begin(), inputs_.end(), node);
|
|
if (i == inputs_.end())
|
|
return;
|
|
for (i = outputs_.begin(); i != outputs_.end(); ++i)
|
|
(*i)->MarkDirty();
|
|
}
|
|
|
|
struct EdgeEnv : public EvalString::Env {
|
|
EdgeEnv(Edge* edge) : edge_(edge) {}
|
|
virtual string Evaluate(const string& var) {
|
|
string result;
|
|
if (var == "@in") {
|
|
for (vector<Node*>::iterator i = edge_->inputs_.begin();
|
|
i != edge_->inputs_.end(); ++i) {
|
|
if (!result.empty())
|
|
result.push_back(' ');
|
|
result.append((*i)->file_->path_);
|
|
}
|
|
} else if (var == "$out") {
|
|
result = edge_->outputs_[0]->file_->path_;
|
|
} else if (edge_->env_) {
|
|
return edge_->env_->Evaluate(var);
|
|
}
|
|
return result;
|
|
}
|
|
Edge* edge_;
|
|
};
|
|
|
|
string Edge::EvaluateCommand() {
|
|
EdgeEnv env(this);
|
|
return rule_->command_.Evaluate(&env);
|
|
}
|
|
|
|
struct StatCache {
|
|
typedef map<string, FileStat*> Paths;
|
|
Paths paths_;
|
|
FileStat* GetFile(const string& path);
|
|
void Dump();
|
|
void Reload();
|
|
};
|
|
|
|
FileStat* StatCache::GetFile(const string& path) {
|
|
Paths::iterator i = paths_.find(path);
|
|
if (i != paths_.end())
|
|
return i->second;
|
|
FileStat* file = new FileStat(path);
|
|
paths_[path] = file;
|
|
return file;
|
|
}
|
|
|
|
#include <stdio.h>
|
|
|
|
void StatCache::Dump() {
|
|
for (Paths::iterator i = paths_.begin(); i != paths_.end(); ++i) {
|
|
printf("%s %s\n", i->second->path_.c_str(),
|
|
i->second->node_->dirty_ ? "dirty" : "clean");
|
|
}
|
|
}
|
|
|
|
struct State : public EvalString::Env {
|
|
StatCache stat_cache_;
|
|
map<string, Rule*> rules_;
|
|
vector<Edge*> edges_;
|
|
map<string, string> env_;
|
|
|
|
StatCache* stat_cache() { return &stat_cache_; }
|
|
|
|
// EvalString::Env impl
|
|
virtual string Evaluate(const string& var);
|
|
|
|
Rule* AddRule(const string& name, const string& command);
|
|
Edge* AddEdge(Rule* rule);
|
|
Edge* AddEdge(const string& rule_name);
|
|
Node* GetNode(const string& path);
|
|
void AddInOut(Edge* edge, Edge::InOut inout, const string& path);
|
|
void AddBinding(const string& key, const string& val);
|
|
};
|
|
|
|
string State::Evaluate(const string& var) {
|
|
if (var.size() > 1 && var[0] == '$') {
|
|
map<string, string>::iterator i = env_.find(var.substr(1));
|
|
if (i != env_.end())
|
|
return i->second;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
Rule* State::AddRule(const string& name, const string& command) {
|
|
Rule* rule = new Rule(name, command);
|
|
rules_[name] = rule;
|
|
return rule;
|
|
}
|
|
|
|
Edge* State::AddEdge(const string& rule_name) {
|
|
return AddEdge(rules_[rule_name]);
|
|
}
|
|
|
|
Edge* State::AddEdge(Rule* rule) {
|
|
Edge* edge = new Edge();
|
|
edge->rule_ = rule;
|
|
edge->env_ = this;
|
|
edges_.push_back(edge);
|
|
return edge;
|
|
}
|
|
|
|
Node* State::GetNode(const string& path) {
|
|
FileStat* file = stat_cache_.GetFile(path);
|
|
if (!file->node_)
|
|
file->node_ = new Node(file);
|
|
return file->node_;
|
|
}
|
|
|
|
void State::AddInOut(Edge* edge, Edge::InOut inout, const string& path) {
|
|
Node* node = GetNode(path);
|
|
if (inout == Edge::IN) {
|
|
edge->inputs_.push_back(node);
|
|
node->out_edges_.push_back(edge);
|
|
} else {
|
|
edge->outputs_.push_back(node);
|
|
assert(node->in_edge_ == NULL);
|
|
node->in_edge_ = edge;
|
|
}
|
|
}
|
|
|
|
void State::AddBinding(const string& key, const string& val) {
|
|
env_[key] = val;
|
|
}
|
|
|
|
struct Plan {
|
|
explicit Plan(State* state) : state_(state) {}
|
|
|
|
void AddTarget(const string& path);
|
|
bool AddTarget(Node* node);
|
|
|
|
Edge* FindWork();
|
|
void EdgeFinished(Edge* edge);
|
|
void NodeFinished(Node* node);
|
|
|
|
State* state_;
|
|
set<Node*> want_;
|
|
queue<Edge*> ready_;
|
|
|
|
private:
|
|
Plan();
|
|
Plan(const Plan&);
|
|
};
|
|
|
|
void Plan::AddTarget(const string& path) {
|
|
AddTarget(state_->GetNode(path));
|
|
}
|
|
bool Plan::AddTarget(Node* node) {
|
|
if (!node->dirty())
|
|
return false;
|
|
|
|
Edge* edge = node->in_edge_;
|
|
assert(edge); // Only nodes with in-edges can be dirty.
|
|
|
|
want_.insert(node);
|
|
|
|
bool awaiting_inputs = false;
|
|
for (vector<Node*>::iterator i = edge->inputs_.begin();
|
|
i != edge->inputs_.end(); ++i) {
|
|
if (AddTarget(*i))
|
|
awaiting_inputs = true;
|
|
}
|
|
|
|
if (!awaiting_inputs)
|
|
ready_.push(edge);
|
|
|
|
return true;
|
|
}
|
|
|
|
Edge* Plan::FindWork() {
|
|
if (ready_.empty())
|
|
return NULL;
|
|
Edge* edge = ready_.front();
|
|
ready_.pop();
|
|
return edge;
|
|
}
|
|
|
|
void Plan::EdgeFinished(Edge* edge) {
|
|
// Check off any nodes we were waiting for with this edge.
|
|
for (vector<Node*>::iterator i = edge->outputs_.begin();
|
|
i != edge->outputs_.end(); ++i) {
|
|
set<Node*>::iterator j = want_.find(*i);
|
|
if (j != want_.end()) {
|
|
NodeFinished(*j);
|
|
want_.erase(j);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Plan::NodeFinished(Node* node) {
|
|
// See if we we want any edges from this node.
|
|
for (vector<Edge*>::iterator i = node->out_edges_.begin();
|
|
i != node->out_edges_.end(); ++i) {
|
|
// See if we want any outputs from this edge.
|
|
for (vector<Node*>::iterator j = (*i)->outputs_.begin();
|
|
j != (*i)->outputs_.end(); ++j) {
|
|
if (want_.find(*j) != want_.end()) {
|
|
// See if the edge is ready.
|
|
// XXX just track dirty counts.
|
|
// XXX may double-enqueue edge.
|
|
bool ready = true;
|
|
for (vector<Node*>::iterator k = (*i)->inputs_.begin();
|
|
k != (*i)->inputs_.end(); ++k) {
|
|
if ((*k)->dirty()) {
|
|
ready = false;
|
|
break;
|
|
}
|
|
}
|
|
if (ready)
|
|
ready_.push(*i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#include "manifest_parser.h"
|
|
|
|
struct Shell {
|
|
virtual ~Shell() {}
|
|
virtual bool RunCommand(Edge* edge);
|
|
};
|
|
|
|
bool Shell::RunCommand(Edge* edge) {
|
|
string err;
|
|
string command = edge->EvaluateCommand();
|
|
printf(" %s\n", command.c_str());
|
|
int ret = system(command.c_str());
|
|
if (WIFEXITED(ret)) {
|
|
int exit = WEXITSTATUS(ret);
|
|
if (exit == 0)
|
|
return true;
|
|
err = "nonzero exit status";
|
|
} else {
|
|
err = "something else went wrong";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct Builder {
|
|
Builder(State* state) : plan_(state) {}
|
|
virtual ~Builder() {}
|
|
|
|
void AddTarget(const string& name) {
|
|
plan_.AddTarget(name);
|
|
}
|
|
bool Build(Shell* shell, string* err);
|
|
|
|
Plan plan_;
|
|
};
|
|
|
|
bool Builder::Build(Shell* shell, string* err) {
|
|
if (plan_.want_.empty()) {
|
|
*err = "no work to do";
|
|
return true;
|
|
}
|
|
|
|
Edge* edge = plan_.FindWork();
|
|
if (!edge) {
|
|
*err = "unable to find work";
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
string command = edge->EvaluateCommand();
|
|
if (!shell->RunCommand(edge)) {
|
|
err->assign("command '" + command + "' failed.");
|
|
return false;
|
|
}
|
|
for (vector<Node*>::iterator i = edge->outputs_.begin();
|
|
i != edge->outputs_.end(); ++i) {
|
|
// XXX check that the output actually changed
|
|
// XXX just notify node and have it propagate?
|
|
(*i)->dirty_ = false;
|
|
}
|
|
plan_.EdgeFinished(edge);
|
|
} while ((edge = plan_.FindWork()) != NULL);
|
|
|
|
if (!plan_.want_.empty()) {
|
|
*err = "ran out of work";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|