summaryrefslogtreecommitdiffstats
path: root/src/ninja_jumble.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninja_jumble.cc')
-rw-r--r--src/ninja_jumble.cc405
1 files changed, 405 insertions, 0 deletions
diff --git a/src/ninja_jumble.cc b/src/ninja_jumble.cc
new file mode 100644
index 0000000..4a30eba
--- /dev/null
+++ b/src/ninja_jumble.cc
@@ -0,0 +1,405 @@
+// This file is all the code that used to be in one file.
+// TODO: split into modules, delete this file.
+
+#include "ninja.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <string.h>
+
+int ReadFile(const string& path, string* contents, string* err) {
+ FILE* f = fopen(path.c_str(), "r");
+ if (!f) {
+ err->assign(strerror(errno));
+ return -errno;
+ }
+
+ char buf[64 << 10];
+ size_t len;
+ while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
+ contents->append(buf, len);
+ }
+ if (ferror(f)) {
+ err->assign(strerror(errno)); // XXX errno?
+ contents->clear();
+ fclose(f);
+ return -errno;
+ }
+ fclose(f);
+ return 0;
+}
+
+int RealDiskInterface::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;
+}
+
+string DirName(const string& path) {
+ string::size_type slash_pos = path.rfind('/');
+ if (slash_pos == string::npos)
+ return ""; // Nothing to do.
+ while (slash_pos > 0 && path[slash_pos - 1] == '/')
+ --slash_pos;
+ return path.substr(0, slash_pos);
+}
+
+bool DiskInterface::MakeDirs(const string& path) {
+ string dir = DirName(path);
+ if (dir.empty())
+ return true; // Reached root; assume it's there.
+ int mtime = Stat(dir);
+ if (mtime < 0)
+ return false; // Error.
+ if (mtime > 0)
+ return true; // Exists already; we're done.
+
+ // Directory doesn't exist. Try creating its parent first.
+ bool success = MakeDirs(dir);
+ if (!success)
+ return false;
+ return MakeDir(dir);
+}
+
+string RealDiskInterface::ReadFile(const string& path, string* err) {
+ string contents;
+ int ret = ::ReadFile(path, &contents, err);
+ if (ret == -ENOENT) {
+ // Swallow ENOENT.
+ err->clear();
+ }
+ return contents;
+}
+
+bool RealDiskInterface::MakeDir(const string& path) {
+ if (mkdir(path.c_str(), 0777) < 0) {
+ fprintf(stderr, "mkdir(%s): %s\n", path.c_str(), strerror(errno));
+ return false;
+ }
+ return true;
+}
+void FileStat::Touch(int mtime) {
+ mtime_ = mtime;
+ if (node_)
+ node_->MarkDirty();
+}
+
+bool FileStat::Stat(DiskInterface* disk_interface) {
+ mtime_ = disk_interface->Stat(path_);
+ return mtime_ > 0;
+}
+
+void Node::MarkDirty() {
+ if (dirty_)
+ return; // We already know.
+
+ dirty_ = true;
+ MarkDependentsDirty();
+}
+
+void Node::MarkDependentsDirty() {
+ for (vector<Edge*>::iterator i = out_edges_.begin(); i != out_edges_.end(); ++i)
+ (*i)->MarkDirty(this);
+}
+
+bool Edge::RecomputeDirty(State* state, DiskInterface* disk_interface,
+ string* err) {
+ bool dirty = false;
+
+ if (!rule_->depfile_.empty()) {
+ if (!LoadDepFile(state, disk_interface, err))
+ return false;
+ }
+
+ time_t most_recent_input = 1;
+ for (vector<Node*>::iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
+ if ((*i)->file_->StatIfNecessary(disk_interface)) {
+ if (Edge* edge = (*i)->in_edge_) {
+ if (!edge->RecomputeDirty(state, disk_interface, err))
+ return false;
+ } else {
+ (*i)->dirty_ = !(*i)->file_->exists();
+ }
+ }
+
+ if (is_order_only(i - inputs_.begin())) {
+ // Order-only deps only make us dirty if they're missing.
+ if (!(*i)->file_->exists())
+ dirty = true;
+ continue;
+ }
+
+ // If a regular input is dirty (or missing), we're dirty.
+ // Otherwise consider mtime.
+ if ((*i)->dirty_) {
+ dirty = 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) {
+ // We may have other outputs, that our input-recursive traversal hasn't hit
+ // yet (or never will). Stat them if we haven't already.
+ (*i)->file_->StatIfNecessary(disk_interface);
+
+ // Output is dirty if we're dirty, we're missing the output,
+ // or if it's older than the mostt recent input mtime.
+ if (dirty || !(*i)->file_->exists() ||
+ (*i)->file_->mtime_ < most_recent_input) {
+ (*i)->dirty_ = true;
+ }
+ }
+ return true;
+}
+
+void Edge::MarkDirty(Node* node) {
+ if (rule_ == &State::kPhonyRule)
+ return;
+
+ vector<Node*>::iterator i = find(inputs_.begin(), inputs_.end(), node);
+ if (i == inputs_.end())
+ return;
+ if (i - inputs_.begin() >= ((int)inputs_.size()) - order_only_deps_)
+ return; // Order-only deps don't cause us to become dirty.
+ for (i = outputs_.begin(); i != outputs_.end(); ++i)
+ (*i)->MarkDirty();
+}
+
+struct EdgeEnv : public Env {
+ EdgeEnv(Edge* edge) : edge_(edge) {}
+ virtual string LookupVariable(const string& var) {
+ string result;
+ if (var == "in") {
+ int explicit_deps = edge_->inputs_.size() - edge_->implicit_deps_ -
+ edge_->order_only_deps_;
+ for (vector<Node*>::iterator i = edge_->inputs_.begin();
+ i != edge_->inputs_.end() && explicit_deps; ++i, --explicit_deps) {
+ 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_->LookupVariable(var);
+ }
+ return result;
+ }
+ Edge* edge_;
+};
+
+string Edge::EvaluateCommand() {
+ EdgeEnv env(this);
+ return rule_->command_.Evaluate(&env);
+}
+
+string Edge::GetDescription() {
+ EdgeEnv env(this);
+ return rule_->description_.Evaluate(&env);
+}
+
+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) {
+ FileStat* file = i->second;
+ printf("%s %s\n",
+ file->path_.c_str(),
+ file->status_known()
+ ? (file->node_->dirty_ ? "dirty" : "clean")
+ : "unknown");
+ }
+}
+
+#include "parsers.h"
+
+bool Edge::LoadDepFile(State* state, DiskInterface* disk_interface, string* err) {
+ EdgeEnv env(this);
+ string path = rule_->depfile_.Evaluate(&env);
+
+ string content = disk_interface->ReadFile(path, err);
+ if (!err->empty())
+ return false;
+ if (content.empty())
+ return true;
+
+ MakefileParser makefile;
+ if (!makefile.Parse(content, err))
+ return false;
+
+ // Check that this depfile matches our output.
+ if (outputs_.size() != 1) {
+ *err = "expected only one output";
+ return false;
+ }
+ if (outputs_[0]->file_->path_ != makefile.out_) {
+ *err = "expected makefile to mention '" + outputs_[0]->file_->path_ + "', "
+ "got '" + makefile.out_ + "'";
+ return false;
+ }
+
+ // Add all its in-edges.
+ for (vector<string>::iterator i = makefile.ins_.begin();
+ i != makefile.ins_.end(); ++i) {
+ Node* node = state->GetNode(*i);
+ for (vector<Node*>::iterator j = inputs_.begin(); j != inputs_.end(); ++j) {
+ if (*j == node) {
+ node = NULL;
+ break;
+ }
+ }
+ if (node) {
+ inputs_.insert(inputs_.end() - order_only_deps_, node);
+ node->out_edges_.push_back(this);
+ ++implicit_deps_;
+ }
+ }
+
+ return true;
+}
+
+void Edge::Dump() {
+ printf("[ ");
+ for (vector<Node*>::iterator i = inputs_.begin(); i != inputs_.end(); ++i) {
+ printf("%s ", (*i)->file_->path_.c_str());
+ }
+ printf("--%s-> ", rule_->name_.c_str());
+ for (vector<Node*>::iterator i = outputs_.begin(); i != outputs_.end(); ++i) {
+ printf("%s ", (*i)->file_->path_.c_str());
+ }
+ printf("]\n");
+}
+
+const Rule State::kPhonyRule("phony");
+
+State::State() {
+ AddRule(&kPhonyRule);
+}
+
+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::AddRule(const Rule* rule) {
+ assert(LookupRule(rule->name_) == NULL);
+ rules_[rule->name_] = rule;
+}
+
+Edge* State::AddEdge(const Rule* rule) {
+ Edge* edge = new Edge();
+ edge->rule_ = rule;
+ edge->env_ = &bindings_;
+ edges_.push_back(edge);
+ return edge;
+}
+
+Node* State::LookupNode(const string& path) {
+ FileStat* file = stat_cache_.GetFile(path);
+ if (!file->node_)
+ return NULL;
+ return file->node_;
+}
+
+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);
+ if (node->in_edge_) {
+ fprintf(stderr, "WARNING: multiple rules generate %s. "
+ "build will not be correct; continuing anyway\n", path.c_str());
+ }
+ node->in_edge_ = edge;
+ }
+}
+
+bool EvalString::Parse(const string& input, string* err) {
+ unparsed_ = input;
+
+ string::size_type start, end;
+ start = 0;
+ do {
+ end = input.find('$', start);
+ if (end == string::npos) {
+ end = input.size();
+ break;
+ }
+ if (end > start)
+ parsed_.push_back(make_pair(input.substr(start, end - start), RAW));
+ start = end + 1;
+ if (start < input.size() && input[start] == '{') {
+ ++start;
+ for (end = start + 1; end < input.size(); ++end) {
+ if (input[end] == '}')
+ break;
+ }
+ if (end >= input.size()) {
+ *err = "expected closing curly after ${";
+ return false;
+ }
+ parsed_.push_back(make_pair(input.substr(start, end - start), SPECIAL));
+ ++end;
+ } else {
+ for (end = start + 1; end < input.size(); ++end) {
+ char c = input[end];
+ if (!(('a' <= c && c <= 'z') || c == '_'))
+ break;
+ }
+ if (end == start + 1) {
+ *err = "expected variable after $";
+ return false;
+ }
+ parsed_.push_back(make_pair(input.substr(start, end - start), SPECIAL));
+ }
+ start = end;
+ } while (end < input.size());
+ if (end > start)
+ parsed_.push_back(make_pair(input.substr(start, end - start), RAW));
+
+ return true;
+}
+
+string EvalString::Evaluate(Env* env) const {
+ string result;
+ for (TokenList::const_iterator i = parsed_.begin(); i != parsed_.end(); ++i) {
+ if (i->second == RAW)
+ result.append(i->first);
+ else
+ result.append(env->LookupVariable(i->first));
+ }
+ return result;
+}