[C++] Implement the first version of NinjaGenerator
diff --git a/eval.h b/eval.h
index c07d2bd..179b825 100644
--- a/eval.h
+++ b/eval.h
@@ -70,6 +70,9 @@
void set_current_scope(Vars* v) { current_scope_ = v; }
+ bool avoid_io() const { return avoid_io_; }
+ void set_avoid_io(bool a) { avoid_io_ = a; }
+
private:
Var* EvalRHS(StringPiece lhs, Value* rhs, StringPiece orig_rhs, AssignOp op,
bool is_override = false);
@@ -86,6 +89,7 @@
Loc loc_;
bool is_bootstrap_;
+ bool avoid_io_;
};
#endif // EVAL_H_
diff --git a/func.cc b/func.cc
index de8b9a1..ff18937 100644
--- a/func.cc
+++ b/func.cc
@@ -242,6 +242,13 @@
void WildcardFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> pat = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "$(/bin/ls -d ";
+ *s += *pat;
+ *s += " 2> /dev/null)";
+ return;
+ }
+
WordWriter ww(s);
for (StringPiece tok : WordScanner(*pat)) {
ScopedTerminator st(tok);
@@ -317,6 +324,11 @@
void RealpathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> text = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "KATI_TODO(realpath)";
+ return;
+ }
+
WordWriter ww(s);
for (StringPiece tok : WordScanner(*text)) {
ScopedTerminator st(tok);
@@ -390,6 +402,13 @@
void ShellFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> cmd = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "$(";
+ *s += *cmd;
+ *s += ")";
+ return;
+ }
+
LOG("ShellFunc: %s", cmd->c_str());
string out;
// TODO: Handle $(SHELL).
@@ -459,20 +478,32 @@
*s += var->Flavor();
}
-void InfoFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+void InfoFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "KATI_TODO(info)";
+ return;
+ }
printf("%s\n", a->c_str());
fflush(stdout);
}
-void WarningFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+void WarningFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "KATI_TODO(warning)";
+ return;
+ }
printf("%s:%d: %s\n", LOCF(ev->loc()), a->c_str());
fflush(stdout);
}
-void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
shared_ptr<string> a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "KATI_TODO(error)";
+ return;
+ }
ev->Error(StringPrintf("*** %s.", a->c_str()));
}
diff --git a/ninja.cc b/ninja.cc
index a14e3f1..9bb665e 100644
--- a/ninja.cc
+++ b/ninja.cc
@@ -16,6 +16,235 @@
#include "ninja.h"
-void GenerateNinja(const vector<DepNode*>& nodes, Evaluator* ev) {
+#include <stdio.h>
+#include <memory>
+#include <string>
+#include <unordered_set>
+
+#include "command.h"
+#include "dep.h"
+#include "eval.h"
+#include "log.h"
+#include "string_piece.h"
+#include "stringprintf.h"
+#include "strutil.h"
+#include "var.h"
+
+class NinjaGenerator {
+ public:
+ explicit NinjaGenerator(Evaluator* ev)
+ : ce_(ev), ev_(ev), fp_(NULL), rule_id_(0) {
+ ev_->set_avoid_io(true);
+ }
+
+ ~NinjaGenerator() {
+ ev_->set_avoid_io(false);
+ }
+
+ void Generate(const vector<DepNode*>& nodes) {
+ GenerateShell();
+ GenerateNinja(nodes);
+ }
+
+ private:
+ string GenRuleName() {
+ return StringPrintf("rule%d", rule_id_++);
+ }
+
+ StringPiece TranslateCommand(const char* in) {
+ const size_t orig_size = cmd_buf_.size();
+ bool prev_backslash = false;
+ char quote = 0;
+ bool done = false;
+ for (; *in && !done; in++) {
+ switch (*in) {
+ case '#':
+ if (quote == 0 && !prev_backslash) {
+ done = true;
+ break;
+ }
+
+ case '\'':
+ case '"':
+ case '`':
+ if (quote) {
+ if (quote == *in)
+ quote = 0;
+ } else if (!prev_backslash) {
+ quote = *in;
+ }
+
+ case '$':
+ cmd_buf_ += "$$";
+ break;
+
+ case '\t':
+ cmd_buf_ += ' ';
+ break;
+
+ case '\n':
+ if (prev_backslash) {
+ cmd_buf_[cmd_buf_.size()-1] = ' ';
+ } else {
+ cmd_buf_ += ' ';
+ }
+
+ case '\\':
+ prev_backslash = !prev_backslash;
+ cmd_buf_ += '\\';
+ break;
+
+ default:
+ cmd_buf_ += *in;
+ prev_backslash = false;
+ }
+ }
+
+ while (true) {
+ char c = cmd_buf_[cmd_buf_.size()-1];
+ if (!isspace(c) && c != ';')
+ break;
+ cmd_buf_.resize(cmd_buf_.size() - 1);
+ }
+
+ return StringPiece(cmd_buf_.data() + orig_size,
+ cmd_buf_.size() - orig_size);
+ }
+
+ void GenShellScript(const vector<Command*>& commands) {
+ //bool use_gomacc = false;
+ bool should_ignore_error = false;
+ cmd_buf_.clear();
+ for (const Command* c : commands) {
+ if (!cmd_buf_.empty()) {
+ if (should_ignore_error) {
+ cmd_buf_ += " ; ";
+ } else {
+ cmd_buf_ += " && ";
+ }
+ }
+ should_ignore_error = c->ignore_error;
+
+ const char* in = c->cmd->c_str();
+ while (isspace(*in))
+ in++;
+
+ bool needs_subshell = commands.size() > 1;
+ if (*in == '(') {
+ needs_subshell = false;
+ }
+
+ if (needs_subshell)
+ cmd_buf_ += '(';
+
+ StringPiece translated = TranslateCommand(in);
+ if (translated.empty()) {
+ cmd_buf_ += "true";
+ } else {
+ // TODO: flip use_gomacc
+ }
+
+ if (c == commands.back() && c->ignore_error) {
+ cmd_buf_ += " ; true";
+ }
+
+ if (needs_subshell)
+ cmd_buf_ += ')';
+ }
+ }
+
+ void EmitNode(DepNode* node) {
+ auto p = done_.insert(node->output);
+ if (!p.second)
+ return;
+
+ if (node->cmds.empty() && node->deps.empty() && !node->is_phony)
+ return;
+
+ vector<Command*> commands;
+ ce_.Eval(node, &commands);
+
+ string rule_name = "phony";
+ if (!commands.empty()) {
+ rule_name = GenRuleName();
+ fprintf(fp_, "rule %s\n", rule_name.c_str());
+ fprintf(fp_, " description = build $out\n");
+
+ GenShellScript(commands);
+ // TODO: depfile
+
+ // It seems Linux is OK with ~130kB.
+ // TODO: Find this number automatically.
+ if (cmd_buf_.size() > 100 * 1000) {
+ fprintf(fp_, " rspfile = $out.rsp\n");
+ fprintf(fp_, " rspfile_content = %s\n", cmd_buf_.c_str());
+ fprintf(fp_, " command = sh $out.rsp\n");
+ } else {
+ fprintf(fp_, " command = %s\n", cmd_buf_.c_str());
+ }
+ }
+
+ EmitBuild(node, rule_name);
+ // goma
+
+ for (DepNode* d : node->deps) {
+ EmitNode(d);
+ }
+ }
+
+ void EmitBuild(DepNode* node, const string& rule_name) {
+ fprintf(fp_, "build %.*s: %s", SPF(node->output), rule_name.c_str());
+ vector<StringPiece> order_onlys;
+ for (DepNode* d : node->deps) {
+ if (d->is_order_only) {
+ order_onlys.push_back(d->output);
+ } else {
+ fprintf(fp_, " %.*s", SPF(d->output));
+ }
+ }
+ if (!order_onlys.empty()) {
+ fprintf(fp_, " |");
+ for (StringPiece oo : order_onlys) {
+ fprintf(fp_, " %.*s", SPF(oo));
+ }
+ }
+ fprintf(fp_, "\n");
+ }
+
+ void GenerateNinja(const vector<DepNode*>& nodes) {
+ fp_ = fopen("build.ninja", "wb");
+ if (fp_ == NULL)
+ PERROR("fopen(build.ninja) failed");
+
+ fprintf(fp_, "# Generated by kati\n");
+ fprintf(fp_, "\n");
+
+ for (DepNode* node : nodes) {
+ EmitNode(node);
+ }
+
+ fclose(fp_);
+ }
+
+ void GenerateShell() {
+#if 0
+ Var* v = ev->LookupVar("SHELL");
+ shell_ = v->Eval(ev);
+ if (shell_->empty())
+ shell_ = make_shared<string>("/bin/sh");
+#endif
+ }
+
+ CommandEvaluator ce_;
+ Evaluator* ev_;
+ FILE* fp_;
+ unordered_set<StringPiece> done_;
+ int rule_id_;
+ string cmd_buf_;
+};
+
+void GenerateNinja(const vector<DepNode*>& nodes, Evaluator* ev) {
+ NinjaGenerator ng(ev);
+ ng.Generate(nodes);
}