aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Android.bp102
-rw-r--r--src/affinity.cc66
-rw-r--r--src/affinity.h21
-rw-r--r--src/command.cc240
-rw-r--r--src/command.h46
-rw-r--r--src/dep.cc923
-rw-r--r--src/dep.h69
-rw-r--r--src/eval.cc556
-rw-r--r--src/eval.h173
-rw-r--r--src/exec.cc151
-rw-r--r--src/exec.h26
-rw-r--r--src/expr.cc591
-rw-r--r--src/expr.h77
-rw-r--r--src/file.cc62
-rw-r--r--src/file.h48
-rw-r--r--src/file_cache.cc65
-rw-r--r--src/file_cache.h40
-rw-r--r--src/fileutil.cc211
-rw-r--r--src/fileutil.h62
-rw-r--r--src/fileutil_bench.cc45
-rw-r--r--src/find.cc1168
-rw-r--r--src/find.h79
-rw-r--r--src/find_test.cc192
-rw-r--r--src/flags.cc197
-rw-r--r--src/flags.h85
-rw-r--r--src/func.cc1022
-rw-r--r--src/func.h66
-rw-r--r--src/io.cc49
-rw-r--r--src/io.h45
-rw-r--r--src/loc.h32
-rw-r--r--src/log.cc62
-rw-r--r--src/log.h107
-rw-r--r--src/main.cc370
-rw-r--r--src/ninja.cc854
-rw-r--r--src/ninja.h43
-rw-r--r--src/ninja_test.cc87
-rw-r--r--src/parser.cc625
-rw-r--r--src/parser.h45
-rw-r--r--src/regen.cc485
-rw-r--r--src/regen.h24
-rw-r--r--src/regen_dump.cc224
-rw-r--r--src/rule.cc122
-rw-r--r--src/rule.h65
-rw-r--r--src/stats.cc145
-rw-r--r--src/stats.h74
-rw-r--r--src/stmt.cc180
-rw-r--r--src/stmt.h167
-rw-r--r--src/string_piece.cc239
-rw-r--r--src/string_piece.h224
-rw-r--r--src/string_piece_test.cc37
-rw-r--r--src/stringprintf.cc39
-rw-r--r--src/stringprintf.h24
-rw-r--r--src/strutil.cc556
-rw-r--r--src/strutil.h149
-rw-r--r--src/strutil_bench.cc42
-rw-r--r--src/strutil_test.cc231
-rw-r--r--src/symtab.cc190
-rw-r--r--src/symtab.h228
-rw-r--r--src/testutil.h38
-rw-r--r--src/thread_pool.cc90
-rw-r--r--src/thread_pool.h35
-rw-r--r--src/timeutil.cc43
-rw-r--r--src/timeutil.h30
-rw-r--r--src/var.cc230
-rw-r--r--src/var.h180
-rw-r--r--src/version.h20
-rw-r--r--src/version_unknown.cc17
67 files changed, 12830 insertions, 0 deletions
diff --git a/src/Android.bp b/src/Android.bp
new file mode 100644
index 0000000..ebbef98
--- /dev/null
+++ b/src/Android.bp
@@ -0,0 +1,102 @@
+// Copyright 2016 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.
+
+cc_defaults {
+ name: "ckati_defaults",
+ cflags: [
+ "-W",
+ "-Wall",
+ "-Werror",
+ "-DNOLOG",
+ ],
+ compile_multilib: "64",
+ tidy_checks: [
+ "-google-global-names-in-headers",
+ "-google-build-using-namespace",
+ "-google-explicit-constructor",
+ ],
+}
+
+cc_library_host_static {
+ name: "libckati",
+ defaults: ["ckati_defaults"],
+ srcs: [
+ "affinity.cc",
+ "command.cc",
+ "dep.cc",
+ "eval.cc",
+ "exec.cc",
+ "expr.cc",
+ "file.cc",
+ "file_cache.cc",
+ "fileutil.cc",
+ "find.cc",
+ "flags.cc",
+ "func.cc",
+ "io.cc",
+ "log.cc",
+ "ninja.cc",
+ "parser.cc",
+ "regen.cc",
+ "rule.cc",
+ "stats.cc",
+ "stmt.cc",
+ "string_piece.cc",
+ "stringprintf.cc",
+ "strutil.cc",
+ "symtab.cc",
+ "thread_pool.cc",
+ "timeutil.cc",
+ "var.cc",
+ "version_unknown.cc",
+ ],
+}
+
+cc_binary_host {
+ name: "ckati",
+ defaults: ["ckati_defaults"],
+ srcs: ["main.cc"],
+ whole_static_libs: ["libckati"],
+}
+
+cc_binary_host {
+ name: "ckati_stamp_dump",
+ defaults: ["ckati_defaults"],
+ srcs: ["regen_dump.cc"],
+ static_libs: ["libckati"],
+}
+
+cc_test_host {
+ name: "ckati_test",
+ defaults: ["ckati_defaults"],
+ test_per_src: true,
+ srcs: [
+ "find_test.cc",
+ "ninja_test.cc",
+ "string_piece_test.cc",
+ "strutil_bench.cc",
+ "strutil_test.cc",
+ ],
+ gtest: false,
+ static_libs: ["libckati"],
+}
+
+cc_benchmark_host {
+ name: "ckati_fileutil_bench",
+ defaults: ["ckati_defaults"],
+ srcs: [
+ "fileutil_bench.cc",
+ ],
+ static_libs: ["libckati"],
+}
diff --git a/src/affinity.cc b/src/affinity.cc
new file mode 100644
index 0000000..9e855f3
--- /dev/null
+++ b/src/affinity.cc
@@ -0,0 +1,66 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+#include "affinity.h"
+
+#include "flags.h"
+#include "log.h"
+
+#ifdef __linux__
+
+#include <sched.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <random>
+
+void SetAffinityForSingleThread() {
+ cpu_set_t cs;
+ CPU_ZERO(&cs);
+ std::random_device generator;
+ std::uniform_int_distribution<int> distribution(0, g_flags.num_cpus - 1);
+ int cpu = distribution(generator);
+
+ // Try to come up with a CPU and one close to it. This should work on most
+ // hyperthreaded system, but may be less optimal under stranger setups.
+ // Choosing two completely different CPUs would work here as well, it's just a
+ // couple percent faster if they're close (and still faster than letting the
+ // scheduler do whatever it wants).
+ cpu = cpu - (cpu % 2);
+ CPU_SET(cpu, &cs);
+ if (g_flags.num_cpus > 1)
+ CPU_SET(cpu + 1, &cs);
+
+ if (sched_setaffinity(0, sizeof(cs), &cs) < 0)
+ WARN("sched_setaffinity: %s", strerror(errno));
+}
+
+void SetAffinityForMultiThread() {
+ cpu_set_t cs;
+ CPU_ZERO(&cs);
+ for (int i = 0; i < g_flags.num_cpus; i++) {
+ CPU_SET(i, &cs);
+ }
+ if (sched_setaffinity(0, sizeof(cs), &cs) < 0)
+ WARN("sched_setaffinity: %s", strerror(errno));
+}
+
+#else
+
+void SetAffinityForSingleThread() {}
+void SetAffinityForMultiThread() {}
+
+#endif
diff --git a/src/affinity.h b/src/affinity.h
new file mode 100644
index 0000000..e6f6adc
--- /dev/null
+++ b/src/affinity.h
@@ -0,0 +1,21 @@
+// Copyright 2016 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.
+
+#ifndef AFFINITY_H_
+#define AFFINITY_H_
+
+void SetAffinityForSingleThread();
+void SetAffinityForMultiThread();
+
+#endif
diff --git a/src/command.cc b/src/command.cc
new file mode 100644
index 0000000..04bb168
--- /dev/null
+++ b/src/command.cc
@@ -0,0 +1,240 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "command.h"
+
+#include <unordered_map>
+#include <unordered_set>
+
+#include "dep.h"
+#include "eval.h"
+#include "flags.h"
+#include "log.h"
+#include "strutil.h"
+#include "var.h"
+
+namespace {
+
+class AutoVar : public Var {
+ public:
+ AutoVar() : Var(VarOrigin::AUTOMATIC) {}
+ virtual const char* Flavor() const override { return "undefined"; }
+
+ virtual void AppendVar(Evaluator*, Value*) override { CHECK(false); }
+
+ virtual StringPiece String() const override {
+ ERROR("$(value %s) is not implemented yet", sym_);
+ return "";
+ }
+
+ virtual string DebugString() const override {
+ return string("AutoVar(") + sym_ + ")";
+ }
+
+ protected:
+ AutoVar(CommandEvaluator* ce, const char* sym) : ce_(ce), sym_(sym) {}
+ virtual ~AutoVar() = default;
+
+ CommandEvaluator* ce_;
+ const char* sym_;
+};
+
+#define DECLARE_AUTO_VAR_CLASS(name) \
+ class name : public AutoVar { \
+ public: \
+ name(CommandEvaluator* ce, const char* sym) : AutoVar(ce, sym) {} \
+ virtual ~name() = default; \
+ virtual void Eval(Evaluator* ev, string* s) const override; \
+ }
+
+DECLARE_AUTO_VAR_CLASS(AutoAtVar);
+DECLARE_AUTO_VAR_CLASS(AutoLessVar);
+DECLARE_AUTO_VAR_CLASS(AutoHatVar);
+DECLARE_AUTO_VAR_CLASS(AutoPlusVar);
+DECLARE_AUTO_VAR_CLASS(AutoStarVar);
+DECLARE_AUTO_VAR_CLASS(AutoNotImplementedVar);
+
+class AutoSuffixDVar : public AutoVar {
+ public:
+ AutoSuffixDVar(CommandEvaluator* ce, const char* sym, Var* wrapped)
+ : AutoVar(ce, sym), wrapped_(wrapped) {}
+ virtual ~AutoSuffixDVar() = default;
+ virtual void Eval(Evaluator* ev, string* s) const override;
+
+ private:
+ Var* wrapped_;
+};
+
+class AutoSuffixFVar : public AutoVar {
+ public:
+ AutoSuffixFVar(CommandEvaluator* ce, const char* sym, Var* wrapped)
+ : AutoVar(ce, sym), wrapped_(wrapped) {}
+ virtual ~AutoSuffixFVar() = default;
+ virtual void Eval(Evaluator* ev, string* s) const override;
+
+ private:
+ Var* wrapped_;
+};
+
+void AutoAtVar::Eval(Evaluator*, string* s) const {
+ *s += ce_->current_dep_node()->output.str();
+}
+
+void AutoLessVar::Eval(Evaluator*, string* s) const {
+ auto& ai = ce_->current_dep_node()->actual_inputs;
+ if (!ai.empty())
+ *s += ai[0].str();
+}
+
+void AutoHatVar::Eval(Evaluator*, string* s) const {
+ unordered_set<StringPiece> seen;
+ WordWriter ww(s);
+ for (Symbol ai : ce_->current_dep_node()->actual_inputs) {
+ if (seen.insert(ai.str()).second)
+ ww.Write(ai.str());
+ }
+}
+
+void AutoPlusVar::Eval(Evaluator*, string* s) const {
+ WordWriter ww(s);
+ for (Symbol ai : ce_->current_dep_node()->actual_inputs) {
+ ww.Write(ai.str());
+ }
+}
+
+void AutoStarVar::Eval(Evaluator*, string* s) const {
+ const DepNode* n = ce_->current_dep_node();
+ if (!n->output_pattern.IsValid())
+ return;
+ Pattern pat(n->output_pattern.str());
+ pat.Stem(n->output.str()).AppendToString(s);
+}
+
+void AutoNotImplementedVar::Eval(Evaluator* ev, string*) const {
+ ev->Error(StringPrintf("Automatic variable `$%s' isn't supported yet", sym_));
+}
+
+void AutoSuffixDVar::Eval(Evaluator* ev, string* s) const {
+ string buf;
+ wrapped_->Eval(ev, &buf);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(buf)) {
+ ww.Write(Dirname(tok));
+ }
+}
+
+void AutoSuffixFVar::Eval(Evaluator* ev, string* s) const {
+ string buf;
+ wrapped_->Eval(ev, &buf);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(buf)) {
+ ww.Write(Basename(tok));
+ }
+}
+
+void ParseCommandPrefixes(StringPiece* s, bool* echo, bool* ignore_error) {
+ *s = TrimLeftSpace(*s);
+ while (true) {
+ char c = s->get(0);
+ if (c == '@') {
+ *echo = false;
+ } else if (c == '-') {
+ *ignore_error = true;
+ } else if (c == '+') {
+ // ignore recursion marker
+ } else {
+ break;
+ }
+ *s = TrimLeftSpace(s->substr(1));
+ }
+}
+
+} // namespace
+
+CommandEvaluator::CommandEvaluator(Evaluator* ev) : ev_(ev) {
+#define INSERT_AUTO_VAR(name, sym) \
+ do { \
+ Var* v = new name(this, sym); \
+ Intern(sym).SetGlobalVar(v); \
+ Intern(sym "D").SetGlobalVar(new AutoSuffixDVar(this, sym "D", v)); \
+ Intern(sym "F").SetGlobalVar(new AutoSuffixFVar(this, sym "F", v)); \
+ } while (0)
+ INSERT_AUTO_VAR(AutoAtVar, "@");
+ INSERT_AUTO_VAR(AutoLessVar, "<");
+ INSERT_AUTO_VAR(AutoHatVar, "^");
+ INSERT_AUTO_VAR(AutoPlusVar, "+");
+ INSERT_AUTO_VAR(AutoStarVar, "*");
+ // TODO: Implement them.
+ INSERT_AUTO_VAR(AutoNotImplementedVar, "%");
+ INSERT_AUTO_VAR(AutoNotImplementedVar, "?");
+ INSERT_AUTO_VAR(AutoNotImplementedVar, "|");
+}
+
+void CommandEvaluator::Eval(DepNode* n, vector<Command*>* commands) {
+ ev_->set_loc(n->loc);
+ ev_->set_current_scope(n->rule_vars);
+ current_dep_node_ = n;
+ for (Value* v : n->cmds) {
+ const string&& cmds_buf = v->Eval(ev_);
+ StringPiece cmds = cmds_buf;
+ bool global_echo = !g_flags.is_silent_mode;
+ bool global_ignore_error = false;
+ ParseCommandPrefixes(&cmds, &global_echo, &global_ignore_error);
+ if (cmds == "")
+ continue;
+ while (true) {
+ size_t lf_cnt;
+ size_t index = FindEndOfLine(cmds, 0, &lf_cnt);
+ if (index == cmds.size())
+ index = string::npos;
+ StringPiece cmd = TrimLeftSpace(cmds.substr(0, index));
+ cmds = cmds.substr(index + 1);
+
+ bool echo = global_echo;
+ bool ignore_error = global_ignore_error;
+ ParseCommandPrefixes(&cmd, &echo, &ignore_error);
+
+ if (!cmd.empty()) {
+ Command* command = new Command(n->output);
+ command->cmd = cmd.as_string();
+ command->echo = echo;
+ command->ignore_error = ignore_error;
+ commands->push_back(command);
+ }
+ if (index == string::npos)
+ break;
+ }
+ continue;
+ }
+
+ if (!ev_->delayed_output_commands().empty()) {
+ vector<Command*> output_commands;
+ for (const string& cmd : ev_->delayed_output_commands()) {
+ Command* c = new Command(n->output);
+ c->cmd = cmd;
+ c->echo = false;
+ c->ignore_error = false;
+ output_commands.push_back(c);
+ }
+ // Prepend |output_commands|.
+ commands->swap(output_commands);
+ copy(output_commands.begin(), output_commands.end(),
+ back_inserter(*commands));
+ ev_->clear_delayed_output_commands();
+ }
+
+ ev_->set_current_scope(NULL);
+}
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..5e86333
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,46 @@
+// Copyright 2015 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.
+
+#ifndef COMMAND_H_
+#define COMMAND_H_
+
+#include <vector>
+
+#include "symtab.h"
+
+using namespace std;
+
+struct DepNode;
+class Evaluator;
+
+struct Command {
+ explicit Command(Symbol o) : output(o), echo(true), ignore_error(false) {}
+ Symbol output;
+ string cmd;
+ bool echo;
+ bool ignore_error;
+};
+
+class CommandEvaluator {
+ public:
+ explicit CommandEvaluator(Evaluator* ev);
+ void Eval(DepNode* n, vector<Command*>* commands);
+ const DepNode* current_dep_node() const { return current_dep_node_; }
+
+ private:
+ Evaluator* ev_;
+ DepNode* current_dep_node_;
+};
+
+#endif // COMMAND_H_
diff --git a/src/dep.cc b/src/dep.cc
new file mode 100644
index 0000000..3289d7a
--- /dev/null
+++ b/src/dep.cc
@@ -0,0 +1,923 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "dep.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "eval.h"
+#include "fileutil.h"
+#include "flags.h"
+#include "log.h"
+#include "rule.h"
+#include "stats.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "timeutil.h"
+#include "var.h"
+
+namespace {
+
+static vector<DepNode*>* g_dep_node_pool;
+
+static Symbol ReplaceSuffix(Symbol s, Symbol newsuf) {
+ string r;
+ AppendString(StripExt(s.str()), &r);
+ r += '.';
+ AppendString(newsuf.str(), &r);
+ return Intern(r);
+}
+
+void ApplyOutputPattern(const Rule& r,
+ Symbol output,
+ const vector<Symbol>& inputs,
+ vector<Symbol>* out_inputs) {
+ if (inputs.empty())
+ return;
+ if (r.is_suffix_rule) {
+ for (Symbol input : inputs) {
+ out_inputs->push_back(ReplaceSuffix(output, input));
+ }
+ return;
+ }
+ if (r.output_patterns.empty()) {
+ copy(inputs.begin(), inputs.end(), back_inserter(*out_inputs));
+ return;
+ }
+ CHECK(r.output_patterns.size() == 1);
+ Pattern pat(r.output_patterns[0].str());
+ for (Symbol input : inputs) {
+ string buf;
+ pat.AppendSubst(output.str(), input.str(), &buf);
+ out_inputs->push_back(Intern(buf));
+ }
+}
+
+class RuleTrie {
+ struct Entry {
+ Entry(const Rule* r, StringPiece s) : rule(r), suffix(s) {}
+ const Rule* rule;
+ StringPiece suffix;
+ };
+
+ public:
+ RuleTrie() {}
+ ~RuleTrie() {
+ for (auto& p : children_)
+ delete p.second;
+ }
+
+ void Add(StringPiece name, const Rule* rule) {
+ if (name.empty() || name[0] == '%') {
+ rules_.push_back(Entry(rule, name));
+ return;
+ }
+ const char c = name[0];
+ auto p = children_.emplace(c, nullptr);
+ if (p.second) {
+ p.first->second = new RuleTrie();
+ }
+ p.first->second->Add(name.substr(1), rule);
+ }
+
+ void Get(StringPiece name, vector<const Rule*>* rules) const {
+ for (const Entry& ent : rules_) {
+ if ((ent.suffix.empty() && name.empty()) ||
+ HasSuffix(name, ent.suffix.substr(1))) {
+ rules->push_back(ent.rule);
+ }
+ }
+ if (name.empty())
+ return;
+ auto found = children_.find(name[0]);
+ if (found != children_.end()) {
+ found->second->Get(name.substr(1), rules);
+ }
+ }
+
+ size_t size() const {
+ size_t r = rules_.size();
+ for (const auto& c : children_)
+ r += c.second->size();
+ return r;
+ }
+
+ private:
+ vector<Entry> rules_;
+ unordered_map<char, RuleTrie*> children_;
+};
+
+bool IsSuffixRule(Symbol output) {
+ if (output.empty() || !IsSpecialTarget(output))
+ return false;
+ const StringPiece rest = StringPiece(output.str()).substr(1);
+ size_t dot_index = rest.find('.');
+ // If there is only a single dot or the third dot, this is not a
+ // suffix rule.
+ if (dot_index == string::npos ||
+ rest.substr(dot_index + 1).find('.') != string::npos) {
+ return false;
+ }
+ return true;
+}
+
+struct RuleMerger {
+ vector<const Rule*> rules;
+ vector<pair<Symbol, RuleMerger*>> implicit_outputs;
+ vector<Symbol> validations;
+ const Rule* primary_rule;
+ const RuleMerger* parent;
+ Symbol parent_sym;
+ bool is_double_colon;
+
+ RuleMerger()
+ : primary_rule(nullptr), parent(nullptr), is_double_colon(false) {}
+
+ void AddImplicitOutput(Symbol output, RuleMerger* merger) {
+ implicit_outputs.push_back(make_pair(output, merger));
+ }
+
+ void AddValidation(Symbol validation) { validations.push_back(validation); }
+
+ void SetImplicitOutput(Symbol output, Symbol p, const RuleMerger* merger) {
+ if (!merger->primary_rule) {
+ ERROR("*** implicit output `%s' on phony target `%s'", output.c_str(),
+ p.c_str());
+ }
+ if (parent) {
+ ERROR_LOC(merger->primary_rule->cmd_loc(),
+ "*** implicit output `%s' of `%s' was already defined by `%s' "
+ "at %s:%d",
+ output.c_str(), p.c_str(), parent_sym.c_str(),
+ LOCF(parent->primary_rule->cmd_loc()));
+ }
+ if (primary_rule) {
+ ERROR_LOC(primary_rule->cmd_loc(),
+ "*** implicit output `%s' may not have commands",
+ output.c_str());
+ }
+ parent = merger;
+ parent_sym = p;
+ }
+
+ void AddRule(Symbol output, const Rule* r) {
+ if (rules.empty()) {
+ is_double_colon = r->is_double_colon;
+ } else if (is_double_colon != r->is_double_colon) {
+ ERROR_LOC(r->loc, "*** target file `%s' has both : and :: entries.",
+ output.c_str());
+ }
+
+ if (primary_rule && !r->cmds.empty() && !IsSuffixRule(output) &&
+ !r->is_double_colon) {
+ if (g_flags.werror_overriding_commands) {
+ ERROR_LOC(r->cmd_loc(),
+ "*** overriding commands for target `%s', previously defined "
+ "at %s:%d",
+ output.c_str(), LOCF(primary_rule->cmd_loc()));
+ } else {
+ WARN_LOC(r->cmd_loc(), "warning: overriding commands for target `%s'",
+ output.c_str());
+ WARN_LOC(primary_rule->cmd_loc(),
+ "warning: ignoring old commands for target `%s'",
+ output.c_str());
+ }
+ primary_rule = r;
+ }
+ if (!primary_rule && !r->cmds.empty()) {
+ primary_rule = r;
+ }
+
+ rules.push_back(r);
+ }
+
+ void FillDepNodeFromRule(Symbol output, const Rule* r, DepNode* n) const {
+ if (is_double_colon)
+ copy(r->cmds.begin(), r->cmds.end(), back_inserter(n->cmds));
+
+ ApplyOutputPattern(*r, output, r->inputs, &n->actual_inputs);
+ ApplyOutputPattern(*r, output, r->order_only_inputs,
+ &n->actual_order_only_inputs);
+
+ if (r->output_patterns.size() >= 1) {
+ CHECK(r->output_patterns.size() == 1);
+ n->output_pattern = r->output_patterns[0];
+ }
+ }
+
+ void FillDepNodeLoc(const Rule* r, DepNode* n) const {
+ n->loc = r->loc;
+ if (!r->cmds.empty() && r->cmd_lineno)
+ n->loc.lineno = r->cmd_lineno;
+ }
+
+ void FillDepNode(Symbol output, const Rule* pattern_rule, DepNode* n) const {
+ if (primary_rule) {
+ CHECK(!pattern_rule);
+ FillDepNodeFromRule(output, primary_rule, n);
+ FillDepNodeLoc(primary_rule, n);
+ n->cmds = primary_rule->cmds;
+ } else if (pattern_rule) {
+ FillDepNodeFromRule(output, pattern_rule, n);
+ FillDepNodeLoc(pattern_rule, n);
+ n->cmds = pattern_rule->cmds;
+ }
+
+ for (const Rule* r : rules) {
+ if (r == primary_rule)
+ continue;
+ FillDepNodeFromRule(output, r, n);
+ if (n->loc.filename == NULL)
+ n->loc = r->loc;
+ }
+
+ for (auto& implicit_output : implicit_outputs) {
+ n->implicit_outputs.push_back(implicit_output.first);
+ for (const Rule* r : implicit_output.second->rules) {
+ FillDepNodeFromRule(output, r, n);
+ }
+ }
+
+ for (auto& validation : validations) {
+ n->actual_validations.push_back(validation);
+ }
+ }
+};
+
+} // namespace
+
+DepNode::DepNode(Symbol o, bool p, bool r)
+ : output(o),
+ has_rule(false),
+ is_default_target(false),
+ is_phony(p),
+ is_restat(r),
+ rule_vars(NULL),
+ depfile_var(NULL),
+ ninja_pool_var(NULL) {
+ g_dep_node_pool->push_back(this);
+}
+
+class DepBuilder {
+ public:
+ DepBuilder(Evaluator* ev,
+ const vector<const Rule*>& rules,
+ const unordered_map<Symbol, Vars*>& rule_vars)
+ : ev_(ev),
+ rule_vars_(rule_vars),
+ implicit_rules_(new RuleTrie()),
+ depfile_var_name_(Intern(".KATI_DEPFILE")),
+ implicit_outputs_var_name_(Intern(".KATI_IMPLICIT_OUTPUTS")),
+ ninja_pool_var_name_(Intern(".KATI_NINJA_POOL")),
+ validations_var_name_(Intern(".KATI_VALIDATIONS")) {
+ ScopedTimeReporter tr("make dep (populate)");
+ PopulateRules(rules);
+ // TODO?
+ // LOG_STAT("%zu variables", ev->mutable_vars()->size());
+ LOG_STAT("%zu explicit rules", rules_.size());
+ LOG_STAT("%zu implicit rules", implicit_rules_->size());
+ LOG_STAT("%zu suffix rules", suffix_rules_.size());
+
+ HandleSpecialTargets();
+ }
+
+ void HandleSpecialTargets() {
+ Loc loc;
+ vector<Symbol> targets;
+
+ if (GetRuleInputs(Intern(".PHONY"), &targets, &loc)) {
+ for (Symbol t : targets)
+ phony_.insert(t);
+ }
+ if (GetRuleInputs(Intern(".KATI_RESTAT"), &targets, &loc)) {
+ for (Symbol t : targets)
+ restat_.insert(t);
+ }
+ if (GetRuleInputs(Intern(".SUFFIXES"), &targets, &loc)) {
+ if (targets.empty()) {
+ suffix_rules_.clear();
+ } else {
+ WARN_LOC(loc, "kati doesn't support .SUFFIXES with prerequisites");
+ }
+ }
+
+ // Note we can safely ignore .DELETE_ON_ERROR for --ninja mode.
+ static const char* kUnsupportedBuiltinTargets[] = {".DEFAULT",
+ ".PRECIOUS",
+ ".INTERMEDIATE",
+ ".SECONDARY",
+ ".SECONDEXPANSION",
+ ".IGNORE",
+ ".LOW_RESOLUTION_TIME",
+ ".SILENT",
+ ".EXPORT_ALL_VARIABLES",
+ ".NOTPARALLEL",
+ ".ONESHELL",
+ NULL};
+ for (const char** p = kUnsupportedBuiltinTargets; *p; p++) {
+ if (GetRuleInputs(Intern(*p), &targets, &loc)) {
+ WARN_LOC(loc, "kati doesn't support %s", *p);
+ }
+ }
+ }
+
+ ~DepBuilder() {}
+
+ void Build(vector<Symbol> targets, vector<NamedDepNode>* nodes) {
+ if (!first_rule_.IsValid()) {
+ ERROR("*** No targets.");
+ }
+
+ if (!g_flags.gen_all_targets && targets.empty()) {
+ targets.push_back(first_rule_);
+ }
+ if (g_flags.gen_all_targets) {
+ SymbolSet non_root_targets;
+ for (const auto& p : rules_) {
+ if (IsSpecialTarget(p.first))
+ continue;
+ for (const Rule* r : p.second.rules) {
+ for (Symbol t : r->inputs)
+ non_root_targets.insert(t);
+ for (Symbol t : r->order_only_inputs)
+ non_root_targets.insert(t);
+ }
+ }
+
+ for (const auto& p : rules_) {
+ Symbol t = p.first;
+ if (!non_root_targets.exists(t) && !IsSpecialTarget(t)) {
+ targets.push_back(p.first);
+ }
+ }
+ }
+
+ // TODO: LogStats?
+
+ for (Symbol target : targets) {
+ cur_rule_vars_.reset(new Vars);
+ ev_->set_current_scope(cur_rule_vars_.get());
+ DepNode* n = BuildPlan(target, Intern(""));
+ nodes->push_back({target, n});
+ ev_->set_current_scope(NULL);
+ cur_rule_vars_.reset(NULL);
+ }
+ }
+
+ private:
+ bool Exists(Symbol target) {
+ return (rules_.find(target) != rules_.end()) || phony_.exists(target) ||
+ ::Exists(target.str());
+ }
+
+ bool GetRuleInputs(Symbol s, vector<Symbol>* o, Loc* l) {
+ auto found = rules_.find(s);
+ if (found == rules_.end())
+ return false;
+
+ o->clear();
+ CHECK(!found->second.rules.empty());
+ *l = found->second.rules.front()->loc;
+ for (const Rule* r : found->second.rules) {
+ for (Symbol i : r->inputs)
+ o->push_back(i);
+ }
+ return true;
+ }
+
+ void PopulateRules(const vector<const Rule*>& rules) {
+ for (const Rule* rule : rules) {
+ if (rule->outputs.empty()) {
+ PopulateImplicitRule(rule);
+ } else {
+ PopulateExplicitRule(rule);
+ }
+ }
+ for (auto& p : suffix_rules_) {
+ reverse(p.second.begin(), p.second.end());
+ }
+ for (auto& p : rules_) {
+ auto vars = LookupRuleVars(p.first);
+ if (!vars) {
+ continue;
+ }
+ auto var = vars->Lookup(implicit_outputs_var_name_);
+ if (var->IsDefined()) {
+ string implicit_outputs;
+ var->Eval(ev_, &implicit_outputs);
+
+ for (StringPiece output : WordScanner(implicit_outputs)) {
+ Symbol sym = Intern(TrimLeadingCurdir(output));
+ rules_[sym].SetImplicitOutput(sym, p.first, &p.second);
+ p.second.AddImplicitOutput(sym, &rules_[sym]);
+ }
+ }
+
+ var = vars->Lookup(validations_var_name_);
+ if (var->IsDefined()) {
+ string validations;
+ var->Eval(ev_, &validations);
+
+ for (StringPiece validation : WordScanner(validations)) {
+ Symbol sym = Intern(TrimLeadingCurdir(validation));
+ p.second.AddValidation(sym);
+ }
+ }
+ }
+ }
+
+ bool PopulateSuffixRule(const Rule* rule, Symbol output) {
+ if (!IsSuffixRule(output))
+ return false;
+
+ if (g_flags.werror_suffix_rules) {
+ ERROR_LOC(rule->loc, "*** suffix rules are obsolete: %s", output.c_str());
+ } else if (g_flags.warn_suffix_rules) {
+ WARN_LOC(rule->loc, "warning: suffix rules are deprecated: %s",
+ output.c_str());
+ }
+
+ const StringPiece rest = StringPiece(output.str()).substr(1);
+ size_t dot_index = rest.find('.');
+
+ StringPiece input_suffix = rest.substr(0, dot_index);
+ StringPiece output_suffix = rest.substr(dot_index + 1);
+ shared_ptr<Rule> r = make_shared<Rule>(*rule);
+ r->inputs.clear();
+ r->inputs.push_back(Intern(input_suffix));
+ r->is_suffix_rule = true;
+ suffix_rules_[output_suffix].push_back(r);
+ return true;
+ }
+
+ void PopulateExplicitRule(const Rule* rule) {
+ for (Symbol output : rule->outputs) {
+ if (!first_rule_.IsValid() && !IsSpecialTarget(output)) {
+ first_rule_ = output;
+ }
+ rules_[output].AddRule(output, rule);
+ PopulateSuffixRule(rule, output);
+ }
+ }
+
+ static bool IsIgnorableImplicitRule(const Rule* rule) {
+ // As kati doesn't have RCS/SCCS related default rules, we can
+ // safely ignore suppression for them.
+ if (rule->inputs.size() != 1)
+ return false;
+ if (!rule->order_only_inputs.empty())
+ return false;
+ if (!rule->cmds.empty())
+ return false;
+ const string& i = rule->inputs[0].str();
+ return (i == "RCS/%,v" || i == "RCS/%" || i == "%,v" || i == "s.%" ||
+ i == "SCCS/s.%");
+ }
+
+ void PopulateImplicitRule(const Rule* rule) {
+ for (Symbol output_pattern : rule->output_patterns) {
+ if (output_pattern.str() != "%" || !IsIgnorableImplicitRule(rule)) {
+ if (g_flags.werror_implicit_rules) {
+ ERROR_LOC(rule->loc, "*** implicit rules are obsolete: %s",
+ output_pattern.c_str());
+ } else if (g_flags.warn_implicit_rules) {
+ WARN_LOC(rule->loc, "warning: implicit rules are deprecated: %s",
+ output_pattern.c_str());
+ }
+
+ implicit_rules_->Add(output_pattern.str(), rule);
+ }
+ }
+ }
+
+ const RuleMerger* LookupRuleMerger(Symbol o) {
+ auto found = rules_.find(o);
+ if (found != rules_.end()) {
+ return &found->second;
+ }
+ return nullptr;
+ }
+
+ Vars* LookupRuleVars(Symbol o) {
+ auto found = rule_vars_.find(o);
+ if (found != rule_vars_.end())
+ return found->second;
+ return nullptr;
+ }
+
+ bool CanPickImplicitRule(const Rule* rule,
+ Symbol output,
+ DepNode* n,
+ shared_ptr<Rule>* out_rule) {
+ Symbol matched;
+ for (Symbol output_pattern : rule->output_patterns) {
+ Pattern pat(output_pattern.str());
+ if (pat.Match(output.str())) {
+ bool ok = true;
+ for (Symbol input : rule->inputs) {
+ string buf;
+ pat.AppendSubst(output.str(), input.str(), &buf);
+ if (!Exists(Intern(buf))) {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok) {
+ matched = output_pattern;
+ break;
+ }
+ }
+ }
+ if (!matched.IsValid())
+ return false;
+
+ *out_rule = make_shared<Rule>(*rule);
+ if ((*out_rule)->output_patterns.size() > 1) {
+ // We should mark all other output patterns as used.
+ Pattern pat(matched.str());
+ for (Symbol output_pattern : rule->output_patterns) {
+ if (output_pattern == matched)
+ continue;
+ string buf;
+ pat.AppendSubst(output.str(), output_pattern.str(), &buf);
+ done_[Intern(buf)] = n;
+ }
+ (*out_rule)->output_patterns.clear();
+ (*out_rule)->output_patterns.push_back(matched);
+ }
+
+ return true;
+ }
+
+ Vars* MergeImplicitRuleVars(Symbol output, Vars* vars) {
+ auto found = rule_vars_.find(output);
+ if (found == rule_vars_.end())
+ return vars;
+ if (vars == NULL)
+ return found->second;
+ // TODO: leak.
+ Vars* r = new Vars(*found->second);
+ for (auto p : *vars) {
+ (*r)[p.first] = p.second;
+ }
+ return r;
+ }
+
+ bool PickRule(Symbol output,
+ DepNode* n,
+ const RuleMerger** out_rule_merger,
+ shared_ptr<Rule>* pattern_rule,
+ Vars** out_var) {
+ const RuleMerger* rule_merger = LookupRuleMerger(output);
+ Vars* vars = LookupRuleVars(output);
+ *out_rule_merger = rule_merger;
+ *out_var = vars;
+ if (rule_merger && rule_merger->primary_rule) {
+ for (auto implicit_output : rule_merger->implicit_outputs) {
+ vars = MergeImplicitRuleVars(implicit_output.first, vars);
+ }
+ *out_var = vars;
+ return true;
+ }
+
+ vector<const Rule*> irules;
+ implicit_rules_->Get(output.str(), &irules);
+ for (auto iter = irules.rbegin(); iter != irules.rend(); ++iter) {
+ if (!CanPickImplicitRule(*iter, output, n, pattern_rule))
+ continue;
+ if (rule_merger) {
+ return true;
+ }
+ CHECK((*pattern_rule)->output_patterns.size() == 1);
+ vars = MergeImplicitRuleVars((*pattern_rule)->output_patterns[0], vars);
+ *out_var = vars;
+ return true;
+ }
+
+ StringPiece output_suffix = GetExt(output.str());
+ if (output_suffix.get(0) != '.')
+ return rule_merger;
+ output_suffix = output_suffix.substr(1);
+
+ SuffixRuleMap::const_iterator found = suffix_rules_.find(output_suffix);
+ if (found == suffix_rules_.end())
+ return rule_merger;
+
+ for (const shared_ptr<Rule>& irule : found->second) {
+ CHECK(irule->inputs.size() == 1);
+ Symbol input = ReplaceSuffix(output, irule->inputs[0]);
+ if (!Exists(input))
+ continue;
+
+ *pattern_rule = irule;
+ if (rule_merger)
+ return true;
+ if (vars) {
+ CHECK(irule->outputs.size() == 1);
+ vars = MergeImplicitRuleVars(irule->outputs[0], vars);
+ *out_var = vars;
+ }
+ return true;
+ }
+
+ return rule_merger;
+ }
+
+ DepNode* BuildPlan(Symbol output, Symbol needed_by UNUSED) {
+ LOG("BuildPlan: %s for %s", output.c_str(), needed_by.c_str());
+
+ auto found = done_.find(output);
+ if (found != done_.end()) {
+ return found->second;
+ }
+
+ DepNode* n =
+ new DepNode(output, phony_.exists(output), restat_.exists(output));
+ done_[output] = n;
+
+ const RuleMerger* rule_merger = nullptr;
+ shared_ptr<Rule> pattern_rule;
+ Vars* vars;
+ if (!PickRule(output, n, &rule_merger, &pattern_rule, &vars)) {
+ return n;
+ }
+ if (rule_merger && rule_merger->parent) {
+ output = rule_merger->parent_sym;
+ done_[output] = n;
+ n->output = output;
+ if (!PickRule(output, n, &rule_merger, &pattern_rule, &vars)) {
+ return n;
+ }
+ }
+ if (rule_merger)
+ rule_merger->FillDepNode(output, pattern_rule.get(), n);
+ else
+ RuleMerger().FillDepNode(output, pattern_rule.get(), n);
+
+ vector<unique_ptr<ScopedVar>> sv;
+ if (vars) {
+ for (const auto& p : *vars) {
+ Symbol name = p.first;
+ Var* var = p.second;
+ CHECK(var);
+ Var* new_var = var;
+ if (var->op() == AssignOp::PLUS_EQ) {
+ Var* old_var = ev_->LookupVar(name);
+ if (old_var->IsDefined()) {
+ // TODO: This would be incorrect and has a leak.
+ shared_ptr<string> s = make_shared<string>();
+ old_var->Eval(ev_, s.get());
+ if (!s->empty())
+ *s += ' ';
+ new_var->Eval(ev_, s.get());
+ new_var = new SimpleVar(*s, old_var->Origin());
+ }
+ } else if (var->op() == AssignOp::QUESTION_EQ) {
+ Var* old_var = ev_->LookupVar(name);
+ if (old_var->IsDefined()) {
+ continue;
+ }
+ }
+
+ if (name == depfile_var_name_) {
+ n->depfile_var = new_var;
+ } else if (name == implicit_outputs_var_name_) {
+ } else if (name == validations_var_name_) {
+ } else if (name == ninja_pool_var_name_) {
+ n->ninja_pool_var = new_var;
+ } else {
+ sv.emplace_back(new ScopedVar(cur_rule_vars_.get(), name, new_var));
+ }
+ }
+ }
+
+ if (g_flags.warn_phony_looks_real && n->is_phony &&
+ output.str().find("/") != string::npos) {
+ if (g_flags.werror_phony_looks_real) {
+ ERROR_LOC(
+ n->loc,
+ "*** PHONY target \"%s\" looks like a real file (contains a \"/\")",
+ output.c_str());
+ } else {
+ WARN_LOC(n->loc,
+ "warning: PHONY target \"%s\" looks like a real file "
+ "(contains a \"/\")",
+ output.c_str());
+ }
+ }
+
+ if (!g_flags.writable.empty() && !n->is_phony) {
+ bool found = false;
+ for (const auto& w : g_flags.writable) {
+ if (StringPiece(output.str()).starts_with(w)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (g_flags.werror_writable) {
+ ERROR_LOC(n->loc, "*** writing to readonly directory: \"%s\"",
+ output.c_str());
+ } else {
+ WARN_LOC(n->loc, "warning: writing to readonly directory: \"%s\"",
+ output.c_str());
+ }
+ }
+ }
+
+ for (Symbol output : n->implicit_outputs) {
+ done_[output] = n;
+
+ if (g_flags.warn_phony_looks_real && n->is_phony &&
+ output.str().find("/") != string::npos) {
+ if (g_flags.werror_phony_looks_real) {
+ ERROR_LOC(n->loc,
+ "*** PHONY target \"%s\" looks like a real file (contains "
+ "a \"/\")",
+ output.c_str());
+ } else {
+ WARN_LOC(n->loc,
+ "warning: PHONY target \"%s\" looks like a real file "
+ "(contains a \"/\")",
+ output.c_str());
+ }
+ }
+
+ if (!g_flags.writable.empty() && !n->is_phony) {
+ bool found = false;
+ for (const auto& w : g_flags.writable) {
+ if (StringPiece(output.str()).starts_with(w)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (g_flags.werror_writable) {
+ ERROR_LOC(n->loc, "*** writing to readonly directory: \"%s\"",
+ output.c_str());
+ } else {
+ WARN_LOC(n->loc, "warning: writing to readonly directory: \"%s\"",
+ output.c_str());
+ }
+ }
+ }
+ }
+
+ for (Symbol input : n->actual_inputs) {
+ DepNode* c = BuildPlan(input, output);
+ n->deps.push_back({input, c});
+
+ bool is_phony = c->is_phony;
+ if (!is_phony && !c->has_rule && g_flags.top_level_phony) {
+ is_phony = input.str().find("/") == string::npos;
+ }
+ if (!n->is_phony && is_phony) {
+ if (g_flags.werror_real_to_phony) {
+ ERROR_LOC(n->loc,
+ "*** real file \"%s\" depends on PHONY target \"%s\"",
+ output.c_str(), input.c_str());
+ } else if (g_flags.warn_real_to_phony) {
+ WARN_LOC(n->loc,
+ "warning: real file \"%s\" depends on PHONY target \"%s\"",
+ output.c_str(), input.c_str());
+ }
+ }
+ }
+
+ for (Symbol input : n->actual_order_only_inputs) {
+ DepNode* c = BuildPlan(input, output);
+ n->order_onlys.push_back({input, c});
+ }
+
+ for (Symbol validation : n->actual_validations) {
+ if (!g_flags.use_ninja_validations) {
+ ERROR_LOC(
+ n->loc,
+ ".KATI_VALIDATIONS not allowed without --use_ninja_validations");
+ }
+ DepNode* c = BuildPlan(validation, output);
+ n->validations.push_back({validation, c});
+ }
+
+ // Block on werror_writable/werror_phony_looks_real, because otherwise we
+ // can't rely on is_phony being valid for this check.
+ if (!n->is_phony && n->cmds.empty() && g_flags.werror_writable &&
+ g_flags.werror_phony_looks_real) {
+ if (n->deps.empty() && n->order_onlys.empty()) {
+ if (g_flags.werror_real_no_cmds_or_deps) {
+ ERROR_LOC(
+ n->loc,
+ "*** target \"%s\" has no commands or deps that could create it",
+ output.c_str());
+ } else if (g_flags.warn_real_no_cmds_or_deps) {
+ WARN_LOC(n->loc,
+ "warning: target \"%s\" has no commands or deps that could "
+ "create it",
+ output.c_str());
+ }
+ } else {
+ if (n->actual_inputs.size() == 1) {
+ if (g_flags.werror_real_no_cmds) {
+ ERROR_LOC(n->loc,
+ "*** target \"%s\" has no commands. Should \"%s\" be "
+ "using .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str(), n->actual_inputs[0].c_str());
+ } else if (g_flags.warn_real_no_cmds) {
+ WARN_LOC(n->loc,
+ "warning: target \"%s\" has no commands. Should \"%s\" be "
+ "using .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str(), n->actual_inputs[0].c_str());
+ }
+ } else {
+ if (g_flags.werror_real_no_cmds) {
+ ERROR_LOC(
+ n->loc,
+ "*** target \"%s\" has no commands that could create output "
+ "file. Is a dependency missing .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str());
+ } else if (g_flags.warn_real_no_cmds) {
+ WARN_LOC(
+ n->loc,
+ "warning: target \"%s\" has no commands that could create "
+ "output file. Is a dependency missing .KATI_IMPLICIT_OUTPUTS?",
+ output.c_str());
+ }
+ }
+ }
+ }
+
+ n->has_rule = true;
+ n->is_default_target = first_rule_ == output;
+ if (cur_rule_vars_->empty()) {
+ n->rule_vars = NULL;
+ } else {
+ n->rule_vars = new Vars;
+ for (auto p : *cur_rule_vars_) {
+ n->rule_vars->insert(p);
+ }
+ }
+
+ return n;
+ }
+
+ Evaluator* ev_;
+ map<Symbol, RuleMerger> rules_;
+ const unordered_map<Symbol, Vars*>& rule_vars_;
+ unique_ptr<Vars> cur_rule_vars_;
+
+ unique_ptr<RuleTrie> implicit_rules_;
+ typedef unordered_map<StringPiece, vector<shared_ptr<Rule>>> SuffixRuleMap;
+ SuffixRuleMap suffix_rules_;
+
+ Symbol first_rule_;
+ unordered_map<Symbol, DepNode*> done_;
+ SymbolSet phony_;
+ SymbolSet restat_;
+ Symbol depfile_var_name_;
+ Symbol implicit_outputs_var_name_;
+ Symbol ninja_pool_var_name_;
+ Symbol validations_var_name_;
+};
+
+void MakeDep(Evaluator* ev,
+ const vector<const Rule*>& rules,
+ const unordered_map<Symbol, Vars*>& rule_vars,
+ const vector<Symbol>& targets,
+ vector<NamedDepNode>* nodes) {
+ DepBuilder db(ev, rules, rule_vars);
+ ScopedTimeReporter tr("make dep (build)");
+ db.Build(targets, nodes);
+}
+
+void InitDepNodePool() {
+ g_dep_node_pool = new vector<DepNode*>;
+}
+
+void QuitDepNodePool() {
+ for (DepNode* n : *g_dep_node_pool)
+ delete n;
+ delete g_dep_node_pool;
+}
+
+bool IsSpecialTarget(Symbol output) {
+ return output.get(0) == '.' && output.get(1) != '.';
+}
diff --git a/src/dep.h b/src/dep.h
new file mode 100644
index 0000000..3ab52ac
--- /dev/null
+++ b/src/dep.h
@@ -0,0 +1,69 @@
+// Copyright 2015 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.
+
+#ifndef DEP_H_
+#define DEP_H_
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "loc.h"
+#include "string_piece.h"
+#include "symtab.h"
+
+class Evaluator;
+class Rule;
+class Value;
+class Var;
+class Vars;
+
+typedef pair<Symbol, struct DepNode*> NamedDepNode;
+
+struct DepNode {
+ DepNode(Symbol output, bool is_phony, bool is_restat);
+ string DebugString();
+
+ Symbol output;
+ vector<Value*> cmds;
+ vector<NamedDepNode> deps;
+ vector<NamedDepNode> order_onlys;
+ vector<NamedDepNode> validations;
+ bool has_rule;
+ bool is_default_target;
+ bool is_phony;
+ bool is_restat;
+ vector<Symbol> implicit_outputs;
+ vector<Symbol> actual_inputs;
+ vector<Symbol> actual_order_only_inputs;
+ vector<Symbol> actual_validations;
+ Vars* rule_vars;
+ Var* depfile_var;
+ Var* ninja_pool_var;
+ Symbol output_pattern;
+ Loc loc;
+};
+
+void InitDepNodePool();
+void QuitDepNodePool();
+
+void MakeDep(Evaluator* ev,
+ const vector<const Rule*>& rules,
+ const unordered_map<Symbol, Vars*>& rule_vars,
+ const vector<Symbol>& targets,
+ vector<NamedDepNode>* nodes);
+
+bool IsSpecialTarget(Symbol output);
+
+#endif // DEP_H_
diff --git a/src/eval.cc b/src/eval.cc
new file mode 100644
index 0000000..3772763
--- /dev/null
+++ b/src/eval.cc
@@ -0,0 +1,556 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "eval.h"
+
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+
+#include "expr.h"
+#include "file.h"
+#include "file_cache.h"
+#include "fileutil.h"
+#include "parser.h"
+#include "rule.h"
+#include "stats.h"
+#include "stmt.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "var.h"
+
+Evaluator::Evaluator()
+ : last_rule_(NULL),
+ current_scope_(NULL),
+ avoid_io_(false),
+ eval_depth_(0),
+ posix_sym_(Intern(".POSIX")),
+ is_posix_(false),
+ export_error_(false) {
+#if defined(__APPLE__)
+ stack_size_ = pthread_get_stacksize_np(pthread_self());
+ stack_addr_ = (char*)pthread_get_stackaddr_np(pthread_self()) - stack_size_;
+#else
+ pthread_attr_t attr;
+ CHECK(pthread_getattr_np(pthread_self(), &attr) == 0);
+ CHECK(pthread_attr_getstack(&attr, &stack_addr_, &stack_size_) == 0);
+ CHECK(pthread_attr_destroy(&attr) == 0);
+#endif
+
+ lowest_stack_ = (char*)stack_addr_ + stack_size_;
+ LOG_STAT("Stack size: %zd bytes", stack_size_);
+}
+
+Evaluator::~Evaluator() {
+ // delete vars_;
+ // for (auto p : rule_vars) {
+ // delete p.second;
+ // }
+}
+
+Var* Evaluator::EvalRHS(Symbol lhs,
+ Value* rhs_v,
+ StringPiece orig_rhs,
+ AssignOp op,
+ bool is_override,
+ bool* needs_assign) {
+ VarOrigin origin =
+ ((is_bootstrap_ ? VarOrigin::DEFAULT
+ : is_commandline_ ? VarOrigin::COMMAND_LINE
+ : is_override ? VarOrigin::OVERRIDE
+ : VarOrigin::FILE));
+
+ Var* result = NULL;
+ Var* prev = NULL;
+ *needs_assign = true;
+
+ switch (op) {
+ case AssignOp::COLON_EQ: {
+ prev = PeekVarInCurrentScope(lhs);
+ result = new SimpleVar(origin, this, rhs_v);
+ break;
+ }
+ case AssignOp::EQ:
+ prev = PeekVarInCurrentScope(lhs);
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ break;
+ case AssignOp::PLUS_EQ: {
+ prev = LookupVarInCurrentScope(lhs);
+ if (!prev->IsDefined()) {
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ } else if (prev->ReadOnly()) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ lhs.c_str()));
+ } else {
+ result = prev;
+ result->AppendVar(this, rhs_v);
+ *needs_assign = false;
+ }
+ break;
+ }
+ case AssignOp::QUESTION_EQ: {
+ prev = LookupVarInCurrentScope(lhs);
+ if (!prev->IsDefined()) {
+ result = new RecursiveVar(rhs_v, origin, orig_rhs);
+ } else {
+ result = prev;
+ *needs_assign = false;
+ }
+ break;
+ }
+ }
+
+ if (prev != NULL) {
+ prev->Used(this, lhs);
+ if (prev->Deprecated() && *needs_assign) {
+ result->SetDeprecated(prev->DeprecatedMessage());
+ }
+ }
+
+ LOG("Assign: %s=%s", lhs.c_str(), result->DebugString().c_str());
+ return result;
+}
+
+void Evaluator::EvalAssign(const AssignStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+ Symbol lhs = stmt->GetLhsSymbol(this);
+ if (lhs.empty())
+ Error("*** empty variable name.");
+
+ if (lhs == kKatiReadonlySym) {
+ string rhs;
+ stmt->rhs->Eval(this, &rhs);
+ for (auto const& name : WordScanner(rhs)) {
+ Var* var = Intern(name).GetGlobalVar();
+ if (!var->IsDefined()) {
+ Error(
+ StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
+ }
+ var->SetReadOnly();
+ }
+ return;
+ }
+
+ bool needs_assign;
+ Var* var =
+ EvalRHS(lhs, stmt->rhs, stmt->orig_rhs, stmt->op,
+ stmt->directive == AssignDirective::OVERRIDE, &needs_assign);
+ if (needs_assign) {
+ bool readonly;
+ lhs.SetGlobalVar(var, stmt->directive == AssignDirective::OVERRIDE,
+ &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ lhs.c_str()));
+ }
+ }
+
+ if (stmt->is_final) {
+ var->SetReadOnly();
+ }
+}
+
+// With rule broken into
+// <before_term> <term> <after_term>
+// parses <before_term> into Symbol instances until encountering ':'
+// Returns the remainder of <before_term>.
+static StringPiece ParseRuleTargets(const Loc& loc,
+ const StringPiece& before_term,
+ vector<Symbol>* targets,
+ bool* is_pattern_rule) {
+ size_t pos = before_term.find(':');
+ if (pos == string::npos) {
+ ERROR_LOC(loc, "*** missing separator.");
+ }
+ StringPiece targets_string = before_term.substr(0, pos);
+ size_t pattern_rule_count = 0;
+ for (auto const& word : WordScanner(targets_string)) {
+ StringPiece target = TrimLeadingCurdir(word);
+ targets->push_back(Intern(target));
+ if (Rule::IsPatternRule(target)) {
+ ++pattern_rule_count;
+ }
+ }
+ // Check consistency: either all outputs are patterns or none.
+ if (pattern_rule_count && (pattern_rule_count != targets->size())) {
+ ERROR_LOC(loc, "*** mixed implicit and normal rules: deprecated syntax");
+ }
+ *is_pattern_rule = pattern_rule_count;
+ return before_term.substr(pos + 1);
+}
+
+void Evaluator::MarkVarsReadonly(Value* vars_list) {
+ string vars_list_string;
+ vars_list->Eval(this, &vars_list_string);
+ for (auto const& name : WordScanner(vars_list_string)) {
+ Var* var = current_scope_->Lookup(Intern(name));
+ if (!var->IsDefined()) {
+ Error(StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
+ }
+ var->SetReadOnly();
+ }
+}
+
+void Evaluator::EvalRuleSpecificAssign(const vector<Symbol>& targets,
+ const RuleStmt* stmt,
+ const StringPiece& after_targets,
+ size_t separator_pos) {
+ StringPiece var_name;
+ StringPiece rhs_string;
+ AssignOp assign_op;
+ ParseAssignStatement(after_targets, separator_pos, &var_name, &rhs_string,
+ &assign_op);
+ Symbol var_sym = Intern(var_name);
+ bool is_final = (stmt->sep == RuleStmt::SEP_FINALEQ);
+ for (Symbol target : targets) {
+ auto p = rule_vars_.emplace(target, nullptr);
+ if (p.second) {
+ p.first->second = new Vars;
+ }
+
+ Value* rhs;
+ if (rhs_string.empty()) {
+ rhs = stmt->rhs;
+ } else if (stmt->rhs) {
+ StringPiece sep(stmt->sep == RuleStmt::SEP_SEMICOLON ? " ; " : " = ");
+ rhs = Value::NewExpr(Value::NewLiteral(rhs_string),
+ Value::NewLiteral(sep), stmt->rhs);
+ } else {
+ rhs = Value::NewLiteral(rhs_string);
+ }
+
+ current_scope_ = p.first->second;
+ if (var_sym == kKatiReadonlySym) {
+ MarkVarsReadonly(rhs);
+ } else {
+ bool needs_assign;
+ Var* rhs_var = EvalRHS(var_sym, rhs, StringPiece("*TODO*"), assign_op,
+ false, &needs_assign);
+ if (needs_assign) {
+ bool readonly;
+ rhs_var->SetAssignOp(assign_op);
+ current_scope_->Assign(var_sym, rhs_var, &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s",
+ var_name));
+ }
+ }
+ if (is_final) {
+ rhs_var->SetReadOnly();
+ }
+ }
+ current_scope_ = NULL;
+ }
+}
+
+void Evaluator::EvalRule(const RuleStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& before_term = stmt->lhs->Eval(this);
+ // See semicolon.mk.
+ if (before_term.find_first_not_of(" \t;") == string::npos) {
+ if (stmt->sep == RuleStmt::SEP_SEMICOLON)
+ Error("*** missing rule before commands.");
+ return;
+ }
+
+ vector<Symbol> targets;
+ bool is_pattern_rule;
+ StringPiece after_targets =
+ ParseRuleTargets(loc_, before_term, &targets, &is_pattern_rule);
+ bool is_double_colon = (after_targets[0] == ':');
+ if (is_double_colon) {
+ after_targets = after_targets.substr(1);
+ }
+
+ // Figure out if this is a rule-specific variable assignment.
+ // It is an assignment when either after_targets contains an assignment token
+ // or separator is an assignment token, but only if there is no ';' before the
+ // first assignment token.
+ size_t separator_pos = after_targets.find_first_of("=;");
+ char separator = '\0';
+ if (separator_pos != string::npos) {
+ separator = after_targets[separator_pos];
+ } else if (separator_pos == string::npos &&
+ (stmt->sep == RuleStmt::SEP_EQ ||
+ stmt->sep == RuleStmt::SEP_FINALEQ)) {
+ separator_pos = after_targets.size();
+ separator = '=';
+ }
+
+ // If variable name is not empty, we have rule- or target-specific
+ // variable assignment.
+ if (separator == '=' && separator_pos) {
+ EvalRuleSpecificAssign(targets, stmt, after_targets, separator_pos);
+ return;
+ }
+
+ if (!separator_pos) {
+ // We used to make this a warning and otherwise accept it, but Make 4.1
+ // calls this out as an error, so let's follow.
+ Error("*** empty variable name.");
+ }
+
+ Rule* rule = new Rule();
+ rule->loc = loc_;
+ rule->is_double_colon = is_double_colon;
+ if (is_pattern_rule) {
+ rule->output_patterns.swap(targets);
+ } else {
+ rule->outputs.swap(targets);
+ }
+ rule->ParsePrerequisites(after_targets, separator_pos, stmt);
+
+ if (stmt->sep == RuleStmt::SEP_SEMICOLON) {
+ rule->cmds.push_back(stmt->rhs);
+ }
+
+ for (Symbol o : rule->outputs) {
+ if (o == posix_sym_)
+ is_posix_ = true;
+ }
+
+ LOG("Rule: %s", rule->DebugString().c_str());
+ rules_.push_back(rule);
+ last_rule_ = rule;
+}
+
+void Evaluator::EvalCommand(const CommandStmt* stmt) {
+ loc_ = stmt->loc();
+
+ if (!last_rule_) {
+ vector<Stmt*> stmts;
+ ParseNotAfterRule(stmt->orig, stmt->loc(), &stmts);
+ for (Stmt* a : stmts)
+ a->Eval(this);
+ return;
+ }
+
+ last_rule_->cmds.push_back(stmt->expr);
+ if (last_rule_->cmd_lineno == 0)
+ last_rule_->cmd_lineno = stmt->loc().lineno;
+ LOG("Command: %s", Value::DebugString(stmt->expr).c_str());
+}
+
+void Evaluator::EvalIf(const IfStmt* stmt) {
+ loc_ = stmt->loc();
+
+ bool is_true;
+ switch (stmt->op) {
+ case CondOp::IFDEF:
+ case CondOp::IFNDEF: {
+ string var_name;
+ stmt->lhs->Eval(this, &var_name);
+ Symbol lhs = Intern(TrimRightSpace(var_name));
+ if (lhs.str().find_first_of(" \t") != string::npos)
+ Error("*** invalid syntax in conditional.");
+ Var* v = LookupVarInCurrentScope(lhs);
+ v->Used(this, lhs);
+ is_true = (v->String().empty() == (stmt->op == CondOp::IFNDEF));
+ break;
+ }
+ case CondOp::IFEQ:
+ case CondOp::IFNEQ: {
+ const string&& lhs = stmt->lhs->Eval(this);
+ const string&& rhs = stmt->rhs->Eval(this);
+ is_true = ((lhs == rhs) == (stmt->op == CondOp::IFEQ));
+ break;
+ }
+ default:
+ CHECK(false);
+ abort();
+ }
+
+ const vector<Stmt*>* stmts;
+ if (is_true) {
+ stmts = &stmt->true_stmts;
+ } else {
+ stmts = &stmt->false_stmts;
+ }
+ for (Stmt* a : *stmts) {
+ LOG("%s", a->DebugString().c_str());
+ a->Eval(this);
+ }
+}
+
+void Evaluator::DoInclude(const string& fname) {
+ CheckStack();
+ COLLECT_STATS_WITH_SLOW_REPORT("included makefiles", fname.c_str());
+
+ Makefile* mk = MakefileCacheManager::Get()->ReadMakefile(fname);
+ if (!mk->Exists()) {
+ Error(StringPrintf("%s does not exist", fname.c_str()));
+ }
+
+ Var* var_list = LookupVar(Intern("MAKEFILE_LIST"));
+ var_list->AppendVar(
+ this, Value::NewLiteral(Intern(TrimLeadingCurdir(fname)).str()));
+ for (Stmt* stmt : mk->stmts()) {
+ LOG("%s", stmt->DebugString().c_str());
+ stmt->Eval(this);
+ }
+
+ for (auto& mk : profiled_files_) {
+ stats.MarkInteresting(mk);
+ }
+ profiled_files_.clear();
+}
+
+void Evaluator::EvalInclude(const IncludeStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& pats = stmt->expr->Eval(this);
+ for (StringPiece pat : WordScanner(pats)) {
+ ScopedTerminator st(pat);
+ vector<string>* files;
+ Glob(pat.data(), &files);
+
+ if (stmt->should_exist) {
+ if (files->empty()) {
+ // TODO: Kati does not support building a missing include file.
+ Error(StringPrintf("%s: %s", pat.data(), strerror(errno)));
+ }
+ }
+
+ include_stack_.push_back(stmt->loc());
+ for (const string& fname : *files) {
+ if (!stmt->should_exist && g_flags.ignore_optional_include_pattern &&
+ Pattern(g_flags.ignore_optional_include_pattern).Match(fname)) {
+ continue;
+ }
+ DoInclude(fname);
+ }
+ include_stack_.pop_back();
+ }
+}
+
+void Evaluator::EvalExport(const ExportStmt* stmt) {
+ loc_ = stmt->loc();
+ last_rule_ = NULL;
+
+ const string&& exports = stmt->expr->Eval(this);
+ for (StringPiece tok : WordScanner(exports)) {
+ size_t equal_index = tok.find('=');
+ StringPiece lhs;
+ if (equal_index == string::npos) {
+ lhs = tok;
+ } else if (equal_index == 0 ||
+ (equal_index == 1 &&
+ (tok[0] == ':' || tok[0] == '?' || tok[0] == '+'))) {
+ // Do not export tokens after an assignment.
+ break;
+ } else {
+ StringPiece rhs;
+ AssignOp op;
+ ParseAssignStatement(tok, equal_index, &lhs, &rhs, &op);
+ }
+ Symbol sym = Intern(lhs);
+ exports_[sym] = stmt->is_export;
+
+ if (export_message_) {
+ const char* prefix = "";
+ if (!stmt->is_export) {
+ prefix = "un";
+ }
+
+ if (export_error_) {
+ Error(StringPrintf("*** %s: %sexport is obsolete%s.", sym.c_str(),
+ prefix, export_message_->c_str()));
+ } else {
+ WARN_LOC(loc(), "%s: %sexport has been deprecated%s.", sym.c_str(),
+ prefix, export_message_->c_str());
+ }
+ }
+ }
+}
+
+Var* Evaluator::LookupVarGlobal(Symbol name) {
+ Var* v = name.GetGlobalVar();
+ if (v->IsDefined())
+ return v;
+ used_undefined_vars_.insert(name);
+ return v;
+}
+
+Var* Evaluator::LookupVar(Symbol name) {
+ if (current_scope_) {
+ Var* v = current_scope_->Lookup(name);
+ if (v->IsDefined())
+ return v;
+ }
+ return LookupVarGlobal(name);
+}
+
+Var* Evaluator::PeekVar(Symbol name) {
+ if (current_scope_) {
+ Var* v = current_scope_->Peek(name);
+ if (v->IsDefined())
+ return v;
+ }
+ return name.PeekGlobalVar();
+}
+
+Var* Evaluator::LookupVarInCurrentScope(Symbol name) {
+ if (current_scope_) {
+ return current_scope_->Lookup(name);
+ }
+ return LookupVarGlobal(name);
+}
+
+Var* Evaluator::PeekVarInCurrentScope(Symbol name) {
+ if (current_scope_) {
+ return current_scope_->Peek(name);
+ }
+ return name.PeekGlobalVar();
+}
+
+string Evaluator::EvalVar(Symbol name) {
+ return LookupVar(name)->Eval(this);
+}
+
+string Evaluator::GetShell() {
+ return EvalVar(kShellSym);
+}
+
+string Evaluator::GetShellFlag() {
+ // TODO: Handle $(.SHELLFLAGS)
+ return is_posix_ ? "-ec" : "-c";
+}
+
+string Evaluator::GetShellAndFlag() {
+ string shell = GetShell();
+ shell += ' ';
+ shell += GetShellFlag();
+ return shell;
+}
+
+void Evaluator::Error(const string& msg) {
+ for (auto& inc : include_stack_) {
+ fprintf(stderr, "In file included from %s:%d:\n", LOCF(inc));
+ }
+ ERROR_LOC(loc_, "%s", msg.c_str());
+}
+
+void Evaluator::DumpStackStats() const {
+ LOG_STAT("Max stack use: %zd bytes at %s:%d",
+ ((char*)stack_addr_ - (char*)lowest_stack_) + stack_size_,
+ LOCF(lowest_loc_));
+}
+
+SymbolSet Evaluator::used_undefined_vars_;
diff --git a/src/eval.h b/src/eval.h
new file mode 100644
index 0000000..e8a95ed
--- /dev/null
+++ b/src/eval.h
@@ -0,0 +1,173 @@
+// Copyright 2015 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.
+
+#ifndef EVAL_H_
+#define EVAL_H_
+
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "loc.h"
+#include "stmt.h"
+#include "string_piece.h"
+#include "symtab.h"
+
+using namespace std;
+
+class Makefile;
+class Rule;
+class Var;
+class Vars;
+
+class Evaluator {
+ public:
+ Evaluator();
+ ~Evaluator();
+
+ void EvalAssign(const AssignStmt* stmt);
+ void EvalRule(const RuleStmt* stmt);
+ void EvalCommand(const CommandStmt* stmt);
+ void EvalIf(const IfStmt* stmt);
+ void EvalInclude(const IncludeStmt* stmt);
+ void EvalExport(const ExportStmt* stmt);
+
+ Var* LookupVar(Symbol name);
+ // For target specific variables.
+ Var* LookupVarInCurrentScope(Symbol name);
+
+ // Equivalent to LookupVar, but doesn't mark as used.
+ Var* PeekVar(Symbol name);
+
+ string EvalVar(Symbol name);
+
+ const Loc& loc() const { return loc_; }
+ void set_loc(const Loc& loc) { loc_ = loc; }
+
+ const vector<const Rule*>& rules() const { return rules_; }
+ const unordered_map<Symbol, Vars*>& rule_vars() const { return rule_vars_; }
+ const unordered_map<Symbol, bool>& exports() const { return exports_; }
+
+ void Error(const string& msg);
+
+ void set_is_bootstrap(bool b) { is_bootstrap_ = b; }
+ void set_is_commandline(bool c) { is_commandline_ = c; }
+
+ 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; }
+
+ const vector<string>& delayed_output_commands() const {
+ return delayed_output_commands_;
+ }
+ void add_delayed_output_command(const string& c) {
+ delayed_output_commands_.push_back(c);
+ }
+ void clear_delayed_output_commands() { delayed_output_commands_.clear(); }
+
+ static const SymbolSet& used_undefined_vars() { return used_undefined_vars_; }
+
+ int eval_depth() const { return eval_depth_; }
+ void IncrementEvalDepth() { eval_depth_++; }
+ void DecrementEvalDepth() { eval_depth_--; }
+
+ string GetShell();
+ string GetShellFlag();
+ string GetShellAndFlag();
+
+ void CheckStack() {
+ void* addr = __builtin_frame_address(0);
+ if (__builtin_expect(addr < lowest_stack_ && addr >= stack_addr_, 0)) {
+ lowest_stack_ = addr;
+ lowest_loc_ = loc_;
+ }
+ }
+ void DumpStackStats() const;
+
+ bool ExportDeprecated() const { return export_message_ && !export_error_; };
+ bool ExportObsolete() const { return export_error_; };
+ void SetExportDeprecated(StringPiece msg) {
+ export_message_.reset(new string(msg.as_string()));
+ }
+ void SetExportObsolete(StringPiece msg) {
+ export_message_.reset(new string(msg.as_string()));
+ export_error_ = true;
+ }
+
+ void ProfileMakefile(StringPiece mk) {
+ profiled_files_.emplace_back(mk.as_string());
+ }
+
+ private:
+ Var* EvalRHS(Symbol lhs,
+ Value* rhs,
+ StringPiece orig_rhs,
+ AssignOp op,
+ bool is_override,
+ bool* needs_assign);
+ void DoInclude(const string& fname);
+
+ Var* LookupVarGlobal(Symbol name);
+
+ // Equivalent to LookupVarInCurrentScope, but doesn't mark as used.
+ Var* PeekVarInCurrentScope(Symbol name);
+
+ void MarkVarsReadonly(Value* var_list);
+
+ void EvalRuleSpecificAssign(const vector<Symbol>& targets,
+ const RuleStmt* stmt,
+ const StringPiece& lhs_string,
+ size_t separator_pos);
+
+ unordered_map<Symbol, Vars*> rule_vars_;
+ vector<const Rule*> rules_;
+ unordered_map<Symbol, bool> exports_;
+
+ Rule* last_rule_;
+ Vars* current_scope_;
+
+ Loc loc_;
+ bool is_bootstrap_;
+ bool is_commandline_;
+
+ std::vector<Loc> include_stack_;
+
+ bool avoid_io_;
+ // This value tracks the nest level of make expressions. For
+ // example, $(YYY) in $(XXX $(YYY)) is evaluated with depth==2.
+ // This will be used to disallow $(shell) in other make constructs.
+ int eval_depth_;
+ // Commands which should run at ninja-time (i.e., info, warning, and
+ // error).
+ vector<string> delayed_output_commands_;
+
+ Symbol posix_sym_;
+ bool is_posix_;
+
+ void* stack_addr_;
+ size_t stack_size_;
+ void* lowest_stack_;
+ Loc lowest_loc_;
+
+ unique_ptr<string> export_message_;
+ bool export_error_;
+
+ vector<string> profiled_files_;
+
+ static SymbolSet used_undefined_vars_;
+};
+
+#endif // EVAL_H_
diff --git a/src/exec.cc b/src/exec.cc
new file mode 100644
index 0000000..75f5358
--- /dev/null
+++ b/src/exec.cc
@@ -0,0 +1,151 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "exec.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "command.h"
+#include "dep.h"
+#include "eval.h"
+#include "expr.h"
+#include "fileutil.h"
+#include "flags.h"
+#include "log.h"
+#include "string_piece.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "var.h"
+
+namespace {
+
+const double kNotExist = -2.0;
+const double kProcessing = -1.0;
+
+class Executor {
+ public:
+ explicit Executor(Evaluator* ev) : ce_(ev), num_commands_(0) {
+ shell_ = ev->GetShell();
+ shellflag_ = ev->GetShellFlag();
+ }
+
+ double ExecNode(DepNode* n, DepNode* needed_by) {
+ auto found = done_.find(n->output);
+ if (found != done_.end()) {
+ if (found->second == kProcessing) {
+ WARN("Circular %s <- %s dependency dropped.",
+ needed_by ? needed_by->output.c_str() : "(null)",
+ n->output.c_str());
+ }
+ return found->second;
+ }
+ done_[n->output] = kProcessing;
+ double output_ts = GetTimestamp(n->output.c_str());
+
+ LOG("ExecNode: %s for %s", n->output.c_str(),
+ needed_by ? needed_by->output.c_str() : "(null)");
+
+ if (!n->has_rule && output_ts == kNotExist && !n->is_phony) {
+ if (needed_by) {
+ ERROR("*** No rule to make target '%s', needed by '%s'.",
+ n->output.c_str(), needed_by->output.c_str());
+ } else {
+ ERROR("*** No rule to make target '%s'.", n->output.c_str());
+ }
+ }
+
+ double latest = kProcessing;
+ for (auto const& d : n->order_onlys) {
+ if (Exists(d.second->output.str())) {
+ continue;
+ }
+ double ts = ExecNode(d.second, n);
+ if (latest < ts)
+ latest = ts;
+ }
+
+ for (auto const& d : n->deps) {
+ double ts = ExecNode(d.second, n);
+ if (latest < ts)
+ latest = ts;
+ }
+
+ if (output_ts >= latest && !n->is_phony) {
+ done_[n->output] = output_ts;
+ return output_ts;
+ }
+
+ vector<Command*> commands;
+ ce_.Eval(n, &commands);
+ for (Command* command : commands) {
+ num_commands_ += 1;
+ if (command->echo) {
+ printf("%s\n", command->cmd.c_str());
+ fflush(stdout);
+ }
+ if (!g_flags.is_dry_run) {
+ string out;
+ int result = RunCommand(shell_, shellflag_, command->cmd.c_str(),
+ RedirectStderr::STDOUT, &out);
+ printf("%s", out.c_str());
+ if (result != 0) {
+ if (command->ignore_error) {
+ fprintf(stderr, "[%s] Error %d (ignored)\n",
+ command->output.c_str(), WEXITSTATUS(result));
+ } else {
+ fprintf(stderr, "*** [%s] Error %d\n", command->output.c_str(),
+ WEXITSTATUS(result));
+ exit(1);
+ }
+ }
+ }
+ delete command;
+ }
+
+ done_[n->output] = output_ts;
+ return output_ts;
+ }
+
+ uint64_t Count() { return num_commands_; }
+
+ private:
+ CommandEvaluator ce_;
+ unordered_map<Symbol, double> done_;
+ string shell_;
+ string shellflag_;
+ uint64_t num_commands_;
+};
+
+} // namespace
+
+void Exec(const vector<NamedDepNode>& roots, Evaluator* ev) {
+ unique_ptr<Executor> executor(new Executor(ev));
+ for (auto const& root : roots) {
+ executor->ExecNode(root.second, NULL);
+ }
+ if (executor->Count() == 0) {
+ for (auto const& root : roots) {
+ printf("kati: Nothing to be done for `%s'.\n", root.first.c_str());
+ }
+ }
+}
diff --git a/src/exec.h b/src/exec.h
new file mode 100644
index 0000000..34fda96
--- /dev/null
+++ b/src/exec.h
@@ -0,0 +1,26 @@
+// Copyright 2015 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.
+
+#ifndef EXEC_H_
+#define EXEC_H_
+
+#include <vector>
+
+using namespace std;
+#include "dep.h"
+class Evaluator;
+
+void Exec(const vector<NamedDepNode>& roots, Evaluator* ev);
+
+#endif // EXEC_H_
diff --git a/src/expr.cc b/src/expr.cc
new file mode 100644
index 0000000..e0f5be1
--- /dev/null
+++ b/src/expr.cc
@@ -0,0 +1,591 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "expr.h"
+
+#include <vector>
+
+#include "eval.h"
+#include "func.h"
+#include "log.h"
+#include "stringprintf.h"
+#include "strutil.h"
+#include "var.h"
+
+Evaluable::Evaluable() {}
+
+Evaluable::~Evaluable() {}
+
+string Evaluable::Eval(Evaluator* ev) const {
+ string s;
+ Eval(ev, &s);
+ return s;
+}
+
+Value::Value() {}
+
+Value::~Value() {}
+
+string Value::DebugString(const Value* v) {
+ return v ? NoLineBreak(v->DebugString_()) : "(null)";
+}
+
+class Literal : public Value {
+ public:
+ explicit Literal(StringPiece s) : s_(s) {}
+
+ StringPiece val() const { return s_; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ s->append(s_.begin(), s_.end());
+ }
+
+ virtual bool IsLiteral() const override { return true; }
+ virtual StringPiece GetLiteralValueUnsafe() const override { return s_; }
+
+ virtual string DebugString_() const override { return s_.as_string(); }
+
+ private:
+ StringPiece s_;
+};
+
+class ValueList : public Value {
+ public:
+ ValueList() {}
+
+ ValueList(Value* v1, Value* v2, Value* v3) : ValueList() {
+ vals_.reserve(3);
+ vals_.push_back(v1);
+ vals_.push_back(v2);
+ vals_.push_back(v3);
+ }
+
+ ValueList(Value* v1, Value* v2) : ValueList() {
+ vals_.reserve(2);
+ vals_.push_back(v1);
+ vals_.push_back(v2);
+ }
+
+ ValueList(vector<Value*>* values) : ValueList() {
+ values->shrink_to_fit();
+ values->swap(vals_);
+ }
+
+ virtual ~ValueList() {
+ for (Value* v : vals_) {
+ delete v;
+ }
+ }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ for (Value* v : vals_) {
+ v->Eval(ev, s);
+ }
+ }
+
+ virtual string DebugString_() const override {
+ string r;
+ for (Value* v : vals_) {
+ if (r.empty()) {
+ r += "ValueList(";
+ } else {
+ r += ", ";
+ }
+ r += DebugString(v);
+ }
+ if (!r.empty())
+ r += ")";
+ return r;
+ }
+
+ private:
+ vector<Value*> vals_;
+};
+
+class SymRef : public Value {
+ public:
+ explicit SymRef(Symbol n) : name_(n) {}
+ virtual ~SymRef() {}
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ Var* v = ev->LookupVar(name_);
+ v->Used(ev, name_);
+ v->Eval(ev, s);
+ }
+
+ virtual string DebugString_() const override {
+ return StringPrintf("SymRef(%s)", name_.c_str());
+ }
+
+ private:
+ Symbol name_;
+};
+
+class VarRef : public Value {
+ public:
+ explicit VarRef(Value* n) : name_(n) {}
+ virtual ~VarRef() { delete name_; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ ev->IncrementEvalDepth();
+ const string&& name = name_->Eval(ev);
+ ev->DecrementEvalDepth();
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
+ v->Used(ev, sym);
+ v->Eval(ev, s);
+ }
+
+ virtual string DebugString_() const override {
+ return StringPrintf("VarRef(%s)", Value::DebugString(name_).c_str());
+ }
+
+ private:
+ Value* name_;
+};
+
+class VarSubst : public Value {
+ public:
+ explicit VarSubst(Value* n, Value* p, Value* s)
+ : name_(n), pat_(p), subst_(s) {}
+ virtual ~VarSubst() {
+ delete name_;
+ delete pat_;
+ delete subst_;
+ }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ ev->IncrementEvalDepth();
+ const string&& name = name_->Eval(ev);
+ Symbol sym = Intern(name);
+ Var* v = ev->LookupVar(sym);
+ const string&& pat_str = pat_->Eval(ev);
+ const string&& subst = subst_->Eval(ev);
+ ev->DecrementEvalDepth();
+ v->Used(ev, sym);
+ const string&& value = v->Eval(ev);
+ WordWriter ww(s);
+ Pattern pat(pat_str);
+ for (StringPiece tok : WordScanner(value)) {
+ ww.MaybeAddWhitespace();
+ pat.AppendSubstRef(tok, subst, s);
+ }
+ }
+
+ virtual string DebugString_() const override {
+ return StringPrintf("VarSubst(%s:%s=%s)", Value::DebugString(name_).c_str(),
+ Value::DebugString(pat_).c_str(),
+ Value::DebugString(subst_).c_str());
+ }
+
+ private:
+ Value* name_;
+ Value* pat_;
+ Value* subst_;
+};
+
+class Func : public Value {
+ public:
+ explicit Func(FuncInfo* fi) : fi_(fi) {}
+
+ ~Func() {
+ for (Value* a : args_)
+ delete a;
+ }
+
+ virtual void Eval(Evaluator* ev, string* s) const override {
+ ev->CheckStack();
+ LOG("Invoke func %s(%s)", name(), JoinValues(args_, ",").c_str());
+ ev->IncrementEvalDepth();
+ fi_->func(args_, ev, s);
+ ev->DecrementEvalDepth();
+ }
+
+ virtual string DebugString_() const override {
+ return StringPrintf("Func(%s %s)", fi_->name,
+ JoinValues(args_, ",").c_str());
+ }
+
+ void AddArg(Value* v) { args_.push_back(v); }
+
+ const char* name() const { return fi_->name; }
+ int arity() const { return fi_->arity; }
+ int min_arity() const { return fi_->min_arity; }
+ bool trim_space() const { return fi_->trim_space; }
+ bool trim_right_space_1st() const { return fi_->trim_right_space_1st; }
+
+ private:
+ FuncInfo* fi_;
+ vector<Value*> args_;
+};
+
+static char CloseParen(char c) {
+ switch (c) {
+ case '(':
+ return ')';
+ case '{':
+ return '}';
+ }
+ return 0;
+}
+
+static size_t SkipSpaces(StringPiece s, const char* terms) {
+ for (size_t i = 0; i < s.size(); i++) {
+ char c = s[i];
+ if (strchr(terms, c))
+ return i;
+ if (!isspace(c)) {
+ if (c != '\\')
+ return i;
+ char n = s.get(i + 1);
+ if (n != '\r' && n != '\n')
+ return i;
+ }
+ }
+ return s.size();
+}
+
+Value* Value::NewExpr(Value* v1, Value* v2) {
+ return new ValueList(v1, v2);
+}
+
+Value* Value::NewExpr(Value* v1, Value* v2, Value* v3) {
+ return new ValueList(v1, v2, v3);
+}
+
+Value* Value::NewExpr(vector<Value*>* values) {
+ if (values->size() == 1) {
+ Value* v = (*values)[0];
+ values->clear();
+ return v;
+ }
+ return new ValueList(values);
+}
+
+Value* Value::NewLiteral(StringPiece s) {
+ return new Literal(s);
+}
+
+bool ShouldHandleComments(ParseExprOpt opt) {
+ return opt != ParseExprOpt::DEFINE && opt != ParseExprOpt::COMMAND;
+}
+
+void ParseFunc(const Loc& loc,
+ Func* f,
+ StringPiece s,
+ size_t i,
+ char* terms,
+ size_t* index_out) {
+ terms[1] = ',';
+ terms[2] = '\0';
+ i += SkipSpaces(s.substr(i), terms);
+ if (i == s.size()) {
+ *index_out = i;
+ return;
+ }
+
+ int nargs = 1;
+ while (true) {
+ if (f->arity() && nargs >= f->arity()) {
+ terms[1] = '\0'; // Drop ','.
+ }
+
+ if (f->trim_space()) {
+ for (; i < s.size(); i++) {
+ if (isspace(s[i]))
+ continue;
+ if (s[i] == '\\') {
+ char c = s.get(i + 1);
+ if (c == '\r' || c == '\n')
+ continue;
+ }
+ break;
+ }
+ }
+ const bool trim_right_space =
+ (f->trim_space() || (nargs == 1 && f->trim_right_space_1st()));
+ size_t n;
+ Value* v = ParseExprImpl(loc, s.substr(i), terms, ParseExprOpt::FUNC, &n,
+ trim_right_space);
+ // TODO: concatLine???
+ f->AddArg(v);
+ i += n;
+ if (i == s.size()) {
+ ERROR_LOC(loc,
+ "*** unterminated call to function '%s': "
+ "missing '%c'.",
+ f->name(), terms[0]);
+ }
+ nargs++;
+ if (s[i] == terms[0]) {
+ i++;
+ break;
+ }
+ i++; // Should be ','.
+ if (i == s.size())
+ break;
+ }
+
+ if (nargs <= f->min_arity()) {
+ ERROR_LOC(loc,
+ "*** insufficient number of arguments (%d) to function `%s'.",
+ nargs - 1, f->name());
+ }
+
+ *index_out = i;
+ return;
+}
+
+Value* ParseDollar(const Loc& loc, StringPiece s, size_t* index_out) {
+ CHECK(s.size() >= 2);
+ CHECK(s[0] == '$');
+ CHECK(s[1] != '$');
+
+ char cp = CloseParen(s[1]);
+ if (cp == 0) {
+ *index_out = 2;
+ return new SymRef(Intern(s.substr(1, 1)));
+ }
+
+ char terms[] = {cp, ':', ' ', 0};
+ for (size_t i = 2;;) {
+ size_t n;
+ Value* vname =
+ ParseExprImpl(loc, s.substr(i), terms, ParseExprOpt::NORMAL, &n);
+ i += n;
+ if (s[i] == cp) {
+ *index_out = i + 1;
+ if (vname->IsLiteral()) {
+ Literal* lit = static_cast<Literal*>(vname);
+ Symbol sym = Intern(lit->val());
+ if (g_flags.enable_kati_warnings) {
+ size_t found = sym.str().find_first_of(" ({");
+ if (found != string::npos) {
+ KATI_WARN_LOC(loc, "*warning*: variable lookup with '%c': %.*s",
+ sym.str()[found], SPF(s));
+ }
+ }
+ Value* r = new SymRef(sym);
+ delete lit;
+ return r;
+ }
+ return new VarRef(vname);
+ }
+
+ if (s[i] == ' ' || s[i] == '\\') {
+ // ${func ...}
+ if (vname->IsLiteral()) {
+ Literal* lit = static_cast<Literal*>(vname);
+ if (FuncInfo* fi = GetFuncInfo(lit->val())) {
+ delete lit;
+ Func* func = new Func(fi);
+ ParseFunc(loc, func, s, i + 1, terms, index_out);
+ return func;
+ } else {
+ KATI_WARN_LOC(loc, "*warning*: unknown make function '%.*s': %.*s",
+ SPF(lit->val()), SPF(s));
+ }
+ }
+
+ // Not a function. Drop ' ' from |terms| and parse it
+ // again. This is inefficient, but this code path should be
+ // rarely used.
+ delete vname;
+ terms[2] = 0;
+ i = 2;
+ continue;
+ }
+
+ if (s[i] == ':') {
+ terms[2] = '\0';
+ terms[1] = '=';
+ size_t n;
+ Value* pat =
+ ParseExprImpl(loc, s.substr(i + 1), terms, ParseExprOpt::NORMAL, &n);
+ i += 1 + n;
+ if (s[i] == cp) {
+ *index_out = i + 1;
+ return new VarRef(Value::NewExpr(vname, new Literal(":"), pat));
+ }
+
+ terms[1] = '\0';
+ Value* subst =
+ ParseExprImpl(loc, s.substr(i + 1), terms, ParseExprOpt::NORMAL, &n);
+ i += 1 + n;
+ *index_out = i + 1;
+ return new VarSubst(vname, pat, subst);
+ }
+
+ // GNU make accepts expressions like $((). See unmatched_paren*.mk
+ // for detail.
+ size_t found = s.find(cp);
+ if (found != string::npos) {
+ KATI_WARN_LOC(loc, "*warning*: unmatched parentheses: %.*s", SPF(s));
+ *index_out = s.size();
+ return new SymRef(Intern(s.substr(2, found - 2)));
+ }
+ ERROR_LOC(loc, "*** unterminated variable reference.");
+ }
+}
+
+Value* ParseExprImpl(const Loc& loc,
+ StringPiece s,
+ const char* terms,
+ ParseExprOpt opt,
+ size_t* index_out,
+ bool trim_right_space) {
+ if (s.get(s.size() - 1) == '\r')
+ s.remove_suffix(1);
+
+ size_t b = 0;
+ char save_paren = 0;
+ int paren_depth = 0;
+ size_t i;
+ vector<Value*> list;
+ for (i = 0; i < s.size(); i++) {
+ char c = s[i];
+ if (terms && strchr(terms, c) && !save_paren) {
+ break;
+ }
+
+ // Handle a comment.
+ if (!terms && c == '#' && ShouldHandleComments(opt)) {
+ if (i > b)
+ list.push_back(new Literal(s.substr(b, i - b)));
+ bool was_backslash = false;
+ for (; i < s.size() && !(s[i] == '\n' && !was_backslash); i++) {
+ was_backslash = !was_backslash && s[i] == '\\';
+ }
+ *index_out = i;
+ return Value::NewExpr(&list);
+ }
+
+ if (c == '$') {
+ if (i + 1 >= s.size()) {
+ break;
+ }
+
+ if (i > b)
+ list.push_back(new Literal(s.substr(b, i - b)));
+
+ if (s[i + 1] == '$') {
+ list.push_back(new Literal(StringPiece("$")));
+ i += 1;
+ b = i + 1;
+ continue;
+ }
+
+ if (terms && strchr(terms, s[i + 1])) {
+ *index_out = i + 1;
+ return Value::NewExpr(&list);
+ }
+
+ size_t n;
+ list.push_back(ParseDollar(loc, s.substr(i), &n));
+ i += n;
+ b = i;
+ i--;
+ continue;
+ }
+
+ if ((c == '(' || c == '{') && opt == ParseExprOpt::FUNC) {
+ char cp = CloseParen(c);
+ if (terms && terms[0] == cp) {
+ paren_depth++;
+ save_paren = cp;
+ terms++;
+ } else if (cp == save_paren) {
+ paren_depth++;
+ }
+ continue;
+ }
+
+ if (c == save_paren) {
+ paren_depth--;
+ if (paren_depth == 0) {
+ terms--;
+ save_paren = 0;
+ }
+ }
+
+ if (c == '\\' && i + 1 < s.size() && opt != ParseExprOpt::COMMAND) {
+ char n = s[i + 1];
+ if (n == '\\') {
+ i++;
+ continue;
+ }
+ if (n == '#' && ShouldHandleComments(opt)) {
+ list.push_back(new Literal(s.substr(b, i - b)));
+ i++;
+ b = i;
+ continue;
+ }
+ if (n == '\r' || n == '\n') {
+ if (terms && strchr(terms, ' ')) {
+ break;
+ }
+ if (i > b) {
+ list.push_back(new Literal(TrimRightSpace(s.substr(b, i - b))));
+ }
+ list.push_back(new Literal(StringPiece(" ")));
+ // Skip the current escaped newline
+ i += 2;
+ if (n == '\r' && s.get(i) == '\n')
+ i++;
+ // Then continue skipping escaped newlines, spaces, and tabs
+ for (; i < s.size(); i++) {
+ if (s[i] == '\\' && (s.get(i + 1) == '\r' || s.get(i + 1) == '\n')) {
+ i++;
+ continue;
+ }
+ if (s[i] != ' ' && s[i] != '\t') {
+ break;
+ }
+ }
+ b = i;
+ i--;
+ }
+ }
+ }
+
+ if (i > b) {
+ StringPiece rest = s.substr(b, i - b);
+ if (trim_right_space)
+ rest = TrimRightSpace(rest);
+ if (!rest.empty())
+ list.push_back(new Literal(rest));
+ }
+ *index_out = i;
+ return Value::NewExpr(&list);
+}
+
+Value* ParseExpr(const Loc& loc, StringPiece s, ParseExprOpt opt) {
+ size_t n;
+ return ParseExprImpl(loc, s, NULL, opt, &n);
+}
+
+string JoinValues(const vector<Value*>& vals, const char* sep) {
+ vector<string> val_strs;
+ for (Value* v : vals) {
+ val_strs.push_back(Value::DebugString(v));
+ }
+ return JoinStrings(val_strs, sep);
+}
diff --git a/src/expr.h b/src/expr.h
new file mode 100644
index 0000000..2fe9527
--- /dev/null
+++ b/src/expr.h
@@ -0,0 +1,77 @@
+// Copyright 2015 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.
+
+#ifndef EXPR_H_
+#define EXPR_H_
+
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+
+using namespace std;
+
+class Evaluator;
+struct Loc;
+
+class Evaluable {
+ public:
+ virtual void Eval(Evaluator* ev, string* s) const = 0;
+ string Eval(Evaluator*) const;
+
+ protected:
+ Evaluable();
+ virtual ~Evaluable();
+};
+
+class Value : public Evaluable {
+ public:
+ // All NewExpr calls take ownership of the Value instances.
+ static Value* NewExpr(Value* v1, Value* v2);
+ static Value* NewExpr(Value* v1, Value* v2, Value* v3);
+ static Value* NewExpr(vector<Value*>* values);
+
+ static Value* NewLiteral(StringPiece s);
+ virtual ~Value();
+ virtual bool IsLiteral() const { return false; }
+ // Only safe after IsLiteral() returns true.
+ virtual StringPiece GetLiteralValueUnsafe() const { return ""; }
+
+ static string DebugString(const Value*);
+
+ protected:
+ Value();
+ virtual string DebugString_() const = 0;
+};
+
+enum struct ParseExprOpt {
+ NORMAL = 0,
+ DEFINE,
+ COMMAND,
+ FUNC,
+};
+
+Value* ParseExprImpl(const Loc& loc,
+ StringPiece s,
+ const char* terms,
+ ParseExprOpt opt,
+ size_t* index_out,
+ bool trim_right_space = false);
+Value* ParseExpr(const Loc& loc,
+ StringPiece s,
+ ParseExprOpt opt = ParseExprOpt::NORMAL);
+
+string JoinValues(const vector<Value*>& vals, const char* sep);
+
+#endif // EXPR_H_
diff --git a/src/file.cc b/src/file.cc
new file mode 100644
index 0000000..9ef6708
--- /dev/null
+++ b/src/file.cc
@@ -0,0 +1,62 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "file.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "fileutil.h"
+#include "log.h"
+#include "parser.h"
+#include "stmt.h"
+
+Makefile::Makefile(const string& filename)
+ : mtime_(0), filename_(filename), exists_(false) {
+ int fd = open(filename.c_str(), O_RDONLY);
+ if (fd < 0) {
+ return;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ PERROR("fstat failed for %s", filename.c_str());
+ }
+
+ size_t len = st.st_size;
+ mtime_ = st.st_mtime;
+ buf_.resize(len);
+ exists_ = true;
+ ssize_t r = HANDLE_EINTR(read(fd, &buf_[0], len));
+ if (r != static_cast<ssize_t>(len)) {
+ if (r < 0)
+ PERROR("read failed for %s", filename.c_str());
+ ERROR("Unexpected read length=%zd expected=%zu", r, len);
+ }
+
+ if (close(fd) < 0) {
+ PERROR("close failed for %s", filename.c_str());
+ }
+
+ Parse(this);
+}
+
+Makefile::~Makefile() {
+ for (Stmt* stmt : stmts_)
+ delete stmt;
+}
diff --git a/src/file.h b/src/file.h
new file mode 100644
index 0000000..6af6696
--- /dev/null
+++ b/src/file.h
@@ -0,0 +1,48 @@
+// Copyright 2015 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.
+
+#ifndef FILE_H_
+#define FILE_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+struct Stmt;
+
+class Makefile {
+ public:
+ explicit Makefile(const string& filename);
+ ~Makefile();
+
+ const string& buf() const { return buf_; }
+ const string& filename() const { return filename_; }
+
+ const vector<Stmt*>& stmts() const { return stmts_; }
+ vector<Stmt*>* mutable_stmts() { return &stmts_; }
+
+ bool Exists() const { return exists_; }
+
+ private:
+ string buf_;
+ uint64_t mtime_;
+ string filename_;
+ vector<Stmt*> stmts_;
+ bool exists_;
+};
+
+#endif // FILE_H_
diff --git a/src/file_cache.cc b/src/file_cache.cc
new file mode 100644
index 0000000..fe16c3b
--- /dev/null
+++ b/src/file_cache.cc
@@ -0,0 +1,65 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "file_cache.h"
+
+#include <unordered_map>
+
+#include "file.h"
+
+static MakefileCacheManager* g_instance;
+
+MakefileCacheManager::MakefileCacheManager() {}
+
+MakefileCacheManager::~MakefileCacheManager() {}
+
+MakefileCacheManager* MakefileCacheManager::Get() {
+ return g_instance;
+}
+
+class MakefileCacheManagerImpl : public MakefileCacheManager {
+ public:
+ MakefileCacheManagerImpl() { g_instance = this; }
+
+ virtual ~MakefileCacheManagerImpl() {
+ for (auto p : cache_) {
+ delete p.second;
+ }
+ }
+
+ virtual Makefile* ReadMakefile(const string& filename) override {
+ Makefile* result = NULL;
+ auto p = cache_.emplace(filename, result);
+ if (p.second) {
+ p.first->second = result = new Makefile(filename);
+ } else {
+ result = p.first->second;
+ }
+ return result;
+ }
+
+ virtual void GetAllFilenames(unordered_set<string>* out) override {
+ for (const auto& p : cache_)
+ out->insert(p.first);
+ }
+
+ private:
+ unordered_map<string, Makefile*> cache_;
+};
+
+MakefileCacheManager* NewMakefileCacheManager() {
+ return new MakefileCacheManagerImpl();
+}
diff --git a/src/file_cache.h b/src/file_cache.h
new file mode 100644
index 0000000..fac2077
--- /dev/null
+++ b/src/file_cache.h
@@ -0,0 +1,40 @@
+// Copyright 2015 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.
+
+#ifndef FILE_CACHE_H_
+#define FILE_CACHE_H_
+
+#include <string>
+#include <unordered_set>
+
+using namespace std;
+
+class Makefile;
+
+class MakefileCacheManager {
+ public:
+ virtual ~MakefileCacheManager();
+
+ virtual Makefile* ReadMakefile(const string& filename) = 0;
+ virtual void GetAllFilenames(unordered_set<string>* out) = 0;
+
+ static MakefileCacheManager* Get();
+
+ protected:
+ MakefileCacheManager();
+};
+
+MakefileCacheManager* NewMakefileCacheManager();
+
+#endif // FILE_CACHE_H_
diff --git a/src/fileutil.cc b/src/fileutil.cc
new file mode 100644
index 0000000..7ebb8ec
--- /dev/null
+++ b/src/fileutil.cc
@@ -0,0 +1,211 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "fileutil.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <limits.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#if defined(__APPLE__)
+#include <mach-o/dyld.h>
+#endif
+
+#include <unordered_map>
+
+#include "log.h"
+#include "strutil.h"
+
+bool Exists(StringPiece filename) {
+ CHECK(filename.size() < PATH_MAX);
+ struct stat st;
+ if (stat(filename.as_string().c_str(), &st) < 0) {
+ return false;
+ }
+ return true;
+}
+
+double GetTimestampFromStat(const struct stat& st) {
+#if defined(__linux__)
+ return st.st_mtime + st.st_mtim.tv_nsec * 0.001 * 0.001 * 0.001;
+#else
+ return st.st_mtime;
+#endif
+}
+
+double GetTimestamp(StringPiece filename) {
+ CHECK(filename.size() < PATH_MAX);
+ struct stat st;
+ if (stat(filename.as_string().c_str(), &st) < 0) {
+ return -2.0;
+ }
+ return GetTimestampFromStat(st);
+}
+
+int RunCommand(const string& shell,
+ const string& shellflag,
+ const string& cmd,
+ RedirectStderr redirect_stderr,
+ string* s) {
+ const char* argv[] = {NULL, NULL, NULL, NULL};
+ string cmd_with_shell;
+ if (shell[0] != '/' || shell.find_first_of(" $") != string::npos) {
+ string cmd_escaped = cmd;
+ EscapeShell(&cmd_escaped);
+ cmd_with_shell = shell + " " + shellflag + " \"" + cmd_escaped + "\"";
+ argv[0] = "/bin/sh";
+ argv[1] = "-c";
+ argv[2] = cmd_with_shell.c_str();
+ } else {
+ // If the shell isn't complicated, we don't need to wrap in /bin/sh
+ argv[0] = shell.c_str();
+ argv[1] = shellflag.c_str();
+ argv[2] = cmd.c_str();
+ }
+
+ int pipefd[2];
+ if (pipe(pipefd) != 0)
+ PERROR("pipe failed");
+ int pid;
+ if ((pid = vfork())) {
+ int status;
+ close(pipefd[1]);
+ while (true) {
+ int result = waitpid(pid, &status, WNOHANG);
+ if (result < 0)
+ PERROR("waitpid failed");
+
+ while (true) {
+ char buf[4096];
+ ssize_t r = HANDLE_EINTR(read(pipefd[0], buf, 4096));
+ if (r < 0)
+ PERROR("read failed");
+ if (r == 0)
+ break;
+ s->append(buf, buf + r);
+ }
+
+ if (result != 0) {
+ break;
+ }
+ }
+ close(pipefd[0]);
+
+ return status;
+ } else {
+ close(pipefd[0]);
+ if (redirect_stderr == RedirectStderr::STDOUT) {
+ if (dup2(pipefd[1], 2) < 0)
+ PERROR("dup2 failed");
+ } else if (redirect_stderr == RedirectStderr::DEV_NULL) {
+ int fd = open("/dev/null", O_WRONLY);
+ if (dup2(fd, 2) < 0)
+ PERROR("dup2 failed");
+ close(fd);
+ }
+ if (dup2(pipefd[1], 1) < 0)
+ PERROR("dup2 failed");
+ close(pipefd[1]);
+
+ execvp(argv[0], const_cast<char**>(argv));
+ PLOG("execvp for %s failed", argv[0]);
+ kill(getppid(), SIGTERM);
+ _exit(1);
+ }
+}
+
+void GetExecutablePath(string* path) {
+#if defined(__linux__)
+ char mypath[PATH_MAX + 1];
+ ssize_t l = readlink("/proc/self/exe", mypath, PATH_MAX);
+ if (l < 0) {
+ PERROR("readlink for /proc/self/exe");
+ }
+ mypath[l] = '\0';
+ *path = mypath;
+#elif defined(__APPLE__)
+ char mypath[PATH_MAX + 1];
+ uint32_t size = PATH_MAX;
+ if (_NSGetExecutablePath(mypath, &size) != 0) {
+ ERROR("_NSGetExecutablePath failed");
+ }
+ mypath[size] = 0;
+ *path = mypath;
+#else
+#error "Unsupported OS"
+#endif
+}
+
+namespace {
+
+class GlobCache {
+ public:
+ ~GlobCache() { Clear(); }
+
+ void Get(const char* pat, vector<string>** files) {
+ auto p = cache_.emplace(pat, nullptr);
+ if (p.second) {
+ vector<string>* files = p.first->second = new vector<string>;
+ if (strcspn(pat, "?*[\\") != strlen(pat)) {
+ glob_t gl;
+ glob(pat, 0, NULL, &gl);
+ for (size_t i = 0; i < gl.gl_pathc; i++) {
+ files->push_back(gl.gl_pathv[i]);
+ }
+ globfree(&gl);
+ } else {
+ if (Exists(pat))
+ files->push_back(pat);
+ }
+ }
+ *files = p.first->second;
+ }
+
+ const unordered_map<string, vector<string>*>& GetAll() const {
+ return cache_;
+ }
+
+ void Clear() {
+ for (auto& p : cache_) {
+ delete p.second;
+ }
+ cache_.clear();
+ }
+
+ private:
+ unordered_map<string, vector<string>*> cache_;
+};
+
+static GlobCache g_gc;
+
+} // namespace
+
+void Glob(const char* pat, vector<string>** files) {
+ g_gc.Get(pat, files);
+}
+
+const unordered_map<string, vector<string>*>& GetAllGlobCache() {
+ return g_gc.GetAll();
+}
+
+void ClearGlobCache() {
+ g_gc.Clear();
+}
diff --git a/src/fileutil.h b/src/fileutil.h
new file mode 100644
index 0000000..ba5b9b6
--- /dev/null
+++ b/src/fileutil.h
@@ -0,0 +1,62 @@
+// Copyright 2015 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.
+
+#ifndef FILEUTIL_H_
+#define FILEUTIL_H_
+
+#include <errno.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "string_piece.h"
+
+using namespace std;
+
+bool Exists(StringPiece f);
+double GetTimestampFromStat(const struct stat& st);
+double GetTimestamp(StringPiece f);
+
+enum struct RedirectStderr {
+ NONE,
+ STDOUT,
+ DEV_NULL,
+};
+
+int RunCommand(const string& shell,
+ const string& shellflag,
+ const string& cmd,
+ RedirectStderr redirect_stderr,
+ string* out);
+
+void GetExecutablePath(string* path);
+
+void Glob(const char* pat, vector<string>** files);
+
+const unordered_map<string, vector<string>*>& GetAllGlobCache();
+
+void ClearGlobCache();
+
+#define HANDLE_EINTR(x) \
+ ({ \
+ int r; \
+ do { \
+ r = (x); \
+ } while (r == -1 && errno == EINTR); \
+ r; \
+ })
+
+#endif // FILEUTIL_H_
diff --git a/src/fileutil_bench.cc b/src/fileutil_bench.cc
new file mode 100644
index 0000000..429d004
--- /dev/null
+++ b/src/fileutil_bench.cc
@@ -0,0 +1,45 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+#include <benchmark/benchmark.h>
+
+#include <cstdio>
+
+#include "fileutil.h"
+
+static void BM_RunCommand(benchmark::State& state) {
+ std::string shell = "/bin/bash";
+ std::string shellflag = "-c";
+ std::string cmd = "echo $((1+3))";
+ while (state.KeepRunning()) {
+ std::string result;
+ RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, &result);
+ }
+}
+BENCHMARK(BM_RunCommand);
+
+static void BM_RunCommand_ComplexShell(benchmark::State& state) {
+ std::string shell = "/bin/bash ";
+ std::string shellflag = "-c";
+ std::string cmd = "echo $((1+3))";
+ while (state.KeepRunning()) {
+ std::string result;
+ RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, &result);
+ }
+}
+BENCHMARK(BM_RunCommand_ComplexShell);
+
+BENCHMARK_MAIN();
diff --git a/src/find.cc b/src/find.cc
new file mode 100644
index 0000000..b693449
--- /dev/null
+++ b/src/find.cc
@@ -0,0 +1,1168 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "find.h"
+
+#include <dirent.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+//#undef NOLOG
+
+#include "fileutil.h"
+#include "log.h"
+#include "stats.h"
+#include "string_piece.h"
+#include "strutil.h"
+#include "timeutil.h"
+
+#define FIND_WARN_LOC(...) \
+ do { \
+ if (g_flags.werror_find_emulator) { \
+ ERROR_LOC(__VA_ARGS__); \
+ } else { \
+ WARN_LOC(__VA_ARGS__); \
+ } \
+ } while (0)
+
+static unsigned int find_emulator_node_cnt = 0;
+
+class FindCond {
+ public:
+ virtual ~FindCond() = default;
+ virtual bool IsTrue(const string& path, unsigned char type) const = 0;
+ virtual bool Countable() const = 0;
+ virtual unsigned Count() const = 0;
+
+ protected:
+ FindCond() = default;
+};
+
+namespace {
+
+class NameCond : public FindCond {
+ public:
+ explicit NameCond(const string& n) : name_(n) {
+ has_wildcard_ = (n.find_first_of("?*[") != string::npos);
+ }
+ virtual bool IsTrue(const string& path, unsigned char) const override {
+ return fnmatch(name_.c_str(), Basename(path).data(), 0) == 0;
+ }
+ virtual bool Countable() const override { return !has_wildcard_; }
+ virtual unsigned Count() const override { return 1; }
+
+ private:
+ string name_;
+ bool has_wildcard_;
+};
+
+class TypeCond : public FindCond {
+ public:
+ explicit TypeCond(unsigned char t) : type_(t) {}
+ virtual bool IsTrue(const string&, unsigned char type) const override {
+ return type == type_;
+ }
+ virtual bool Countable() const override { return false; }
+ virtual unsigned Count() const override { return 0; }
+
+ private:
+ unsigned char type_;
+};
+
+class NotCond : public FindCond {
+ public:
+ NotCond(FindCond* c) : c_(c) {}
+ virtual bool IsTrue(const string& path, unsigned char type) const override {
+ return !c_->IsTrue(path, type);
+ }
+ virtual bool Countable() const override { return false; }
+ virtual unsigned Count() const override { return 0; }
+
+ private:
+ unique_ptr<FindCond> c_;
+};
+
+class AndCond : public FindCond {
+ public:
+ AndCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
+ virtual bool IsTrue(const string& path, unsigned char type) const override {
+ if (c1_->IsTrue(path, type))
+ return c2_->IsTrue(path, type);
+ return false;
+ }
+ virtual bool Countable() const override { return false; }
+ virtual unsigned Count() const override { return 0; }
+
+ private:
+ unique_ptr<FindCond> c1_, c2_;
+};
+
+class OrCond : public FindCond {
+ public:
+ OrCond(FindCond* c1, FindCond* c2) : c1_(c1), c2_(c2) {}
+ virtual bool IsTrue(const string& path, unsigned char type) const override {
+ if (!c1_->IsTrue(path, type))
+ return c2_->IsTrue(path, type);
+ return true;
+ }
+ virtual bool Countable() const override {
+ return c1_->Countable() && c2_->Countable();
+ ;
+ }
+ virtual unsigned Count() const override {
+ return c1_->Count() + c2_->Count();
+ }
+
+ private:
+ unique_ptr<FindCond> c1_, c2_;
+};
+
+class DirentNode {
+ public:
+ virtual ~DirentNode() = default;
+
+ virtual const DirentNode* FindDir(StringPiece) const { return NULL; }
+ virtual bool FindNodes(const FindCommand&,
+ vector<pair<string, const DirentNode*>>&,
+ string*,
+ StringPiece) const {
+ return true;
+ }
+ virtual bool RunFind(const FindCommand& fc,
+ const Loc& loc,
+ int d,
+ string* path,
+ unordered_map<const DirentNode*, string>* cur_read_dirs,
+ vector<string>& out) const = 0;
+
+ virtual bool IsDirectory() const = 0;
+
+ const string& base() const { return base_; }
+
+ protected:
+ explicit DirentNode(const string& name) {
+ base_ = Basename(name).as_string();
+ }
+
+ void PrintIfNecessary(const FindCommand& fc,
+ const string& path,
+ unsigned char type,
+ int d,
+ vector<string>& out) const {
+ if (fc.print_cond && !fc.print_cond->IsTrue(path, type))
+ return;
+ if (d < fc.mindepth)
+ return;
+ out.push_back(path);
+ }
+
+ string base_;
+};
+
+class DirentFileNode : public DirentNode {
+ public:
+ DirentFileNode(const string& name, unsigned char type)
+ : DirentNode(name), type_(type) {}
+
+ virtual bool RunFind(const FindCommand& fc,
+ const Loc&,
+ int d,
+ string* path,
+ unordered_map<const DirentNode*, string>*,
+ vector<string>& out) const override {
+ PrintIfNecessary(fc, *path, type_, d, out);
+ return true;
+ }
+
+ virtual bool IsDirectory() const override { return false; }
+
+ private:
+ unsigned char type_;
+};
+
+struct ScopedReadDirTracker {
+ public:
+ ScopedReadDirTracker(const DirentNode* n,
+ const string& path,
+ unordered_map<const DirentNode*, string>* cur_read_dirs)
+ : n_(NULL), cur_read_dirs_(cur_read_dirs) {
+ const auto& p = cur_read_dirs->emplace(n, path);
+ if (p.second) {
+ n_ = n;
+ } else {
+ conflicted_ = p.first->second;
+ }
+ }
+
+ ~ScopedReadDirTracker() {
+ if (n_)
+ cur_read_dirs_->erase(n_);
+ }
+
+ bool ok() const { return conflicted_.empty(); }
+ const string& conflicted() const { return conflicted_; }
+
+ private:
+ string conflicted_;
+ const DirentNode* n_;
+ unordered_map<const DirentNode*, string>* cur_read_dirs_;
+};
+
+class DirentDirNode : public DirentNode {
+ public:
+ explicit DirentDirNode(const DirentDirNode* parent, const string& name)
+ : DirentNode(name), parent_(parent), name_(name) {}
+
+ ~DirentDirNode() {
+ for (auto& p : children_) {
+ delete p.second;
+ }
+ }
+
+ virtual const DirentNode* FindDir(StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
+ if (d.empty() || d == ".")
+ return this;
+ if (d == "..")
+ return parent_;
+
+ size_t index = d.find('/');
+ const string& p = d.substr(0, index).as_string();
+ if (p.empty() || p == ".")
+ return FindDir(d.substr(index + 1));
+ if (p == "..") {
+ if (parent_ == NULL)
+ return NULL;
+ return parent_->FindDir(d.substr(index + 1));
+ }
+
+ for (auto& child : children_) {
+ if (p == child.first) {
+ if (index == string::npos)
+ return child.second;
+ StringPiece nd = d.substr(index + 1);
+ return child.second->FindDir(nd);
+ }
+ }
+ return NULL;
+ }
+
+ virtual bool FindNodes(const FindCommand& fc,
+ vector<pair<string, const DirentNode*>>& results,
+ string* path,
+ StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
+ if (!path->empty())
+ path->append("/");
+
+ size_t orig_path_size = path->size();
+
+ size_t index = d.find('/');
+ const string& p = d.substr(0, index).as_string();
+
+ if (p.empty() || p == ".") {
+ path->append(p);
+ if (index == string::npos) {
+ results.emplace_back(*path, this);
+ return true;
+ }
+ return FindNodes(fc, results, path, d.substr(index + 1));
+ }
+ if (p == "..") {
+ if (parent_ == NULL) {
+ LOG("FindEmulator does not support leaving the source directory: %s",
+ path->c_str());
+ return false;
+ }
+ path->append(p);
+ if (index == string::npos) {
+ results.emplace_back(*path, parent_);
+ return true;
+ }
+ return parent_->FindNodes(fc, results, path, d.substr(index + 1));
+ }
+
+ bool is_wild = p.find_first_of("?*[") != string::npos;
+ if (is_wild) {
+ fc.read_dirs->insert(*path);
+ }
+
+ for (auto& child : children_) {
+ bool matches = false;
+ if (is_wild) {
+ matches = (fnmatch(p.c_str(), child.first.c_str(), FNM_PERIOD) == 0);
+ } else {
+ matches = (p == child.first);
+ }
+ if (matches) {
+ path->append(child.first);
+ if (index == string::npos) {
+ results.emplace_back(*path, child.second);
+ } else {
+ if (!child.second->FindNodes(fc, results, path,
+ d.substr(index + 1))) {
+ return false;
+ }
+ }
+ path->resize(orig_path_size);
+ }
+ }
+
+ return true;
+ }
+
+ virtual bool RunFind(const FindCommand& fc,
+ const Loc& loc,
+ int d,
+ string* path,
+ unordered_map<const DirentNode*, string>* cur_read_dirs,
+ vector<string>& out) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+
+ ScopedReadDirTracker srdt(this, *path, cur_read_dirs);
+ if (!srdt.ok()) {
+ FIND_WARN_LOC(loc,
+ "FindEmulator: find: File system loop detected; `%s' "
+ "is part of the same file system loop as `%s'.",
+ path->c_str(), srdt.conflicted().c_str());
+ return true;
+ }
+
+ fc.read_dirs->insert(*path);
+
+ if (fc.prune_cond && fc.prune_cond->IsTrue(*path, DT_DIR)) {
+ if (fc.type != FindCommandType::FINDLEAVES) {
+ out.push_back(*path);
+ }
+ return true;
+ }
+
+ PrintIfNecessary(fc, *path, DT_DIR, d, out);
+
+ if (d >= fc.depth)
+ return true;
+
+ size_t orig_path_size = path->size();
+ if (fc.type == FindCommandType::FINDLEAVES) {
+ size_t orig_out_size = out.size();
+ for (const auto& p : children_) {
+ DirentNode* c = p.second;
+ // We will handle directories later.
+ if (c->IsDirectory())
+ continue;
+ if ((*path)[path->size() - 1] != '/')
+ *path += '/';
+ *path += c->base();
+ if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
+ return false;
+ path->resize(orig_path_size);
+ }
+
+ // Found a leaf, stop the search.
+ if (orig_out_size != out.size()) {
+ // If we've found all possible files in this directory, we don't need
+ // to add a regen dependency on the directory, we just need to ensure
+ // that the files are not removed.
+ if (fc.print_cond->Countable() &&
+ fc.print_cond->Count() == out.size() - orig_out_size) {
+ fc.read_dirs->erase(*path);
+ for (unsigned i = orig_out_size; i < out.size(); i++) {
+ fc.found_files->push_back(out[i]);
+ }
+ }
+
+ return true;
+ }
+
+ for (const auto& p : children_) {
+ DirentNode* c = p.second;
+ if (!c->IsDirectory())
+ continue;
+ if ((*path)[path->size() - 1] != '/')
+ *path += '/';
+ *path += c->base();
+ if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
+ return false;
+ path->resize(orig_path_size);
+ }
+ } else {
+ for (const auto& p : children_) {
+ DirentNode* c = p.second;
+ if ((*path)[path->size() - 1] != '/')
+ *path += '/';
+ *path += c->base();
+ if (!c->RunFind(fc, loc, d + 1, path, cur_read_dirs, out))
+ return false;
+ path->resize(orig_path_size);
+ }
+ }
+ return true;
+ }
+
+ virtual bool IsDirectory() const override { return true; }
+
+ private:
+ static unsigned char GetDtTypeFromStat(const struct stat& st) {
+ if (S_ISREG(st.st_mode)) {
+ return DT_REG;
+ } else if (S_ISDIR(st.st_mode)) {
+ return DT_DIR;
+ } else if (S_ISCHR(st.st_mode)) {
+ return DT_CHR;
+ } else if (S_ISBLK(st.st_mode)) {
+ return DT_BLK;
+ } else if (S_ISFIFO(st.st_mode)) {
+ return DT_FIFO;
+ } else if (S_ISLNK(st.st_mode)) {
+ return DT_LNK;
+ } else if (S_ISSOCK(st.st_mode)) {
+ return DT_SOCK;
+ } else {
+ return DT_UNKNOWN;
+ }
+ }
+
+ static unsigned char GetDtType(const string& path) {
+ struct stat st;
+ if (lstat(path.c_str(), &st)) {
+ PERROR("stat for %s", path.c_str());
+ }
+ return GetDtTypeFromStat(st);
+ }
+
+ void initialize() const;
+
+ const DirentDirNode* parent_;
+
+ mutable vector<pair<string, DirentNode*>> children_;
+ mutable string name_;
+ mutable bool is_initialized_ = false;
+};
+
+class DirentSymlinkNode : public DirentNode {
+ public:
+ explicit DirentSymlinkNode(const DirentDirNode* parent, const string& name)
+ : DirentNode(name), name_(name), parent_(parent) {}
+
+ virtual const DirentNode* FindDir(StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+ if (errno_ == 0 && to_)
+ return to_->FindDir(d);
+ return NULL;
+ }
+
+ virtual bool FindNodes(const FindCommand& fc,
+ vector<pair<string, const DirentNode*>>& results,
+ string* path,
+ StringPiece d) const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+ if (errno_ != 0) {
+ return true;
+ }
+ if (!to_) {
+ LOG("FindEmulator does not support symlink %s", path->c_str());
+ return false;
+ }
+ if (to_->IsDirectory())
+ fc.read_dirs->insert(*path);
+ return to_->FindNodes(fc, results, path, d);
+ }
+
+ virtual bool RunFind(const FindCommand& fc,
+ const Loc& loc,
+ int d,
+ string* path,
+ unordered_map<const DirentNode*, string>* cur_read_dirs,
+ vector<string>& out) const override {
+ unsigned char type = DT_LNK;
+ if (fc.follows_symlinks && !is_initialized_) {
+ initialize();
+ }
+ if (fc.follows_symlinks && errno_ != ENOENT) {
+ if (errno_) {
+ if (fc.type != FindCommandType::FINDLEAVES) {
+ FIND_WARN_LOC(loc, "FindEmulator: find: `%s': %s", path->c_str(),
+ strerror(errno_));
+ }
+ return true;
+ }
+
+ if (!to_) {
+ LOG("FindEmulator does not support %s", path->c_str());
+ return false;
+ }
+
+ return to_->RunFind(fc, loc, d, path, cur_read_dirs, out);
+ }
+ PrintIfNecessary(fc, *path, type, d, out);
+ return true;
+ }
+
+ virtual bool IsDirectory() const override {
+ if (!is_initialized_) {
+ initialize();
+ }
+ return errno_ == 0 && to_ && to_->IsDirectory();
+ }
+
+ private:
+ void initialize() const {
+ COLLECT_STATS("init find emulator DirentSymlinkNode::initialize");
+ char buf[PATH_MAX + 1];
+ buf[PATH_MAX] = 0;
+ ssize_t len = readlink(name_.c_str(), buf, PATH_MAX);
+ if (len <= 0) {
+ errno_ = errno;
+ WARN("readlink failed: %s", name_.c_str());
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ }
+ buf[len] = 0;
+
+ struct stat st;
+ if (stat(name_.c_str(), &st) != 0) {
+ errno_ = errno;
+ LOG("stat failed: %s: %s", name_.c_str(), strerror(errno));
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ }
+
+ // absolute symlinks aren't supported by the find emulator
+ if (*buf != '/') {
+ to_ = parent_->FindDir(buf);
+ }
+
+ name_ = "";
+ is_initialized_ = true;
+ }
+
+ mutable string name_;
+ const DirentDirNode* parent_;
+
+ mutable const DirentNode* to_ = nullptr;
+ mutable int errno_ = 0;
+ mutable bool is_initialized_ = false;
+};
+
+void DirentDirNode::initialize() const {
+ COLLECT_STATS("init find emulator DirentDirNode::initialize");
+ DIR* dir = opendir(name_.empty() ? "." : name_.c_str());
+ if (!dir) {
+ if (errno == ENOENT || errno == EACCES) {
+ LOG("opendir failed: %s", name_.c_str());
+ name_ = "";
+ is_initialized_ = true;
+ return;
+ } else {
+ PERROR("opendir failed: %s", name_.c_str());
+ }
+ }
+
+ struct dirent* ent;
+ while ((ent = readdir(dir)) != NULL) {
+ if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
+ !strcmp(ent->d_name, ".repo") || !strcmp(ent->d_name, ".git"))
+ continue;
+
+ string npath = name_;
+ if (!name_.empty())
+ npath += '/';
+ npath += ent->d_name;
+
+ DirentNode* c = NULL;
+ auto d_type = ent->d_type;
+ if (d_type == DT_UNKNOWN) {
+ d_type = GetDtType(npath);
+ CHECK(d_type != DT_UNKNOWN);
+ }
+ if (d_type == DT_DIR) {
+ c = new DirentDirNode(this, npath);
+ } else if (d_type == DT_LNK) {
+ c = new DirentSymlinkNode(this, npath);
+ } else {
+ c = new DirentFileNode(npath, d_type);
+ }
+ find_emulator_node_cnt++;
+ children_.emplace(children_.end(), ent->d_name, c);
+ }
+ closedir(dir);
+
+ name_ = "";
+ is_initialized_ = true;
+}
+
+class FindCommandParser {
+ public:
+ FindCommandParser(StringPiece cmd, FindCommand* fc)
+ : cmd_(cmd), fc_(fc), has_if_(false) {}
+
+ bool Parse() {
+ cur_ = cmd_;
+ if (!ParseImpl()) {
+ LOG("FindEmulator: Unsupported find command: %.*s", SPF(cmd_));
+ return false;
+ }
+ CHECK(TrimLeftSpace(cur_).empty());
+ return true;
+ }
+
+ private:
+ bool GetNextToken(StringPiece* tok) {
+ if (!unget_tok_.empty()) {
+ *tok = unget_tok_;
+ unget_tok_.clear();
+ return true;
+ }
+
+ cur_ = TrimLeftSpace(cur_);
+
+ if (cur_[0] == ';') {
+ *tok = cur_.substr(0, 1);
+ cur_ = cur_.substr(1);
+ return true;
+ }
+ if (cur_[0] == '&') {
+ if (cur_.get(1) != '&') {
+ return false;
+ }
+ *tok = cur_.substr(0, 2);
+ cur_ = cur_.substr(2);
+ return true;
+ }
+
+ size_t i = 0;
+ while (i < cur_.size() && !isspace(cur_[i]) && cur_[i] != ';' &&
+ cur_[i] != '&') {
+ i++;
+ }
+
+ *tok = cur_.substr(0, i);
+ cur_ = cur_.substr(i);
+
+ const char c = tok->get(0);
+ if (c == '\'' || c == '"') {
+ if (tok->size() < 2 || (*tok)[tok->size() - 1] != c)
+ return false;
+ *tok = tok->substr(1, tok->size() - 2);
+ return true;
+ } else {
+ // Support stripping off a leading backslash
+ if (c == '\\') {
+ *tok = tok->substr(1);
+ }
+ // But if there are any others, we can't support it, as unescaping would
+ // require allocation
+ if (tok->find("\\") != string::npos) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void UngetToken(StringPiece tok) {
+ CHECK(unget_tok_.empty());
+ if (!tok.empty())
+ unget_tok_ = tok;
+ }
+
+ bool ParseTest() {
+ if (has_if_ || !fc_->testdir.empty())
+ return false;
+ StringPiece tok;
+ if (!GetNextToken(&tok) || tok != "-d")
+ return false;
+ if (!GetNextToken(&tok) || tok.empty())
+ return false;
+ fc_->testdir = tok.as_string();
+ return true;
+ }
+
+ FindCond* ParseFact(StringPiece tok) {
+ if (tok == "-not" || tok == "!") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ unique_ptr<FindCond> c(ParseFact(tok));
+ if (!c.get())
+ return NULL;
+ return new NotCond(c.release());
+ } else if (tok == "(") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ unique_ptr<FindCond> c(ParseExpr(tok));
+ if (!GetNextToken(&tok) || tok != ")") {
+ return NULL;
+ }
+ return c.release();
+ } else if (tok == "-name") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ return new NameCond(tok.as_string());
+ } else if (tok == "-type") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ char type;
+ if (tok == "b")
+ type = DT_BLK;
+ else if (tok == "c")
+ type = DT_CHR;
+ else if (tok == "d")
+ type = DT_DIR;
+ else if (tok == "p")
+ type = DT_FIFO;
+ else if (tok == "l")
+ type = DT_LNK;
+ else if (tok == "f")
+ type = DT_REG;
+ else if (tok == "s")
+ type = DT_SOCK;
+ else
+ return NULL;
+ return new TypeCond(type);
+ } else {
+ UngetToken(tok);
+ return NULL;
+ }
+ }
+
+ FindCond* ParseTerm(StringPiece tok) {
+ unique_ptr<FindCond> c(ParseFact(tok));
+ if (!c.get())
+ return NULL;
+ while (true) {
+ if (!GetNextToken(&tok))
+ return NULL;
+ if (tok == "-and" || tok == "-a") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ } else {
+ if (tok != "-not" && tok != "!" && tok != "(" && tok != "-name" &&
+ tok != "-type") {
+ UngetToken(tok);
+ return c.release();
+ }
+ }
+ unique_ptr<FindCond> r(ParseFact(tok));
+ if (!r.get()) {
+ return NULL;
+ }
+ c.reset(new AndCond(c.release(), r.release()));
+ }
+ }
+
+ FindCond* ParseExpr(StringPiece tok) {
+ unique_ptr<FindCond> c(ParseTerm(tok));
+ if (!c.get())
+ return NULL;
+ while (true) {
+ if (!GetNextToken(&tok))
+ return NULL;
+ if (tok != "-or" && tok != "-o") {
+ UngetToken(tok);
+ return c.release();
+ }
+ if (!GetNextToken(&tok) || tok.empty())
+ return NULL;
+ unique_ptr<FindCond> r(ParseTerm(tok));
+ if (!r.get()) {
+ return NULL;
+ }
+ c.reset(new OrCond(c.release(), r.release()));
+ }
+ }
+
+ // <expr> ::= <term> {<or> <term>}
+ // <term> ::= <fact> {[<and>] <fact>}
+ // <fact> ::= <not> <fact> | '(' <expr> ')' | <pred>
+ // <not> ::= '-not' | '!'
+ // <and> ::= '-and' | '-a'
+ // <or> ::= '-or' | '-o'
+ // <pred> ::= <name> | <type> | <maxdepth>
+ // <name> ::= '-name' NAME
+ // <type> ::= '-type' TYPE
+ // <maxdepth> ::= '-maxdepth' MAXDEPTH
+ FindCond* ParseFindCond(StringPiece tok) { return ParseExpr(tok); }
+
+ bool ParseFind() {
+ fc_->type = FindCommandType::FIND;
+ StringPiece tok;
+ while (true) {
+ if (!GetNextToken(&tok))
+ return false;
+ if (tok.empty() || tok == ";")
+ return true;
+
+ if (tok == "-L") {
+ fc_->follows_symlinks = true;
+ } else if (tok == "-prune") {
+ if (!fc_->print_cond || fc_->prune_cond)
+ return false;
+ if (!GetNextToken(&tok) || tok != "-o")
+ return false;
+ fc_->prune_cond.reset(fc_->print_cond.release());
+ } else if (tok == "-print") {
+ if (!GetNextToken(&tok) || !tok.empty())
+ return false;
+ return true;
+ } else if (tok == "-maxdepth") {
+ if (!GetNextToken(&tok) || tok.empty())
+ return false;
+ const string& depth_str = tok.as_string();
+ char* endptr;
+ long d = strtol(depth_str.c_str(), &endptr, 10);
+ if (endptr != depth_str.data() + depth_str.size() || d < 0 ||
+ d > INT_MAX) {
+ return false;
+ }
+ fc_->depth = d;
+ } else if (tok[0] == '-' || tok == "(" || tok == "!") {
+ if (fc_->print_cond.get())
+ return false;
+ FindCond* c = ParseFindCond(tok);
+ if (!c)
+ return false;
+ fc_->print_cond.reset(c);
+ } else if (tok == "2>") {
+ if (!GetNextToken(&tok) || tok != "/dev/null") {
+ return false;
+ }
+ fc_->redirect_to_devnull = true;
+ } else if (tok.find_first_of("|;&><'\"") != string::npos) {
+ return false;
+ } else {
+ fc_->finddirs.push_back(tok.as_string());
+ }
+ }
+ }
+
+ bool ParseFindLeaves() {
+ fc_->type = FindCommandType::FINDLEAVES;
+ fc_->follows_symlinks = true;
+ StringPiece tok;
+ vector<string> findfiles;
+ while (true) {
+ if (!GetNextToken(&tok))
+ return false;
+ if (tok.empty()) {
+ if (fc_->finddirs.size() == 0) {
+ // backwards compatibility
+ if (findfiles.size() < 2)
+ return false;
+ fc_->finddirs.swap(findfiles);
+ fc_->print_cond.reset(new NameCond(fc_->finddirs.back()));
+ fc_->finddirs.pop_back();
+ } else {
+ if (findfiles.size() < 1)
+ return false;
+ for (auto& file : findfiles) {
+ FindCond* cond = new NameCond(file);
+ if (fc_->print_cond.get()) {
+ cond = new OrCond(fc_->print_cond.release(), cond);
+ }
+ CHECK(!fc_->print_cond.get());
+ fc_->print_cond.reset(cond);
+ }
+ }
+ return true;
+ }
+
+ if (HasPrefix(tok, "--prune=")) {
+ FindCond* cond =
+ new NameCond(tok.substr(strlen("--prune=")).as_string());
+ if (fc_->prune_cond.get()) {
+ cond = new OrCond(fc_->prune_cond.release(), cond);
+ }
+ CHECK(!fc_->prune_cond.get());
+ fc_->prune_cond.reset(cond);
+ } else if (HasPrefix(tok, "--mindepth=")) {
+ string mindepth_str = tok.substr(strlen("--mindepth=")).as_string();
+ char* endptr;
+ long d = strtol(mindepth_str.c_str(), &endptr, 10);
+ if (endptr != mindepth_str.data() + mindepth_str.size() ||
+ d < INT_MIN || d > INT_MAX) {
+ return false;
+ }
+ fc_->mindepth = d;
+ } else if (HasPrefix(tok, "--dir=")) {
+ StringPiece dir = tok.substr(strlen("--dir="));
+ fc_->finddirs.push_back(dir.as_string());
+ } else if (HasPrefix(tok, "--")) {
+ if (g_flags.werror_find_emulator) {
+ ERROR("Unknown flag in findleaves.py: %.*s", SPF(tok));
+ } else {
+ WARN("Unknown flag in findleaves.py: %.*s", SPF(tok));
+ }
+ return false;
+ } else {
+ findfiles.push_back(tok.as_string());
+ }
+ }
+ }
+
+ bool ParseImpl() {
+ while (true) {
+ StringPiece tok;
+ if (!GetNextToken(&tok))
+ return false;
+
+ if (tok.empty())
+ return true;
+
+ if (tok == "cd") {
+ if (!GetNextToken(&tok) || tok.empty() || !fc_->chdir.empty())
+ return false;
+ if (tok.find_first_of("?*[") != string::npos)
+ return false;
+ fc_->chdir = tok.as_string();
+ if (!GetNextToken(&tok) || (tok != ";" && tok != "&&"))
+ return false;
+ } else if (tok == "if") {
+ if (!GetNextToken(&tok) || tok != "[")
+ return false;
+ if (!ParseTest())
+ return false;
+ if (!GetNextToken(&tok) || tok != "]")
+ return false;
+ if (!GetNextToken(&tok) || tok != ";")
+ return false;
+ if (!GetNextToken(&tok) || tok != "then")
+ return false;
+ has_if_ = true;
+ } else if (tok == "test") {
+ if (!fc_->chdir.empty())
+ return false;
+ if (!ParseTest())
+ return false;
+ if (!GetNextToken(&tok) || tok != "&&")
+ return false;
+ } else if (tok == "find") {
+ if (!ParseFind())
+ return false;
+ if (has_if_) {
+ if (!GetNextToken(&tok) || tok != "fi")
+ return false;
+ }
+ if (!GetNextToken(&tok) || !tok.empty())
+ return false;
+ return true;
+ } else if (tok == "build/tools/findleaves.py" ||
+ tok == "build/make/tools/findleaves.py") {
+ if (!ParseFindLeaves())
+ return false;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ StringPiece cmd_;
+ StringPiece cur_;
+ FindCommand* fc_;
+ bool has_if_;
+ StringPiece unget_tok_;
+};
+
+static FindEmulator* g_instance;
+
+class FindEmulatorImpl : public FindEmulator {
+ public:
+ FindEmulatorImpl() { g_instance = this; }
+
+ virtual ~FindEmulatorImpl() = default;
+
+ bool CanHandle(StringPiece s) const {
+ return (!HasPrefix(s, "/") && !HasPrefix(s, ".repo") &&
+ !HasPrefix(s, ".git"));
+ }
+
+ const DirentNode* FindDir(StringPiece d, bool* should_fallback) {
+ const DirentNode* r = root_->FindDir(d);
+ if (!r) {
+ *should_fallback = Exists(d);
+ }
+ return r;
+ }
+
+ virtual bool HandleFind(const string& cmd UNUSED,
+ const FindCommand& fc,
+ const Loc& loc,
+ string* out) override {
+ if (!CanHandle(fc.chdir)) {
+ LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
+ cmd.c_str());
+ return false;
+ }
+
+ if (!fc.testdir.empty()) {
+ if (!CanHandle(fc.testdir)) {
+ LOG("FindEmulator: Cannot handle test dir (%.*s): %s", SPF(fc.testdir),
+ cmd.c_str());
+ return false;
+ }
+ bool should_fallback = false;
+ if (!FindDir(fc.testdir, &should_fallback)) {
+ LOG("FindEmulator: Test dir (%.*s) not found: %s", SPF(fc.testdir),
+ cmd.c_str());
+ return !should_fallback;
+ }
+ }
+
+ const DirentNode* root = root_;
+
+ if (!fc.chdir.empty()) {
+ if (!CanHandle(fc.chdir)) {
+ LOG("FindEmulator: Cannot handle chdir (%.*s): %s", SPF(fc.chdir),
+ cmd.c_str());
+ return false;
+ }
+ root = root->FindDir(fc.chdir);
+ if (!root) {
+ if (Exists(fc.chdir))
+ return false;
+ if (!fc.redirect_to_devnull) {
+ FIND_WARN_LOC(loc,
+ "FindEmulator: cd: %.*s: No such file or directory",
+ SPF(fc.chdir));
+ }
+ return true;
+ }
+ }
+
+ vector<string> results;
+ for (const string& finddir : fc.finddirs) {
+ string fullpath = ConcatDir(fc.chdir, finddir);
+ if (!CanHandle(fullpath)) {
+ LOG("FindEmulator: Cannot handle find dir (%s): %s", fullpath.c_str(),
+ cmd.c_str());
+ return false;
+ }
+
+ string findnodestr;
+ vector<pair<string, const DirentNode*>> bases;
+ if (!root->FindNodes(fc, bases, &findnodestr, finddir)) {
+ return false;
+ }
+ if (bases.empty()) {
+ if (Exists(fullpath)) {
+ return false;
+ }
+ if (!fc.redirect_to_devnull) {
+ FIND_WARN_LOC(loc,
+ "FindEmulator: find: `%s': No such file or directory",
+ ConcatDir(fc.chdir, finddir).c_str());
+ }
+ continue;
+ }
+
+ // bash guarantees that globs are sorted
+ sort(bases.begin(), bases.end());
+
+ for (auto [path, base] : bases) {
+ unordered_map<const DirentNode*, string> cur_read_dirs;
+ if (!base->RunFind(fc, loc, 0, &path, &cur_read_dirs, results)) {
+ LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
+ return false;
+ }
+ }
+ }
+
+ if (results.size() > 0) {
+ // Calculate and reserve necessary space in out
+ size_t new_length = 0;
+ for (const string& result : results) {
+ new_length += result.size() + 1;
+ }
+ out->reserve(out->size() + new_length - 1);
+
+ if (fc.type == FindCommandType::FINDLEAVES) {
+ sort(results.begin(), results.end());
+ }
+
+ WordWriter writer(out);
+ for (const string& result : results) {
+ writer.Write(result);
+ }
+ }
+
+ LOG("FindEmulator: OK");
+ return true;
+ }
+
+ private:
+ DirentNode* root_ = new DirentDirNode(nullptr, "");
+};
+
+} // namespace
+
+FindCommand::FindCommand()
+ : follows_symlinks(false),
+ depth(INT_MAX),
+ mindepth(INT_MIN),
+ redirect_to_devnull(false),
+ found_files(new vector<string>()),
+ read_dirs(new unordered_set<string>()) {}
+
+FindCommand::~FindCommand() {}
+
+bool FindCommand::Parse(const string& cmd) {
+ FindCommandParser fcp(cmd, this);
+ if (!HasWord(cmd, "find") && !HasWord(cmd, "build/tools/findleaves.py") &&
+ !HasWord(cmd, "build/make/tools/findleaves.py"))
+ return false;
+
+ if (!fcp.Parse())
+ return false;
+
+ NormalizePath(&chdir);
+ NormalizePath(&testdir);
+ if (finddirs.empty())
+ finddirs.push_back(".");
+ return true;
+}
+
+FindEmulator* FindEmulator::Get() {
+ return g_instance;
+}
+
+unsigned int FindEmulator::GetNodeCount() {
+ return find_emulator_node_cnt;
+}
+
+void InitFindEmulator() {
+ new FindEmulatorImpl();
+}
diff --git a/src/find.h b/src/find.h
new file mode 100644
index 0000000..2a1abe4
--- /dev/null
+++ b/src/find.h
@@ -0,0 +1,79 @@
+// Copyright 2015 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.
+
+#ifndef FIND_H_
+#define FIND_H_
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "loc.h"
+#include "string_piece.h"
+
+using namespace std;
+
+class FindCond;
+
+enum struct FindCommandType {
+ FIND,
+ FINDLEAVES,
+ LS,
+};
+
+struct FindCommand {
+ FindCommand();
+ ~FindCommand();
+
+ bool Parse(const string& cmd);
+
+ FindCommandType type;
+ string chdir;
+ string testdir;
+ vector<string> finddirs;
+ bool follows_symlinks;
+ unique_ptr<FindCond> print_cond;
+ unique_ptr<FindCond> prune_cond;
+ int depth;
+ int mindepth;
+ bool redirect_to_devnull;
+
+ unique_ptr<vector<string>> found_files;
+ unique_ptr<unordered_set<string>> read_dirs;
+
+ private:
+ FindCommand(const FindCommand&) = delete;
+ void operator=(FindCommand) = delete;
+};
+
+class FindEmulator {
+ public:
+ virtual ~FindEmulator() = default;
+
+ virtual bool HandleFind(const string& cmd,
+ const FindCommand& fc,
+ const Loc& loc,
+ string* out) = 0;
+
+ static FindEmulator* Get();
+ static unsigned int GetNodeCount();
+
+ protected:
+ FindEmulator() = default;
+};
+
+void InitFindEmulator();
+
+#endif // FIND_H_
diff --git a/src/find_test.cc b/src/find_test.cc
new file mode 100644
index 0000000..e8d521c
--- /dev/null
+++ b/src/find_test.cc
@@ -0,0 +1,192 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "find.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <string>
+
+#include "fileutil.h"
+#include "strutil.h"
+
+int FindUnitTests();
+
+int main(int argc, char* argv[]) {
+ if (argc == 1) {
+ return FindUnitTests();
+ }
+
+ InitFindEmulator();
+ string cmd;
+ for (int i = 1; i < argc; i++) {
+ if (i > 1)
+ cmd += ' ';
+ cmd += argv[i];
+ }
+ FindCommand fc;
+ if (!fc.Parse(cmd)) {
+ fprintf(stderr, "Find emulator does not support this command\n");
+ return 1;
+ }
+ string out;
+ if (!FindEmulator::Get()->HandleFind(cmd, fc, Loc(), &out)) {
+ fprintf(stderr, "Find emulator does not support this command\n");
+ return 1;
+ }
+
+ for (StringPiece tok : WordScanner(out)) {
+ printf("%.*s\n", SPF(tok));
+ }
+}
+
+string Run(const string& cmd) {
+ string s;
+ int ret = RunCommand("/bin/sh", "-c", cmd, RedirectStderr::NONE, &s);
+
+ if (ret != 0) {
+ fprintf(stderr, "Failed to run `%s`\n", cmd.c_str());
+ exit(ret);
+ }
+
+ return s;
+}
+
+static bool unit_test_failed = false;
+
+void CompareFind(const string& cmd) {
+ string native = Run(cmd);
+
+ FindCommand fc;
+ if (!fc.Parse(cmd)) {
+ fprintf(stderr, "Find emulator cannot parse `%s`\n", cmd.c_str());
+ exit(1);
+ }
+ string emulated;
+ if (!FindEmulator::Get()->HandleFind(cmd, fc, Loc(), &emulated)) {
+ fprintf(stderr, "Find emulator cannot handle `%s`\n", cmd.c_str());
+ exit(1);
+ }
+
+ vector<StringPiece> nativeWords;
+ vector<StringPiece> emulatedWords;
+
+ WordScanner(native).Split(&nativeWords);
+ WordScanner(emulated).Split(&emulatedWords);
+
+ if (nativeWords != emulatedWords) {
+ fprintf(stderr, "Failed to match `%s`:\n", cmd.c_str());
+
+ auto nativeIter = nativeWords.begin();
+ auto emulatedIter = emulatedWords.begin();
+ fprintf(stderr, "%-20s %-20s\n", "Native:", "Emulated:");
+ while (nativeIter != nativeWords.end() ||
+ emulatedIter != emulatedWords.end()) {
+ fprintf(stderr, " %-20s %-20s\n",
+ (nativeIter == nativeWords.end())
+ ? ""
+ : (*nativeIter++).as_string().c_str(),
+ (emulatedIter == emulatedWords.end())
+ ? ""
+ : (*emulatedIter++).as_string().c_str());
+ }
+ fprintf(stderr, "------------------------------------------\n");
+ unit_test_failed = true;
+ }
+}
+
+void ExpectParseFailure(const string& cmd) {
+ FindCommand fc;
+ if (fc.Parse(cmd)) {
+ fprintf(stderr, "Expected parse failure for `%s`\n", cmd.c_str());
+ fprintf(stderr, "------------------------------------------\n");
+ unit_test_failed = true;
+ }
+}
+
+int FindUnitTests() {
+ Run("rm -rf out/find");
+ Run("mkdir -p out/find");
+ if (chdir("out/find")) {
+ perror("Failed to chdir(out/find)");
+ return 1;
+ }
+
+ // Set up files under out/find:
+ // drwxr-x--- top
+ // lrwxrwxrwx top/E -> missing
+ // lrwxrwxrwx top/C -> A
+ // lrwxrwxrwx top/F -> A/B
+ // -rw-r----- top/a
+ // drwxr-x--- top/A
+ // lrwxrwxrwx top/A/D -> B
+ // -rw-r----- top/A/b
+ // drwxr-x--- top/A/B
+ // -rw-r----- top/A/B/z
+ Run("mkdir -p top/A/B");
+ Run("cd top && ln -s A C");
+ Run("cd top/A && ln -s B D");
+ Run("cd top && ln -s missing E");
+ Run("cd top && ln -s A/B F");
+ Run("touch top/a top/A/b top/A/B/z");
+
+ InitFindEmulator();
+
+ CompareFind("find .");
+ CompareFind("find -L .");
+
+ CompareFind("find top/C");
+ CompareFind("find top/C/.");
+ CompareFind("find -L top/C");
+ CompareFind("find -L top/C/.");
+
+ // A file in finddir
+ CompareFind("find top/A/b");
+
+ CompareFind("cd top && find C");
+ CompareFind("cd top && find -L C");
+ CompareFind("cd top/C && find .");
+
+ CompareFind("cd top/C && find D/./z");
+
+ CompareFind("find .//top");
+
+ CompareFind("find top -type f -name 'a*' -o -name \\*b");
+ CompareFind("find top \\! -name 'a*'");
+ CompareFind("find top \\( -name 'a*' \\)");
+
+ // Basic use of ..
+ CompareFind("cd top/C; find ../A");
+
+ // Use of .. in chdir
+ CompareFind("cd top/A/..; find .");
+
+ // .. through a symlink in chdir, should list under top/A/...
+ CompareFind("cd top/F; find ../");
+ // .. through a symlink in finddir, should do the same
+ CompareFind("cd top; find F/..");
+
+ // * in a finddir
+ CompareFind("find top/*/B");
+
+ ExpectParseFailure("find top -name a\\*");
+
+ // * in a chdir is not supported
+ ExpectParseFailure("cd top/*/B && find .");
+
+ return unit_test_failed ? 1 : 0;
+}
diff --git a/src/flags.cc b/src/flags.cc
new file mode 100644
index 0000000..329e0fd
--- /dev/null
+++ b/src/flags.cc
@@ -0,0 +1,197 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "flags.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "log.h"
+#include "strutil.h"
+
+Flags g_flags;
+
+static bool ParseCommandLineOptionWithArg(StringPiece option,
+ char* argv[],
+ int* index,
+ const char** out_arg) {
+ const char* arg = argv[*index];
+ if (!HasPrefix(arg, option))
+ return false;
+ if (arg[option.size()] == '\0') {
+ ++*index;
+ *out_arg = argv[*index];
+ return true;
+ }
+ if (arg[option.size()] == '=') {
+ *out_arg = arg + option.size() + 1;
+ return true;
+ }
+ // E.g, -j999
+ if (option.size() == 2) {
+ *out_arg = arg + option.size();
+ return true;
+ }
+ return false;
+}
+
+void Flags::Parse(int argc, char** argv) {
+ subkati_args.push_back(argv[0]);
+ num_jobs = num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ const char* num_jobs_str;
+ const char* writable_str;
+
+ if (const char* makeflags = getenv("MAKEFLAGS")) {
+ for (StringPiece tok : WordScanner(makeflags)) {
+ if (!HasPrefix(tok, "-") && tok.find('=') != string::npos)
+ cl_vars.push_back(tok);
+ }
+ }
+
+ for (int i = 1; i < argc; i++) {
+ const char* arg = argv[i];
+ bool should_propagate = true;
+ int pi = i;
+ if (!strcmp(arg, "-f")) {
+ makefile = argv[++i];
+ should_propagate = false;
+ } else if (!strcmp(arg, "-c")) {
+ is_syntax_check_only = true;
+ } else if (!strcmp(arg, "-i")) {
+ is_dry_run = true;
+ } else if (!strcmp(arg, "-s")) {
+ is_silent_mode = true;
+ } else if (!strcmp(arg, "-d")) {
+ enable_debug = true;
+ } else if (!strcmp(arg, "--kati_stats")) {
+ enable_stat_logs = true;
+ } else if (!strcmp(arg, "--warn")) {
+ enable_kati_warnings = true;
+ } else if (!strcmp(arg, "--ninja")) {
+ generate_ninja = true;
+ } else if (!strcmp(arg, "--empty_ninja_file")) {
+ generate_empty_ninja = true;
+ } else if (!strcmp(arg, "--gen_all_targets")) {
+ gen_all_targets = true;
+ } else if (!strcmp(arg, "--regen")) {
+ // TODO: Make this default.
+ regen = true;
+ } else if (!strcmp(arg, "--regen_debug")) {
+ regen_debug = true;
+ } else if (!strcmp(arg, "--regen_ignoring_kati_binary")) {
+ regen_ignoring_kati_binary = true;
+ } else if (!strcmp(arg, "--dump_kati_stamp")) {
+ dump_kati_stamp = true;
+ regen_debug = true;
+ } else if (!strcmp(arg, "--detect_android_echo")) {
+ detect_android_echo = true;
+ } else if (!strcmp(arg, "--detect_depfiles")) {
+ detect_depfiles = true;
+ } else if (!strcmp(arg, "--color_warnings")) {
+ color_warnings = true;
+ } else if (!strcmp(arg, "--no_builtin_rules")) {
+ no_builtin_rules = true;
+ } else if (!strcmp(arg, "--no_ninja_prelude")) {
+ no_ninja_prelude = true;
+ } else if (!strcmp(arg, "--use_ninja_phony_output")) {
+ use_ninja_phony_output = true;
+ } else if (!strcmp(arg, "--use_ninja_validations")) {
+ use_ninja_validations = true;
+ } else if (!strcmp(arg, "--werror_find_emulator")) {
+ werror_find_emulator = true;
+ } else if (!strcmp(arg, "--werror_overriding_commands")) {
+ werror_overriding_commands = true;
+ } else if (!strcmp(arg, "--warn_implicit_rules")) {
+ warn_implicit_rules = true;
+ } else if (!strcmp(arg, "--werror_implicit_rules")) {
+ werror_implicit_rules = true;
+ } else if (!strcmp(arg, "--warn_suffix_rules")) {
+ warn_suffix_rules = true;
+ } else if (!strcmp(arg, "--werror_suffix_rules")) {
+ werror_suffix_rules = true;
+ } else if (!strcmp(arg, "--top_level_phony")) {
+ top_level_phony = true;
+ } else if (!strcmp(arg, "--warn_real_to_phony")) {
+ warn_real_to_phony = true;
+ } else if (!strcmp(arg, "--werror_real_to_phony")) {
+ warn_real_to_phony = true;
+ werror_real_to_phony = true;
+ } else if (!strcmp(arg, "--warn_phony_looks_real")) {
+ warn_phony_looks_real = true;
+ } else if (!strcmp(arg, "--werror_phony_looks_real")) {
+ warn_phony_looks_real = true;
+ werror_phony_looks_real = true;
+ } else if (!strcmp(arg, "--werror_writable")) {
+ werror_writable = true;
+ } else if (!strcmp(arg, "--warn_real_no_cmds_or_deps")) {
+ warn_real_no_cmds_or_deps = true;
+ } else if (!strcmp(arg, "--werror_real_no_cmds_or_deps")) {
+ warn_real_no_cmds_or_deps = true;
+ werror_real_no_cmds_or_deps = true;
+ } else if (!strcmp(arg, "--warn_real_no_cmds")) {
+ warn_real_no_cmds = true;
+ } else if (!strcmp(arg, "--werror_real_no_cmds")) {
+ warn_real_no_cmds = true;
+ werror_real_no_cmds = true;
+ } else if (ParseCommandLineOptionWithArg("-j", argv, &i, &num_jobs_str)) {
+ num_jobs = strtol(num_jobs_str, NULL, 10);
+ if (num_jobs <= 0) {
+ ERROR("Invalid -j flag: %s", num_jobs_str);
+ }
+ } else if (ParseCommandLineOptionWithArg("--remote_num_jobs", argv, &i,
+ &num_jobs_str)) {
+ remote_num_jobs = strtol(num_jobs_str, NULL, 10);
+ if (remote_num_jobs <= 0) {
+ ERROR("Invalid -j flag: %s", num_jobs_str);
+ }
+ } else if (ParseCommandLineOptionWithArg("--ninja_suffix", argv, &i,
+ &ninja_suffix)) {
+ } else if (ParseCommandLineOptionWithArg("--ninja_dir", argv, &i,
+ &ninja_dir)) {
+ } else if (!strcmp(arg, "--use_find_emulator")) {
+ use_find_emulator = true;
+ } else if (ParseCommandLineOptionWithArg("--goma_dir", argv, &i,
+ &goma_dir)) {
+ } else if (ParseCommandLineOptionWithArg(
+ "--ignore_optional_include", argv, &i,
+ &ignore_optional_include_pattern)) {
+ } else if (ParseCommandLineOptionWithArg("--ignore_dirty", argv, &i,
+ &ignore_dirty_pattern)) {
+ } else if (ParseCommandLineOptionWithArg("--no_ignore_dirty", argv, &i,
+ &no_ignore_dirty_pattern)) {
+ } else if (ParseCommandLineOptionWithArg("--writable", argv, &i,
+ &writable_str)) {
+ writable.push_back(writable_str);
+ } else if (ParseCommandLineOptionWithArg("--default_pool", argv, &i,
+ &default_pool)) {
+ } else if (arg[0] == '-') {
+ ERROR("Unknown flag: %s", arg);
+ } else {
+ if (strchr(arg, '=')) {
+ cl_vars.push_back(arg);
+ } else {
+ should_propagate = false;
+ targets.push_back(Intern(arg));
+ }
+ }
+
+ if (should_propagate) {
+ for (; pi <= i; pi++) {
+ subkati_args.push_back(argv[pi]);
+ }
+ }
+ }
+}
diff --git a/src/flags.h b/src/flags.h
new file mode 100644
index 0000000..066fb38
--- /dev/null
+++ b/src/flags.h
@@ -0,0 +1,85 @@
+// Copyright 2015 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.
+
+#ifndef FLAGS_H_
+#define FLAGS_H_
+
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+#include "symtab.h"
+
+using namespace std;
+
+struct Flags {
+ bool detect_android_echo;
+ bool detect_depfiles;
+ bool dump_kati_stamp;
+ bool enable_debug;
+ bool enable_kati_warnings;
+ bool enable_stat_logs;
+ bool gen_all_targets;
+ bool generate_ninja;
+ bool generate_empty_ninja;
+ bool is_dry_run;
+ bool is_silent_mode;
+ bool is_syntax_check_only;
+ bool regen;
+ bool regen_debug;
+ bool regen_ignoring_kati_binary;
+ bool use_find_emulator;
+ bool color_warnings;
+ bool no_builtin_rules;
+ bool no_ninja_prelude;
+ bool use_ninja_phony_output;
+ bool use_ninja_validations;
+ bool werror_find_emulator;
+ bool werror_overriding_commands;
+ bool warn_implicit_rules;
+ bool werror_implicit_rules;
+ bool warn_suffix_rules;
+ bool werror_suffix_rules;
+ bool top_level_phony;
+ bool warn_real_to_phony;
+ bool werror_real_to_phony;
+ bool warn_phony_looks_real;
+ bool werror_phony_looks_real;
+ bool werror_writable;
+ bool warn_real_no_cmds_or_deps;
+ bool werror_real_no_cmds_or_deps;
+ bool warn_real_no_cmds;
+ bool werror_real_no_cmds;
+ const char* default_pool;
+ const char* goma_dir;
+ const char* ignore_dirty_pattern;
+ const char* no_ignore_dirty_pattern;
+ const char* ignore_optional_include_pattern;
+ const char* makefile;
+ const char* ninja_dir;
+ const char* ninja_suffix;
+ int num_cpus;
+ int num_jobs;
+ int remote_num_jobs;
+ vector<const char*> subkati_args;
+ vector<Symbol> targets;
+ vector<StringPiece> cl_vars;
+ vector<string> writable;
+
+ void Parse(int argc, char** argv);
+};
+
+extern Flags g_flags;
+
+#endif // FLAGS_H_
diff --git a/src/func.cc b/src/func.cc
new file mode 100644
index 0000000..b0837da
--- /dev/null
+++ b/src/func.cc
@@ -0,0 +1,1022 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "func.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <iterator>
+#include <memory>
+#include <unordered_map>
+
+#include "eval.h"
+#include "fileutil.h"
+#include "find.h"
+#include "log.h"
+#include "parser.h"
+#include "stats.h"
+#include "stmt.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "var.h"
+
+namespace {
+
+// TODO: This code is very similar to
+// NinjaGenerator::TranslateCommand. Factor them out.
+void StripShellComment(string* cmd) {
+ if (cmd->find('#') == string::npos)
+ return;
+
+ string res;
+ bool prev_backslash = false;
+ // Set space as an initial value so the leading comment will be
+ // stripped out.
+ char prev_char = ' ';
+ char quote = 0;
+ bool done = false;
+ const char* in = cmd->c_str();
+ for (; *in && !done; in++) {
+ switch (*in) {
+ case '#':
+ if (quote == 0 && isspace(prev_char)) {
+ while (in[1] && *in != '\n')
+ in++;
+ break;
+ }
+#if defined(__has_cpp_attribute) && __has_cpp_attribute(clang::fallthrough)
+ [[clang::fallthrough]];
+#endif
+
+ case '\'':
+ case '"':
+ case '`':
+ if (quote) {
+ if (quote == *in)
+ quote = 0;
+ } else if (!prev_backslash) {
+ quote = *in;
+ }
+ res += *in;
+ break;
+
+ case '\\':
+ res += '\\';
+ break;
+
+ default:
+ res += *in;
+ }
+
+ if (*in == '\\') {
+ prev_backslash = !prev_backslash;
+ } else {
+ prev_backslash = false;
+ }
+
+ prev_char = *in;
+ }
+ cmd->swap(res);
+}
+
+void PatsubstFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pat_str = args[0]->Eval(ev);
+ const string&& repl = args[1]->Eval(ev);
+ const string&& str = args[2]->Eval(ev);
+ WordWriter ww(s);
+ Pattern pat(pat_str);
+ for (StringPiece tok : WordScanner(str)) {
+ ww.MaybeAddWhitespace();
+ pat.AppendSubst(tok, repl, s);
+ }
+}
+
+void StripFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& str = args[0]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(str)) {
+ ww.Write(tok);
+ }
+}
+
+void SubstFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pat = args[0]->Eval(ev);
+ const string&& repl = args[1]->Eval(ev);
+ const string&& str = args[2]->Eval(ev);
+ if (pat.empty()) {
+ *s += str;
+ *s += repl;
+ return;
+ }
+ size_t index = 0;
+ while (index < str.size()) {
+ size_t found = str.find(pat, index);
+ if (found == string::npos)
+ break;
+ AppendString(StringPiece(str).substr(index, found - index), s);
+ AppendString(repl, s);
+ index = found + pat.size();
+ }
+ AppendString(StringPiece(str).substr(index), s);
+}
+
+void FindstringFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& find = args[0]->Eval(ev);
+ const string&& in = args[1]->Eval(ev);
+ if (in.find(find) != string::npos)
+ AppendString(find, s);
+}
+
+void FilterFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pat_buf = args[0]->Eval(ev);
+ const string&& text = args[1]->Eval(ev);
+ vector<Pattern> pats;
+ for (StringPiece pat : WordScanner(pat_buf)) {
+ pats.push_back(Pattern(pat));
+ }
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ for (const Pattern& pat : pats) {
+ if (pat.Match(tok)) {
+ ww.Write(tok);
+ break;
+ }
+ }
+ }
+}
+
+void FilterOutFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pat_buf = args[0]->Eval(ev);
+ const string&& text = args[1]->Eval(ev);
+ vector<Pattern> pats;
+ for (StringPiece pat : WordScanner(pat_buf)) {
+ pats.push_back(Pattern(pat));
+ }
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ bool matched = false;
+ for (const Pattern& pat : pats) {
+ if (pat.Match(tok)) {
+ matched = true;
+ break;
+ }
+ }
+ if (!matched)
+ ww.Write(tok);
+ }
+}
+
+void SortFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ string list;
+ args[0]->Eval(ev, &list);
+ COLLECT_STATS("func sort time");
+ // TODO(hamaji): Probably we could use a faster string-specific sort
+ // algorithm.
+ vector<StringPiece> toks;
+ WordScanner(list).Split(&toks);
+ stable_sort(toks.begin(), toks.end());
+ WordWriter ww(s);
+ StringPiece prev;
+ for (StringPiece tok : toks) {
+ if (prev != tok) {
+ ww.Write(tok);
+ prev = tok;
+ }
+ }
+}
+
+static int GetNumericValueForFunc(const string& buf) {
+ StringPiece s = TrimLeftSpace(buf);
+ char* end;
+ long n = strtol(s.data(), &end, 10);
+ if (n < 0 || n == LONG_MAX || s.data() + s.size() != end) {
+ return -1;
+ }
+ return n;
+}
+
+void WordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& n_str = args[0]->Eval(ev);
+ int n = GetNumericValueForFunc(n_str);
+ if (n < 0) {
+ ev->Error(
+ StringPrintf("*** non-numeric first argument to `word' function: '%s'.",
+ n_str.c_str()));
+ }
+ if (n == 0) {
+ ev->Error("*** first argument to `word' function must be greater than 0.");
+ }
+
+ const string&& text = args[1]->Eval(ev);
+ for (StringPiece tok : WordScanner(text)) {
+ n--;
+ if (n == 0) {
+ AppendString(tok, s);
+ break;
+ }
+ }
+}
+
+void WordlistFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& s_str = args[0]->Eval(ev);
+ int si = GetNumericValueForFunc(s_str);
+ if (si < 0) {
+ ev->Error(StringPrintf(
+ "*** non-numeric first argument to `wordlist' function: '%s'.",
+ s_str.c_str()));
+ }
+ if (si == 0) {
+ ev->Error(
+ StringPrintf("*** invalid first argument to `wordlist' function: %s`",
+ s_str.c_str()));
+ }
+
+ const string&& e_str = args[1]->Eval(ev);
+ int ei = GetNumericValueForFunc(e_str);
+ if (ei < 0) {
+ ev->Error(StringPrintf(
+ "*** non-numeric second argument to `wordlist' function: '%s'.",
+ e_str.c_str()));
+ }
+
+ const string&& text = args[2]->Eval(ev);
+ int i = 0;
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ i++;
+ if (si <= i && i <= ei) {
+ ww.Write(tok);
+ }
+ }
+}
+
+void WordsFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordScanner ws(text);
+ int n = 0;
+ for (auto iter = ws.begin(); iter != ws.end(); ++iter)
+ n++;
+ char buf[32];
+ sprintf(buf, "%d", n);
+ *s += buf;
+}
+
+void FirstwordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordScanner ws(text);
+ auto begin = ws.begin();
+ if (begin != ws.end()) {
+ AppendString(*begin, s);
+ }
+}
+
+void LastwordFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ StringPiece last;
+ for (StringPiece tok : WordScanner(text)) {
+ last = tok;
+ }
+ AppendString(last, s);
+}
+
+void JoinFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& list1 = args[0]->Eval(ev);
+ const string&& list2 = args[1]->Eval(ev);
+ WordScanner ws1(list1);
+ WordScanner ws2(list2);
+ WordWriter ww(s);
+ WordScanner::Iterator iter1, iter2;
+ for (iter1 = ws1.begin(), iter2 = ws2.begin();
+ iter1 != ws1.end() && iter2 != ws2.end(); ++iter1, ++iter2) {
+ ww.Write(*iter1);
+ // Use |AppendString| not to append extra ' '.
+ AppendString(*iter2, s);
+ }
+ for (; iter1 != ws1.end(); ++iter1)
+ ww.Write(*iter1);
+ for (; iter2 != ws2.end(); ++iter2)
+ ww.Write(*iter2);
+}
+
+void WildcardFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pat = args[0]->Eval(ev);
+ COLLECT_STATS("func wildcard time");
+ // Note GNU make does not delay the execution of $(wildcard) so we
+ // do not need to check avoid_io here.
+ WordWriter ww(s);
+ vector<string>* files;
+ for (StringPiece tok : WordScanner(pat)) {
+ ScopedTerminator st(tok);
+ Glob(tok.data(), &files);
+ for (const string& file : *files) {
+ ww.Write(file);
+ }
+ }
+}
+
+void DirFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ ww.Write(Dirname(tok));
+ s->push_back('/');
+ }
+}
+
+void NotdirFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ if (tok == "/") {
+ ww.Write(StringPiece(""));
+ } else {
+ ww.Write(Basename(tok));
+ }
+ }
+}
+
+void SuffixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ StringPiece suf = GetExt(tok);
+ if (!suf.empty())
+ ww.Write(suf);
+ }
+}
+
+void BasenameFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ ww.Write(StripExt(tok));
+ }
+}
+
+void AddsuffixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& suf = args[0]->Eval(ev);
+ const string&& text = args[1]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ ww.Write(tok);
+ *s += suf;
+ }
+}
+
+void AddprefixFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& pre = args[0]->Eval(ev);
+ const string&& text = args[1]->Eval(ev);
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ ww.Write(pre);
+ AppendString(tok, s);
+ }
+}
+
+void RealpathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ *s += "$(";
+ string kati_binary;
+ GetExecutablePath(&kati_binary);
+ *s += kati_binary;
+ *s += " --realpath ";
+ *s += text;
+ *s += " 2> /dev/null)";
+ return;
+ }
+
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(text)) {
+ ScopedTerminator st(tok);
+ char buf[PATH_MAX];
+ if (realpath(tok.data(), buf))
+ ww.Write(buf);
+ }
+}
+
+void AbspathFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& text = args[0]->Eval(ev);
+ WordWriter ww(s);
+ string buf;
+ for (StringPiece tok : WordScanner(text)) {
+ AbsPath(tok, &buf);
+ ww.Write(buf);
+ }
+}
+
+void IfFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& cond = args[0]->Eval(ev);
+ if (cond.empty()) {
+ if (args.size() > 2)
+ args[2]->Eval(ev, s);
+ } else {
+ args[1]->Eval(ev, s);
+ }
+}
+
+void AndFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ string cond;
+ for (Value* a : args) {
+ cond = a->Eval(ev);
+ if (cond.empty())
+ return;
+ }
+ if (!cond.empty()) {
+ *s += cond;
+ }
+}
+
+void OrFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ for (Value* a : args) {
+ const string&& cond = a->Eval(ev);
+ if (!cond.empty()) {
+ *s += cond;
+ return;
+ }
+ }
+}
+
+void ValueFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& var_name = args[0]->Eval(ev);
+ Var* var = ev->LookupVar(Intern(var_name));
+ AppendString(var->String().as_string(), s);
+}
+
+void EvalFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ // TODO: eval leaks everything... for now.
+ // const string text = args[0]->Eval(ev);
+ ev->CheckStack();
+ string* text = new string;
+ args[0]->Eval(ev, text);
+ if (ev->avoid_io()) {
+ KATI_WARN_LOC(ev->loc(),
+ "*warning*: $(eval) in a recipe is not recommended: %s",
+ text->c_str());
+ }
+ vector<Stmt*> stmts;
+ Parse(*text, ev->loc(), &stmts);
+ for (Stmt* stmt : stmts) {
+ LOG("%s", stmt->DebugString().c_str());
+ stmt->Eval(ev);
+ // delete stmt;
+ }
+}
+
+//#define TEST_FIND_EMULATOR
+
+// A hack for Android build. We need to evaluate things like $((3+4))
+// when we emit ninja file, because the result of such expressions
+// will be passed to other make functions.
+// TODO: Maybe we should introduce a helper binary which evaluate
+// make expressions at ninja-time.
+static bool HasNoIoInShellScript(const string& cmd) {
+ if (cmd.empty())
+ return true;
+ if (HasPrefix(cmd, "echo $((") && cmd[cmd.size() - 1] == ')')
+ return true;
+ return false;
+}
+
+static void ShellFuncImpl(const string& shell,
+ const string& shellflag,
+ const string& cmd,
+ const Loc& loc,
+ string* s,
+ FindCommand** fc) {
+ LOG("ShellFunc: %s", cmd.c_str());
+
+#ifdef TEST_FIND_EMULATOR
+ bool need_check = false;
+ string out2;
+#endif
+ if (FindEmulator::Get()) {
+ *fc = new FindCommand();
+ if ((*fc)->Parse(cmd)) {
+#ifdef TEST_FIND_EMULATOR
+ if (FindEmulator::Get()->HandleFind(cmd, **fc, loc, &out2)) {
+ need_check = true;
+ }
+#else
+ if (FindEmulator::Get()->HandleFind(cmd, **fc, loc, s)) {
+ return;
+ }
+#endif
+ }
+ delete *fc;
+ *fc = NULL;
+ }
+
+ COLLECT_STATS_WITH_SLOW_REPORT("func shell time", cmd.c_str());
+ RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, s);
+ FormatForCommandSubstitution(s);
+
+#ifdef TEST_FIND_EMULATOR
+ if (need_check) {
+ if (*s != out2) {
+ ERROR("FindEmulator is broken: %s\n%s\nvs\n%s", cmd.c_str(), s->c_str(),
+ out2.c_str());
+ }
+ }
+#endif
+}
+
+static vector<CommandResult*> g_command_results;
+
+bool ShouldStoreCommandResult(StringPiece cmd) {
+ // We really just want to ignore this one, or remove BUILD_DATETIME from
+ // Android completely
+ if (cmd == "date +%s")
+ return false;
+
+ Pattern pat(g_flags.ignore_dirty_pattern);
+ Pattern nopat(g_flags.no_ignore_dirty_pattern);
+ for (StringPiece tok : WordScanner(cmd)) {
+ if (pat.Match(tok) && !nopat.Match(tok)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void ShellFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ string cmd = args[0]->Eval(ev);
+ if (ev->avoid_io() && !HasNoIoInShellScript(cmd)) {
+ if (ev->eval_depth() > 1) {
+ ERROR_LOC(ev->loc(),
+ "kati doesn't support passing results of $(shell) "
+ "to other make constructs: %s",
+ cmd.c_str());
+ }
+ StripShellComment(&cmd);
+ *s += "$(";
+ *s += cmd;
+ *s += ")";
+ return;
+ }
+
+ const string&& shell = ev->GetShell();
+ const string&& shellflag = ev->GetShellFlag();
+
+ string out;
+ FindCommand* fc = NULL;
+ ShellFuncImpl(shell, shellflag, cmd, ev->loc(), &out, &fc);
+ if (ShouldStoreCommandResult(cmd)) {
+ CommandResult* cr = new CommandResult();
+ cr->op = (fc == NULL) ? CommandOp::SHELL : CommandOp::FIND,
+ cr->shell = shell;
+ cr->shellflag = shellflag;
+ cr->cmd = cmd;
+ cr->find.reset(fc);
+ cr->result = out;
+ cr->loc = ev->loc();
+ g_command_results.push_back(cr);
+ }
+ *s += out;
+}
+
+void CallFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ static const Symbol tmpvar_names[] = {
+ Intern("0"), Intern("1"), Intern("2"), Intern("3"), Intern("4"),
+ Intern("5"), Intern("6"), Intern("7"), Intern("8"), Intern("9")};
+
+ ev->CheckStack();
+ const string&& func_name_buf = args[0]->Eval(ev);
+ Symbol func_sym = Intern(TrimSpace(func_name_buf));
+ Var* func = ev->LookupVar(func_sym);
+ func->Used(ev, func_sym);
+ if (!func->IsDefined()) {
+ KATI_WARN_LOC(ev->loc(), "*warning*: undefined user function: %s",
+ func_sym.c_str());
+ }
+ vector<unique_ptr<SimpleVar>> av;
+ for (size_t i = 1; i < args.size(); i++) {
+ unique_ptr<SimpleVar> s(
+ new SimpleVar(args[i]->Eval(ev), VarOrigin::AUTOMATIC));
+ av.push_back(move(s));
+ }
+ vector<unique_ptr<ScopedGlobalVar>> sv;
+ for (size_t i = 1;; i++) {
+ string s;
+ Symbol tmpvar_name_sym;
+ if (i < sizeof(tmpvar_names) / sizeof(tmpvar_names[0])) {
+ tmpvar_name_sym = tmpvar_names[i];
+ } else {
+ s = StringPrintf("%d", i);
+ tmpvar_name_sym = Intern(s);
+ }
+ if (i < args.size()) {
+ sv.emplace_back(new ScopedGlobalVar(tmpvar_name_sym, av[i - 1].get()));
+ } else {
+ // We need to blank further automatic vars
+ Var* v = ev->LookupVar(tmpvar_name_sym);
+ if (!v->IsDefined())
+ break;
+ if (v->Origin() != VarOrigin::AUTOMATIC)
+ break;
+
+ av.emplace_back(new SimpleVar("", VarOrigin::AUTOMATIC));
+ sv.emplace_back(new ScopedGlobalVar(tmpvar_name_sym, av[i - 1].get()));
+ }
+ }
+
+ ev->DecrementEvalDepth();
+ func->Eval(ev, s);
+ ev->IncrementEvalDepth();
+}
+
+void ForeachFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& varname = args[0]->Eval(ev);
+ const string&& list = args[1]->Eval(ev);
+ ev->DecrementEvalDepth();
+ WordWriter ww(s);
+ for (StringPiece tok : WordScanner(list)) {
+ unique_ptr<SimpleVar> v(
+ new SimpleVar(tok.as_string(), VarOrigin::AUTOMATIC));
+ ScopedGlobalVar sv(Intern(varname), v.get());
+ ww.MaybeAddWhitespace();
+ args[2]->Eval(ev, s);
+ }
+ ev->IncrementEvalDepth();
+}
+
+void OriginFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& var_name = args[0]->Eval(ev);
+ Var* var = ev->LookupVar(Intern(var_name));
+ *s += GetOriginStr(var->Origin());
+}
+
+void FlavorFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ const string&& var_name = args[0]->Eval(ev);
+ Var* var = ev->LookupVar(Intern(var_name));
+ *s += var->Flavor();
+}
+
+void InfoFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ const string&& a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ ev->add_delayed_output_command(
+ StringPrintf("echo -e \"%s\"", EchoEscape(a).c_str()));
+ return;
+ }
+ printf("%s\n", a.c_str());
+ fflush(stdout);
+}
+
+void WarningFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ const string&& a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ ev->add_delayed_output_command(StringPrintf(
+ "echo -e \"%s:%d: %s\" 2>&1", LOCF(ev->loc()), EchoEscape(a).c_str()));
+ return;
+ }
+ WARN_LOC(ev->loc(), "%s", a.c_str());
+}
+
+void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ const string&& a = args[0]->Eval(ev);
+ if (ev->avoid_io()) {
+ ev->add_delayed_output_command(
+ StringPrintf("echo -e \"%s:%d: *** %s.\" 2>&1 && false",
+ LOCF(ev->loc()), EchoEscape(a).c_str()));
+ return;
+ }
+ ev->Error(StringPrintf("*** %s.", a.c_str()));
+}
+
+static void FileReadFunc(Evaluator* ev, const string& filename, string* s) {
+ int fd = open(filename.c_str(), O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT) {
+ if (ShouldStoreCommandResult(filename)) {
+ CommandResult* cr = new CommandResult();
+ cr->op = CommandOp::READ_MISSING;
+ cr->cmd = filename;
+ cr->loc = ev->loc();
+ g_command_results.push_back(cr);
+ }
+ return;
+ } else {
+ ev->Error("*** open failed.");
+ }
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ev->Error("*** fstat failed.");
+ }
+
+ size_t len = st.st_size;
+ string out;
+ out.resize(len);
+ ssize_t r = HANDLE_EINTR(read(fd, &out[0], len));
+ if (r != static_cast<ssize_t>(len)) {
+ ev->Error("*** read failed.");
+ }
+
+ if (close(fd) < 0) {
+ ev->Error("*** close failed.");
+ }
+
+ if (out.back() == '\n') {
+ out.pop_back();
+ }
+
+ if (ShouldStoreCommandResult(filename)) {
+ CommandResult* cr = new CommandResult();
+ cr->op = CommandOp::READ;
+ cr->cmd = filename;
+ cr->loc = ev->loc();
+ g_command_results.push_back(cr);
+ }
+ *s += out;
+}
+
+static void FileWriteFunc(Evaluator* ev,
+ const string& filename,
+ bool append,
+ string text) {
+ FILE* f = fopen(filename.c_str(), append ? "ab" : "wb");
+ if (f == NULL) {
+ ev->Error("*** fopen failed.");
+ }
+
+ if (fwrite(&text[0], text.size(), 1, f) != 1) {
+ ev->Error("*** fwrite failed.");
+ }
+
+ if (fclose(f) != 0) {
+ ev->Error("*** fclose failed.");
+ }
+
+ if (ShouldStoreCommandResult(filename)) {
+ CommandResult* cr = new CommandResult();
+ cr->op = CommandOp::WRITE;
+ cr->cmd = filename;
+ cr->result = text;
+ cr->loc = ev->loc();
+ g_command_results.push_back(cr);
+ }
+}
+
+void FileFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
+ if (ev->avoid_io()) {
+ ev->Error("*** $(file ...) is not supported in rules.");
+ }
+
+ string arg = args[0]->Eval(ev);
+ StringPiece filename = TrimSpace(arg);
+
+ if (filename.size() <= 1) {
+ ev->Error("*** Missing filename");
+ }
+
+ if (filename[0] == '<') {
+ filename = TrimLeftSpace(filename.substr(1));
+ if (!filename.size()) {
+ ev->Error("*** Missing filename");
+ }
+ if (args.size() > 1) {
+ ev->Error("*** invalid argument");
+ }
+
+ FileReadFunc(ev, filename.as_string(), s);
+ } else if (filename[0] == '>') {
+ bool append = false;
+ if (filename[1] == '>') {
+ append = true;
+ filename = filename.substr(2);
+ } else {
+ filename = filename.substr(1);
+ }
+ filename = TrimLeftSpace(filename);
+ if (!filename.size()) {
+ ev->Error("*** Missing filename");
+ }
+
+ string text;
+ if (args.size() > 1) {
+ text = args[1]->Eval(ev);
+ if (text.size() == 0 || text.back() != '\n') {
+ text.push_back('\n');
+ }
+ }
+
+ FileWriteFunc(ev, filename.as_string(), append, text);
+ } else {
+ ev->Error(StringPrintf("*** Invalid file operation: %s. Stop.",
+ filename.as_string().c_str()));
+ }
+}
+
+void DeprecatedVarFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ string vars_str = args[0]->Eval(ev);
+ string msg;
+
+ if (args.size() == 2) {
+ msg = ". " + args[1]->Eval(ev);
+ }
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_deprecated_var ...) is not supported in rules.");
+ }
+
+ for (StringPiece var : WordScanner(vars_str)) {
+ Symbol sym = Intern(var);
+ Var* v = ev->PeekVar(sym);
+ if (!v->IsDefined()) {
+ v = new SimpleVar(VarOrigin::FILE);
+ sym.SetGlobalVar(v, false, nullptr);
+ }
+
+ if (v->Deprecated()) {
+ ev->Error(
+ StringPrintf("*** Cannot call KATI_deprecated_var on already "
+ "deprecated variable: %s.",
+ sym.c_str()));
+ } else if (v->Obsolete()) {
+ ev->Error(
+ StringPrintf("*** Cannot call KATI_deprecated_var on already "
+ "obsolete variable: %s.",
+ sym.c_str()));
+ }
+
+ v->SetDeprecated(msg);
+ }
+}
+
+void ObsoleteVarFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ string vars_str = args[0]->Eval(ev);
+ string msg;
+
+ if (args.size() == 2) {
+ msg = ". " + args[1]->Eval(ev);
+ }
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_obsolete_var ...) is not supported in rules.");
+ }
+
+ for (StringPiece var : WordScanner(vars_str)) {
+ Symbol sym = Intern(var);
+ Var* v = ev->PeekVar(sym);
+ if (!v->IsDefined()) {
+ v = new SimpleVar(VarOrigin::FILE);
+ sym.SetGlobalVar(v, false, nullptr);
+ }
+
+ if (v->Deprecated()) {
+ ev->Error(
+ StringPrintf("*** Cannot call KATI_obsolete_var on already "
+ "deprecated variable: %s.",
+ sym.c_str()));
+ } else if (v->Obsolete()) {
+ ev->Error(StringPrintf(
+ "*** Cannot call KATI_obsolete_var on already obsolete variable: %s.",
+ sym.c_str()));
+ }
+
+ v->SetObsolete(msg);
+ }
+}
+
+void DeprecateExportFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ string msg = ". " + args[0]->Eval(ev);
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_deprecate_export) is not supported in rules.");
+ }
+
+ if (ev->ExportObsolete()) {
+ ev->Error("*** Export is already obsolete.");
+ } else if (ev->ExportDeprecated()) {
+ ev->Error("*** Export is already deprecated.");
+ }
+
+ ev->SetExportDeprecated(msg);
+}
+
+void ObsoleteExportFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ string msg = ". " + args[0]->Eval(ev);
+
+ if (ev->avoid_io()) {
+ ev->Error("*** $(KATI_obsolete_export) is not supported in rules.");
+ }
+
+ if (ev->ExportObsolete()) {
+ ev->Error("*** Export is already obsolete.");
+ }
+
+ ev->SetExportObsolete(msg);
+}
+
+void ProfileFunc(const vector<Value*>& args, Evaluator* ev, string*) {
+ for (auto arg : args) {
+ string files = arg->Eval(ev);
+ for (StringPiece file : WordScanner(files)) {
+ ev->ProfileMakefile(file);
+ }
+ }
+}
+
+FuncInfo g_func_infos[] = {
+ {"patsubst", &PatsubstFunc, 3, 3, false, false},
+ {"strip", &StripFunc, 1, 1, false, false},
+ {"subst", &SubstFunc, 3, 3, false, false},
+ {"findstring", &FindstringFunc, 2, 2, false, false},
+ {"filter", &FilterFunc, 2, 2, false, false},
+ {"filter-out", &FilterOutFunc, 2, 2, false, false},
+ {"sort", &SortFunc, 1, 1, false, false},
+ {"word", &WordFunc, 2, 2, false, false},
+ {"wordlist", &WordlistFunc, 3, 3, false, false},
+ {"words", &WordsFunc, 1, 1, false, false},
+ {"firstword", &FirstwordFunc, 1, 1, false, false},
+ {"lastword", &LastwordFunc, 1, 1, false, false},
+
+ {"join", &JoinFunc, 2, 2, false, false},
+ {"wildcard", &WildcardFunc, 1, 1, false, false},
+ {"dir", &DirFunc, 1, 1, false, false},
+ {"notdir", &NotdirFunc, 1, 1, false, false},
+ {"suffix", &SuffixFunc, 1, 1, false, false},
+ {"basename", &BasenameFunc, 1, 1, false, false},
+ {"addsuffix", &AddsuffixFunc, 2, 2, false, false},
+ {"addprefix", &AddprefixFunc, 2, 2, false, false},
+ {"realpath", &RealpathFunc, 1, 1, false, false},
+ {"abspath", &AbspathFunc, 1, 1, false, false},
+
+ {"if", &IfFunc, 3, 2, false, true},
+ {"and", &AndFunc, 0, 0, true, false},
+ {"or", &OrFunc, 0, 0, true, false},
+
+ {"value", &ValueFunc, 1, 1, false, false},
+ {"eval", &EvalFunc, 1, 1, false, false},
+ {"shell", &ShellFunc, 1, 1, false, false},
+ {"call", &CallFunc, 0, 0, false, false},
+ {"foreach", &ForeachFunc, 3, 3, false, false},
+
+ {"origin", &OriginFunc, 1, 1, false, false},
+ {"flavor", &FlavorFunc, 1, 1, false, false},
+
+ {"info", &InfoFunc, 1, 1, false, false},
+ {"warning", &WarningFunc, 1, 1, false, false},
+ {"error", &ErrorFunc, 1, 1, false, false},
+
+ {"file", &FileFunc, 2, 1, false, false},
+
+ /* Kati custom extension functions */
+ {"KATI_deprecated_var", &DeprecatedVarFunc, 2, 1, false, false},
+ {"KATI_obsolete_var", &ObsoleteVarFunc, 2, 1, false, false},
+ {"KATI_deprecate_export", &DeprecateExportFunc, 1, 1, false, false},
+ {"KATI_obsolete_export", &ObsoleteExportFunc, 1, 1, false, false},
+
+ {"KATI_profile_makefile", &ProfileFunc, 0, 0, false, false},
+};
+
+unordered_map<StringPiece, FuncInfo*>* g_func_info_map;
+
+} // namespace
+
+void InitFuncTable() {
+ g_func_info_map = new unordered_map<StringPiece, FuncInfo*>;
+ for (size_t i = 0; i < sizeof(g_func_infos) / sizeof(g_func_infos[0]); i++) {
+ FuncInfo* fi = &g_func_infos[i];
+ bool ok = g_func_info_map->emplace(fi->name, fi).second;
+ CHECK(ok);
+ }
+}
+
+void QuitFuncTable() {
+ delete g_func_info_map;
+}
+
+FuncInfo* GetFuncInfo(StringPiece name) {
+ auto found = g_func_info_map->find(name);
+ if (found == g_func_info_map->end())
+ return NULL;
+ return found->second;
+}
+
+const vector<CommandResult*>& GetShellCommandResults() {
+ return g_command_results;
+}
diff --git a/src/func.h b/src/func.h
new file mode 100644
index 0000000..72f90fd
--- /dev/null
+++ b/src/func.h
@@ -0,0 +1,66 @@
+// Copyright 2015 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.
+
+#ifndef FUNC_H_
+#define FUNC_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "expr.h"
+#include "loc.h"
+
+using namespace std;
+
+struct FuncInfo {
+ const char* name;
+ void (*func)(const vector<Value*>& args, Evaluator* ev, string* s);
+ int arity;
+ int min_arity;
+ // For all parameters.
+ bool trim_space;
+ // Only for the first parameter.
+ bool trim_right_space_1st;
+};
+
+void InitFuncTable();
+void QuitFuncTable();
+
+FuncInfo* GetFuncInfo(StringPiece name);
+
+struct FindCommand;
+
+enum struct CommandOp {
+ SHELL,
+ FIND,
+ READ,
+ READ_MISSING,
+ WRITE,
+ APPEND,
+};
+
+struct CommandResult {
+ CommandOp op;
+ string shell;
+ string shellflag;
+ string cmd;
+ unique_ptr<FindCommand> find;
+ string result;
+ Loc loc;
+};
+
+const vector<CommandResult*>& GetShellCommandResults();
+
+#endif // FUNC_H_
diff --git a/src/io.cc b/src/io.cc
new file mode 100644
index 0000000..9ae1c5e
--- /dev/null
+++ b/src/io.cc
@@ -0,0 +1,49 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "io.h"
+
+#include "log.h"
+
+void DumpInt(FILE* fp, int v) {
+ size_t r = fwrite(&v, sizeof(v), 1, fp);
+ CHECK(r == 1);
+}
+
+void DumpString(FILE* fp, StringPiece s) {
+ DumpInt(fp, s.size());
+ size_t r = fwrite(s.data(), 1, s.size(), fp);
+ CHECK(r == s.size());
+}
+
+int LoadInt(FILE* fp) {
+ int v;
+ size_t r = fread(&v, sizeof(v), 1, fp);
+ if (r != 1)
+ return -1;
+ return v;
+}
+
+bool LoadString(FILE* fp, string* s) {
+ int len = LoadInt(fp);
+ if (len < 0)
+ return false;
+ s->resize(len);
+ size_t r = fread(&(*s)[0], 1, s->size(), fp);
+ if (r != s->size())
+ return false;
+ return true;
+}
diff --git a/src/io.h b/src/io.h
new file mode 100644
index 0000000..28316f4
--- /dev/null
+++ b/src/io.h
@@ -0,0 +1,45 @@
+// Copyright 2015 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.
+
+#ifndef IO_H_
+#define IO_H_
+
+#include <stdio.h>
+
+#include <string>
+
+#include "string_piece.h"
+
+using namespace std;
+
+void DumpInt(FILE* fp, int v);
+void DumpString(FILE* fp, StringPiece s);
+
+int LoadInt(FILE* fp);
+bool LoadString(FILE* fp, string* s);
+
+struct ScopedFile {
+ public:
+ explicit ScopedFile(FILE* fp) : fp_(fp) {}
+
+ ~ScopedFile() {
+ if (fp_)
+ fclose(fp_);
+ }
+
+ private:
+ FILE* fp_;
+};
+
+#endif // IO_H_
diff --git a/src/loc.h b/src/loc.h
new file mode 100644
index 0000000..8c9ed9f
--- /dev/null
+++ b/src/loc.h
@@ -0,0 +1,32 @@
+// Copyright 2015 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.
+
+#ifndef LOC_H_
+#define LOC_H_
+
+#include <string>
+
+#include "stringprintf.h"
+
+struct Loc {
+ Loc() : filename(0), lineno(-1) {}
+ Loc(const char* f, int l) : filename(f), lineno(l) {}
+
+ const char* filename;
+ int lineno;
+};
+
+#define LOCF(x) (x).filename, (x).lineno
+
+#endif // LOC_H_
diff --git a/src/log.cc b/src/log.cc
new file mode 100644
index 0000000..62f2422
--- /dev/null
+++ b/src/log.cc
@@ -0,0 +1,62 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "log.h"
+
+#include "flags.h"
+#include "strutil.h"
+
+#define BOLD "\033[1m"
+#define RESET "\033[0m"
+#define MAGENTA "\033[35m"
+#define RED "\033[31m"
+
+void ColorErrorLog(const char* file, int line, const char* msg) {
+ if (file == nullptr) {
+ ERROR("%s", msg);
+ return;
+ }
+
+ if (g_flags.color_warnings) {
+ StringPiece filtered = TrimPrefix(msg, "*** ");
+
+ ERROR(BOLD "%s:%d: " RED "error: " RESET BOLD "%s" RESET, file, line,
+ filtered.as_string().c_str());
+ } else {
+ ERROR("%s:%d: %s", file, line, msg);
+ }
+}
+
+void ColorWarnLog(const char* file, int line, const char* msg) {
+ if (file == nullptr) {
+ fprintf(stderr, "%s\n", msg);
+ return;
+ }
+
+ if (g_flags.color_warnings) {
+ StringPiece filtered = TrimPrefix(msg, "*warning*: ");
+ filtered = TrimPrefix(filtered, "warning: ");
+
+ fprintf(stderr,
+ BOLD "%s:%d: " MAGENTA "warning: " RESET BOLD "%s" RESET "\n", file,
+ line, filtered.as_string().c_str());
+ } else {
+ fprintf(stderr, "%s:%d: %s\n", file, line, msg);
+ }
+}
+
+bool g_log_no_exit;
+string* g_last_error;
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..ba0bd8b
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,107 @@
+// Copyright 2015 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.
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "flags.h"
+#include "log.h"
+#include "stringprintf.h"
+
+using namespace std;
+
+extern bool g_log_no_exit;
+extern string* g_last_error;
+
+// Useful for logging-only arguments.
+#define UNUSED __attribute__((unused))
+
+#ifdef NOLOG
+#define LOG(args...)
+#else
+#define LOG(args...) \
+ do { \
+ fprintf(stderr, "*kati*: %s\n", StringPrintf(args).c_str()); \
+ } while (0)
+#endif
+
+#define LOG_STAT(args...) \
+ do { \
+ if (g_flags.enable_stat_logs) \
+ fprintf(stderr, "*kati*: %s\n", StringPrintf(args).c_str()); \
+ } while (0)
+
+#define PLOG(...) \
+ do { \
+ fprintf(stderr, "%s: %s\n", StringPrintf(__VA_ARGS__).c_str(), \
+ strerror(errno)); \
+ } while (0)
+
+#define PERROR(...) \
+ do { \
+ PLOG(__VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#define WARN(...) \
+ do { \
+ fprintf(stderr, "%s\n", StringPrintf(__VA_ARGS__).c_str()); \
+ } while (0)
+
+#define KATI_WARN(...) \
+ do { \
+ if (g_flags.enable_kati_warnings) \
+ fprintf(stderr, "%s\n", StringPrintf(__VA_ARGS__).c_str()); \
+ } while (0)
+
+#define ERROR(...) \
+ do { \
+ if (!g_log_no_exit) { \
+ fprintf(stderr, "%s\n", StringPrintf(__VA_ARGS__).c_str()); \
+ exit(1); \
+ } \
+ g_last_error = new string(StringPrintf(__VA_ARGS__)); \
+ } while (0)
+
+#define CHECK(c) \
+ if (!(c)) \
+ ERROR("%s:%d: %s", __FILE__, __LINE__, #c)
+
+// Set of logging functions that will automatically colorize lines that have
+// location information when --color_warnings is set.
+void ColorWarnLog(const char* file, int line, const char* msg);
+void ColorErrorLog(const char* file, int line, const char* msg);
+
+#define WARN_LOC(loc, ...) \
+ do { \
+ ColorWarnLog(LOCF(loc), StringPrintf(__VA_ARGS__).c_str()); \
+ } while (0)
+
+#define KATI_WARN_LOC(loc, ...) \
+ do { \
+ if (g_flags.enable_kati_warnings) \
+ ColorWarnLog(LOCF(loc), StringPrintf(__VA_ARGS__).c_str()); \
+ } while (0)
+
+#define ERROR_LOC(loc, ...) \
+ do { \
+ ColorErrorLog(LOCF(loc), StringPrintf(__VA_ARGS__).c_str()); \
+ } while (0)
+
+#endif // LOG_H_
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..f2360cf
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,370 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "affinity.h"
+#include "dep.h"
+#include "eval.h"
+#include "exec.h"
+#include "file.h"
+#include "file_cache.h"
+#include "fileutil.h"
+#include "find.h"
+#include "flags.h"
+#include "func.h"
+#include "log.h"
+#include "ninja.h"
+#include "parser.h"
+#include "regen.h"
+#include "stats.h"
+#include "stmt.h"
+#include "string_piece.h"
+#include "stringprintf.h"
+#include "strutil.h"
+#include "symtab.h"
+#include "timeutil.h"
+#include "var.h"
+
+// We know that there are leaks in Kati. Turn off LeakSanitizer by default.
+extern "C" const char* __asan_default_options() {
+ return "detect_leaks=0:allow_user_segv_handler=1";
+}
+
+static void Init() {
+ InitSymtab();
+ InitFuncTable();
+ InitDepNodePool();
+ InitParser();
+}
+
+static void Quit() {
+ ReportAllStats();
+
+ QuitParser();
+ QuitDepNodePool();
+ QuitFuncTable();
+ QuitSymtab();
+}
+
+static void ReadBootstrapMakefile(const vector<Symbol>& targets,
+ vector<Stmt*>* stmts) {
+ string bootstrap =
+ ("CC?=cc\n"
+#if defined(__APPLE__)
+ "CXX?=c++\n"
+#else
+ "CXX?=g++\n"
+#endif
+ "AR?=ar\n"
+ // Pretend to be GNU make 3.81, for compatibility.
+ "MAKE_VERSION?=3.81\n"
+ "KATI?=ckati\n"
+ // Overwrite $SHELL environment variable.
+ "SHELL=/bin/sh\n"
+ // TODO: Add more builtin vars.
+ );
+
+ if (!g_flags.no_builtin_rules) {
+ bootstrap += (
+ // http://www.gnu.org/software/make/manual/make.html#Catalogue-of-Rules
+ // The document above is actually not correct. See default.c:
+ // http://git.savannah.gnu.org/cgit/make.git/tree/default.c?id=4.1
+ ".c.o:\n"
+ "\t$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<\n"
+ ".cc.o:\n"
+ "\t$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<\n"
+ // TODO: Add more builtin rules.
+ );
+ }
+ if (g_flags.generate_ninja) {
+ bootstrap += StringPrintf("MAKE?=make -j%d\n",
+ g_flags.num_jobs <= 1 ? 1 : g_flags.num_jobs / 2);
+ } else {
+ bootstrap += StringPrintf("MAKE?=%s\n",
+ JoinStrings(g_flags.subkati_args, " ").c_str());
+ }
+ bootstrap +=
+ StringPrintf("MAKECMDGOALS?=%s\n", JoinSymbols(targets, " ").c_str());
+
+ char cwd[PATH_MAX];
+ if (!getcwd(cwd, PATH_MAX)) {
+ fprintf(stderr, "getcwd failed\n");
+ CHECK(false);
+ }
+ bootstrap += StringPrintf("CURDIR:=%s\n", cwd);
+ Parse(Intern(bootstrap).str(), Loc("*bootstrap*", 0), stmts);
+}
+
+static void SetVar(StringPiece l, VarOrigin origin) {
+ size_t found = l.find('=');
+ CHECK(found != string::npos);
+ Symbol lhs = Intern(l.substr(0, found));
+ StringPiece rhs = l.substr(found + 1);
+ lhs.SetGlobalVar(
+ new RecursiveVar(Value::NewLiteral(rhs.data()), origin, rhs.data()));
+}
+
+extern "C" char** environ;
+
+class SegfaultHandler {
+ public:
+ explicit SegfaultHandler(Evaluator* ev);
+ ~SegfaultHandler();
+
+ void handle(int, siginfo_t*, void*);
+
+ private:
+ static SegfaultHandler* global_handler;
+
+ void dumpstr(const char* s) const {
+ (void)write(STDERR_FILENO, s, strlen(s));
+ }
+ void dumpint(int i) const {
+ char buf[11];
+ char* ptr = buf + sizeof(buf) - 1;
+
+ if (i < 0) {
+ i = -i;
+ dumpstr("-");
+ } else if (i == 0) {
+ dumpstr("0");
+ return;
+ }
+
+ *ptr = '\0';
+ while (ptr > buf && i > 0) {
+ *--ptr = '0' + (i % 10);
+ i = i / 10;
+ }
+
+ dumpstr(ptr);
+ }
+
+ Evaluator* ev_;
+
+ struct sigaction orig_action_;
+ struct sigaction new_action_;
+};
+
+SegfaultHandler* SegfaultHandler::global_handler = nullptr;
+
+SegfaultHandler::SegfaultHandler(Evaluator* ev) : ev_(ev) {
+ CHECK(global_handler == nullptr);
+ global_handler = this;
+
+ // Construct an alternate stack, so that we can handle stack overflows.
+ stack_t ss;
+ ss.ss_sp = malloc(SIGSTKSZ * 2);
+ CHECK(ss.ss_sp != nullptr);
+ ss.ss_size = SIGSTKSZ * 2;
+ ss.ss_flags = 0;
+ if (sigaltstack(&ss, nullptr) == -1) {
+ PERROR("sigaltstack");
+ }
+
+ // Register our segfault handler using the alternate stack, falling
+ // back to the default handler.
+ sigemptyset(&new_action_.sa_mask);
+ new_action_.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESETHAND;
+ new_action_.sa_sigaction = [](int sig, siginfo_t* info, void* context) {
+ if (global_handler != nullptr) {
+ global_handler->handle(sig, info, context);
+ }
+
+ raise(SIGSEGV);
+ };
+ sigaction(SIGSEGV, &new_action_, &orig_action_);
+}
+
+void SegfaultHandler::handle(int sig, siginfo_t* info, void* context) {
+ // Avoid fprintf in case it allocates or tries to do anything else that may
+ // hang.
+ dumpstr("*kati*: Segmentation fault, last evaluated line was ");
+ dumpstr(ev_->loc().filename);
+ dumpstr(":");
+ dumpint(ev_->loc().lineno);
+ dumpstr("\n");
+
+ // Run the original handler, in case we've been preloaded with libSegFault
+ // or similar.
+ if (orig_action_.sa_sigaction != nullptr) {
+ orig_action_.sa_sigaction(sig, info, context);
+ }
+}
+
+SegfaultHandler::~SegfaultHandler() {
+ sigaction(SIGSEGV, &orig_action_, nullptr);
+ global_handler = nullptr;
+}
+
+static int Run(const vector<Symbol>& targets,
+ const vector<StringPiece>& cl_vars,
+ const string& orig_args) {
+ double start_time = GetTime();
+
+ if (g_flags.generate_ninja && (g_flags.regen || g_flags.dump_kati_stamp)) {
+ ScopedTimeReporter tr("regen check time");
+ if (!NeedsRegen(start_time, orig_args)) {
+ fprintf(stderr, "No need to regenerate ninja file\n");
+ return 0;
+ }
+ if (g_flags.dump_kati_stamp) {
+ printf("Need to regenerate ninja file\n");
+ return 0;
+ }
+ ClearGlobCache();
+ }
+
+ SetAffinityForSingleThread();
+
+ MakefileCacheManager* cache_mgr = NewMakefileCacheManager();
+
+ Intern("MAKEFILE_LIST")
+ .SetGlobalVar(new SimpleVar(StringPrintf(" %s", g_flags.makefile),
+ VarOrigin::FILE));
+ for (char** p = environ; *p; p++) {
+ SetVar(*p, VarOrigin::ENVIRONMENT);
+ }
+ unique_ptr<Evaluator> ev(new Evaluator());
+ SegfaultHandler segfault(ev.get());
+
+ vector<Stmt*> bootstrap_asts;
+ ReadBootstrapMakefile(targets, &bootstrap_asts);
+ ev->set_is_bootstrap(true);
+ for (Stmt* stmt : bootstrap_asts) {
+ LOG("%s", stmt->DebugString().c_str());
+ stmt->Eval(ev.get());
+ }
+ ev->set_is_bootstrap(false);
+
+ ev->set_is_commandline(true);
+ for (StringPiece l : cl_vars) {
+ vector<Stmt*> asts;
+ Parse(Intern(l).str(), Loc("*bootstrap*", 0), &asts);
+ CHECK(asts.size() == 1);
+ asts[0]->Eval(ev.get());
+ }
+ ev->set_is_commandline(false);
+
+ {
+ ScopedTimeReporter tr("eval time");
+ Makefile* mk = cache_mgr->ReadMakefile(g_flags.makefile);
+ for (Stmt* stmt : mk->stmts()) {
+ LOG("%s", stmt->DebugString().c_str());
+ stmt->Eval(ev.get());
+ }
+ }
+
+ for (ParseErrorStmt* err : GetParseErrors()) {
+ WARN_LOC(err->loc(), "warning for parse error in an unevaluated line: %s",
+ err->msg.c_str());
+ }
+
+ vector<NamedDepNode> nodes;
+ {
+ ScopedTimeReporter tr("make dep time");
+ MakeDep(ev.get(), ev->rules(), ev->rule_vars(), targets, &nodes);
+ }
+
+ if (g_flags.is_syntax_check_only)
+ return 0;
+
+ if (g_flags.generate_ninja) {
+ ScopedTimeReporter tr("generate ninja time");
+ GenerateNinja(nodes, ev.get(), orig_args, start_time);
+ ev->DumpStackStats();
+ return 0;
+ }
+
+ for (const auto& p : ev->exports()) {
+ const Symbol name = p.first;
+ if (p.second) {
+ Var* v = ev->LookupVar(name);
+ const string&& value = v->Eval(ev.get());
+ LOG("setenv(%s, %s)", name.c_str(), value.c_str());
+ setenv(name.c_str(), value.c_str(), 1);
+ } else {
+ LOG("unsetenv(%s)", name.c_str());
+ unsetenv(name.c_str());
+ }
+ }
+
+ {
+ ScopedTimeReporter tr("exec time");
+ Exec(nodes, ev.get());
+ }
+
+ ev->DumpStackStats();
+
+ for (Stmt* stmt : bootstrap_asts)
+ delete stmt;
+ delete cache_mgr;
+
+ return 0;
+}
+
+static void FindFirstMakefie() {
+ if (g_flags.makefile != NULL)
+ return;
+ if (Exists("GNUmakefile")) {
+ g_flags.makefile = "GNUmakefile";
+#if !defined(__APPLE__)
+ } else if (Exists("makefile")) {
+ g_flags.makefile = "makefile";
+#endif
+ } else if (Exists("Makefile")) {
+ g_flags.makefile = "Makefile";
+ }
+}
+
+static void HandleRealpath(int argc, char** argv) {
+ char buf[PATH_MAX];
+ for (int i = 0; i < argc; i++) {
+ if (realpath(argv[i], buf))
+ printf("%s\n", buf);
+ }
+}
+
+int main(int argc, char* argv[]) {
+ if (argc >= 2 && !strcmp(argv[1], "--realpath")) {
+ HandleRealpath(argc - 2, argv + 2);
+ return 0;
+ }
+ Init();
+ string orig_args;
+ for (int i = 0; i < argc; i++) {
+ if (i)
+ orig_args += ' ';
+ orig_args += argv[i];
+ }
+ g_flags.Parse(argc, argv);
+ FindFirstMakefie();
+ if (g_flags.makefile == NULL)
+ ERROR("*** No targets specified and no makefile found.");
+ // This depends on command line flags.
+ if (g_flags.use_find_emulator)
+ InitFindEmulator();
+ int r = Run(g_flags.targets, g_flags.cl_vars, orig_args);
+ Quit();
+ return r;
+}
diff --git a/src/ninja.cc b/src/ninja.cc
new file mode 100644
index 0000000..9b6989b
--- /dev/null
+++ b/src/ninja.cc
@@ -0,0 +1,854 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "ninja.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "command.h"
+#include "dep.h"
+#include "eval.h"
+#include "file_cache.h"
+#include "fileutil.h"
+#include "find.h"
+#include "flags.h"
+#include "func.h"
+#include "io.h"
+#include "log.h"
+#include "stats.h"
+#include "string_piece.h"
+#include "stringprintf.h"
+#include "strutil.h"
+#include "thread_pool.h"
+#include "timeutil.h"
+#include "var.h"
+#include "version.h"
+
+static size_t FindCommandLineFlag(StringPiece cmd, StringPiece name) {
+ const size_t found = cmd.find(name);
+ if (found == string::npos || found == 0)
+ return string::npos;
+ return found;
+}
+
+static StringPiece FindCommandLineFlagWithArg(StringPiece cmd,
+ StringPiece name) {
+ size_t index = FindCommandLineFlag(cmd, name);
+ if (index == string::npos)
+ return StringPiece();
+
+ StringPiece val = TrimLeftSpace(cmd.substr(index + name.size()));
+ index = val.find(name);
+ while (index != string::npos) {
+ val = TrimLeftSpace(val.substr(index + name.size()));
+ index = val.find(name);
+ }
+
+ index = val.find_first_of(" \t");
+ return val.substr(0, index);
+}
+
+static bool StripPrefix(StringPiece p, StringPiece* s) {
+ if (!HasPrefix(*s, p))
+ return false;
+ *s = s->substr(p.size());
+ return true;
+}
+
+size_t GetGomaccPosForAndroidCompileCommand(StringPiece cmdline) {
+ size_t index = cmdline.find(' ');
+ if (index == string::npos)
+ return string::npos;
+ StringPiece cmd = cmdline.substr(0, index);
+ if (HasSuffix(cmd, "ccache")) {
+ index++;
+ size_t pos = GetGomaccPosForAndroidCompileCommand(cmdline.substr(index));
+ return pos == string::npos ? string::npos : pos + index;
+ }
+ if (!StripPrefix("prebuilts/", &cmd))
+ return string::npos;
+ if (!StripPrefix("gcc/", &cmd) && !StripPrefix("clang/", &cmd))
+ return string::npos;
+ if (!HasSuffix(cmd, "gcc") && !HasSuffix(cmd, "g++") &&
+ !HasSuffix(cmd, "clang") && !HasSuffix(cmd, "clang++")) {
+ return string::npos;
+ }
+
+ StringPiece rest = cmdline.substr(index);
+ return rest.find(" -c ") != string::npos ? 0 : string::npos;
+}
+
+static bool GetDepfileFromCommandImpl(StringPiece cmd, string* out) {
+ if ((FindCommandLineFlag(cmd, " -MD") == string::npos &&
+ FindCommandLineFlag(cmd, " -MMD") == string::npos) ||
+ FindCommandLineFlag(cmd, " -c") == string::npos) {
+ return false;
+ }
+
+ StringPiece mf = FindCommandLineFlagWithArg(cmd, " -MF");
+ if (!mf.empty()) {
+ mf.AppendToString(out);
+ return true;
+ }
+
+ StringPiece o = FindCommandLineFlagWithArg(cmd, " -o");
+ if (o.empty()) {
+ ERROR("Cannot find the depfile in %s", cmd.as_string().c_str());
+ return false;
+ }
+
+ StripExt(o).AppendToString(out);
+ *out += ".d";
+ return true;
+}
+
+bool GetDepfileFromCommand(string* cmd, string* out) {
+ CHECK(!cmd->empty());
+ if (!GetDepfileFromCommandImpl(*cmd, out))
+ return false;
+
+ // A hack for Android - llvm-rs-cc seems not to emit a dep file.
+ if (cmd->find("bin/llvm-rs-cc ") != string::npos) {
+ return false;
+ }
+
+ // TODO: A hack for Makefiles generated by automake.
+
+ // A hack for Android to get .P files instead of .d.
+ string p;
+ StripExt(*out).AppendToString(&p);
+ p += ".P";
+ if (cmd->find(p) != string::npos) {
+ const string rm_f = "; rm -f " + *out;
+ const size_t found = cmd->find(rm_f);
+ if (found == string::npos) {
+ ERROR("Cannot find removal of .d file: %s", cmd->c_str());
+ }
+ cmd->erase(found, rm_f.size());
+ return true;
+ }
+
+ // A hack for Android. For .s files, GCC does not use C
+ // preprocessor, so it ignores -MF flag.
+ string as = "/";
+ StripExt(Basename(*out)).AppendToString(&as);
+ as += ".s";
+ if (cmd->find(as) != string::npos) {
+ return false;
+ }
+
+ *cmd += "&& cp ";
+ *cmd += *out;
+ *cmd += ' ';
+ *cmd += *out;
+ *cmd += ".tmp ";
+ *out += ".tmp";
+ return true;
+}
+
+struct NinjaNode {
+ const DepNode* node;
+ vector<Command*> commands;
+ int rule_id;
+};
+
+class NinjaGenerator {
+ public:
+ NinjaGenerator(Evaluator* ev, double start_time)
+ : ce_(ev),
+ ev_(ev),
+ fp_(NULL),
+ rule_id_(0),
+ start_time_(start_time),
+ default_target_(NULL) {
+ ev_->set_avoid_io(true);
+ shell_ = EscapeNinja(ev->GetShell());
+ shell_flags_ = EscapeNinja(ev->GetShellFlag());
+ const string use_goma_str = ev->EvalVar(Intern("USE_GOMA"));
+ use_goma_ = !(use_goma_str.empty() || use_goma_str == "false");
+ if (g_flags.goma_dir)
+ gomacc_ = StringPrintf("%s/gomacc ", g_flags.goma_dir);
+
+ GetExecutablePath(&kati_binary_);
+ }
+
+ ~NinjaGenerator() {
+ ev_->set_avoid_io(false);
+ for (NinjaNode* nn : nodes_)
+ delete nn;
+ }
+
+ void Generate(const vector<NamedDepNode>& nodes, const string& orig_args) {
+ unlink(GetNinjaStampFilename().c_str());
+ PopulateNinjaNodes(nodes);
+ GenerateNinja();
+ GenerateShell();
+ GenerateStamp(orig_args);
+ }
+
+ static string GetStampTempFilename() {
+ return GetFilename(".kati_stamp%s.tmp");
+ }
+
+ static string GetFilename(const char* fmt) {
+ string r = g_flags.ninja_dir ? g_flags.ninja_dir : ".";
+ r += '/';
+ r += StringPrintf(fmt, g_flags.ninja_suffix ? g_flags.ninja_suffix : "");
+ return r;
+ }
+
+ private:
+ void PopulateNinjaNodes(const vector<NamedDepNode>& nodes) {
+ ScopedTimeReporter tr("ninja gen (eval)");
+ for (auto const& node : nodes) {
+ PopulateNinjaNode(node.second);
+ }
+ }
+
+ void PopulateNinjaNode(DepNode* node) {
+ if (done_.exists(node->output)) {
+ return;
+ }
+ done_.insert(node->output);
+
+ // A hack to exclude out phony target in Android. If this exists,
+ // "ninja -t clean" tries to remove this directory and fails.
+ if (g_flags.detect_android_echo && node->output.str() == "out")
+ return;
+
+ // This node is a leaf node
+ if (!node->has_rule && !node->is_phony) {
+ return;
+ }
+
+ NinjaNode* nn = new NinjaNode;
+ nn->node = node;
+ ce_.Eval(node, &nn->commands);
+ nn->rule_id = nn->commands.empty() ? -1 : rule_id_++;
+ nodes_.push_back(nn);
+
+ for (auto const& d : node->deps) {
+ PopulateNinjaNode(d.second);
+ }
+ for (auto const& d : node->order_onlys) {
+ PopulateNinjaNode(d.second);
+ }
+ for (auto const& d : node->validations) {
+ PopulateNinjaNode(d.second);
+ }
+ }
+
+ StringPiece TranslateCommand(const char* in, string* cmd_buf) {
+ const size_t orig_size = cmd_buf->size();
+ bool prev_backslash = false;
+ // Set space as an initial value so the leading comment will be
+ // stripped out.
+ char prev_char = ' ';
+ char quote = 0;
+ for (; *in; in++) {
+ switch (*in) {
+ case '#':
+ if (quote == 0 && isspace(prev_char)) {
+ while (in[1] && *in != '\n')
+ in++;
+ } else {
+ *cmd_buf += *in;
+ }
+ break;
+
+ case '\'':
+ case '"':
+ case '`':
+ if (quote) {
+ if (quote == *in)
+ quote = 0;
+ } else if (!prev_backslash) {
+ quote = *in;
+ }
+ *cmd_buf += *in;
+ break;
+
+ case '$':
+ *cmd_buf += "$$";
+ break;
+
+ case '\n':
+ if (prev_backslash) {
+ cmd_buf->resize(cmd_buf->size() - 1);
+ } else {
+ *cmd_buf += ' ';
+ }
+ break;
+
+ case '\\':
+ *cmd_buf += '\\';
+ break;
+
+ default:
+ *cmd_buf += *in;
+ }
+
+ if (*in == '\\') {
+ prev_backslash = !prev_backslash;
+ } else {
+ prev_backslash = false;
+ }
+
+ prev_char = *in;
+ }
+
+ if (prev_backslash) {
+ cmd_buf->resize(cmd_buf->size() - 1);
+ }
+
+ 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);
+ }
+
+ bool IsOutputMkdir(const char* name, StringPiece cmd) {
+ if (!HasPrefix(cmd, "mkdir -p ")) {
+ return false;
+ }
+ cmd = cmd.substr(9, cmd.size());
+ if (cmd.get(cmd.size() - 1) == '/') {
+ cmd = cmd.substr(0, cmd.size() - 1);
+ }
+
+ StringPiece dir = Dirname(name);
+ if (cmd == dir) {
+ return true;
+ }
+ return false;
+ }
+
+ bool GetDescriptionFromCommand(StringPiece cmd, string* out) {
+ if (!HasPrefix(cmd, "echo ")) {
+ return false;
+ }
+ cmd = cmd.substr(5, cmd.size());
+
+ bool prev_backslash = false;
+ char quote = 0;
+ string out_buf;
+
+ // Strip outer quotes, and fail if it is not a single echo command
+ for (StringPiece::iterator in = cmd.begin(); in != cmd.end(); in++) {
+ if (prev_backslash) {
+ prev_backslash = false;
+ out_buf += *in;
+ } else if (*in == '\\') {
+ prev_backslash = true;
+ out_buf += *in;
+ } else if (quote) {
+ if (*in == quote) {
+ quote = 0;
+ } else {
+ out_buf += *in;
+ }
+ } else {
+ switch (*in) {
+ case '\'':
+ case '"':
+ case '`':
+ quote = *in;
+ break;
+
+ case '<':
+ case '>':
+ case '&':
+ case '|':
+ case ';':
+ return false;
+
+ default:
+ out_buf += *in;
+ }
+ }
+ }
+
+ *out = out_buf;
+ return true;
+ }
+
+ bool GenShellScript(const char* name,
+ const vector<Command*>& commands,
+ string* cmd_buf,
+ string* description) {
+ bool got_descritpion = false;
+ bool use_gomacc = false;
+ auto command_count = commands.size();
+ for (const Command* c : commands) {
+ size_t cmd_begin = cmd_buf->size();
+
+ if (!cmd_buf->empty()) {
+ *cmd_buf += " && ";
+ }
+
+ const char* in = c->cmd.c_str();
+ while (isspace(*in))
+ in++;
+
+ bool needs_subshell = (command_count > 1 || c->ignore_error);
+
+ if (needs_subshell)
+ *cmd_buf += '(';
+
+ size_t cmd_start = cmd_buf->size();
+ StringPiece translated = TranslateCommand(in, cmd_buf);
+ if (g_flags.detect_android_echo && !got_descritpion && !c->echo &&
+ GetDescriptionFromCommand(translated, description)) {
+ got_descritpion = true;
+ translated.clear();
+ } else if (IsOutputMkdir(name, translated) && !c->echo &&
+ cmd_begin == 0) {
+ translated.clear();
+ }
+ if (translated.empty()) {
+ cmd_buf->resize(cmd_begin);
+ command_count -= 1;
+ continue;
+ } else if (g_flags.goma_dir) {
+ size_t pos = GetGomaccPosForAndroidCompileCommand(translated);
+ if (pos != string::npos) {
+ cmd_buf->insert(cmd_start + pos, gomacc_);
+ use_gomacc = true;
+ }
+ } else if (translated.find("/gomacc") != string::npos) {
+ use_gomacc = true;
+ }
+
+ if (c->ignore_error) {
+ *cmd_buf += " ; true";
+ }
+
+ if (needs_subshell)
+ *cmd_buf += " )";
+ }
+ return (use_goma_ || g_flags.remote_num_jobs || g_flags.goma_dir) &&
+ !use_gomacc;
+ }
+
+ bool GetDepfile(const DepNode* node, string* cmd_buf, string* depfile) {
+ if (node->depfile_var) {
+ node->depfile_var->Eval(ev_, depfile);
+ return true;
+ }
+ if (!g_flags.detect_depfiles)
+ return false;
+
+ *cmd_buf += ' ';
+ bool result = GetDepfileFromCommand(cmd_buf, depfile);
+ cmd_buf->resize(cmd_buf->size() - 1);
+ return result;
+ }
+
+ void EmitDepfile(NinjaNode* nn, string* cmd_buf, ostringstream* o) {
+ const DepNode* node = nn->node;
+ string depfile;
+ if (!GetDepfile(node, cmd_buf, &depfile))
+ return;
+ *o << " depfile = " << depfile << "\n";
+ *o << " deps = gcc\n";
+ }
+
+ void EmitNode(NinjaNode* nn, ostringstream* o) {
+ const DepNode* node = nn->node;
+ const vector<Command*>& commands = nn->commands;
+
+ string rule_name = "phony";
+ bool use_local_pool = false;
+ if (IsSpecialTarget(node->output)) {
+ return;
+ }
+ if (g_flags.enable_debug) {
+ *o << "# " << (node->loc.filename ? node->loc.filename : "(null)") << ':'
+ << node->loc.lineno << "\n";
+ }
+ if (!commands.empty()) {
+ rule_name = StringPrintf("rule%d", nn->rule_id);
+ *o << "rule " << rule_name << "\n";
+
+ string description = "build $out";
+ string cmd_buf;
+ use_local_pool |= GenShellScript(node->output.c_str(), commands, &cmd_buf,
+ &description);
+ *o << " description = " << description << "\n";
+ EmitDepfile(nn, &cmd_buf, o);
+
+ // It seems Linux is OK with ~130kB and Mac's limit is ~250kB.
+ // TODO: Find this number automatically.
+ if (cmd_buf.size() > 100 * 1000) {
+ *o << " rspfile = $out.rsp\n";
+ *o << " rspfile_content = " << cmd_buf << "\n";
+ *o << " command = " << shell_ << " $out.rsp\n";
+ } else {
+ EscapeShell(&cmd_buf);
+ *o << " command = " << shell_ << ' ' << shell_flags_ << " \"" << cmd_buf
+ << "\"\n";
+ }
+ if (node->is_restat) {
+ *o << " restat = 1\n";
+ }
+ }
+
+ EmitBuild(nn, rule_name, use_local_pool, o);
+ }
+
+ string EscapeNinja(const string& s) const {
+ if (s.find_first_of("$: ") == string::npos)
+ return s;
+ string r;
+ for (char c : s) {
+ switch (c) {
+ case '$':
+ case ':':
+ case ' ':
+ r += '$';
+#if defined(__has_cpp_attribute) && __has_cpp_attribute(clang::fallthrough)
+ [[clang::fallthrough]];
+#endif
+ default:
+ r += c;
+ }
+ }
+ return r;
+ }
+
+ string EscapeBuildTarget(Symbol s) const { return EscapeNinja(s.str()); }
+
+ void EmitBuild(NinjaNode* nn,
+ const string& rule_name,
+ bool use_local_pool,
+ ostringstream* o) {
+ const DepNode* node = nn->node;
+ string target = EscapeBuildTarget(node->output);
+ *o << "build " << target;
+ if (!node->implicit_outputs.empty()) {
+ *o << " |";
+ for (Symbol output : node->implicit_outputs) {
+ *o << " " << EscapeBuildTarget(output);
+ }
+ }
+ *o << ": " << rule_name;
+ vector<Symbol> order_onlys;
+ if (node->is_phony && !g_flags.use_ninja_phony_output) {
+ *o << " _kati_always_build_";
+ }
+ for (auto const& d : node->deps) {
+ *o << " " << EscapeBuildTarget(d.first).c_str();
+ }
+ if (!node->order_onlys.empty()) {
+ *o << " ||";
+ for (auto const& d : node->order_onlys) {
+ *o << " " << EscapeBuildTarget(d.first).c_str();
+ }
+ }
+ if (!node->validations.empty()) {
+ *o << " |@";
+ for (auto const& d : node->validations) {
+ *o << " " << EscapeBuildTarget(d.first).c_str();
+ }
+ }
+
+ *o << "\n";
+
+ string pool;
+ if (node->ninja_pool_var) {
+ node->ninja_pool_var->Eval(ev_, &pool);
+ }
+
+ if (pool != "") {
+ if (pool != "none") {
+ *o << " pool = " << pool << "\n";
+ }
+ } else if (g_flags.default_pool && rule_name != "phony") {
+ *o << " pool = " << g_flags.default_pool << "\n";
+ } else if (use_local_pool) {
+ *o << " pool = local_pool\n";
+ }
+ if (node->is_phony && g_flags.use_ninja_phony_output) {
+ *o << " phony_output = true\n";
+ }
+ if (node->is_default_target) {
+ unique_lock<mutex> lock(mu_);
+ default_target_ = node;
+ }
+ }
+
+ static string GetEnvScriptFilename() { return GetFilename("env%s.sh"); }
+
+ void GenerateNinja() {
+ ScopedTimeReporter tr("ninja gen (emit)");
+ fp_ = fopen(GetNinjaFilename().c_str(), "wb");
+ if (fp_ == NULL)
+ PERROR("fopen(build.ninja) failed");
+
+ fprintf(fp_, "# Generated by kati %s\n", kGitVersion);
+ fprintf(fp_, "\n");
+
+ if (!used_envs_.empty()) {
+ fprintf(fp_, "# Environment variables used:\n");
+ for (const auto& p : used_envs_) {
+ fprintf(fp_, "# %s=%s\n", p.first.c_str(), p.second.c_str());
+ }
+ fprintf(fp_, "\n");
+ }
+
+ if (!g_flags.no_ninja_prelude) {
+ if (g_flags.ninja_dir) {
+ fprintf(fp_, "builddir = %s\n\n", g_flags.ninja_dir);
+ }
+
+ fprintf(fp_, "pool local_pool\n");
+ fprintf(fp_, " depth = %d\n\n", g_flags.num_jobs);
+
+ if (!g_flags.use_ninja_phony_output) {
+ fprintf(fp_, "build _kati_always_build_: phony\n\n");
+ }
+ }
+
+ unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
+ CHECK(g_flags.num_jobs);
+ int num_nodes_per_task = nodes_.size() / (g_flags.num_jobs * 10) + 1;
+ int num_tasks = nodes_.size() / num_nodes_per_task + 1;
+ vector<ostringstream> bufs(num_tasks);
+ for (int i = 0; i < num_tasks; i++) {
+ tp->Submit([this, i, num_nodes_per_task, &bufs]() {
+ int l =
+ min(num_nodes_per_task * (i + 1), static_cast<int>(nodes_.size()));
+ for (int j = num_nodes_per_task * i; j < l; j++) {
+ EmitNode(nodes_[j], &bufs[i]);
+ }
+ });
+ }
+ tp->Wait();
+
+ if (!g_flags.generate_empty_ninja) {
+ for (const ostringstream& buf : bufs) {
+ fprintf(fp_, "%s", buf.str().c_str());
+ }
+ }
+
+ SymbolSet used_env_vars(Vars::used_env_vars());
+ // PATH changes $(shell).
+ used_env_vars.insert(Intern("PATH"));
+ for (Symbol e : used_env_vars) {
+ StringPiece val(getenv(e.c_str()));
+ used_envs_.emplace(e.str(), val.as_string());
+ }
+
+ string default_targets;
+ if (g_flags.targets.empty() || g_flags.gen_all_targets) {
+ CHECK(default_target_);
+ default_targets = EscapeBuildTarget(default_target_->output);
+ } else {
+ for (Symbol s : g_flags.targets) {
+ if (!default_targets.empty())
+ default_targets += ' ';
+ default_targets += EscapeBuildTarget(s);
+ }
+ }
+ if (!g_flags.generate_empty_ninja) {
+ fprintf(fp_, "\n");
+ fprintf(fp_, "default %s\n", default_targets.c_str());
+ }
+
+ fclose(fp_);
+ }
+
+ void GenerateShell() {
+ FILE* fp = fopen(GetEnvScriptFilename().c_str(), "wb");
+ if (fp == NULL)
+ PERROR("fopen(env.sh) failed");
+
+ fprintf(fp, "#!/bin/sh\n");
+ fprintf(fp, "# Generated by kati %s\n", kGitVersion);
+ fprintf(fp, "\n");
+
+ for (const auto& p : ev_->exports()) {
+ if (p.second) {
+ const string val = ev_->EvalVar(p.first);
+ fprintf(fp, "export '%s'='%s'\n", p.first.c_str(), val.c_str());
+ } else {
+ fprintf(fp, "unset '%s'\n", p.first.c_str());
+ }
+ }
+
+ fclose(fp);
+
+ fp = fopen(GetNinjaShellScriptFilename().c_str(), "wb");
+ if (fp == NULL)
+ PERROR("fopen(ninja.sh) failed");
+
+ fprintf(fp, "#!/bin/sh\n");
+ fprintf(fp, "# Generated by kati %s\n", kGitVersion);
+ fprintf(fp, "\n");
+
+ fprintf(fp, ". %s\n", GetEnvScriptFilename().c_str());
+
+ fprintf(fp, "exec ninja -f %s ", GetNinjaFilename().c_str());
+ if (g_flags.remote_num_jobs > 0) {
+ fprintf(fp, "-j%d ", g_flags.remote_num_jobs);
+ } else if (g_flags.goma_dir) {
+ fprintf(fp, "-j500 ");
+ }
+ fprintf(fp, "\"$@\"\n");
+
+ fclose(fp);
+
+ if (chmod(GetNinjaShellScriptFilename().c_str(), 0755) != 0)
+ PERROR("chmod ninja.sh failed");
+ }
+
+ void GenerateStamp(const string& orig_args) {
+ FILE* fp = fopen(GetStampTempFilename().c_str(), "wb");
+ CHECK(fp);
+
+ size_t r = fwrite(&start_time_, sizeof(start_time_), 1, fp);
+ CHECK(r == 1);
+
+ unordered_set<string> makefiles;
+ MakefileCacheManager::Get()->GetAllFilenames(&makefiles);
+ DumpInt(fp, makefiles.size() + 1);
+ DumpString(fp, kati_binary_);
+ for (const string& makefile : makefiles) {
+ DumpString(fp, makefile);
+ }
+
+ DumpInt(fp, Evaluator::used_undefined_vars().size());
+ for (Symbol v : Evaluator::used_undefined_vars()) {
+ DumpString(fp, v.str());
+ }
+ DumpInt(fp, used_envs_.size());
+ for (const auto& p : used_envs_) {
+ DumpString(fp, p.first);
+ DumpString(fp, p.second);
+ }
+
+ const unordered_map<string, vector<string>*>& globs = GetAllGlobCache();
+ DumpInt(fp, globs.size());
+ for (const auto& p : globs) {
+ DumpString(fp, p.first);
+ const vector<string>& files = *p.second;
+#if 0
+ unordered_set<string> dirs;
+ GetReadDirs(p.first, files, &dirs);
+ DumpInt(fp, dirs.size());
+ for (const string& dir : dirs) {
+ DumpString(fp, dir);
+ }
+#endif
+ DumpInt(fp, files.size());
+ for (const string& file : files) {
+ DumpString(fp, file);
+ }
+ }
+
+ const vector<CommandResult*>& crs = GetShellCommandResults();
+ DumpInt(fp, crs.size());
+ for (CommandResult* cr : crs) {
+ DumpInt(fp, static_cast<int>(cr->op));
+ DumpString(fp, cr->shell);
+ DumpString(fp, cr->shellflag);
+ DumpString(fp, cr->cmd);
+ DumpString(fp, cr->result);
+ DumpString(fp, cr->loc.filename);
+ DumpInt(fp, cr->loc.lineno);
+
+ if (cr->op == CommandOp::FIND) {
+ vector<string> missing_dirs;
+ for (StringPiece fd : cr->find->finddirs) {
+ const string& d = ConcatDir(cr->find->chdir, fd);
+ if (!Exists(d))
+ missing_dirs.push_back(d);
+ }
+ DumpInt(fp, missing_dirs.size());
+ for (const string& d : missing_dirs) {
+ DumpString(fp, d);
+ }
+
+ DumpInt(fp, cr->find->found_files->size());
+ for (StringPiece s : *cr->find->found_files) {
+ DumpString(fp, ConcatDir(cr->find->chdir, s));
+ }
+
+ DumpInt(fp, cr->find->read_dirs->size());
+ for (StringPiece s : *cr->find->read_dirs) {
+ DumpString(fp, ConcatDir(cr->find->chdir, s));
+ }
+ }
+ }
+
+ DumpString(fp, orig_args);
+
+ fclose(fp);
+
+ rename(GetStampTempFilename().c_str(), GetNinjaStampFilename().c_str());
+ }
+
+ CommandEvaluator ce_;
+ Evaluator* ev_;
+ FILE* fp_;
+ SymbolSet done_;
+ int rule_id_;
+ bool use_goma_;
+ string gomacc_;
+ string shell_;
+ string shell_flags_;
+ map<string, string> used_envs_;
+ string kati_binary_;
+ const double start_time_;
+ vector<NinjaNode*> nodes_;
+
+ mutex mu_;
+ const DepNode* default_target_;
+};
+
+string GetNinjaFilename() {
+ return NinjaGenerator::GetFilename("build%s.ninja");
+}
+
+string GetNinjaShellScriptFilename() {
+ return NinjaGenerator::GetFilename("ninja%s.sh");
+}
+
+string GetNinjaStampFilename() {
+ return NinjaGenerator::GetFilename(".kati_stamp%s");
+}
+
+void GenerateNinja(const vector<NamedDepNode>& nodes,
+ Evaluator* ev,
+ const string& orig_args,
+ double start_time) {
+ NinjaGenerator ng(ev, start_time);
+ ng.Generate(nodes, orig_args);
+}
diff --git a/src/ninja.h b/src/ninja.h
new file mode 100644
index 0000000..35053c0
--- /dev/null
+++ b/src/ninja.h
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+#ifndef NINJA_H_
+#define NINJA_H_
+
+#include <time.h>
+
+#include <string>
+#include <vector>
+
+#include "dep.h"
+#include "string_piece.h"
+
+using namespace std;
+
+class Evaluator;
+
+void GenerateNinja(const vector<NamedDepNode>& nodes,
+ Evaluator* ev,
+ const string& orig_args,
+ double start_time);
+
+string GetNinjaFilename();
+string GetNinjaShellScriptFilename();
+string GetNinjaStampFilename();
+
+// Exposed only for test.
+bool GetDepfileFromCommand(string* cmd, string* out);
+size_t GetGomaccPosForAndroidCompileCommand(StringPiece cmdline);
+
+#endif // NINJA_H_
diff --git a/src/ninja_test.cc b/src/ninja_test.cc
new file mode 100644
index 0000000..15b0693
--- /dev/null
+++ b/src/ninja_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "ninja.h"
+
+#include <assert.h>
+
+#include "log.h"
+#include "testutil.h"
+
+namespace {
+
+string GetDepfile(string cmd, string* new_cmd) {
+ new_cmd->clear();
+ string r;
+ if (GetDepfileFromCommand(&cmd, &r)) {
+ *new_cmd = cmd;
+ return r;
+ }
+ return "";
+}
+
+void TestGetDepfile() {
+ string new_cmd;
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD ", &new_cmd), "");
+ assert(g_last_error);
+ delete g_last_error;
+ g_last_error = NULL;
+
+ // clang-format off
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -o fat.o", &new_cmd), "");
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD -o fat.o -o fuga.o", &new_cmd), "fuga.d.tmp");
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD -o fat.o", &new_cmd), "fat.d.tmp");
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD -o fat", &new_cmd), "fat.d.tmp");
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD -MF foo.d -o fat.o", &new_cmd), "foo.d.tmp");
+ ASSERT_EQ(GetDepfile("g++ -c fat.cc -MD -o fat.o -MF foo.d", &new_cmd), "foo.d.tmp");
+ // A real example from maloader.
+ ASSERT_EQ(GetDepfile("g++ -g -Iinclude -Wall -MMD -fno-omit-frame-pointer -O -m64 -W -Werror -c -o fat.o fat.cc", &new_cmd), "fat.d.tmp");
+ // A real example from Android.
+ ASSERT_EQ(GetDepfile("mkdir -p out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/ && echo \"host C++: llvm-rs-cc <= frameworks/compile/slang/llvm-rs-cc.cpp\" && prebuilts/clang/linux-x86/host/3.6/bin/clang++ -I external/llvm -I external/llvm/include -I external/llvm/host/include -I external/clang/include -I external/clang/lib/CodeGen -I frameworks/compile/libbcc/include -I out/host/linux-x86/gen/EXECUTABLES/llvm-rs-cc_intermediates/include -I external/libcxx/include -I frameworks/compile/slang -I out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates -I out/host/linux-x86/gen/EXECUTABLES/llvm-rs-cc_intermediates -I libnativehelper/include/nativehelper $(cat out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/import_includes) -isystem system/core/include -isystem hardware/libhardware/include -isystem hardware/libhardware_legacy/include -isystem hardware/ril/include -isystem libnativehelper/include -isystem frameworks/native/include -isystem frameworks/native/opengl/include -isystem frameworks/av/include -isystem frameworks/base/include -isystem tools/include -isystem out/host/linux-x86/obj/include -c -fno-exceptions -Wno-multichar -m64 -Wa,--noexecstack -fPIC -no-canonical-prefixes -include build/core/combo/include/arch/linux-x86/AndroidConfig.h -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -O2 -g -fno-strict-aliasing -DNDEBUG -UDEBUG -D__compiler_offsetof=__builtin_offsetof -Werror=int-conversion -Wno-unused-command-line-argument --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --sysroot=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//sysroot -target x86_64-linux-gnu -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Wsign-promo -std=gnu++11 -DNDEBUG -UDEBUG -Wno-inconsistent-missing-override --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --sysroot=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//sysroot -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8 -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8/x86_64-linux -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8/backward -target x86_64-linux-gnu -pedantic -Wcast-qual -Wno-long-long -Wno-sign-promo -Wall -Wno-unused-parameter -Wno-return-type -Werror -std=c++11 -O0 -DTARGET_BUILD_VARIANT=eng -DRS_VERSION=23 -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -O2 -fomit-frame-pointer -Wall -W -Wno-unused-parameter -Wwrite-strings -Dsprintf=sprintf -pedantic -Wcast-qual -Wno-long-long -Wno-sign-promo -Wall -Wno-unused-parameter -Wno-return-type -Werror -std=c++11 -O0 -DTARGET_BUILD_VARIANT=eng -DRS_VERSION=23 -fno-exceptions -fpie -D_USING_LIBCXX -Wno-sign-promo -fno-rtti -Woverloaded-virtual -Wno-sign-promo -std=c++11 -nostdinc++ -MD -MF out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d -o out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.o frameworks/compile/slang/llvm-rs-cc.cpp && cp out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$//' -e '/^$/ d' -e 's/$/ :/' < out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d >> out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.P; rm -f out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d", &new_cmd), "out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d");
+ ASSERT_EQ(new_cmd, "mkdir -p out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/ && echo \"host C++: llvm-rs-cc <= frameworks/compile/slang/llvm-rs-cc.cpp\" && prebuilts/clang/linux-x86/host/3.6/bin/clang++ -I external/llvm -I external/llvm/include -I external/llvm/host/include -I external/clang/include -I external/clang/lib/CodeGen -I frameworks/compile/libbcc/include -I out/host/linux-x86/gen/EXECUTABLES/llvm-rs-cc_intermediates/include -I external/libcxx/include -I frameworks/compile/slang -I out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates -I out/host/linux-x86/gen/EXECUTABLES/llvm-rs-cc_intermediates -I libnativehelper/include/nativehelper $(cat out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/import_includes) -isystem system/core/include -isystem hardware/libhardware/include -isystem hardware/libhardware_legacy/include -isystem hardware/ril/include -isystem libnativehelper/include -isystem frameworks/native/include -isystem frameworks/native/opengl/include -isystem frameworks/av/include -isystem frameworks/base/include -isystem tools/include -isystem out/host/linux-x86/obj/include -c -fno-exceptions -Wno-multichar -m64 -Wa,--noexecstack -fPIC -no-canonical-prefixes -include build/core/combo/include/arch/linux-x86/AndroidConfig.h -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -O2 -g -fno-strict-aliasing -DNDEBUG -UDEBUG -D__compiler_offsetof=__builtin_offsetof -Werror=int-conversion -Wno-unused-command-line-argument --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --sysroot=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//sysroot -target x86_64-linux-gnu -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Wsign-promo -std=gnu++11 -DNDEBUG -UDEBUG -Wno-inconsistent-missing-override --gcc-toolchain=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8/ --sysroot=prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//sysroot -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8 -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8/x86_64-linux -isystem prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8//x86_64-linux/include/c++/4.8/backward -target x86_64-linux-gnu -pedantic -Wcast-qual -Wno-long-long -Wno-sign-promo -Wall -Wno-unused-parameter -Wno-return-type -Werror -std=c++11 -O0 -DTARGET_BUILD_VARIANT=eng -DRS_VERSION=23 -D_GNU_SOURCE -D__STDC_LIMIT_MACROS -O2 -fomit-frame-pointer -Wall -W -Wno-unused-parameter -Wwrite-strings -Dsprintf=sprintf -pedantic -Wcast-qual -Wno-long-long -Wno-sign-promo -Wall -Wno-unused-parameter -Wno-return-type -Werror -std=c++11 -O0 -DTARGET_BUILD_VARIANT=eng -DRS_VERSION=23 -fno-exceptions -fpie -D_USING_LIBCXX -Wno-sign-promo -fno-rtti -Woverloaded-virtual -Wno-sign-promo -std=c++11 -nostdinc++ -MD -MF out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d -o out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.o frameworks/compile/slang/llvm-rs-cc.cpp && cp out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$//' -e '/^$/ d' -e 's/$/ :/' < out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.d >> out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/llvm-rs-cc.P");
+ ASSERT_EQ(GetDepfile("echo \"target asm: libsonivox <= external/sonivox/arm-wt-22k/lib_src/ARM-E_filter_gnu.s\" && mkdir -p out/target/product/generic/obj/SHARED_LIBRARIES/libsonivox_intermediates/lib_src/ && prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin/arm-linux-androideabi-gcc -I external/sonivox/arm-wt-22k/host_src -I external/sonivox/arm-wt-22k/lib_src -I external/libcxx/include -I external/sonivox/arm-wt-22k -I out/target/product/generic/obj/SHARED_LIBRARIES/libsonivox_intermediates -I out/target/product/generic/gen/SHARED_LIBRARIES/libsonivox_intermediates -I libnativehelper/include/nativehelper $$(cat out/target/product/generic/obj/SHARED_LIBRARIES/libsonivox_intermediates/import_includes) -isystem system/core/include -isystem hardware/libhardware/include -isystem hardware/libhardware_legacy/include -isystem hardware/ril/include -isystem libnativehelper/include -isystem frameworks/native/include -isystem frameworks/native/opengl/include -isystem frameworks/av/include -isystem frameworks/base/include -isystem out/target/product/generic/obj/include -isystem bionic/libc/arch-arm/include -isystem bionic/libc/include -isystem bionic/libc/kernel/uapi -isystem bionic/libc/kernel/uapi/asm-arm -isystem bionic/libm/include -isystem bionic/libm/include/arm -c -fno-exceptions -Wno-multichar -msoft-float -ffunction-sections -fdata-sections -funwind-tables -fstack-protector -Wa,--noexecstack -Werror=format-security -D_FORTIFY_SOURCE=2 -fno-short-enums -no-canonical-prefixes -fno-canonical-system-headers -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -include build/core/combo/include/arch/linux-arm/AndroidConfig.h -I build/core/combo/include/arch/linux-arm/ -fno-builtin-sin -fno-strict-volatile-bitfields -Wno-psabi -mthumb-interwork -DANDROID -fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith -Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point -DNDEBUG -g -Wstrict-aliasing=2 -fgcse-after-reload -frerun-cse-after-loop -frename-registers -DNDEBUG -UDEBUG -Wa,\"-I\" -Wa,\"external/sonivox/arm-wt-22k/lib_src\" -Wa,\"--defsym\" -Wa,\"SAMPLE_RATE_22050=1\" -Wa,\"--defsym\" -Wa,\"STEREO_OUTPUT=1\" -Wa,\"--defsym\" -Wa,\"FILTER_ENABLED=1\" -Wa,\"--defsym\" -Wa,\"SAMPLES_8_BIT=1\" -D__ASSEMBLY__ -MD -MF out/target/product/generic/obj/SHARED_LIBRARIES/libsonivox_intermediates/lib_src/ARM-E_filter_gnu.d -o out/target/product/generic/obj/SHARED_LIBRARIES/libsonivox_intermediates/lib_src/ARM-E_filter_gnu.o external/sonivox/arm-wt-22k/lib_src/ARM-E_filter_gnu.s", &new_cmd), "");
+ ASSERT_EQ(GetDepfile("echo \"RenderScript: Galaxy4 <= packages/wallpapers/Galaxy4/src/com/android/galaxy4/galaxy.rs\" && rm -rf out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript && mkdir -p out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript/res/raw && mkdir -p out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript/src && out/host/linux-x86/bin/llvm-rs-cc -o out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript/res/raw -p out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript/src -d out/target/common/obj/APPS/Galaxy4_intermediates/src/renderscript -a out/target/common/obj/APPS/Galaxy4_intermediates/src/RenderScript.stamp -MD -target-api 14 -Wall -Werror -I prebuilts/sdk/renderscript/clang-include -I prebuilts/sdk/renderscript/include packages/wallpapers/Galaxy4/src/com/android/galaxy4/galaxy.rs && mkdir -p out/target/common/obj/APPS/Galaxy4_intermediates/src/ && touch out/target/common/obj/APPS/Galaxy4_intermediates/src/RenderScript.stamp", &new_cmd), "");
+ ASSERT_EQ(GetDepfile("(echo \"bc: libclcore.bc <= frameworks/rs/driver/runtime/arch/generic.c\") && (mkdir -p out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/) && (prebuilts/clang/linux-x86/host/3.6/bin/clang -Iframeworks/rs/scriptc -Iexternal/clang/lib/Headers -MD -DRS_VERSION=23 -std=c99 -c -O3 -fno-builtin -emit-llvm -target armv7-none-linux-gnueabi -fsigned-char -Iframeworks/rs/cpu_ref -DRS_DECLARE_EXPIRED_APIS -Xclang -target-feature -Xclang +long64 frameworks/rs/driver/runtime/arch/generic.c -o out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.bc) && (cp out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' -e '/^$$/ d' -e 's/$$/ :/' < out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d >> out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.P; rm -f out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d)", &new_cmd), "out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d");
+ ASSERT_EQ(new_cmd, "(echo \"bc: libclcore.bc <= frameworks/rs/driver/runtime/arch/generic.c\") && (mkdir -p out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/) && (prebuilts/clang/linux-x86/host/3.6/bin/clang -Iframeworks/rs/scriptc -Iexternal/clang/lib/Headers -MD -DRS_VERSION=23 -std=c99 -c -O3 -fno-builtin -emit-llvm -target armv7-none-linux-gnueabi -fsigned-char -Iframeworks/rs/cpu_ref -DRS_DECLARE_EXPIRED_APIS -Xclang -target-feature -Xclang +long64 frameworks/rs/driver/runtime/arch/generic.c -o out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.bc) && (cp out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' -e '/^$$/ d' -e 's/$$/ :/' < out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.d >> out/target/product/generic/obj/SHARED_LIBRARIES/libclcore.bc_intermediates/arch/generic.P)");
+ ASSERT_EQ(GetDepfile("gcc -c foo.P.c", &new_cmd), "");
+ ASSERT_EQ(GetDepfile("gcc -MMD foo.o -o foo", &new_cmd), "");
+ // TODO: Fix for automake.
+ // ASSERT_EQ(GetDepfile("(/bin/sh ./libtool --tag=CXX --mode=compile g++ -DHAVE_CONFIG_H -I. -I./src -I./src -Wall -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare -DNO_FRAME_POINTER -DNDEBUG -g -O2 -MT libglog_la-logging.lo -MD -MP -MF .deps/libglog_la-logging.Tpo -c -o libglog_la-logging.lo `test -f 'src/logging.cc' || echo './'`src/logging.cc) && (mv -f .deps/libglog_la-logging.Tpo .deps/libglog_la-logging.Plo)", &new_cmd), ".deps/libglog_la-logging.Plo");
+ // ASSERT_EQ(GetDepfile("(g++ -DHAVE_CONFIG_H -I. -I./src -I./src -pthread -Wall -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare -DNO_FRAME_POINTER -g -O2 -MT signalhandler_unittest-signalhandler_unittest.o -MD -MP -MF .deps/signalhandler_unittest-signalhandler_unittest.Tpo -c -o signalhandler_unittest-signalhandler_unittest.o `test -f 'src/signalhandler_unittest.cc' || echo './'`src/signalhandler_unittest.cc) && (mv -f .deps/signalhandler_unittest-signalhandler_unittest.Tpo .deps/signalhandler_unittest-signalhandler_unittest.Po)", &new_cmd), ".deps/signalhandler_unittest-signalhandler_unittest.Po");
+ // clang-format on
+
+ assert(!g_last_error);
+}
+
+static void TestGetGomaccPosForAndroidCompileCommand() {
+ ASSERT_EQ(GetGomaccPosForAndroidCompileCommand(
+ "prebuilts/clang/linux-x86/host/3.6/bin/clang++ -c foo.c"),
+ 0);
+ ASSERT_EQ(GetGomaccPosForAndroidCompileCommand(
+ "prebuilts/misc/linux-x86/ccache/ccache "
+ "prebuilts/clang/linux-x86/host/3.6/bin/clang++ -c foo.c"),
+ 39);
+ ASSERT_EQ(GetGomaccPosForAndroidCompileCommand("echo foo"), string::npos);
+}
+
+} // namespace
+
+int main() {
+ g_log_no_exit = true;
+ TestGetDepfile();
+ TestGetGomaccPosForAndroidCompileCommand();
+ assert(!g_failed);
+}
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 0000000..4980f77
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,625 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "parser.h"
+
+#include <stack>
+#include <unordered_map>
+
+#include "expr.h"
+#include "file.h"
+#include "loc.h"
+#include "log.h"
+#include "stats.h"
+#include "stmt.h"
+#include "string_piece.h"
+#include "strutil.h"
+
+enum struct ParserState {
+ NOT_AFTER_RULE = 0,
+ AFTER_RULE,
+ MAYBE_AFTER_RULE,
+};
+
+class Parser {
+ struct IfState {
+ IfStmt* stmt;
+ bool is_in_else;
+ int num_nest;
+ };
+
+ typedef void (Parser::*DirectiveHandler)(StringPiece line,
+ StringPiece directive);
+ typedef unordered_map<StringPiece, DirectiveHandler> DirectiveMap;
+
+ public:
+ Parser(StringPiece buf, const char* filename, vector<Stmt*>* stmts)
+ : buf_(buf),
+ state_(ParserState::NOT_AFTER_RULE),
+ stmts_(stmts),
+ out_stmts_(stmts),
+ num_define_nest_(0),
+ num_if_nest_(0),
+ loc_(filename, 0),
+ fixed_lineno_(false) {}
+
+ Parser(StringPiece buf, const Loc& loc, vector<Stmt*>* stmts)
+ : buf_(buf),
+ state_(ParserState::NOT_AFTER_RULE),
+ stmts_(stmts),
+ out_stmts_(stmts),
+ num_if_nest_(0),
+ loc_(loc),
+ fixed_lineno_(true) {}
+
+ ~Parser() {}
+
+ void Parse() {
+ l_ = 0;
+
+ for (l_ = 0; l_ < buf_.size();) {
+ size_t lf_cnt = 0;
+ size_t e = FindEndOfLine(&lf_cnt);
+ if (!fixed_lineno_)
+ loc_.lineno++;
+ StringPiece line(buf_.data() + l_, e - l_);
+ if (line.get(line.size() - 1) == '\r')
+ line.remove_suffix(1);
+ orig_line_with_directives_ = line;
+ ParseLine(line);
+ if (!fixed_lineno_)
+ loc_.lineno += lf_cnt - 1;
+ if (e == buf_.size())
+ break;
+
+ l_ = e + 1;
+ }
+
+ if (!if_stack_.empty())
+ ERROR_LOC(Loc(loc_.filename, loc_.lineno + 1), "*** missing `endif'.");
+ if (!define_name_.empty())
+ ERROR_LOC(Loc(loc_.filename, define_start_line_),
+ "*** missing `endef', unterminated `define'.");
+ }
+
+ static void Init() {
+ make_directives_ = new DirectiveMap;
+ (*make_directives_)["include"] = &Parser::ParseInclude;
+ (*make_directives_)["-include"] = &Parser::ParseInclude;
+ (*make_directives_)["sinclude"] = &Parser::ParseInclude;
+ (*make_directives_)["define"] = &Parser::ParseDefine;
+ (*make_directives_)["ifdef"] = &Parser::ParseIfdef;
+ (*make_directives_)["ifndef"] = &Parser::ParseIfdef;
+ (*make_directives_)["ifeq"] = &Parser::ParseIfeq;
+ (*make_directives_)["ifneq"] = &Parser::ParseIfeq;
+ (*make_directives_)["else"] = &Parser::ParseElse;
+ (*make_directives_)["endif"] = &Parser::ParseEndif;
+ (*make_directives_)["override"] = &Parser::ParseOverride;
+ (*make_directives_)["export"] = &Parser::ParseExport;
+ (*make_directives_)["unexport"] = &Parser::ParseUnexport;
+
+ else_if_directives_ = new DirectiveMap;
+ (*else_if_directives_)["ifdef"] = &Parser::ParseIfdef;
+ (*else_if_directives_)["ifndef"] = &Parser::ParseIfdef;
+ (*else_if_directives_)["ifeq"] = &Parser::ParseIfeq;
+ (*else_if_directives_)["ifneq"] = &Parser::ParseIfeq;
+
+ assign_directives_ = new DirectiveMap;
+ (*assign_directives_)["define"] = &Parser::ParseDefine;
+ (*assign_directives_)["export"] = &Parser::ParseExport;
+ (*assign_directives_)["override"] = &Parser::ParseOverride;
+
+ shortest_directive_len_ = 9999;
+ longest_directive_len_ = 0;
+ for (auto p : *make_directives_) {
+ size_t len = p.first.size();
+ shortest_directive_len_ = min(len, shortest_directive_len_);
+ longest_directive_len_ = max(len, longest_directive_len_);
+ }
+ }
+
+ static void Quit() { delete make_directives_; }
+
+ void set_state(ParserState st) { state_ = st; }
+
+ static vector<ParseErrorStmt*> parse_errors;
+
+ private:
+ void Error(const string& msg) {
+ ParseErrorStmt* stmt = new ParseErrorStmt();
+ stmt->set_loc(loc_);
+ stmt->msg = msg;
+ out_stmts_->push_back(stmt);
+ parse_errors.push_back(stmt);
+ }
+
+ size_t FindEndOfLine(size_t* lf_cnt) {
+ return ::FindEndOfLine(buf_, l_, lf_cnt);
+ }
+
+ Value* ParseExpr(StringPiece s, ParseExprOpt opt = ParseExprOpt::NORMAL) {
+ return ::ParseExpr(loc_, s, opt);
+ }
+
+ void ParseLine(StringPiece line) {
+ if (!define_name_.empty()) {
+ ParseInsideDefine(line);
+ return;
+ }
+
+ if (line.empty() || (line.size() == 1 && line[0] == '\r'))
+ return;
+
+ current_directive_ = AssignDirective::NONE;
+
+ if (line[0] == '\t' && state_ != ParserState::NOT_AFTER_RULE) {
+ CommandStmt* stmt = new CommandStmt();
+ stmt->set_loc(loc_);
+ stmt->expr = ParseExpr(line.substr(1), ParseExprOpt::COMMAND);
+ stmt->orig = line;
+ out_stmts_->push_back(stmt);
+ return;
+ }
+
+ line = TrimLeftSpace(line);
+
+ if (line[0] == '#')
+ return;
+
+ if (HandleDirective(line, make_directives_)) {
+ return;
+ }
+
+ ParseRuleOrAssign(line);
+ }
+
+ void ParseRuleOrAssign(StringPiece line) {
+ size_t sep = FindThreeOutsideParen(line, ':', '=', ';');
+ if (sep == string::npos || line[sep] == ';') {
+ ParseRule(line, string::npos);
+ } else if (line[sep] == '=') {
+ ParseAssign(line, sep);
+ } else if (line.get(sep + 1) == '=') {
+ ParseAssign(line, sep + 1);
+ } else if (line[sep] == ':') {
+ ParseRule(line, sep);
+ } else {
+ CHECK(false);
+ }
+ }
+
+ void ParseRule(StringPiece line, size_t sep) {
+ if (current_directive_ != AssignDirective::NONE) {
+ if (IsInExport())
+ return;
+ if (sep != string::npos) {
+ sep += orig_line_with_directives_.size() - line.size();
+ }
+ line = orig_line_with_directives_;
+ }
+
+ line = TrimLeftSpace(line);
+ if (line.empty())
+ return;
+
+ if (orig_line_with_directives_[0] == '\t') {
+ Error("*** commands commence before first target.");
+ return;
+ }
+
+ const bool is_rule = sep != string::npos && line[sep] == ':';
+ RuleStmt* rule_stmt = new RuleStmt();
+ rule_stmt->set_loc(loc_);
+
+ size_t found = FindTwoOutsideParen(line.substr(sep + 1), '=', ';');
+ if (found != string::npos) {
+ found += sep + 1;
+ rule_stmt->lhs = ParseExpr(TrimSpace(line.substr(0, found)));
+ if (line[found] == ';') {
+ rule_stmt->sep = RuleStmt::SEP_SEMICOLON;
+ } else if (line[found] == '=') {
+ if (line.size() > (found + 2) && line[found + 1] == '$' &&
+ line[found + 2] == '=') {
+ rule_stmt->sep = RuleStmt::SEP_FINALEQ;
+ found += 2;
+ } else {
+ rule_stmt->sep = RuleStmt::SEP_EQ;
+ }
+ }
+ ParseExprOpt opt = rule_stmt->sep == RuleStmt::SEP_SEMICOLON
+ ? ParseExprOpt::COMMAND
+ : ParseExprOpt::NORMAL;
+ rule_stmt->rhs = ParseExpr(TrimLeftSpace(line.substr(found + 1)), opt);
+ } else {
+ rule_stmt->lhs = ParseExpr(line);
+ rule_stmt->sep = RuleStmt::SEP_NULL;
+ rule_stmt->rhs = NULL;
+ }
+ out_stmts_->push_back(rule_stmt);
+ state_ = is_rule ? ParserState::AFTER_RULE : ParserState::MAYBE_AFTER_RULE;
+ }
+
+ void ParseAssign(StringPiece line, size_t separator_pos) {
+ if (separator_pos == 0) {
+ Error("*** empty variable name ***");
+ return;
+ }
+ StringPiece lhs;
+ StringPiece rhs;
+ AssignOp op;
+ ParseAssignStatement(line, separator_pos, &lhs, &rhs, &op);
+
+ // If rhs starts with '$=', this is 'final assignment',
+ // e.g., a combination of the assignment and
+ // .KATI_READONLY := <lhs>
+ // statement. Note that we assume that ParseAssignStatement
+ // trimmed the left
+ bool is_final = (rhs.size() >= 2 && rhs[0] == '$' && rhs[1] == '=');
+ if (is_final) {
+ rhs = TrimLeftSpace(rhs.substr(2));
+ }
+
+ AssignStmt* stmt = new AssignStmt();
+ stmt->set_loc(loc_);
+ stmt->lhs = ParseExpr(lhs);
+ stmt->rhs = ParseExpr(rhs);
+ stmt->orig_rhs = rhs;
+ stmt->op = op;
+ stmt->directive = current_directive_;
+ stmt->is_final = is_final;
+ out_stmts_->push_back(stmt);
+ state_ = ParserState::NOT_AFTER_RULE;
+ }
+
+ void ParseInclude(StringPiece line, StringPiece directive) {
+ IncludeStmt* stmt = new IncludeStmt();
+ stmt->set_loc(loc_);
+ stmt->expr = ParseExpr(line);
+ stmt->should_exist = directive[0] == 'i';
+ out_stmts_->push_back(stmt);
+ state_ = ParserState::NOT_AFTER_RULE;
+ }
+
+ void ParseDefine(StringPiece line, StringPiece) {
+ if (line.empty()) {
+ Error("*** empty variable name.");
+ return;
+ }
+ define_name_ = line;
+ num_define_nest_ = 1;
+ define_start_ = 0;
+ define_start_line_ = loc_.lineno;
+ state_ = ParserState::NOT_AFTER_RULE;
+ }
+
+ void ParseInsideDefine(StringPiece line) {
+ line = TrimLeftSpace(line);
+ StringPiece directive = GetDirective(line);
+ if (directive == "define")
+ num_define_nest_++;
+ else if (directive == "endef")
+ num_define_nest_--;
+ if (num_define_nest_ > 0) {
+ if (define_start_ == 0)
+ define_start_ = l_;
+ return;
+ }
+
+ StringPiece rest = TrimRightSpace(
+ RemoveComment(TrimLeftSpace(line.substr(sizeof("endef")))));
+ if (!rest.empty()) {
+ WARN_LOC(loc_, "extraneous text after `endef' directive");
+ }
+
+ AssignStmt* stmt = new AssignStmt();
+ stmt->set_loc(Loc(loc_.filename, define_start_line_));
+ stmt->lhs = ParseExpr(define_name_);
+ StringPiece rhs;
+ if (define_start_)
+ rhs = buf_.substr(define_start_, l_ - define_start_ - 1);
+ stmt->rhs = ParseExpr(rhs, ParseExprOpt::DEFINE);
+ stmt->orig_rhs = rhs;
+ stmt->op = AssignOp::EQ;
+ stmt->directive = current_directive_;
+ out_stmts_->push_back(stmt);
+ define_name_.clear();
+ }
+
+ void EnterIf(IfStmt* stmt) {
+ IfState* st = new IfState();
+ st->stmt = stmt;
+ st->is_in_else = false;
+ st->num_nest = num_if_nest_;
+ if_stack_.push(st);
+ out_stmts_ = &stmt->true_stmts;
+ }
+
+ void ParseIfdef(StringPiece line, StringPiece directive) {
+ IfStmt* stmt = new IfStmt();
+ stmt->set_loc(loc_);
+ stmt->op = directive[2] == 'n' ? CondOp::IFNDEF : CondOp::IFDEF;
+ stmt->lhs = ParseExpr(line);
+ stmt->rhs = NULL;
+ out_stmts_->push_back(stmt);
+ EnterIf(stmt);
+ }
+
+ bool ParseIfEqCond(StringPiece s, IfStmt* stmt) {
+ if (s.empty()) {
+ return false;
+ }
+
+ if (s[0] == '(' && s[s.size() - 1] == ')') {
+ s = s.substr(1, s.size() - 2);
+ char terms[] = {',', '\0'};
+ size_t n;
+ stmt->lhs = ParseExprImpl(loc_, s, terms, ParseExprOpt::NORMAL, &n, true);
+ if (s[n] != ',')
+ return false;
+ s = TrimLeftSpace(s.substr(n + 1));
+ stmt->rhs = ParseExprImpl(loc_, s, NULL, ParseExprOpt::NORMAL, &n);
+ s = TrimLeftSpace(s.substr(n));
+ } else {
+ for (int i = 0; i < 2; i++) {
+ if (s.empty())
+ return false;
+ char quote = s[0];
+ if (quote != '\'' && quote != '"')
+ return false;
+ size_t end = s.find(quote, 1);
+ if (end == string::npos)
+ return false;
+ Value* v = ParseExpr(s.substr(1, end - 1), ParseExprOpt::NORMAL);
+ if (i == 0)
+ stmt->lhs = v;
+ else
+ stmt->rhs = v;
+ s = TrimLeftSpace(s.substr(end + 1));
+ }
+ }
+ if (!s.empty()) {
+ WARN_LOC(loc_, "extraneous text after `ifeq' directive");
+ return true;
+ }
+ return true;
+ }
+
+ void ParseIfeq(StringPiece line, StringPiece directive) {
+ IfStmt* stmt = new IfStmt();
+ stmt->set_loc(loc_);
+ stmt->op = directive[2] == 'n' ? CondOp::IFNEQ : CondOp::IFEQ;
+
+ if (!ParseIfEqCond(line, stmt)) {
+ Error("*** invalid syntax in conditional.");
+ return;
+ }
+
+ out_stmts_->push_back(stmt);
+ EnterIf(stmt);
+ }
+
+ void ParseElse(StringPiece line, StringPiece) {
+ if (!CheckIfStack("else"))
+ return;
+ IfState* st = if_stack_.top();
+ if (st->is_in_else) {
+ Error("*** only one `else' per conditional.");
+ return;
+ }
+ st->is_in_else = true;
+ out_stmts_ = &st->stmt->false_stmts;
+
+ StringPiece next_if = TrimLeftSpace(line);
+ if (next_if.empty())
+ return;
+
+ num_if_nest_ = st->num_nest + 1;
+ if (!HandleDirective(next_if, else_if_directives_)) {
+ WARN_LOC(loc_, "extraneous text after `else' directive");
+ }
+ num_if_nest_ = 0;
+ }
+
+ void ParseEndif(StringPiece line, StringPiece) {
+ if (!CheckIfStack("endif"))
+ return;
+ if (!line.empty()) {
+ Error("extraneous text after `endif` directive");
+ return;
+ }
+ IfState st = *if_stack_.top();
+ for (int t = 0; t <= st.num_nest; t++) {
+ delete if_stack_.top();
+ if_stack_.pop();
+ if (if_stack_.empty()) {
+ out_stmts_ = stmts_;
+ } else {
+ IfState* st = if_stack_.top();
+ if (st->is_in_else)
+ out_stmts_ = &st->stmt->false_stmts;
+ else
+ out_stmts_ = &st->stmt->true_stmts;
+ }
+ }
+ }
+
+ bool IsInExport() const {
+ return (static_cast<int>(current_directive_) &
+ static_cast<int>(AssignDirective::EXPORT));
+ }
+
+ void CreateExport(StringPiece line, bool is_export) {
+ ExportStmt* stmt = new ExportStmt;
+ stmt->set_loc(loc_);
+ stmt->expr = ParseExpr(line);
+ stmt->is_export = is_export;
+ out_stmts_->push_back(stmt);
+ }
+
+ void ParseOverride(StringPiece line, StringPiece) {
+ current_directive_ = static_cast<AssignDirective>(
+ (static_cast<int>(current_directive_) |
+ static_cast<int>(AssignDirective::OVERRIDE)));
+ if (HandleDirective(line, assign_directives_))
+ return;
+ if (IsInExport()) {
+ CreateExport(line, true);
+ }
+ ParseRuleOrAssign(line);
+ }
+
+ void ParseExport(StringPiece line, StringPiece) {
+ current_directive_ = static_cast<AssignDirective>(
+ (static_cast<int>(current_directive_) |
+ static_cast<int>(AssignDirective::EXPORT)));
+ if (HandleDirective(line, assign_directives_))
+ return;
+ CreateExport(line, true);
+ ParseRuleOrAssign(line);
+ }
+
+ void ParseUnexport(StringPiece line, StringPiece) {
+ CreateExport(line, false);
+ }
+
+ bool CheckIfStack(const char* keyword) {
+ if (if_stack_.empty()) {
+ Error(StringPrintf("*** extraneous `%s'.", keyword));
+ return false;
+ }
+ return true;
+ }
+
+ StringPiece RemoveComment(StringPiece line) {
+ size_t i = FindOutsideParen(line, '#');
+ if (i == string::npos)
+ return line;
+ return line.substr(0, i);
+ }
+
+ StringPiece GetDirective(StringPiece line) {
+ if (line.size() < shortest_directive_len_)
+ return StringPiece();
+ StringPiece prefix = line.substr(0, longest_directive_len_ + 1);
+ size_t space_index = prefix.find_first_of(" \t#");
+ return prefix.substr(0, space_index);
+ }
+
+ bool HandleDirective(StringPiece line, const DirectiveMap* directive_map) {
+ StringPiece directive = GetDirective(line);
+ auto found = directive_map->find(directive);
+ if (found == directive_map->end())
+ return false;
+
+ StringPiece rest = TrimRightSpace(
+ RemoveComment(TrimLeftSpace(line.substr(directive.size()))));
+ (this->*found->second)(rest, directive);
+ return true;
+ }
+
+ StringPiece buf_;
+ size_t l_;
+ ParserState state_;
+
+ vector<Stmt*>* stmts_;
+ vector<Stmt*>* out_stmts_;
+
+ StringPiece define_name_;
+ int num_define_nest_;
+ size_t define_start_;
+ int define_start_line_;
+
+ StringPiece orig_line_with_directives_;
+ AssignDirective current_directive_;
+
+ int num_if_nest_;
+ stack<IfState*> if_stack_;
+
+ Loc loc_;
+ bool fixed_lineno_;
+
+ static DirectiveMap* make_directives_;
+ static DirectiveMap* else_if_directives_;
+ static DirectiveMap* assign_directives_;
+ static size_t shortest_directive_len_;
+ static size_t longest_directive_len_;
+};
+
+void Parse(Makefile* mk) {
+ COLLECT_STATS("parse file time");
+ Parser parser(StringPiece(mk->buf()), mk->filename().c_str(),
+ mk->mutable_stmts());
+ parser.Parse();
+}
+
+void Parse(StringPiece buf, const Loc& loc, vector<Stmt*>* out_stmts) {
+ COLLECT_STATS("parse eval time");
+ Parser parser(buf, loc, out_stmts);
+ parser.Parse();
+}
+
+void ParseNotAfterRule(StringPiece buf,
+ const Loc& loc,
+ vector<Stmt*>* out_stmts) {
+ Parser parser(buf, loc, out_stmts);
+ parser.set_state(ParserState::NOT_AFTER_RULE);
+ parser.Parse();
+}
+
+void InitParser() {
+ Parser::Init();
+}
+
+void QuitParser() {
+ Parser::Quit();
+}
+
+Parser::DirectiveMap* Parser::make_directives_;
+Parser::DirectiveMap* Parser::else_if_directives_;
+Parser::DirectiveMap* Parser::assign_directives_;
+size_t Parser::shortest_directive_len_;
+size_t Parser::longest_directive_len_;
+vector<ParseErrorStmt*> Parser::parse_errors;
+
+void ParseAssignStatement(StringPiece line,
+ size_t sep,
+ StringPiece* lhs,
+ StringPiece* rhs,
+ AssignOp* op) {
+ CHECK(sep != 0);
+ *op = AssignOp::EQ;
+ size_t lhs_end = sep;
+ switch (line[sep - 1]) {
+ case ':':
+ lhs_end--;
+ *op = AssignOp::COLON_EQ;
+ break;
+ case '+':
+ lhs_end--;
+ *op = AssignOp::PLUS_EQ;
+ break;
+ case '?':
+ lhs_end--;
+ *op = AssignOp::QUESTION_EQ;
+ break;
+ }
+ *lhs = TrimSpace(line.substr(0, lhs_end));
+ *rhs = TrimLeftSpace(line.substr(sep + 1));
+}
+
+const vector<ParseErrorStmt*>& GetParseErrors() {
+ return Parser::parse_errors;
+}
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..e4cf7c2
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,45 @@
+// Copyright 2015 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.
+
+#ifndef PARSER_H_
+#define PARSER_H_
+
+#include <vector>
+
+#include "loc.h"
+#include "stmt.h"
+#include "string_piece.h"
+
+using namespace std;
+
+class Makefile;
+
+void Parse(Makefile* mk);
+void Parse(StringPiece buf, const Loc& loc, vector<Stmt*>* out_asts);
+void ParseNotAfterRule(StringPiece buf,
+ const Loc& loc,
+ vector<Stmt*>* out_asts);
+
+void ParseAssignStatement(StringPiece line,
+ size_t sep,
+ StringPiece* lhs,
+ StringPiece* rhs,
+ AssignOp* op);
+
+void InitParser();
+void QuitParser();
+
+const vector<ParseErrorStmt*>& GetParseErrors();
+
+#endif // PARSER_H_
diff --git a/src/regen.cc b/src/regen.cc
new file mode 100644
index 0000000..1cb94e0
--- /dev/null
+++ b/src/regen.cc
@@ -0,0 +1,485 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+#include "regen.h"
+
+#include <sys/stat.h>
+
+#include <algorithm>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include "affinity.h"
+#include "fileutil.h"
+#include "find.h"
+#include "func.h"
+#include "io.h"
+#include "log.h"
+#include "ninja.h"
+#include "stats.h"
+#include "strutil.h"
+#include "thread_pool.h"
+
+namespace {
+
+#define RETURN_TRUE \
+ do { \
+ if (g_flags.dump_kati_stamp) \
+ needs_regen_ = true; \
+ else \
+ return true; \
+ } while (0)
+
+bool ShouldIgnoreDirty(StringPiece s) {
+ Pattern pat(g_flags.ignore_dirty_pattern);
+ Pattern nopat(g_flags.no_ignore_dirty_pattern);
+ return pat.Match(s) && !nopat.Match(s);
+}
+
+class StampChecker {
+ struct GlobResult {
+ string pat;
+ vector<string> result;
+ };
+
+ struct ShellResult {
+ CommandOp op;
+ string shell;
+ string shellflag;
+ string cmd;
+ string result;
+ vector<string> missing_dirs;
+ vector<string> files;
+ vector<string> read_dirs;
+ };
+
+ public:
+ StampChecker() : needs_regen_(false) {}
+
+ ~StampChecker() {
+ for (GlobResult* gr : globs_) {
+ delete gr;
+ }
+ for (ShellResult* sr : commands_) {
+ delete sr;
+ }
+ }
+
+ bool NeedsRegen(double start_time, const string& orig_args) {
+ if (IsMissingOutputs())
+ RETURN_TRUE;
+
+ if (CheckStep1(orig_args))
+ RETURN_TRUE;
+
+ if (CheckStep2())
+ RETURN_TRUE;
+
+ if (!needs_regen_) {
+ FILE* fp = fopen(GetNinjaStampFilename().c_str(), "rb+");
+ if (!fp)
+ return true;
+ ScopedFile sfp(fp);
+ if (fseek(fp, 0, SEEK_SET) < 0)
+ PERROR("fseek");
+ size_t r = fwrite(&start_time, sizeof(start_time), 1, fp);
+ CHECK(r == 1);
+ }
+ return needs_regen_;
+ }
+
+ private:
+ bool IsMissingOutputs() {
+ if (!Exists(GetNinjaFilename())) {
+ fprintf(stderr, "%s is missing, regenerating...\n",
+ GetNinjaFilename().c_str());
+ return true;
+ }
+ if (!Exists(GetNinjaShellScriptFilename())) {
+ fprintf(stderr, "%s is missing, regenerating...\n",
+ GetNinjaShellScriptFilename().c_str());
+ return true;
+ }
+ return false;
+ }
+
+ bool CheckStep1(const string& orig_args) {
+#define LOAD_INT(fp) \
+ ({ \
+ int v = LoadInt(fp); \
+ if (v < 0) { \
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
+ RETURN_TRUE; \
+ } \
+ v; \
+ })
+
+#define LOAD_STRING(fp, s) \
+ ({ \
+ if (!LoadString(fp, s)) { \
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n"); \
+ RETURN_TRUE; \
+ } \
+ })
+
+ const string& stamp_filename = GetNinjaStampFilename();
+ FILE* fp = fopen(stamp_filename.c_str(), "rb");
+ if (!fp) {
+ if (g_flags.regen_debug)
+ printf("%s: %s\n", stamp_filename.c_str(), strerror(errno));
+ return true;
+ }
+ ScopedFile sfp(fp);
+
+ double gen_time;
+ size_t r = fread(&gen_time, sizeof(gen_time), 1, fp);
+ gen_time_ = gen_time;
+ if (r != 1) {
+ fprintf(stderr, "incomplete kati_stamp, regenerating...\n");
+ RETURN_TRUE;
+ }
+ if (g_flags.regen_debug)
+ printf("Generated time: %f\n", gen_time);
+
+ string s, s2;
+ int num_files = LOAD_INT(fp);
+ for (int i = 0; i < num_files; i++) {
+ LOAD_STRING(fp, &s);
+ double ts = GetTimestamp(s);
+ if (gen_time < ts) {
+ if (g_flags.regen_ignoring_kati_binary) {
+ string kati_binary;
+ GetExecutablePath(&kati_binary);
+ if (s == kati_binary) {
+ fprintf(stderr, "%s was modified, ignored.\n", s.c_str());
+ continue;
+ }
+ }
+ if (ShouldIgnoreDirty(s)) {
+ if (g_flags.regen_debug)
+ printf("file %s: ignored (%f)\n", s.c_str(), ts);
+ continue;
+ }
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: dirty (%f)\n", s.c_str(), ts);
+ else
+ fprintf(stderr, "%s was modified, regenerating...\n", s.c_str());
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("file %s: clean (%f)\n", s.c_str(), ts);
+ }
+ }
+
+ int num_undefineds = LOAD_INT(fp);
+ for (int i = 0; i < num_undefineds; i++) {
+ LOAD_STRING(fp, &s);
+ if (getenv(s.c_str())) {
+ if (g_flags.dump_kati_stamp) {
+ printf("env %s: dirty (unset => %s)\n", s.c_str(), getenv(s.c_str()));
+ } else {
+ fprintf(stderr, "Environment variable %s was set, regenerating...\n",
+ s.c_str());
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("env %s: clean (unset)\n", s.c_str());
+ }
+ }
+
+ int num_envs = LOAD_INT(fp);
+ for (int i = 0; i < num_envs; i++) {
+ LOAD_STRING(fp, &s);
+ StringPiece val(getenv(s.c_str()));
+ LOAD_STRING(fp, &s2);
+ if (val != s2) {
+ if (g_flags.dump_kati_stamp) {
+ printf("env %s: dirty (%s => %.*s)\n", s.c_str(), s2.c_str(),
+ SPF(val));
+ } else {
+ fprintf(stderr,
+ "Environment variable %s was modified (%s => %.*s), "
+ "regenerating...\n",
+ s.c_str(), s2.c_str(), SPF(val));
+ }
+ RETURN_TRUE;
+ } else if (g_flags.dump_kati_stamp) {
+ printf("env %s: clean (%.*s)\n", s.c_str(), SPF(val));
+ }
+ }
+
+ int num_globs = LOAD_INT(fp);
+ string pat;
+ for (int i = 0; i < num_globs; i++) {
+ GlobResult* gr = new GlobResult;
+ globs_.push_back(gr);
+
+ LOAD_STRING(fp, &gr->pat);
+ int num_files = LOAD_INT(fp);
+ gr->result.resize(num_files);
+ for (int j = 0; j < num_files; j++) {
+ LOAD_STRING(fp, &gr->result[j]);
+ }
+ }
+
+ int num_crs = LOAD_INT(fp);
+ for (int i = 0; i < num_crs; i++) {
+ ShellResult* sr = new ShellResult;
+ commands_.push_back(sr);
+ sr->op = static_cast<CommandOp>(LOAD_INT(fp));
+ LOAD_STRING(fp, &sr->shell);
+ LOAD_STRING(fp, &sr->shellflag);
+ LOAD_STRING(fp, &sr->cmd);
+ LOAD_STRING(fp, &sr->result);
+
+ string file;
+ // Ignore debug info
+ LOAD_STRING(fp, &file);
+ LOAD_INT(fp);
+
+ if (sr->op == CommandOp::FIND) {
+ int num_missing_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_missing_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ sr->missing_dirs.push_back(s);
+ }
+ int num_files = LOAD_INT(fp);
+ for (int j = 0; j < num_files; j++) {
+ LOAD_STRING(fp, &s);
+ sr->files.push_back(s);
+ }
+ int num_read_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_read_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ sr->read_dirs.push_back(s);
+ }
+ }
+ }
+
+ LoadString(fp, &s);
+ if (orig_args != s) {
+ fprintf(stderr, "arguments changed, regenerating...\n");
+ RETURN_TRUE;
+ }
+
+ return needs_regen_;
+ }
+
+ bool CheckGlobResult(const GlobResult* gr, string* err) {
+ COLLECT_STATS("glob time (regen)");
+ vector<string>* files;
+ Glob(gr->pat.c_str(), &files);
+ bool needs_regen = files->size() != gr->result.size();
+ for (size_t i = 0; i < gr->result.size(); i++) {
+ if (!needs_regen) {
+ if ((*files)[i] != gr->result[i]) {
+ needs_regen = true;
+ break;
+ }
+ }
+ }
+ if (needs_regen) {
+ if (ShouldIgnoreDirty(gr->pat)) {
+ if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: ignored\n", gr->pat.c_str());
+ }
+ return false;
+ }
+ if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: dirty\n", gr->pat.c_str());
+ } else {
+ *err = StringPrintf("wildcard(%s) was changed, regenerating...\n",
+ gr->pat.c_str());
+ }
+ } else if (g_flags.dump_kati_stamp) {
+ printf("wildcard %s: clean\n", gr->pat.c_str());
+ }
+ return needs_regen;
+ }
+
+ bool ShouldRunCommand(const ShellResult* sr) {
+ if (sr->op != CommandOp::FIND)
+ return true;
+
+ COLLECT_STATS("stat time (regen)");
+ for (const string& dir : sr->missing_dirs) {
+ if (Exists(dir))
+ return true;
+ }
+ for (const string& file : sr->files) {
+ if (!Exists(file))
+ return true;
+ }
+ for (const string& dir : sr->read_dirs) {
+ // We assume we rarely do a significant change for the top
+ // directory which affects the results of find command.
+ if (dir == "" || dir == "." || ShouldIgnoreDirty(dir))
+ continue;
+
+ struct stat st;
+ if (lstat(dir.c_str(), &st) != 0) {
+ return true;
+ }
+ double ts = GetTimestampFromStat(st);
+ if (gen_time_ < ts) {
+ return true;
+ }
+ if (S_ISLNK(st.st_mode)) {
+ ts = GetTimestamp(dir);
+ if (ts < 0 || gen_time_ < ts)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool CheckShellResult(const ShellResult* sr, string* err) {
+ if (sr->op == CommandOp::READ_MISSING) {
+ if (Exists(sr->cmd)) {
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: dirty\n", sr->cmd.c_str());
+ else
+ *err = StringPrintf("$(file <%s) was changed, regenerating...\n",
+ sr->cmd.c_str());
+ return true;
+ }
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: clean\n", sr->cmd.c_str());
+ return false;
+ }
+
+ if (sr->op == CommandOp::READ) {
+ double ts = GetTimestamp(sr->cmd);
+ if (gen_time_ < ts) {
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: dirty\n", sr->cmd.c_str());
+ else
+ *err = StringPrintf("$(file <%s) was changed, regenerating...\n",
+ sr->cmd.c_str());
+ return true;
+ }
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: clean\n", sr->cmd.c_str());
+ return false;
+ }
+
+ if (sr->op == CommandOp::WRITE || sr->op == CommandOp::APPEND) {
+ FILE* f =
+ fopen(sr->cmd.c_str(), (sr->op == CommandOp::WRITE) ? "wb" : "ab");
+ if (f == NULL) {
+ PERROR("fopen");
+ }
+
+ if (fwrite(&sr->result[0], sr->result.size(), 1, f) != 1) {
+ PERROR("fwrite");
+ }
+
+ if (fclose(f) != 0) {
+ PERROR("fclose");
+ }
+
+ if (g_flags.dump_kati_stamp)
+ printf("file %s: clean (write)\n", sr->cmd.c_str());
+ return false;
+ }
+
+ if (!ShouldRunCommand(sr)) {
+ if (g_flags.regen_debug)
+ printf("shell %s: clean (no rerun)\n", sr->cmd.c_str());
+ return false;
+ }
+
+ FindCommand fc;
+ if (fc.Parse(sr->cmd) && !fc.chdir.empty() && ShouldIgnoreDirty(fc.chdir)) {
+ if (g_flags.dump_kati_stamp)
+ printf("shell %s: ignored\n", sr->cmd.c_str());
+ return false;
+ }
+
+ COLLECT_STATS_WITH_SLOW_REPORT("shell time (regen)", sr->cmd.c_str());
+ string result;
+ RunCommand(sr->shell, sr->shellflag, sr->cmd, RedirectStderr::DEV_NULL,
+ &result);
+ FormatForCommandSubstitution(&result);
+ if (sr->result != result) {
+ if (g_flags.dump_kati_stamp) {
+ printf("shell %s: dirty\n", sr->cmd.c_str());
+ } else {
+ *err = StringPrintf("$(shell %s) was changed, regenerating...\n",
+ sr->cmd.c_str());
+ //*err += StringPrintf("%s => %s\n", expected.c_str(), result.c_str());
+ }
+ return true;
+ } else if (g_flags.regen_debug) {
+ printf("shell %s: clean (rerun)\n", sr->cmd.c_str());
+ }
+ return false;
+ }
+
+ bool CheckStep2() {
+ unique_ptr<ThreadPool> tp(NewThreadPool(g_flags.num_jobs));
+
+ tp->Submit([this]() {
+ string err;
+ // TODO: Make glob cache thread safe and create a task for each glob.
+ SetAffinityForSingleThread();
+ for (GlobResult* gr : globs_) {
+ if (CheckGlobResult(gr, &err)) {
+ unique_lock<mutex> lock(mu_);
+ if (!needs_regen_) {
+ needs_regen_ = true;
+ msg_ = err;
+ }
+ break;
+ }
+ }
+ });
+
+ tp->Submit([this]() {
+ SetAffinityForSingleThread();
+ for (ShellResult* sr : commands_) {
+ string err;
+ if (CheckShellResult(sr, &err)) {
+ unique_lock<mutex> lock(mu_);
+ if (!needs_regen_) {
+ needs_regen_ = true;
+ msg_ = err;
+ }
+ }
+ }
+ });
+
+ tp->Wait();
+ if (needs_regen_) {
+ fprintf(stderr, "%s", msg_.c_str());
+ }
+ return needs_regen_;
+ }
+
+ private:
+ double gen_time_;
+ vector<GlobResult*> globs_;
+ vector<ShellResult*> commands_;
+ mutex mu_;
+ bool needs_regen_;
+ string msg_;
+};
+
+} // namespace
+
+bool NeedsRegen(double start_time, const string& orig_args) {
+ return StampChecker().NeedsRegen(start_time, orig_args);
+}
diff --git a/src/regen.h b/src/regen.h
new file mode 100644
index 0000000..3d43d70
--- /dev/null
+++ b/src/regen.h
@@ -0,0 +1,24 @@
+// Copyright 2016 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.
+
+#ifndef REGEN_H_
+#define REGEN_H_
+
+#include <string>
+
+using namespace std;
+
+bool NeedsRegen(double start_time, const string& orig_args);
+
+#endif // REGEN_H_
diff --git a/src/regen_dump.cc b/src/regen_dump.cc
new file mode 100644
index 0000000..9916a3f
--- /dev/null
+++ b/src/regen_dump.cc
@@ -0,0 +1,224 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+// This command will dump the contents of a kati stamp file into a more portable
+// format for use by other tools. For now, it just exports the files read.
+// Later, this will be expanded to include the Glob and Shell commands, but
+// those require a more complicated output format.
+
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+
+#include "func.h"
+#include "io.h"
+#include "log.h"
+#include "strutil.h"
+
+vector<string> LoadVecString(FILE* fp) {
+ int count = LoadInt(fp);
+ if (count < 0) {
+ ERROR("Incomplete stamp file");
+ }
+ vector<string> ret(count);
+ for (int i = 0; i < count; i++) {
+ if (!LoadString(fp, &ret[i])) {
+ ERROR("Incomplete stamp file");
+ }
+ }
+ return ret;
+}
+
+int main(int argc, char* argv[]) {
+ bool dump_files = false;
+ bool dump_env = false;
+ bool dump_globs = false;
+ bool dump_cmds = false;
+ bool dump_finds = false;
+
+ if (argc == 1) {
+ fprintf(stderr,
+ "Usage: ckati_stamp_dump [--env] [--files] [--globs] [--cmds] "
+ "[--finds] <stamp>\n");
+ return 1;
+ }
+
+ for (int i = 1; i < argc - 1; i++) {
+ const char* arg = argv[i];
+ if (!strcmp(arg, "--env")) {
+ dump_env = true;
+ } else if (!strcmp(arg, "--files")) {
+ dump_files = true;
+ } else if (!strcmp(arg, "--globs")) {
+ dump_globs = true;
+ } else if (!strcmp(arg, "--cmds")) {
+ dump_cmds = true;
+ } else if (!strcmp(arg, "--finds")) {
+ dump_finds = true;
+ } else {
+ fprintf(stderr, "Unknown option: %s", arg);
+ return 1;
+ }
+ }
+
+ if (!dump_files && !dump_env && !dump_globs && !dump_cmds && !dump_finds) {
+ dump_files = true;
+ }
+
+ FILE* fp = fopen(argv[argc - 1], "rb");
+ if (!fp)
+ PERROR("fopen");
+
+ ScopedFile sfp(fp);
+ double gen_time;
+ size_t r = fread(&gen_time, sizeof(gen_time), 1, fp);
+ if (r != 1)
+ ERROR("Incomplete stamp file");
+
+ //
+ // See regen.cc CheckStep1 for how this is read normally
+ //
+
+ {
+ auto files = LoadVecString(fp);
+ if (dump_files) {
+ for (auto f : files) {
+ printf("%s\n", f.c_str());
+ }
+ }
+ }
+
+ {
+ auto undefined = LoadVecString(fp);
+ if (dump_env) {
+ for (auto s : undefined) {
+ printf("undefined: %s\n", s.c_str());
+ }
+ }
+ }
+
+ int num_envs = LoadInt(fp);
+ if (num_envs < 0)
+ ERROR("Incomplete stamp file");
+ for (int i = 0; i < num_envs; i++) {
+ string name;
+ string val;
+ if (!LoadString(fp, &name))
+ ERROR("Incomplete stamp file");
+ if (!LoadString(fp, &val))
+ ERROR("Incomplete stamp file");
+ if (dump_env)
+ printf("%s: %s\n", name.c_str(), val.c_str());
+ }
+
+ int num_globs = LoadInt(fp);
+ if (num_globs < 0)
+ ERROR("Incomplete stamp file");
+ for (int i = 0; i < num_globs; i++) {
+ string pat;
+ if (!LoadString(fp, &pat))
+ ERROR("Incomplete stamp file");
+
+ auto files = LoadVecString(fp);
+ if (dump_globs) {
+ printf("%s\n", pat.c_str());
+
+ for (auto s : files) {
+ printf(" %s\n", s.c_str());
+ }
+ }
+ }
+
+ int num_cmds = LoadInt(fp);
+ if (num_cmds < 0)
+ ERROR("Incomplete stamp file");
+ for (int i = 0; i < num_cmds; i++) {
+ CommandOp op = static_cast<CommandOp>(LoadInt(fp));
+ string shell, shellflag, cmd, result, file;
+ if (!LoadString(fp, &shell))
+ ERROR("Incomplete stamp file");
+ if (!LoadString(fp, &shellflag))
+ ERROR("Incomplete stamp file");
+ if (!LoadString(fp, &cmd))
+ ERROR("Incomplete stamp file");
+ if (!LoadString(fp, &result))
+ ERROR("Incomplete stamp file");
+ if (!LoadString(fp, &file))
+ ERROR("Incomplete stamp file");
+ int line = LoadInt(fp);
+ if (line < 0)
+ ERROR("Incomplete stamp file");
+
+ if (op == CommandOp::FIND) {
+ auto missing_dirs = LoadVecString(fp);
+ auto files = LoadVecString(fp);
+ auto read_dirs = LoadVecString(fp);
+
+ if (dump_finds) {
+ printf("cmd type: FIND\n");
+ printf(" shell: %s\n", shell.c_str());
+ printf(" shell flags: %s\n", shellflag.c_str());
+ printf(" loc: %s:%d\n", file.c_str(), line);
+ printf(" cmd: %s\n", cmd.c_str());
+ if (result.length() > 0 && result.length() < 500 &&
+ result.find('\n') == std::string::npos) {
+ printf(" output: %s\n", result.c_str());
+ } else {
+ printf(" output: <%zu bytes>\n", result.length());
+ }
+ printf(" missing dirs:\n");
+ for (auto d : missing_dirs) {
+ printf(" %s\n", d.c_str());
+ }
+ printf(" files:\n");
+ for (auto f : files) {
+ printf(" %s\n", f.c_str());
+ }
+ printf(" read dirs:\n");
+ for (auto d : read_dirs) {
+ printf(" %s\n", d.c_str());
+ }
+ printf("\n");
+ }
+ } else if (dump_cmds) {
+ if (op == CommandOp::SHELL) {
+ printf("cmd type: SHELL\n");
+ printf(" shell: %s\n", shell.c_str());
+ printf(" shell flags: %s\n", shellflag.c_str());
+ } else if (op == CommandOp::READ) {
+ printf("cmd type: READ\n");
+ } else if (op == CommandOp::READ_MISSING) {
+ printf("cmd type: READ_MISSING\n");
+ } else if (op == CommandOp::WRITE) {
+ printf("cmd type: WRITE\n");
+ } else if (op == CommandOp::APPEND) {
+ printf("cmd type: APPEND\n");
+ }
+ printf(" loc: %s:%d\n", file.c_str(), line);
+ printf(" cmd: %s\n", cmd.c_str());
+ if (result.length() > 0 && result.length() < 500 &&
+ result.find('\n') == std::string::npos) {
+ printf(" output: %s\n", result.c_str());
+ } else {
+ printf(" output: <%zu bytes>\n", result.length());
+ }
+ printf("\n");
+ }
+ }
+
+ return 0;
+}
diff --git a/src/rule.cc b/src/rule.cc
new file mode 100644
index 0000000..867a7e3
--- /dev/null
+++ b/src/rule.cc
@@ -0,0 +1,122 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "rule.h"
+
+#include "expr.h"
+#include "log.h"
+#include "parser.h"
+#include "stringprintf.h"
+#include "strutil.h"
+#include "symtab.h"
+
+Rule::Rule() : is_double_colon(false), is_suffix_rule(false), cmd_lineno(0) {}
+
+void Rule::ParseInputs(const StringPiece& inputs_str) {
+ bool is_order_only = false;
+ for (auto const& input : WordScanner(inputs_str)) {
+ if (input == "|") {
+ is_order_only = true;
+ continue;
+ }
+ Symbol input_sym = Intern(TrimLeadingCurdir(input));
+ (is_order_only ? order_only_inputs : inputs).push_back(input_sym);
+ }
+}
+
+void Rule::ParsePrerequisites(const StringPiece& line,
+ size_t separator_pos,
+ const RuleStmt* rule_stmt) {
+ // line is either
+ // prerequisites [ ; command ]
+ // or
+ // target-prerequisites : prereq-patterns [ ; command ]
+ // First, separate command. At this point separator_pos should point to ';'
+ // unless null.
+ StringPiece prereq_string = line;
+ if (separator_pos != string::npos &&
+ rule_stmt->sep != RuleStmt::SEP_SEMICOLON) {
+ CHECK(line[separator_pos] == ';');
+ // TODO: Maybe better to avoid Intern here?
+ cmds.push_back(Value::NewLiteral(
+ Intern(TrimLeftSpace(line.substr(separator_pos + 1))).str()));
+ prereq_string = line.substr(0, separator_pos);
+ }
+
+ if ((separator_pos = prereq_string.find(':')) == string::npos) {
+ // Simple prerequisites
+ ParseInputs(prereq_string);
+ return;
+ }
+
+ // Static pattern rule.
+ if (!output_patterns.empty()) {
+ ERROR_LOC(loc, "*** mixed implicit and normal rules: deprecated syntax");
+ }
+
+ // Empty static patterns should not produce rules, but need to eat the
+ // commands So return a rule with no outputs nor output_patterns
+ if (outputs.empty()) {
+ return;
+ }
+
+ StringPiece target_prereq = prereq_string.substr(0, separator_pos);
+ StringPiece prereq_patterns = prereq_string.substr(separator_pos + 1);
+
+ for (StringPiece target_pattern : WordScanner(target_prereq)) {
+ target_pattern = TrimLeadingCurdir(target_pattern);
+ for (Symbol target : outputs) {
+ if (!Pattern(target_pattern).Match(target.str())) {
+ WARN_LOC(loc, "target `%s' doesn't match the target pattern",
+ target.c_str());
+ }
+ }
+ output_patterns.push_back(Intern(target_pattern));
+ }
+
+ if (output_patterns.empty()) {
+ ERROR_LOC(loc, "*** missing target pattern.");
+ }
+ if (output_patterns.size() > 1) {
+ ERROR_LOC(loc, "*** multiple target patterns.");
+ }
+ if (!IsPatternRule(output_patterns[0].str())) {
+ ERROR_LOC(loc, "*** target pattern contains no '%%'.");
+ }
+ ParseInputs(prereq_patterns);
+}
+
+string Rule::DebugString() const {
+ vector<string> v;
+ v.push_back(StringPrintf("outputs=[%s]", JoinSymbols(outputs, ",").c_str()));
+ v.push_back(StringPrintf("inputs=[%s]", JoinSymbols(inputs, ",").c_str()));
+ if (!order_only_inputs.empty()) {
+ v.push_back(StringPrintf("order_only_inputs=[%s]",
+ JoinSymbols(order_only_inputs, ",").c_str()));
+ }
+ if (!output_patterns.empty()) {
+ v.push_back(StringPrintf("output_patterns=[%s]",
+ JoinSymbols(output_patterns, ",").c_str()));
+ }
+ if (is_double_colon)
+ v.push_back("is_double_colon");
+ if (is_suffix_rule)
+ v.push_back("is_suffix_rule");
+ if (!cmds.empty()) {
+ v.push_back(StringPrintf("cmds=[%s]", JoinValues(cmds, ",").c_str()));
+ }
+ return JoinStrings(v, " ");
+}
diff --git a/src/rule.h b/src/rule.h
new file mode 100644
index 0000000..237eb02
--- /dev/null
+++ b/src/rule.h
@@ -0,0 +1,65 @@
+// Copyright 2015 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.
+
+#ifndef RULE_H_
+#define RULE_H_
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "loc.h"
+#include "log.h"
+#include "stmt.h"
+#include "string_piece.h"
+#include "symtab.h"
+
+using namespace std;
+
+class Value;
+
+class Rule {
+ public:
+ Rule();
+
+ Loc cmd_loc() const { return Loc(loc.filename, cmd_lineno); }
+
+ string DebugString() const;
+
+ void ParseInputs(const StringPiece& inputs_string);
+
+ void ParsePrerequisites(const StringPiece& line,
+ size_t pos,
+ const RuleStmt* rule_stmt);
+
+ static bool IsPatternRule(const StringPiece& target_string) {
+ return target_string.find('%') != string::npos;
+ }
+
+ vector<Symbol> outputs;
+ vector<Symbol> inputs;
+ vector<Symbol> order_only_inputs;
+ vector<Symbol> output_patterns;
+ vector<Symbol> validations;
+ bool is_double_colon;
+ bool is_suffix_rule;
+ vector<Value*> cmds;
+ Loc loc;
+ int cmd_lineno;
+
+ private:
+ void Error(const string& msg) { ERROR_LOC(loc, "%s", msg.c_str()); }
+};
+
+#endif // RULE_H_
diff --git a/src/stats.cc b/src/stats.cc
new file mode 100644
index 0000000..be03774
--- /dev/null
+++ b/src/stats.cc
@@ -0,0 +1,145 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "stats.h"
+
+#include <algorithm>
+#include <mutex>
+#include <vector>
+
+#include "find.h"
+#include "flags.h"
+#include "log.h"
+#include "stringprintf.h"
+#include "timeutil.h"
+
+namespace {
+
+mutex g_mu;
+vector<Stats*>* g_stats;
+
+} // namespace
+
+Stats::Stats(const char* name) : name_(name), elapsed_(0), cnt_(0) {
+ unique_lock<mutex> lock(g_mu);
+ if (g_stats == NULL)
+ g_stats = new vector<Stats*>;
+ g_stats->push_back(this);
+}
+
+void Stats::DumpTop() const {
+ unique_lock<mutex> lock(mu_);
+ if (detailed_.size() > 0) {
+ vector<pair<string, StatsDetails>> details(detailed_.begin(),
+ detailed_.end());
+ sort(details.begin(), details.end(),
+ [](const pair<string, StatsDetails> a,
+ const pair<string, StatsDetails> b) -> bool {
+ return a.second.elapsed_ > b.second.elapsed_;
+ });
+
+ // Only print the top 10
+ details.resize(min(details.size(), static_cast<size_t>(10)));
+
+ if (!interesting_.empty()) {
+ // No need to print anything out twice
+ auto interesting = interesting_;
+ for (auto& [n, detail] : details) {
+ interesting.erase(n);
+ }
+
+ for (auto& name : interesting) {
+ auto detail = detailed_.find(name);
+ if (detail == detailed_.end()) {
+ details.emplace_back(name, StatsDetails());
+ continue;
+ }
+ details.emplace_back(*detail);
+ }
+ }
+
+ int max_cnt_len = 1;
+ for (auto& [name, detail] : details) {
+ max_cnt_len =
+ max(max_cnt_len, static_cast<int>(to_string(detail.cnt_).length()));
+ }
+
+ for (auto& [name, detail] : details) {
+ LOG_STAT(" %6.3f / %*d %s", detail.elapsed_, max_cnt_len, detail.cnt_,
+ name.c_str());
+ }
+ }
+}
+
+string Stats::String() const {
+ unique_lock<mutex> lock(mu_);
+ if (!detailed_.empty())
+ return StringPrintf("%s: %f / %d (%d unique)", name_, elapsed_, cnt_,
+ detailed_.size());
+ return StringPrintf("%s: %f / %d", name_, elapsed_, cnt_);
+}
+
+double Stats::Start() {
+ double start = GetTime();
+ unique_lock<mutex> lock(mu_);
+ cnt_++;
+ return start;
+}
+
+double Stats::End(double start, const char* msg) {
+ double e = GetTime() - start;
+ unique_lock<mutex> lock(mu_);
+ elapsed_ += e;
+ if (msg != 0) {
+ StatsDetails& details = detailed_[string(msg)];
+ details.elapsed_ += e;
+ details.cnt_++;
+ }
+ return e;
+}
+
+void Stats::MarkInteresting(const string& msg) {
+ unique_lock<mutex> lock(mu_);
+ interesting_.emplace(msg);
+}
+
+ScopedStatsRecorder::ScopedStatsRecorder(Stats* st, const char* msg)
+ : st_(st), msg_(msg), start_time_(0) {
+ if (!g_flags.enable_stat_logs)
+ return;
+ start_time_ = st_->Start();
+}
+
+ScopedStatsRecorder::~ScopedStatsRecorder() {
+ if (!g_flags.enable_stat_logs)
+ return;
+ double e = st_->End(start_time_, msg_);
+ if (msg_ && e > 3.0) {
+ LOG_STAT("slow %s (%f): %s", st_->name_, e, msg_);
+ }
+}
+
+void ReportAllStats() {
+ if (!g_stats)
+ return;
+ for (Stats* st : *g_stats) {
+ LOG_STAT("%s", st->String().c_str());
+ st->DumpTop();
+ }
+ delete g_stats;
+
+ LOG_STAT("%u find nodes", FindEmulator::GetNodeCount());
+}
diff --git a/src/stats.h b/src/stats.h
new file mode 100644
index 0000000..4501353
--- /dev/null
+++ b/src/stats.h
@@ -0,0 +1,74 @@
+// Copyright 2015 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.
+
+#ifndef STATS_H_
+#define STATS_H_
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+using namespace std;
+
+struct StatsDetails {
+ int cnt_ = 0;
+ double elapsed_ = 0;
+};
+
+class Stats {
+ public:
+ explicit Stats(const char* name);
+
+ void DumpTop() const;
+ string String() const;
+
+ void MarkInteresting(const string& msg);
+
+ private:
+ double Start();
+ double End(double start, const char* msg);
+
+ friend class ScopedStatsRecorder;
+
+ const char* name_;
+ double elapsed_;
+ int cnt_;
+ mutable mutex mu_;
+ unordered_map<string, StatsDetails> detailed_;
+ unordered_set<string> interesting_;
+};
+
+class ScopedStatsRecorder {
+ public:
+ explicit ScopedStatsRecorder(Stats* st, const char* msg = 0);
+ ~ScopedStatsRecorder();
+
+ private:
+ Stats* st_;
+ const char* msg_;
+ double start_time_;
+};
+
+void ReportAllStats();
+
+#define COLLECT_STATS(name) \
+ static Stats stats(name); \
+ ScopedStatsRecorder ssr(&stats)
+
+#define COLLECT_STATS_WITH_SLOW_REPORT(name, msg) \
+ static Stats stats(name); \
+ ScopedStatsRecorder ssr(&stats, msg)
+
+#endif // STATS_H_
diff --git a/src/stmt.cc b/src/stmt.cc
new file mode 100644
index 0000000..8ec04c8
--- /dev/null
+++ b/src/stmt.cc
@@ -0,0 +1,180 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "stmt.h"
+
+#include "eval.h"
+#include "expr.h"
+#include "stringprintf.h"
+#include "strutil.h"
+
+Stmt::Stmt() {}
+
+Stmt::~Stmt() {}
+
+string RuleStmt::DebugString() const {
+ return StringPrintf("RuleStmt(lhs=%s sep=%d rhs=%s loc=%s:%d)",
+ Value::DebugString(lhs).c_str(), sep,
+ Value::DebugString(rhs).c_str(), LOCF(loc()));
+}
+
+string AssignStmt::DebugString() const {
+ const char* opstr = "???";
+ switch (op) {
+ case AssignOp::EQ:
+ opstr = "EQ";
+ break;
+ case AssignOp::COLON_EQ:
+ opstr = "COLON_EQ";
+ break;
+ case AssignOp::PLUS_EQ:
+ opstr = "PLUS_EQ";
+ break;
+ case AssignOp::QUESTION_EQ:
+ opstr = "QUESTION_EQ";
+ break;
+ }
+ const char* dirstr = "???";
+ switch (directive) {
+ case AssignDirective::NONE:
+ dirstr = "";
+ break;
+ case AssignDirective::OVERRIDE:
+ dirstr = "override";
+ break;
+ case AssignDirective::EXPORT:
+ dirstr = "export";
+ break;
+ }
+ return StringPrintf(
+ "AssignStmt(lhs=%s rhs=%s (%s) "
+ "opstr=%s dir=%s loc=%s:%d)",
+ Value::DebugString(lhs).c_str(), Value::DebugString(rhs).c_str(),
+ NoLineBreak(orig_rhs.as_string()).c_str(), opstr, dirstr, LOCF(loc()));
+}
+
+Symbol AssignStmt::GetLhsSymbol(Evaluator* ev) const {
+ if (!lhs->IsLiteral()) {
+ string buf;
+ lhs->Eval(ev, &buf);
+ return Intern(buf);
+ }
+
+ if (!lhs_sym_cache_.IsValid()) {
+ lhs_sym_cache_ = Intern(lhs->GetLiteralValueUnsafe());
+ }
+ return lhs_sym_cache_;
+}
+
+string CommandStmt::DebugString() const {
+ return StringPrintf("CommandStmt(%s, loc=%s:%d)",
+ Value::DebugString(expr).c_str(), LOCF(loc()));
+}
+
+string IfStmt::DebugString() const {
+ const char* opstr = "???";
+ switch (op) {
+ case CondOp::IFEQ:
+ opstr = "ifeq";
+ break;
+ case CondOp::IFNEQ:
+ opstr = "ifneq";
+ break;
+ case CondOp::IFDEF:
+ opstr = "ifdef";
+ break;
+ case CondOp::IFNDEF:
+ opstr = "ifndef";
+ break;
+ }
+ return StringPrintf("IfStmt(op=%s, lhs=%s, rhs=%s t=%zu f=%zu loc=%s:%d)",
+ opstr, Value::DebugString(lhs).c_str(),
+ Value::DebugString(rhs).c_str(), true_stmts.size(),
+ false_stmts.size(), LOCF(loc()));
+}
+
+string IncludeStmt::DebugString() const {
+ return StringPrintf("IncludeStmt(%s, loc=%s:%d)",
+ Value::DebugString(expr).c_str(), LOCF(loc()));
+}
+
+string ExportStmt::DebugString() const {
+ return StringPrintf("ExportStmt(%s, %d, loc=%s:%d)",
+ Value::DebugString(expr).c_str(), is_export, LOCF(loc()));
+}
+
+string ParseErrorStmt::DebugString() const {
+ return StringPrintf("ParseErrorStmt(%s, loc=%s:%d)", msg.c_str(),
+ LOCF(loc()));
+}
+
+RuleStmt::~RuleStmt() {
+ delete lhs;
+ delete rhs;
+}
+
+void RuleStmt::Eval(Evaluator* ev) const {
+ ev->EvalRule(this);
+}
+
+AssignStmt::~AssignStmt() {
+ delete lhs;
+ delete rhs;
+}
+
+void AssignStmt::Eval(Evaluator* ev) const {
+ ev->EvalAssign(this);
+}
+
+CommandStmt::~CommandStmt() {
+ delete expr;
+}
+
+void CommandStmt::Eval(Evaluator* ev) const {
+ ev->EvalCommand(this);
+}
+
+IfStmt::~IfStmt() {
+ delete lhs;
+ delete rhs;
+}
+
+void IfStmt::Eval(Evaluator* ev) const {
+ ev->EvalIf(this);
+}
+
+IncludeStmt::~IncludeStmt() {
+ delete expr;
+}
+
+void IncludeStmt::Eval(Evaluator* ev) const {
+ ev->EvalInclude(this);
+}
+
+ExportStmt::~ExportStmt() {
+ delete expr;
+}
+
+void ExportStmt::Eval(Evaluator* ev) const {
+ ev->EvalExport(this);
+}
+
+ParseErrorStmt::~ParseErrorStmt() {}
+
+void ParseErrorStmt::Eval(Evaluator* ev) const {
+ ev->set_loc(loc());
+ ev->Error(msg);
+}
diff --git a/src/stmt.h b/src/stmt.h
new file mode 100644
index 0000000..d7fd067
--- /dev/null
+++ b/src/stmt.h
@@ -0,0 +1,167 @@
+// Copyright 2015 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.
+
+#ifndef STMT_H_
+#define STMT_H_
+
+#include <string>
+#include <vector>
+
+#include "loc.h"
+#include "string_piece.h"
+#include "symtab.h"
+
+using namespace std;
+
+class Evaluator;
+class Value;
+
+enum struct AssignOp : char {
+ EQ,
+ COLON_EQ,
+ PLUS_EQ,
+ QUESTION_EQ,
+};
+
+enum struct AssignDirective {
+ NONE = 0,
+ OVERRIDE = 1,
+ EXPORT = 2,
+};
+
+enum struct CondOp {
+ IFEQ,
+ IFNEQ,
+ IFDEF,
+ IFNDEF,
+};
+
+struct Stmt {
+ public:
+ virtual ~Stmt();
+
+ Loc loc() const { return loc_; }
+ void set_loc(Loc loc) { loc_ = loc; }
+ StringPiece orig() const { return orig_; }
+
+ virtual void Eval(Evaluator* ev) const = 0;
+
+ virtual string DebugString() const = 0;
+
+ protected:
+ Stmt();
+
+ private:
+ Loc loc_;
+ StringPiece orig_;
+};
+
+/* Parsed "rule statement" before evaluation is kept as
+ * <lhs> <sep> <rhs>
+ * where <lhs> and <rhs> as Value instances. <sep> is either command
+ * separator (';') or an assignment ('=' or '=$=').
+ * Until we evaluate <lhs>, we don't know whether it is a rule or
+ * a rule-specific variable assignment.
+ */
+struct RuleStmt : public Stmt {
+ Value* lhs;
+ enum { SEP_NULL, SEP_SEMICOLON, SEP_EQ, SEP_FINALEQ } sep;
+ Value* rhs;
+
+ virtual ~RuleStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+struct AssignStmt : public Stmt {
+ Value* lhs;
+ Value* rhs;
+ StringPiece orig_rhs;
+ AssignOp op;
+ AssignDirective directive;
+ bool is_final;
+
+ AssignStmt() : is_final(false) {}
+ virtual ~AssignStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+
+ Symbol GetLhsSymbol(Evaluator* ev) const;
+
+ private:
+ mutable Symbol lhs_sym_cache_;
+};
+
+struct CommandStmt : public Stmt {
+ Value* expr;
+ StringPiece orig;
+
+ virtual ~CommandStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+struct IfStmt : public Stmt {
+ CondOp op;
+ Value* lhs;
+ Value* rhs;
+ vector<Stmt*> true_stmts;
+ vector<Stmt*> false_stmts;
+
+ virtual ~IfStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+struct IncludeStmt : public Stmt {
+ Value* expr;
+ bool should_exist;
+
+ virtual ~IncludeStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+struct ExportStmt : public Stmt {
+ Value* expr;
+ bool is_export;
+
+ virtual ~ExportStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+struct ParseErrorStmt : public Stmt {
+ string msg;
+
+ virtual ~ParseErrorStmt();
+
+ virtual void Eval(Evaluator* ev) const;
+
+ virtual string DebugString() const;
+};
+
+#endif // STMT_H_
diff --git a/src/string_piece.cc b/src/string_piece.cc
new file mode 100644
index 0000000..32a7be0
--- /dev/null
+++ b/src/string_piece.cc
@@ -0,0 +1,239 @@
+// Copyright 2015 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.
+
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Copied from strings/stringpiece.cc with modifications
+
+// +build ignore
+
+#include "string_piece.h"
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdint.h>
+
+#include <algorithm>
+#include <ostream>
+
+typedef StringPiece::size_type size_type;
+
+bool operator==(const StringPiece& x, const StringPiece& y) {
+ if (x.size() != y.size())
+ return false;
+ size_t len = x.size();
+ if (len >= sizeof(uint64_t)) {
+ len -= sizeof(uint64_t);
+ uint64_t xt = *reinterpret_cast<const uint64_t*>(x.data() + len);
+ uint64_t yt = *reinterpret_cast<const uint64_t*>(y.data() + len);
+ if (xt != yt)
+ return false;
+ }
+ return StringPiece::wordmemcmp(x.data(), y.data(), len) == 0;
+}
+
+void StringPiece::CopyToString(std::string* target) const {
+ target->assign(!empty() ? data() : "", size());
+}
+
+void StringPiece::AppendToString(std::string* target) const {
+ if (!empty())
+ target->append(data(), size());
+}
+
+size_type StringPiece::copy(char* buf, size_type n, size_type pos) const {
+ size_type ret = std::min(length_ - pos, n);
+ memcpy(buf, ptr_ + pos, ret);
+ return ret;
+}
+
+size_type StringPiece::find(const StringPiece& s, size_type pos) const {
+ if (pos > length_)
+ return npos;
+
+ const char* result =
+ std::search(ptr_ + pos, ptr_ + length_, s.ptr_, s.ptr_ + s.length_);
+ const size_type xpos = result - ptr_;
+ return xpos + s.length_ <= length_ ? xpos : npos;
+}
+
+size_type StringPiece::find(char c, size_type pos) const {
+ if (pos >= length_)
+ return npos;
+
+ const char* result = std::find(ptr_ + pos, ptr_ + length_, c);
+ return result != ptr_ + length_ ? static_cast<size_t>(result - ptr_) : npos;
+}
+
+size_type StringPiece::rfind(const StringPiece& s, size_type pos) const {
+ if (length_ < s.length_)
+ return npos;
+
+ if (s.empty())
+ return std::min(length_, pos);
+
+ const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_;
+ const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_);
+ return result != last ? static_cast<size_t>(result - ptr_) : npos;
+}
+
+size_type StringPiece::rfind(char c, size_type pos) const {
+ if (length_ == 0)
+ return npos;
+
+ for (size_type i = std::min(pos, length_ - 1);; --i) {
+ if (ptr_[i] == c)
+ return i;
+ if (i == 0)
+ break;
+ }
+ return npos;
+}
+
+// For each character in characters_wanted, sets the index corresponding
+// to the ASCII code of that character to 1 in table. This is used by
+// the find_.*_of methods below to tell whether or not a character is in
+// the lookup table in constant time.
+// The argument `table' must be an array that is large enough to hold all
+// the possible values of an unsigned char. Thus it should be be declared
+// as follows:
+// bool table[UCHAR_MAX + 1]
+static inline void BuildLookupTable(const StringPiece& characters_wanted,
+ bool* table) {
+ const size_type length = characters_wanted.length();
+ const char* const data = characters_wanted.data();
+ for (size_type i = 0; i < length; ++i) {
+ table[static_cast<unsigned char>(data[i])] = true;
+ }
+}
+
+size_type StringPiece::find_first_of(const StringPiece& s,
+ size_type pos) const {
+ if (length_ == 0 || s.length_ == 0)
+ return npos;
+
+ // Avoid the cost of BuildLookupTable() for a single-character search.
+ if (s.length_ == 1)
+ return find_first_of(s.ptr_[0], pos);
+
+ bool lookup[UCHAR_MAX + 1] = {false};
+ BuildLookupTable(s, lookup);
+ for (size_type i = pos; i < length_; ++i) {
+ if (lookup[static_cast<unsigned char>(ptr_[i])]) {
+ return i;
+ }
+ }
+ return npos;
+}
+
+size_type StringPiece::find_first_not_of(const StringPiece& s,
+ size_type pos) const {
+ if (length_ == 0)
+ return npos;
+
+ if (s.length_ == 0)
+ return 0;
+
+ // Avoid the cost of BuildLookupTable() for a single-character search.
+ if (s.length_ == 1)
+ return find_first_not_of(s.ptr_[0], pos);
+
+ bool lookup[UCHAR_MAX + 1] = {false};
+ BuildLookupTable(s, lookup);
+ for (size_type i = pos; i < length_; ++i) {
+ if (!lookup[static_cast<unsigned char>(ptr_[i])]) {
+ return i;
+ }
+ }
+ return npos;
+}
+
+size_type StringPiece::find_first_not_of(char c, size_type pos) const {
+ if (length_ == 0)
+ return npos;
+
+ for (; pos < length_; ++pos) {
+ if (ptr_[pos] != c) {
+ return pos;
+ }
+ }
+ return npos;
+}
+
+size_type StringPiece::find_last_of(const StringPiece& s, size_type pos) const {
+ if (length_ == 0 || s.length_ == 0)
+ return npos;
+
+ // Avoid the cost of BuildLookupTable() for a single-character search.
+ if (s.length_ == 1)
+ return find_last_of(s.ptr_[0], pos);
+
+ bool lookup[UCHAR_MAX + 1] = {false};
+ BuildLookupTable(s, lookup);
+ for (size_type i = std::min(pos, length_ - 1);; --i) {
+ if (lookup[static_cast<unsigned char>(ptr_[i])])
+ return i;
+ if (i == 0)
+ break;
+ }
+ return npos;
+}
+
+size_type StringPiece::find_last_not_of(const StringPiece& s,
+ size_type pos) const {
+ if (length_ == 0)
+ return npos;
+
+ size_type i = std::min(pos, length_ - 1);
+ if (s.length_ == 0)
+ return i;
+
+ // Avoid the cost of BuildLookupTable() for a single-character search.
+ if (s.length_ == 1)
+ return find_last_not_of(s.ptr_[0], pos);
+
+ bool lookup[UCHAR_MAX + 1] = {false};
+ BuildLookupTable(s, lookup);
+ for (;; --i) {
+ if (!lookup[static_cast<unsigned char>(ptr_[i])])
+ return i;
+ if (i == 0)
+ break;
+ }
+ return npos;
+}
+
+size_type StringPiece::find_last_not_of(char c, size_type pos) const {
+ if (length_ == 0)
+ return npos;
+
+ for (size_type i = std::min(pos, length_ - 1);; --i) {
+ if (ptr_[i] != c)
+ return i;
+ if (i == 0)
+ break;
+ }
+ return npos;
+}
+
+StringPiece StringPiece::substr(size_type pos, size_type n) const {
+ if (pos > length_)
+ pos = length_;
+ if (n > length_ - pos)
+ n = length_ - pos;
+ return StringPiece(ptr_ + pos, n);
+}
+
+const StringPiece::size_type StringPiece::npos = size_type(-1);
diff --git a/src/string_piece.h b/src/string_piece.h
new file mode 100644
index 0000000..df3562f
--- /dev/null
+++ b/src/string_piece.h
@@ -0,0 +1,224 @@
+// Copyright 2015 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.
+
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+// Copied from strings/stringpiece.h with modifications
+//
+// A string-like object that points to a sized piece of memory.
+//
+// Functions or methods may use const StringPiece& parameters to accept either
+// a "const char*" or a "string" value that will be implicitly converted to
+// a StringPiece. The implicit conversion means that it is often appropriate
+// to include this .h file in other files rather than forward-declaring
+// StringPiece as would be appropriate for most other Google classes.
+//
+// Systematic usage of StringPiece is encouraged as it will reduce unnecessary
+// conversions from "const char*" to "string" and back again.
+//
+
+#ifndef BASE_STRING_PIECE_H_
+#define BASE_STRING_PIECE_H_
+#pragma once
+
+#include <stddef.h>
+#include <string.h>
+
+#include <string>
+
+//#include "base/base_api.h"
+//#include "base/basictypes.h"
+
+class StringPiece {
+ public:
+ // standard STL container boilerplate
+ typedef size_t size_type;
+ typedef char value_type;
+ typedef const char* pointer;
+ typedef const char& reference;
+ typedef const char& const_reference;
+ typedef ptrdiff_t difference_type;
+ typedef const char* const_iterator;
+ typedef const char* iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+
+ static const size_type npos;
+
+ public:
+ // We provide non-explicit singleton constructors so users can pass
+ // in a "const char*" or a "string" wherever a "StringPiece" is
+ // expected.
+ StringPiece() : ptr_(NULL), length_(0) {}
+ StringPiece(const char* str)
+ : ptr_(str), length_((str == NULL) ? 0 : strlen(str)) {}
+ StringPiece(const std::string& str) : ptr_(str.data()), length_(str.size()) {}
+ StringPiece(const std::string&& str)
+ : ptr_(str.data()), length_(str.size()) {}
+ StringPiece(const char* offset, size_type len) : ptr_(offset), length_(len) {}
+
+ // data() may return a pointer to a buffer with embedded NULs, and the
+ // returned buffer may or may not be null terminated. Therefore it is
+ // typically a mistake to pass data() to a routine that expects a NUL
+ // terminated string.
+ const char* data() const { return ptr_; }
+ size_type size() const { return length_; }
+ size_type length() const { return length_; }
+ bool empty() const { return length_ == 0; }
+
+ void clear() {
+ ptr_ = NULL;
+ length_ = 0;
+ }
+ void set(const char* data, size_type len) {
+ ptr_ = data;
+ length_ = len;
+ }
+ void set(const char* str) {
+ ptr_ = str;
+ length_ = str ? strlen(str) : 0;
+ }
+ void set(const void* data, size_type len) {
+ ptr_ = reinterpret_cast<const char*>(data);
+ length_ = len;
+ }
+
+ char operator[](size_type i) const { return ptr_[i]; }
+
+ void remove_prefix(size_type n) {
+ ptr_ += n;
+ length_ -= n;
+ }
+
+ void remove_suffix(size_type n) { length_ -= n; }
+
+ int compare(const StringPiece& x) const {
+ int r =
+ wordmemcmp(ptr_, x.ptr_, (length_ < x.length_ ? length_ : x.length_));
+ if (r == 0) {
+ if (length_ < x.length_)
+ r = -1;
+ else if (length_ > x.length_)
+ r = +1;
+ }
+ return r;
+ }
+
+ std::string as_string() const {
+ // std::string doesn't like to take a NULL pointer even with a 0 size.
+ return std::string(!empty() ? data() : "", size());
+ }
+
+ void CopyToString(std::string* target) const;
+ void AppendToString(std::string* target) const;
+
+ // Does "this" start with "x"
+ bool starts_with(const StringPiece& x) const {
+ return ((length_ >= x.length_) &&
+ (wordmemcmp(ptr_, x.ptr_, x.length_) == 0));
+ }
+
+ // Does "this" end with "x"
+ bool ends_with(const StringPiece& x) const {
+ return ((length_ >= x.length_) &&
+ (wordmemcmp(ptr_ + (length_ - x.length_), x.ptr_, x.length_) == 0));
+ }
+
+ iterator begin() const { return ptr_; }
+ iterator end() const { return ptr_ + length_; }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(ptr_ + length_);
+ }
+ const_reverse_iterator rend() const { return const_reverse_iterator(ptr_); }
+
+ size_type max_size() const { return length_; }
+ size_type capacity() const { return length_; }
+
+ size_type copy(char* buf, size_type n, size_type pos = 0) const;
+
+ size_type find(const StringPiece& s, size_type pos = 0) const;
+ size_type find(char c, size_type pos = 0) const;
+ size_type rfind(const StringPiece& s, size_type pos = npos) const;
+ size_type rfind(char c, size_type pos = npos) const;
+
+ size_type find_first_of(const StringPiece& s, size_type pos = 0) const;
+ size_type find_first_of(char c, size_type pos = 0) const {
+ return find(c, pos);
+ }
+ size_type find_first_not_of(const StringPiece& s, size_type pos = 0) const;
+ size_type find_first_not_of(char c, size_type pos = 0) const;
+ size_type find_last_of(const StringPiece& s, size_type pos = npos) const;
+ size_type find_last_of(char c, size_type pos = npos) const {
+ return rfind(c, pos);
+ }
+ size_type find_last_not_of(const StringPiece& s, size_type pos = npos) const;
+ size_type find_last_not_of(char c, size_type pos = npos) const;
+
+ StringPiece substr(size_type pos, size_type n = npos) const;
+
+ static int wordmemcmp(const char* p, const char* p2, size_type N) {
+ return memcmp(p, p2, N);
+ }
+
+ // kati specific functions will follow.
+
+ char get(size_type i) const { return i < length_ ? ptr_[i] : 0; }
+
+ private:
+ const char* ptr_;
+ size_type length_;
+};
+
+bool operator==(const StringPiece& x, const StringPiece& y);
+
+inline bool operator!=(const StringPiece& x, const StringPiece& y) {
+ return !(x == y);
+}
+
+inline bool operator<(const StringPiece& x, const StringPiece& y) {
+ const int r = StringPiece::wordmemcmp(
+ x.data(), y.data(), (x.size() < y.size() ? x.size() : y.size()));
+ return ((r < 0) || ((r == 0) && (x.size() < y.size())));
+}
+
+inline bool operator>(const StringPiece& x, const StringPiece& y) {
+ return y < x;
+}
+
+inline bool operator<=(const StringPiece& x, const StringPiece& y) {
+ return !(x > y);
+}
+
+inline bool operator>=(const StringPiece& x, const StringPiece& y) {
+ return !(x < y);
+}
+
+namespace std {
+template <>
+struct hash<StringPiece> {
+ size_t operator()(const StringPiece& s) const {
+ size_t result = 0;
+ for (char c : s) {
+ result = (result * 131) + c;
+ }
+ return result;
+ }
+};
+
+} // namespace std
+
+#define SPF(s) static_cast<int>((s).size()), (s).data()
+
+#endif // BASE_STRING_PIECE_H_
diff --git a/src/string_piece_test.cc b/src/string_piece_test.cc
new file mode 100644
index 0000000..0434cbe
--- /dev/null
+++ b/src/string_piece_test.cc
@@ -0,0 +1,37 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "string_piece.h"
+
+#include <assert.h>
+
+#include <unordered_set>
+
+using namespace std;
+
+int main() {
+ unordered_set<StringPiece> sps;
+ sps.insert(StringPiece("foo"));
+ sps.insert(StringPiece("foo"));
+ sps.insert(StringPiece("bar"));
+ assert(sps.size() == 2);
+ assert(sps.count(StringPiece("foo")) == 1);
+ assert(sps.count(StringPiece("bar")) == 1);
+
+ assert(StringPiece("hogefugahige") == StringPiece("hogefugahige"));
+ assert(StringPiece("hogefugahoge") != StringPiece("hogefugahige"));
+ assert(StringPiece("hogefugahige") != StringPiece("higefugahige"));
+}
diff --git a/src/stringprintf.cc b/src/stringprintf.cc
new file mode 100644
index 0000000..b0d4b4a
--- /dev/null
+++ b/src/stringprintf.cc
@@ -0,0 +1,39 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "stringprintf.h"
+
+#include <assert.h>
+#include <stdarg.h>
+
+string StringPrintf(const char* format, ...) {
+ string str;
+ str.resize(128);
+ for (int i = 0; i < 2; i++) {
+ va_list args;
+ va_start(args, format);
+ int ret = vsnprintf(&str[0], str.size(), format, args);
+ va_end(args);
+ assert(ret >= 0);
+ if (static_cast<size_t>(ret) < str.size()) {
+ str.resize(ret);
+ return str;
+ }
+ str.resize(ret + 1);
+ }
+ assert(false);
+ __builtin_unreachable();
+}
diff --git a/src/stringprintf.h b/src/stringprintf.h
new file mode 100644
index 0000000..8b67e6c
--- /dev/null
+++ b/src/stringprintf.h
@@ -0,0 +1,24 @@
+// Copyright 2015 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.
+
+#ifndef STRINGPRINTF_H_
+#define STRINGPRINTF_H_
+
+#include <string>
+
+using namespace std;
+
+string StringPrintf(const char* fmt, ...);
+
+#endif // STRINGPRINTF_H_
diff --git a/src/strutil.cc b/src/strutil.cc
new file mode 100644
index 0000000..797775f
--- /dev/null
+++ b/src/strutil.cc
@@ -0,0 +1,556 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "strutil.h"
+
+#include <ctype.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <functional>
+#include <stack>
+#include <utility>
+
+#ifdef __SSE4_2__
+#include <smmintrin.h>
+#endif
+
+#include "log.h"
+
+static bool isSpace(char c) {
+ return (9 <= c && c <= 13) || c == 32;
+}
+
+#ifdef __SSE4_2__
+static int SkipUntilSSE42(const char* s,
+ int len,
+ const char* ranges,
+ int ranges_size) {
+ __m128i ranges16 = _mm_loadu_si128((const __m128i*)ranges);
+ len &= ~15;
+ int i = 0;
+ while (i < len) {
+ __m128i b16 = _mm_loadu_si128((const __m128i*)(s + i));
+ int r = _mm_cmpestri(
+ ranges16, ranges_size, b16, len - i,
+ _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS);
+ if (r != 16) {
+ return i + r;
+ }
+ i += 16;
+ }
+ return len;
+}
+#endif
+
+template <typename Cond>
+static int SkipUntil(const char* s,
+ int len,
+ const char* ranges UNUSED,
+ int ranges_size UNUSED,
+ Cond cond) {
+ int i = 0;
+#ifdef __SSE4_2__
+ i += SkipUntilSSE42(s, len, ranges, ranges_size);
+#endif
+ for (; i < len; i++) {
+ if (cond(s[i]))
+ break;
+ }
+ return i;
+}
+
+WordScanner::Iterator& WordScanner::Iterator::operator++() {
+ int len = static_cast<int>(in->size());
+ for (s = i + 1; s < len; s++) {
+ if (!isSpace((*in)[s]))
+ break;
+ }
+ if (s >= len) {
+ in = NULL;
+ s = 0;
+ i = 0;
+ return *this;
+ }
+
+ static const char ranges[] = "\x09\x0d ";
+ // It's intentional we are not using isSpace here. It seems with
+ // lambda the compiler generates better code.
+ i = s + SkipUntil(in->data() + s, len - s, ranges, 4,
+ [](char c) { return (9 <= c && c <= 13) || c == 32; });
+ return *this;
+}
+
+StringPiece WordScanner::Iterator::operator*() const {
+ return in->substr(s, i - s);
+}
+
+WordScanner::WordScanner(StringPiece in) : in_(in) {}
+
+WordScanner::Iterator WordScanner::begin() const {
+ Iterator iter;
+ iter.in = &in_;
+ iter.s = 0;
+ iter.i = -1;
+ ++iter;
+ return iter;
+}
+
+WordScanner::Iterator WordScanner::end() const {
+ Iterator iter;
+ iter.in = NULL;
+ iter.s = 0;
+ iter.i = 0;
+ return iter;
+}
+
+void WordScanner::Split(vector<StringPiece>* o) {
+ for (StringPiece t : *this)
+ o->push_back(t);
+}
+
+WordWriter::WordWriter(string* o) : out_(o), needs_space_(false) {}
+
+void WordWriter::MaybeAddWhitespace() {
+ if (needs_space_) {
+ out_->push_back(' ');
+ } else {
+ needs_space_ = true;
+ }
+}
+
+void WordWriter::Write(StringPiece s) {
+ MaybeAddWhitespace();
+ AppendString(s, out_);
+}
+
+ScopedTerminator::ScopedTerminator(StringPiece s) : s_(s), c_(s[s.size()]) {
+ const_cast<char*>(s_.data())[s_.size()] = '\0';
+}
+
+ScopedTerminator::~ScopedTerminator() {
+ const_cast<char*>(s_.data())[s_.size()] = c_;
+}
+
+void AppendString(StringPiece str, string* out) {
+ out->append(str.begin(), str.end());
+}
+
+bool HasPrefix(StringPiece str, StringPiece prefix) {
+ ssize_t size_diff = str.size() - prefix.size();
+ return size_diff >= 0 && str.substr(0, prefix.size()) == prefix;
+}
+
+bool HasSuffix(StringPiece str, StringPiece suffix) {
+ ssize_t size_diff = str.size() - suffix.size();
+ return size_diff >= 0 && str.substr(size_diff) == suffix;
+}
+
+bool HasWord(StringPiece str, StringPiece w) {
+ size_t found = str.find(w);
+ if (found == string::npos)
+ return false;
+ if (found != 0 && !isSpace(str[found - 1]))
+ return false;
+ size_t end = found + w.size();
+ if (end != str.size() && !isSpace(str[end]))
+ return false;
+ return true;
+}
+
+StringPiece TrimPrefix(StringPiece str, StringPiece prefix) {
+ ssize_t size_diff = str.size() - prefix.size();
+ if (size_diff < 0 || str.substr(0, prefix.size()) != prefix)
+ return str;
+ return str.substr(prefix.size());
+}
+
+StringPiece TrimSuffix(StringPiece str, StringPiece suffix) {
+ ssize_t size_diff = str.size() - suffix.size();
+ if (size_diff < 0 || str.substr(size_diff) != suffix)
+ return str;
+ return str.substr(0, size_diff);
+}
+
+Pattern::Pattern(StringPiece pat) : pat_(pat), percent_index_(pat.find('%')) {}
+
+bool Pattern::Match(StringPiece str) const {
+ if (percent_index_ == string::npos)
+ return str == pat_;
+ return MatchImpl(str);
+}
+
+bool Pattern::MatchImpl(StringPiece str) const {
+ return (HasPrefix(str, pat_.substr(0, percent_index_)) &&
+ HasSuffix(str, pat_.substr(percent_index_ + 1)));
+}
+
+StringPiece Pattern::Stem(StringPiece str) const {
+ if (!Match(str))
+ return "";
+ return str.substr(percent_index_,
+ str.size() - (pat_.size() - percent_index_ - 1));
+}
+
+void Pattern::AppendSubst(StringPiece str,
+ StringPiece subst,
+ string* out) const {
+ if (percent_index_ == string::npos) {
+ if (str == pat_) {
+ AppendString(subst, out);
+ return;
+ } else {
+ AppendString(str, out);
+ return;
+ }
+ }
+
+ if (MatchImpl(str)) {
+ size_t subst_percent_index = subst.find('%');
+ if (subst_percent_index == string::npos) {
+ AppendString(subst, out);
+ return;
+ } else {
+ AppendString(subst.substr(0, subst_percent_index), out);
+ AppendString(str.substr(percent_index_, str.size() - pat_.size() + 1),
+ out);
+ AppendString(subst.substr(subst_percent_index + 1), out);
+ return;
+ }
+ }
+ AppendString(str, out);
+}
+
+void Pattern::AppendSubstRef(StringPiece str,
+ StringPiece subst,
+ string* out) const {
+ if (percent_index_ != string::npos && subst.find('%') != string::npos) {
+ AppendSubst(str, subst, out);
+ return;
+ }
+ StringPiece s = TrimSuffix(str, pat_);
+ out->append(s.begin(), s.end());
+ out->append(subst.begin(), subst.end());
+}
+
+string NoLineBreak(const string& s) {
+ size_t index = s.find('\n');
+ if (index == string::npos)
+ return s;
+ string r = s;
+ while (index != string::npos) {
+ r = r.substr(0, index) + "\\n" + r.substr(index + 1);
+ index = r.find('\n', index + 2);
+ }
+ return r;
+}
+
+StringPiece TrimLeftSpace(StringPiece s) {
+ size_t i = 0;
+ for (; i < s.size(); i++) {
+ if (isSpace(s[i]))
+ continue;
+ char n = s.get(i + 1);
+ if (s[i] == '\\' && (n == '\r' || n == '\n')) {
+ i++;
+ continue;
+ }
+ break;
+ }
+ return s.substr(i, s.size() - i);
+}
+
+StringPiece TrimRightSpace(StringPiece s) {
+ size_t i = 0;
+ for (; i < s.size(); i++) {
+ char c = s[s.size() - 1 - i];
+ if (isSpace(c)) {
+ if ((c == '\r' || c == '\n') && s.get(s.size() - 2 - i) == '\\')
+ i++;
+ continue;
+ }
+ break;
+ }
+ return s.substr(0, s.size() - i);
+}
+
+StringPiece TrimSpace(StringPiece s) {
+ return TrimRightSpace(TrimLeftSpace(s));
+}
+
+StringPiece Dirname(StringPiece s) {
+ size_t found = s.rfind('/');
+ if (found == string::npos)
+ return StringPiece(".");
+ if (found == 0)
+ return StringPiece("");
+ return s.substr(0, found);
+}
+
+StringPiece Basename(StringPiece s) {
+ size_t found = s.rfind('/');
+ if (found == string::npos || found == 0)
+ return s;
+ return s.substr(found + 1);
+}
+
+StringPiece GetExt(StringPiece s) {
+ size_t found = s.rfind('.');
+ if (found == string::npos)
+ return StringPiece("");
+ return s.substr(found);
+}
+
+StringPiece StripExt(StringPiece s) {
+ size_t slash_index = s.rfind('/');
+ size_t found = s.rfind('.');
+ if (found == string::npos ||
+ (slash_index != string::npos && found < slash_index))
+ return s;
+ return s.substr(0, found);
+}
+
+void NormalizePath(string* o) {
+ if (o->empty())
+ return;
+ size_t start_index = 0;
+ if ((*o)[0] == '/')
+ start_index++;
+ size_t j = start_index;
+ size_t prev_start = start_index;
+ for (size_t i = start_index; i <= o->size(); i++) {
+ char c = (*o)[i];
+ if (c != '/' && c != 0) {
+ (*o)[j] = c;
+ j++;
+ continue;
+ }
+
+ StringPiece prev_dir = StringPiece(o->data() + prev_start, j - prev_start);
+ if (prev_dir == ".") {
+ j--;
+ } else if (prev_dir == ".." && j != 2 /* .. */) {
+ if (j == 3) {
+ // /..
+ j = start_index;
+ } else {
+ size_t orig_j = j;
+ j -= 4;
+ j = o->rfind('/', j);
+ if (j == string::npos) {
+ j = start_index;
+ } else {
+ j++;
+ }
+ if (StringPiece(o->data() + j, 3) == "../") {
+ j = orig_j;
+ (*o)[j] = c;
+ j++;
+ }
+ }
+ } else if (!prev_dir.empty()) {
+ if (c) {
+ (*o)[j] = c;
+ j++;
+ }
+ }
+ prev_start = j;
+ }
+ if (j > 1 && (*o)[j - 1] == '/')
+ j--;
+ o->resize(j);
+}
+
+void AbsPath(StringPiece s, string* o) {
+ if (s.get(0) == '/') {
+ o->clear();
+ } else {
+ char buf[PATH_MAX];
+ if (!getcwd(buf, PATH_MAX)) {
+ fprintf(stderr, "getcwd failed\n");
+ CHECK(false);
+ }
+
+ CHECK(buf[0] == '/');
+ *o = buf;
+ *o += '/';
+ }
+ AppendString(s, o);
+ NormalizePath(o);
+}
+
+template <typename Cond>
+size_t FindOutsideParenImpl(StringPiece s, Cond cond) {
+ bool prev_backslash = false;
+ stack<char> paren_stack;
+ for (size_t i = 0; i < s.size(); i++) {
+ char c = s[i];
+ if (cond(c) && paren_stack.empty() && !prev_backslash) {
+ return i;
+ }
+ switch (c) {
+ case '(':
+ paren_stack.push(')');
+ break;
+ case '{':
+ paren_stack.push('}');
+ break;
+
+ case ')':
+ case '}':
+ if (!paren_stack.empty() && c == paren_stack.top()) {
+ paren_stack.pop();
+ }
+ break;
+ }
+ prev_backslash = c == '\\' && !prev_backslash;
+ }
+ return string::npos;
+}
+
+size_t FindOutsideParen(StringPiece s, char c) {
+ return FindOutsideParenImpl(s, [&c](char d) { return c == d; });
+}
+
+size_t FindTwoOutsideParen(StringPiece s, char c1, char c2) {
+ return FindOutsideParenImpl(
+ s, [&c1, &c2](char d) { return d == c1 || d == c2; });
+}
+
+size_t FindThreeOutsideParen(StringPiece s, char c1, char c2, char c3) {
+ return FindOutsideParenImpl(
+ s, [&c1, &c2, &c3](char d) { return d == c1 || d == c2 || d == c3; });
+}
+
+size_t FindEndOfLine(StringPiece s, size_t e, size_t* lf_cnt) {
+ static const char ranges[] = "\0\0\n\n\\\\";
+ while (e < s.size()) {
+ e += SkipUntil(s.data() + e, s.size() - e, ranges, 6,
+ [](char c) { return c == 0 || c == '\n' || c == '\\'; });
+ if (e >= s.size()) {
+ CHECK(s.size() == e);
+ break;
+ }
+ char c = s[e];
+ if (c == '\0')
+ break;
+ if (c == '\\') {
+ if (s[e + 1] == '\n') {
+ e += 2;
+ ++*lf_cnt;
+ } else if (s[e + 1] == '\r' && s[e + 2] == '\n') {
+ e += 3;
+ ++*lf_cnt;
+ } else if (s[e + 1] == '\\') {
+ e += 2;
+ } else {
+ e++;
+ }
+ } else if (c == '\n') {
+ ++*lf_cnt;
+ return e;
+ }
+ }
+ return e;
+}
+
+StringPiece TrimLeadingCurdir(StringPiece s) {
+ while (s.substr(0, 2) == "./")
+ s = s.substr(2);
+ return s;
+}
+
+void FormatForCommandSubstitution(string* s) {
+ while ((*s)[s->size() - 1] == '\n')
+ s->pop_back();
+ for (size_t i = 0; i < s->size(); i++) {
+ if ((*s)[i] == '\n')
+ (*s)[i] = ' ';
+ }
+}
+
+string SortWordsInString(StringPiece s) {
+ vector<string> toks;
+ for (StringPiece tok : WordScanner(s)) {
+ toks.push_back(tok.as_string());
+ }
+ sort(toks.begin(), toks.end());
+ return JoinStrings(toks, " ");
+}
+
+string ConcatDir(StringPiece b, StringPiece n) {
+ string r;
+ if (!b.empty() && (n.empty() || n[0] != '/')) {
+ b.AppendToString(&r);
+ r += '/';
+ }
+ n.AppendToString(&r);
+ NormalizePath(&r);
+ return r;
+}
+
+string EchoEscape(const string& str) {
+ const char* in = str.c_str();
+ string buf;
+ for (; *in; in++) {
+ switch (*in) {
+ case '\\':
+ buf += "\\\\\\\\";
+ break;
+ case '\n':
+ buf += "\\n";
+ break;
+ case '"':
+ buf += "\\\"";
+ break;
+ default:
+ buf += *in;
+ }
+ }
+ return buf;
+}
+
+static bool NeedsShellEscape(char c) {
+ return c == 0 || c == '"' || c == '$' || c == '\\' || c == '`';
+}
+
+void EscapeShell(string* s) {
+ static const char ranges[] = "\0\0\"\"$$\\\\``";
+ size_t prev = 0;
+ size_t i = SkipUntil(s->c_str(), s->size(), ranges, 10, NeedsShellEscape);
+ if (i == s->size())
+ return;
+
+ string r;
+ for (; i < s->size();) {
+ StringPiece(*s).substr(prev, i - prev).AppendToString(&r);
+ char c = (*s)[i];
+ r += '\\';
+ if (c == '$') {
+ if ((*s)[i + 1] == '$') {
+ r += '$';
+ i++;
+ }
+ }
+ r += c;
+ i++;
+ prev = i;
+ i += SkipUntil(s->c_str() + i, s->size() - i, ranges, 10, NeedsShellEscape);
+ }
+ StringPiece(*s).substr(prev).AppendToString(&r);
+ s->swap(r);
+}
diff --git a/src/strutil.h b/src/strutil.h
new file mode 100644
index 0000000..cc4d519
--- /dev/null
+++ b/src/strutil.h
@@ -0,0 +1,149 @@
+// Copyright 2015 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.
+
+#ifndef STRUTIL_H_
+#define STRUTIL_H_
+
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+
+using namespace std;
+
+class WordScanner {
+ public:
+ struct Iterator {
+ Iterator& operator++();
+ StringPiece operator*() const;
+ bool operator!=(const Iterator& r) const {
+ return in != r.in || s != r.s || i != r.i;
+ }
+
+ const StringPiece* in;
+ int s;
+ int i;
+ };
+
+ explicit WordScanner(StringPiece in);
+
+ Iterator begin() const;
+ Iterator end() const;
+
+ void Split(vector<StringPiece>* o);
+
+ private:
+ StringPiece in_;
+};
+
+class WordWriter {
+ public:
+ explicit WordWriter(string* o);
+ void MaybeAddWhitespace();
+ void Write(StringPiece s);
+
+ private:
+ string* out_;
+ bool needs_space_;
+};
+
+// Temporary modifies s[s.size()] to '\0'.
+class ScopedTerminator {
+ public:
+ explicit ScopedTerminator(StringPiece s);
+ ~ScopedTerminator();
+
+ private:
+ StringPiece s_;
+ char c_;
+};
+
+template <class String>
+inline string JoinStrings(vector<String> v, const char* sep) {
+ string r;
+ for (StringPiece s : v) {
+ if (!r.empty()) {
+ r += sep;
+ }
+ r.append(s.begin(), s.end());
+ }
+ return r;
+}
+
+void AppendString(StringPiece str, string* out);
+
+bool HasPrefix(StringPiece str, StringPiece prefix);
+
+bool HasSuffix(StringPiece str, StringPiece suffix);
+
+bool HasWord(StringPiece str, StringPiece w);
+
+StringPiece TrimPrefix(StringPiece str, StringPiece suffix);
+
+StringPiece TrimSuffix(StringPiece str, StringPiece suffix);
+
+class Pattern {
+ public:
+ explicit Pattern(StringPiece pat);
+
+ bool Match(StringPiece str) const;
+
+ StringPiece Stem(StringPiece str) const;
+
+ void AppendSubst(StringPiece str, StringPiece subst, string* out) const;
+
+ void AppendSubstRef(StringPiece str, StringPiece subst, string* out) const;
+
+ private:
+ bool MatchImpl(StringPiece str) const;
+
+ StringPiece pat_;
+ size_t percent_index_;
+};
+
+string NoLineBreak(const string& s);
+
+StringPiece TrimLeftSpace(StringPiece s);
+StringPiece TrimRightSpace(StringPiece s);
+StringPiece TrimSpace(StringPiece s);
+
+StringPiece Dirname(StringPiece s);
+StringPiece Basename(StringPiece s);
+StringPiece GetExt(StringPiece s);
+StringPiece StripExt(StringPiece s);
+void NormalizePath(string* o);
+void AbsPath(StringPiece s, string* o);
+
+size_t FindOutsideParen(StringPiece s, char c);
+size_t FindTwoOutsideParen(StringPiece s, char c1, char c2);
+size_t FindThreeOutsideParen(StringPiece s, char c1, char c2, char c3);
+
+size_t FindEndOfLine(StringPiece s, size_t e, size_t* lf_cnt);
+
+// Strip leading sequences of './' from file names, so that ./file
+// and file are considered to be the same file.
+// From http://www.gnu.org/software/make/manual/make.html#Features
+StringPiece TrimLeadingCurdir(StringPiece s);
+
+void FormatForCommandSubstitution(string* s);
+
+string SortWordsInString(StringPiece s);
+
+string ConcatDir(StringPiece b, StringPiece n);
+
+string EchoEscape(const string& str);
+
+void EscapeShell(string* s);
+
+#endif // STRUTIL_H_
diff --git a/src/strutil_bench.cc b/src/strutil_bench.cc
new file mode 100644
index 0000000..d3b2e6c
--- /dev/null
+++ b/src/strutil_bench.cc
@@ -0,0 +1,42 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+#include <string>
+#include <vector>
+
+#include "flags.h"
+#include "string_piece.h"
+#include "strutil.h"
+#include "timeutil.h"
+
+using namespace std;
+
+int main() {
+ g_flags.enable_stat_logs = true;
+ string s;
+ while (s.size() < 400000) {
+ if (!s.empty())
+ s += ' ';
+ s += "frameworks/base/docs/html/tv/adt-1/index.jd";
+ }
+
+ ScopedTimeReporter tr("WordScanner");
+ static const int N = 1000;
+ for (int i = 0; i < N; i++) {
+ vector<StringPiece> toks;
+ WordScanner(s).Split(&toks);
+ }
+}
diff --git a/src/strutil_test.cc b/src/strutil_test.cc
new file mode 100644
index 0000000..d0db1f3
--- /dev/null
+++ b/src/strutil_test.cc
@@ -0,0 +1,231 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "strutil.h"
+
+#include <assert.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+#include "testutil.h"
+
+using namespace std;
+
+namespace {
+
+void TestWordScanner() {
+ vector<StringPiece> ss;
+ for (StringPiece tok : WordScanner("foo bar baz hogeeeeeeeeeeeeeeee")) {
+ ss.push_back(tok);
+ }
+ assert(ss.size() == 4LU);
+ ASSERT_EQ(ss[0], "foo");
+ ASSERT_EQ(ss[1], "bar");
+ ASSERT_EQ(ss[2], "baz");
+ ASSERT_EQ(ss[3], "hogeeeeeeeeeeeeeeee");
+}
+
+void TestHasPrefix() {
+ assert(HasPrefix("foo", "foo"));
+ assert(HasPrefix("foo", "fo"));
+ assert(HasPrefix("foo", ""));
+ assert(!HasPrefix("foo", "fooo"));
+}
+
+void TestHasSuffix() {
+ assert(HasSuffix("bar", "bar"));
+ assert(HasSuffix("bar", "ar"));
+ assert(HasSuffix("bar", ""));
+ assert(!HasSuffix("bar", "bbar"));
+}
+
+void TestTrimPrefix() {
+ ASSERT_EQ(TrimPrefix("foo", "foo"), "");
+ ASSERT_EQ(TrimPrefix("foo", "fo"), "o");
+ ASSERT_EQ(TrimPrefix("foo", ""), "foo");
+ ASSERT_EQ(TrimPrefix("foo", "fooo"), "foo");
+}
+
+void TestTrimSuffix() {
+ ASSERT_EQ(TrimSuffix("bar", "bar"), "");
+ ASSERT_EQ(TrimSuffix("bar", "ar"), "b");
+ ASSERT_EQ(TrimSuffix("bar", ""), "bar");
+ ASSERT_EQ(TrimSuffix("bar", "bbar"), "bar");
+}
+
+string SubstPattern(StringPiece str, StringPiece pat, StringPiece subst) {
+ string r;
+ Pattern(pat).AppendSubst(str, subst, &r);
+ return r;
+}
+
+void TestSubstPattern() {
+ ASSERT_EQ(SubstPattern("x.c", "%.c", "%.o"), "x.o");
+ ASSERT_EQ(SubstPattern("c.x", "c.%", "o.%"), "o.x");
+ ASSERT_EQ(SubstPattern("x.c.c", "%.c", "%.o"), "x.c.o");
+ ASSERT_EQ(SubstPattern("x.x y.c", "%.c", "%.o"), "x.x y.o");
+ ASSERT_EQ(SubstPattern("x.%.c", "%.%.c", "OK"), "OK");
+ ASSERT_EQ(SubstPattern("x.c", "x.c", "OK"), "OK");
+ ASSERT_EQ(SubstPattern("x.c.c", "x.c", "XX"), "x.c.c");
+ ASSERT_EQ(SubstPattern("x.x.c", "x.c", "XX"), "x.x.c");
+}
+
+void TestNoLineBreak() {
+ assert(NoLineBreak("a\nb") == "a\\nb");
+ assert(NoLineBreak("a\nb\nc") == "a\\nb\\nc");
+}
+
+void TestHasWord() {
+ assert(HasWord("foo bar baz", "bar"));
+ assert(HasWord("foo bar baz", "foo"));
+ assert(HasWord("foo bar baz", "baz"));
+ assert(!HasWord("foo bar baz", "oo"));
+ assert(!HasWord("foo bar baz", "ar"));
+ assert(!HasWord("foo bar baz", "ba"));
+ assert(!HasWord("foo bar baz", "az"));
+ assert(!HasWord("foo bar baz", "ba"));
+ assert(!HasWord("foo bar baz", "fo"));
+}
+
+static string NormalizePath(string s) {
+ ::NormalizePath(&s);
+ return s;
+}
+
+void TestNormalizePath() {
+ ASSERT_EQ(NormalizePath(""), "");
+ ASSERT_EQ(NormalizePath("."), "");
+ ASSERT_EQ(NormalizePath("/"), "/");
+ ASSERT_EQ(NormalizePath("/tmp"), "/tmp");
+ ASSERT_EQ(NormalizePath("////tmp////"), "/tmp");
+ ASSERT_EQ(NormalizePath("a////b"), "a/b");
+ ASSERT_EQ(NormalizePath("a//.//b"), "a/b");
+ ASSERT_EQ(NormalizePath("a////b//../c/////"), "a/c");
+ ASSERT_EQ(NormalizePath("../foo"), "../foo");
+ ASSERT_EQ(NormalizePath("./foo"), "foo");
+ ASSERT_EQ(NormalizePath("x/y/..//../foo"), "foo");
+ ASSERT_EQ(NormalizePath("x/../../foo"), "../foo");
+ ASSERT_EQ(NormalizePath("/../foo"), "/foo");
+ ASSERT_EQ(NormalizePath("/../../foo"), "/foo");
+ ASSERT_EQ(NormalizePath("/a/../../foo"), "/foo");
+ ASSERT_EQ(NormalizePath("/a/b/.."), "/a");
+ ASSERT_EQ(NormalizePath("../../a/b"), "../../a/b");
+ ASSERT_EQ(NormalizePath("../../../a/b"), "../../../a/b");
+ ASSERT_EQ(NormalizePath(".././../a/b"), "../../a/b");
+ ASSERT_EQ(NormalizePath("./../../a/b"), "../../a/b");
+}
+
+string EscapeShell(string s) {
+ ::EscapeShell(&s);
+ return s;
+}
+
+void TestEscapeShell() {
+ ASSERT_EQ(EscapeShell(""), "");
+ ASSERT_EQ(EscapeShell("foo"), "foo");
+ ASSERT_EQ(EscapeShell("foo$`\\baz\"bar"), "foo\\$\\`\\\\baz\\\"bar");
+ ASSERT_EQ(EscapeShell("$$"), "\\$$");
+ ASSERT_EQ(EscapeShell("$$$"), "\\$$\\$");
+ ASSERT_EQ(EscapeShell("\\\n"), "\\\\\n");
+}
+
+void TestFindEndOfLine() {
+ size_t lf_cnt = 0;
+ ASSERT_EQ(FindEndOfLine("foo", 0, &lf_cnt), 3);
+ char buf[10] = {'f', 'o', '\\', '\0', 'x', 'y'};
+ ASSERT_EQ(FindEndOfLine(StringPiece(buf, 6), 0, &lf_cnt), 3);
+ ASSERT_EQ(FindEndOfLine(StringPiece(buf, 2), 0, &lf_cnt), 2);
+}
+
+// Take a string, and copy it into an allocated buffer where
+// the byte immediately after the null termination character
+// is read protected. Useful for testing, but doesn't support
+// freeing the allocated pages.
+const char* CreateProtectedString(const char* str) {
+ int pagesize = sysconf(_SC_PAGE_SIZE);
+ void* buffer;
+ char* buffer_str;
+
+ // Allocate two pages of memory
+ if (posix_memalign(&buffer, pagesize, pagesize * 2) != 0) {
+ perror("posix_memalign failed");
+ assert(false);
+ }
+
+ // Make the second page unreadable
+ buffer_str = (char*)buffer + pagesize;
+ if (mprotect(buffer_str, pagesize, PROT_NONE) != 0) {
+ perror("mprotect failed");
+ assert(false);
+ }
+
+ // Then move the test string into the very end of the first page
+ buffer_str -= strlen(str) + 1;
+ strcpy(buffer_str, str);
+
+ return buffer_str;
+}
+
+void TestWordScannerInvalidAccess() {
+ vector<StringPiece> ss;
+ for (StringPiece tok : WordScanner(CreateProtectedString("0123 456789"))) {
+ ss.push_back(tok);
+ }
+ assert(ss.size() == 2LU);
+ ASSERT_EQ(ss[0], "0123");
+ ASSERT_EQ(ss[1], "456789");
+}
+
+void TestFindEndOfLineInvalidAccess() {
+ size_t lf_cnt = 0;
+ ASSERT_EQ(FindEndOfLine(CreateProtectedString("a\\"), 0, &lf_cnt), 2);
+}
+
+void TestConcatDir() {
+ ASSERT_EQ(ConcatDir("", ""), "");
+ ASSERT_EQ(ConcatDir(".", ""), "");
+ ASSERT_EQ(ConcatDir("", "."), "");
+ ASSERT_EQ(ConcatDir("a", "b"), "a/b");
+ ASSERT_EQ(ConcatDir("a/", "b"), "a/b");
+ ASSERT_EQ(ConcatDir("a", "/b"), "/b");
+ ASSERT_EQ(ConcatDir("a", ".."), "");
+ ASSERT_EQ(ConcatDir("a", "../b"), "b");
+ ASSERT_EQ(ConcatDir("a", "../../b"), "../b");
+}
+
+} // namespace
+
+int main() {
+ TestWordScanner();
+ TestHasPrefix();
+ TestHasSuffix();
+ TestTrimPrefix();
+ TestTrimSuffix();
+ TestSubstPattern();
+ TestNoLineBreak();
+ TestHasWord();
+ TestNormalizePath();
+ TestEscapeShell();
+ TestFindEndOfLine();
+ TestWordScannerInvalidAccess();
+ TestFindEndOfLineInvalidAccess();
+ TestConcatDir();
+ assert(!g_failed);
+}
diff --git a/src/symtab.cc b/src/symtab.cc
new file mode 100644
index 0000000..3d49f2e
--- /dev/null
+++ b/src/symtab.cc
@@ -0,0 +1,190 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+//#define ENABLE_TID_CHECK
+
+#include "symtab.h"
+
+#ifdef ENABLE_TID_CHECK
+#include <pthread.h>
+#endif
+#include <string.h>
+
+#include <unordered_map>
+
+#include "log.h"
+#include "strutil.h"
+#include "var.h"
+
+struct SymbolData {
+ SymbolData() : gv(Var::Undefined()) {}
+
+ Var* gv;
+};
+
+vector<string*>* g_symbols;
+static vector<SymbolData> g_symbol_data;
+
+Symbol kEmptySym;
+Symbol kShellSym;
+Symbol kKatiReadonlySym;
+
+Symbol::Symbol(int v) : v_(v) {}
+
+Var* Symbol::PeekGlobalVar() const {
+ if (static_cast<size_t>(v_) >= g_symbol_data.size()) {
+ return Var::Undefined();
+ }
+ return g_symbol_data[v_].gv;
+}
+
+Var* Symbol::GetGlobalVar() const {
+ if (static_cast<size_t>(v_) >= g_symbol_data.size()) {
+ g_symbol_data.resize(v_ + 1);
+ }
+ Var* v = g_symbol_data[v_].gv;
+ if (v->Origin() == VarOrigin::ENVIRONMENT ||
+ v->Origin() == VarOrigin::ENVIRONMENT_OVERRIDE) {
+ Vars::add_used_env_vars(*this);
+ }
+ return v;
+}
+
+void Symbol::SetGlobalVar(Var* v, bool is_override, bool* readonly) const {
+ if (static_cast<size_t>(v_) >= g_symbol_data.size()) {
+ g_symbol_data.resize(v_ + 1);
+ }
+ Var* orig = g_symbol_data[v_].gv;
+ if (orig->ReadOnly()) {
+ if (readonly != nullptr)
+ *readonly = true;
+ else
+ ERROR("*** cannot assign to readonly variable: %s", c_str());
+ return;
+ } else if (readonly != nullptr) {
+ *readonly = false;
+ }
+ if (!is_override && (orig->Origin() == VarOrigin::OVERRIDE ||
+ orig->Origin() == VarOrigin::ENVIRONMENT_OVERRIDE)) {
+ return;
+ }
+ if (orig->Origin() == VarOrigin::COMMAND_LINE &&
+ v->Origin() == VarOrigin::FILE) {
+ return;
+ }
+ if (orig->Origin() == VarOrigin::AUTOMATIC) {
+ ERROR("overriding automatic variable is not implemented yet");
+ }
+ if (orig->IsDefined())
+ delete orig;
+ g_symbol_data[v_].gv = v;
+}
+
+ScopedGlobalVar::ScopedGlobalVar(Symbol name, Var* var)
+ : name_(name), orig_(NULL) {
+ orig_ = name.GetGlobalVar();
+ g_symbol_data[name_.val()].gv = var;
+}
+
+ScopedGlobalVar::~ScopedGlobalVar() {
+ g_symbol_data[name_.val()].gv = orig_;
+}
+
+class Symtab {
+ public:
+ Symtab() {
+#ifdef ENABLE_TID_CHECK
+ tid_ = pthread_self();
+#endif
+
+ CHECK(g_symbols == NULL);
+ g_symbols = &symbols_;
+
+ Symbol s = InternImpl("");
+ CHECK(s.v_ == 0);
+ CHECK(Intern("") == s);
+ char b[2];
+ b[1] = 0;
+ for (int i = 1; i < 256; i++) {
+ b[0] = i;
+ s = InternImpl(b);
+ CHECK(s.val() == i);
+ }
+
+ kEmptySym = Intern("");
+ kShellSym = Intern("SHELL");
+ kKatiReadonlySym = Intern(".KATI_READONLY");
+ }
+
+ ~Symtab() {
+ LOG_STAT("%zu symbols", symbols_.size());
+ for (string* s : symbols_)
+ delete s;
+ }
+
+ Symbol InternImpl(StringPiece s) {
+ auto found = symtab_.find(s);
+ if (found != symtab_.end()) {
+ return found->second;
+ }
+ symbols_.push_back(new string(s.data(), s.size()));
+ Symbol sym = Symbol(symtab_.size());
+ bool ok = symtab_.emplace(*symbols_.back(), sym).second;
+ CHECK(ok);
+ return sym;
+ }
+
+ Symbol Intern(StringPiece s) {
+#ifdef ENABLE_TID_CHECK
+ if (tid_ != pthread_self())
+ abort();
+#endif
+
+ if (s.size() <= 1) {
+ return Symbol(s.empty() ? 0 : (unsigned char)s[0]);
+ }
+ return InternImpl(s);
+ }
+
+ private:
+ unordered_map<StringPiece, Symbol> symtab_;
+ vector<string*> symbols_;
+#ifdef ENABLE_TID_CHECK
+ pthread_t tid_;
+#endif
+};
+
+static Symtab* g_symtab;
+
+void InitSymtab() {
+ g_symtab = new Symtab;
+}
+
+void QuitSymtab() {
+ delete g_symtab;
+}
+
+Symbol Intern(StringPiece s) {
+ return g_symtab->Intern(s);
+}
+
+string JoinSymbols(const vector<Symbol>& syms, const char* sep) {
+ vector<string> strs;
+ for (Symbol s : syms) {
+ strs.push_back(s.str());
+ }
+ return JoinStrings(strs, sep);
+}
diff --git a/src/symtab.h b/src/symtab.h
new file mode 100644
index 0000000..455d967
--- /dev/null
+++ b/src/symtab.h
@@ -0,0 +1,228 @@
+// Copyright 2015 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.
+
+#ifndef SYMTAB_H_
+#define SYMTAB_H_
+
+#include <bitset>
+#include <string>
+#include <vector>
+
+#include "string_piece.h"
+
+using namespace std;
+
+extern vector<string*>* g_symbols;
+
+class Symtab;
+class Var;
+
+class Symbol {
+ public:
+ explicit Symbol() : v_(-1) {}
+
+ const string& str() const { return *((*g_symbols)[v_]); }
+
+ const char* c_str() const { return str().c_str(); }
+
+ bool empty() const { return !v_; }
+
+ int val() const { return v_; }
+
+ char get(size_t i) const {
+ const string& s = str();
+ if (i >= s.size())
+ return 0;
+ return s[i];
+ }
+
+ bool IsValid() const { return v_ >= 0; }
+
+ Var* PeekGlobalVar() const;
+ Var* GetGlobalVar() const;
+ void SetGlobalVar(Var* v,
+ bool is_override = false,
+ bool* readonly = nullptr) const;
+
+ private:
+ explicit Symbol(int v);
+
+ int v_;
+
+ friend class Symtab;
+ friend class SymbolSet;
+};
+
+/* A set of symbols represented as bitmap indexed by Symbol's ordinal value. */
+class SymbolSet {
+ public:
+ SymbolSet() : low_(0), high_(0) {}
+
+ /* Returns true if Symbol belongs to this set. */
+ bool exists(Symbol sym) const {
+ size_t bit_nr = static_cast<size_t>(sym.val());
+ return sym.IsValid() && bit_nr >= low_ && bit_nr < high_ &&
+ bits_[(bit_nr - low_) / 64][(bit_nr - low_) % 64];
+ }
+
+ /* Adds Symbol to this set. */
+ void insert(Symbol sym) {
+ if (!sym.IsValid()) {
+ return;
+ }
+ size_t bit_nr = static_cast<size_t>(sym.val());
+ if (bit_nr < low_ || bit_nr >= high_) {
+ resize(bit_nr);
+ }
+ bits_[(bit_nr - low_) / 64][(bit_nr - low_) % 64] = true;
+ }
+
+ /* Returns the number of Symbol's in this set. */
+ size_t size() const {
+ size_t n = 0;
+ for (auto const& bitset : bits_) {
+ n += bitset.count();
+ }
+ return n;
+ }
+
+ /* Allow using foreach.
+ * E.g.,
+ * SymbolSet symbol_set;
+ * for (auto const& symbol: symbol_set) { ... }
+ */
+ class iterator {
+ const SymbolSet* bitset_;
+ size_t pos_;
+
+ iterator(const SymbolSet* bitset, size_t pos)
+ : bitset_(bitset), pos_(pos) {}
+
+ /* Proceed to the next Symbol. */
+ void next() {
+ size_t bit_nr = (pos_ > bitset_->low_) ? pos_ - bitset_->low_ : 0;
+ while (bit_nr < (bitset_->high_ - bitset_->low_)) {
+ if ((bit_nr % 64) == 0 && !bitset_->bits_[bit_nr / 64].any()) {
+ bit_nr += 64;
+ continue;
+ }
+ if (bitset_->bits_[bit_nr / 64][bit_nr % 64]) {
+ break;
+ }
+ ++bit_nr;
+ }
+ pos_ = bitset_->low_ + bit_nr;
+ }
+
+ public:
+ iterator& operator++() {
+ if (pos_ < bitset_->high_) {
+ ++pos_;
+ next();
+ }
+ return *this;
+ }
+
+ bool operator==(iterator other) const {
+ return bitset_ == other.bitset_ && pos_ == other.pos_;
+ }
+
+ bool operator!=(iterator other) const { return !(*this == other); }
+
+ Symbol operator*() { return Symbol(pos_); }
+
+ friend class SymbolSet;
+ };
+
+ iterator begin() const {
+ iterator it(this, low_);
+ it.next();
+ return it;
+ }
+
+ iterator end() const { return iterator(this, high_); }
+
+ private:
+ friend class iterator;
+
+ /* Ensure that given bit number is in [low_, high_) */
+ void resize(size_t bit_nr) {
+ size_t new_low = bit_nr & ~63;
+ size_t new_high = (bit_nr + 64) & ~63;
+ if (bits_.empty()) {
+ high_ = low_ = new_low;
+ }
+ if (new_low > low_) {
+ new_low = low_;
+ }
+ if (new_high <= high_) {
+ new_high = high_;
+ }
+ if (new_low == low_) {
+ bits_.resize((new_high - new_low) / 64);
+ } else {
+ std::vector<std::bitset<64> > newbits((new_high - new_low) / 64);
+ std::copy(bits_.begin(), bits_.end(),
+ newbits.begin() + (low_ - new_low) / 64);
+ bits_.swap(newbits);
+ }
+ low_ = new_low;
+ high_ = new_high;
+ }
+
+ /* Keep only the (aligned) range where at least one bit has been set.
+ * E.g., if we only ever set bits 65 and 141, |low_| will be 64, |high_|
+ * will be 192, and |bits_| will have 2 elements.
+ */
+ size_t low_;
+ size_t high_;
+ std::vector<std::bitset<64> > bits_;
+};
+
+class ScopedGlobalVar {
+ public:
+ ScopedGlobalVar(Symbol name, Var* var);
+ ~ScopedGlobalVar();
+
+ private:
+ Symbol name_;
+ Var* orig_;
+};
+
+inline bool operator==(const Symbol& x, const Symbol& y) {
+ return x.val() == y.val();
+}
+
+inline bool operator<(const Symbol& x, const Symbol& y) {
+ return x.val() < y.val();
+}
+
+namespace std {
+template <>
+struct hash<Symbol> {
+ size_t operator()(const Symbol& s) const { return s.val(); }
+};
+} // namespace std
+
+extern Symbol kEmptySym;
+extern Symbol kShellSym;
+extern Symbol kKatiReadonlySym;
+
+void InitSymtab();
+void QuitSymtab();
+Symbol Intern(StringPiece s);
+
+string JoinSymbols(const vector<Symbol>& syms, const char* sep);
+
+#endif // SYMTAB_H_
diff --git a/src/testutil.h b/src/testutil.h
new file mode 100644
index 0000000..be6125c
--- /dev/null
+++ b/src/testutil.h
@@ -0,0 +1,38 @@
+// Copyright 2015 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 <assert.h>
+
+#include "string_piece.h"
+
+bool g_failed;
+
+#define ASSERT_EQ(a, b) \
+ do { \
+ if ((a) != (b)) { \
+ fprintf(stderr, \
+ "Assertion failure at %s:%d: %s (which is \"%.*s\") vs %s\n", \
+ __FILE__, __LINE__, #a, SPF(GetStringPiece(a)), #b); \
+ g_failed = true; \
+ } \
+ } while (0)
+
+StringPiece GetStringPiece(StringPiece s) {
+ return s;
+}
+StringPiece GetStringPiece(size_t v) {
+ static char buf[64];
+ sprintf(buf, "%zd", v);
+ return buf;
+}
diff --git a/src/thread_pool.cc b/src/thread_pool.cc
new file mode 100644
index 0000000..4acf7b9
--- /dev/null
+++ b/src/thread_pool.cc
@@ -0,0 +1,90 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+#include "thread_pool.h"
+
+#include <condition_variable>
+#include <mutex>
+#include <stack>
+#include <thread>
+#include <vector>
+
+#include "affinity.h"
+
+class ThreadPoolImpl : public ThreadPool {
+ public:
+ explicit ThreadPoolImpl(int num_threads) : is_waiting_(false) {
+ SetAffinityForMultiThread();
+ threads_.reserve(num_threads);
+ for (int i = 0; i < num_threads; i++) {
+ threads_.push_back(thread([this]() { Loop(); }));
+ }
+ }
+
+ virtual ~ThreadPoolImpl() override {}
+
+ virtual void Submit(function<void(void)> task) override {
+ unique_lock<mutex> lock(mu_);
+ tasks_.push(task);
+ cond_.notify_one();
+ }
+
+ virtual void Wait() override {
+ {
+ unique_lock<mutex> lock(mu_);
+ is_waiting_ = true;
+ cond_.notify_all();
+ }
+
+ for (thread& th : threads_) {
+ th.join();
+ }
+
+ SetAffinityForSingleThread();
+ }
+
+ private:
+ void Loop() {
+ while (true) {
+ function<void(void)> task;
+ {
+ unique_lock<mutex> lock(mu_);
+ if (tasks_.empty()) {
+ if (is_waiting_)
+ return;
+ cond_.wait(lock);
+ }
+
+ if (tasks_.empty())
+ continue;
+
+ task = tasks_.top();
+ tasks_.pop();
+ }
+ task();
+ }
+ }
+
+ vector<thread> threads_;
+ mutex mu_;
+ condition_variable cond_;
+ stack<function<void(void)>> tasks_;
+ bool is_waiting_;
+};
+
+ThreadPool* NewThreadPool(int num_threads) {
+ return new ThreadPoolImpl(num_threads);
+}
diff --git a/src/thread_pool.h b/src/thread_pool.h
new file mode 100644
index 0000000..0384ce8
--- /dev/null
+++ b/src/thread_pool.h
@@ -0,0 +1,35 @@
+// Copyright 2016 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.
+
+#ifndef THREAD_POOL_H_
+#define THREAD_POOL_H_
+
+#include <functional>
+
+using namespace std;
+
+class ThreadPool {
+ public:
+ virtual ~ThreadPool() = default;
+
+ virtual void Submit(function<void(void)> task) = 0;
+ virtual void Wait() = 0;
+
+ protected:
+ ThreadPool() = default;
+};
+
+ThreadPool* NewThreadPool(int num_threads);
+
+#endif // THREAD_POOL_H_
diff --git a/src/timeutil.cc b/src/timeutil.cc
new file mode 100644
index 0000000..0b43bf4
--- /dev/null
+++ b/src/timeutil.cc
@@ -0,0 +1,43 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "timeutil.h"
+
+#include <sys/time.h>
+#include <time.h>
+
+#include "log.h"
+
+double GetTime() {
+#if defined(__linux__)
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return ts.tv_sec + ts.tv_nsec * 0.001 * 0.001 * 0.001;
+#else
+ struct timeval tv;
+ if (gettimeofday(&tv, NULL) < 0)
+ PERROR("gettimeofday");
+ return tv.tv_sec + tv.tv_usec * 0.001 * 0.001;
+#endif
+}
+
+ScopedTimeReporter::ScopedTimeReporter(const char* name)
+ : name_(name), start_(GetTime()) {}
+
+ScopedTimeReporter::~ScopedTimeReporter() {
+ double elapsed = GetTime() - start_;
+ LOG_STAT("%s: %f", name_, elapsed);
+}
diff --git a/src/timeutil.h b/src/timeutil.h
new file mode 100644
index 0000000..bff78b9
--- /dev/null
+++ b/src/timeutil.h
@@ -0,0 +1,30 @@
+// Copyright 2015 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.
+
+#ifndef TIMEUTIL_H_
+#define TIMEUTIL_H_
+
+double GetTime();
+
+struct ScopedTimeReporter {
+ public:
+ explicit ScopedTimeReporter(const char* name);
+ ~ScopedTimeReporter();
+
+ private:
+ const char* name_;
+ double start_;
+};
+
+#endif // TIME_H_
diff --git a/src/var.cc b/src/var.cc
new file mode 100644
index 0000000..10b903b
--- /dev/null
+++ b/src/var.cc
@@ -0,0 +1,230 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+#include "var.h"
+
+#include "eval.h"
+#include "expr.h"
+#include "log.h"
+
+unordered_map<const Var*, string> Var::diagnostic_messages_;
+
+const char* GetOriginStr(VarOrigin origin) {
+ switch (origin) {
+ case VarOrigin::UNDEFINED:
+ return "undefined";
+ case VarOrigin::DEFAULT:
+ return "default";
+ case VarOrigin::ENVIRONMENT:
+ return "environment";
+ case VarOrigin::ENVIRONMENT_OVERRIDE:
+ return "environment override";
+ case VarOrigin::FILE:
+ return "file";
+ case VarOrigin::COMMAND_LINE:
+ return "command line";
+ case VarOrigin::OVERRIDE:
+ return "override";
+ case VarOrigin::AUTOMATIC:
+ return "automatic";
+ }
+ CHECK(false);
+ return "*** broken origin ***";
+}
+
+Var::Var() : Var(VarOrigin::UNDEFINED) {}
+
+Var::Var(VarOrigin origin)
+ : origin_(origin), readonly_(false), deprecated_(false), obsolete_(false) {}
+
+Var::~Var() {
+ diagnostic_messages_.erase(this);
+}
+
+void Var::AppendVar(Evaluator*, Value*) {
+ CHECK(false);
+}
+
+void Var::SetDeprecated(const StringPiece& msg) {
+ deprecated_ = true;
+ diagnostic_messages_[this] = msg.as_string();
+}
+
+void Var::SetObsolete(const StringPiece& msg) {
+ obsolete_ = true;
+ diagnostic_messages_[this] = msg.as_string();
+}
+
+void Var::Used(Evaluator* ev, const Symbol& sym) const {
+ if (obsolete_) {
+ ev->Error(StringPrintf("*** %s is obsolete%s.", sym.c_str(),
+ diagnostic_message_text()));
+ } else if (deprecated_) {
+ WARN_LOC(ev->loc(), "%s has been deprecated%s.", sym.c_str(),
+ diagnostic_message_text());
+ }
+}
+
+const char* Var::diagnostic_message_text() const {
+ auto it = diagnostic_messages_.find(this);
+ return it == diagnostic_messages_.end() ? "" : it->second.c_str();
+}
+
+const string& Var::DeprecatedMessage() const {
+ static const string empty_string;
+ auto it = diagnostic_messages_.find(this);
+ return it == diagnostic_messages_.end() ? empty_string : it->second;
+}
+
+Var* Var::Undefined() {
+ static Var* undefined_var;
+ if (!undefined_var) {
+ undefined_var = new UndefinedVar();
+ }
+ return undefined_var;
+}
+
+SimpleVar::SimpleVar(VarOrigin origin) : Var(origin) {}
+
+SimpleVar::SimpleVar(const string& v, VarOrigin origin) : Var(origin), v_(v) {}
+
+SimpleVar::SimpleVar(VarOrigin origin, Evaluator* ev, Value* v) : Var(origin) {
+ v->Eval(ev, &v_);
+}
+
+void SimpleVar::Eval(Evaluator* ev, string* s) const {
+ ev->CheckStack();
+ *s += v_;
+}
+
+void SimpleVar::AppendVar(Evaluator* ev, Value* v) {
+ string buf;
+ v->Eval(ev, &buf);
+ v_.push_back(' ');
+ v_ += buf;
+}
+
+StringPiece SimpleVar::String() const {
+ return v_;
+}
+
+string SimpleVar::DebugString() const {
+ return v_;
+}
+
+RecursiveVar::RecursiveVar(Value* v, VarOrigin origin, StringPiece orig)
+ : Var(origin), v_(v), orig_(orig) {}
+
+void RecursiveVar::Eval(Evaluator* ev, string* s) const {
+ ev->CheckStack();
+ v_->Eval(ev, s);
+}
+
+void RecursiveVar::AppendVar(Evaluator* ev, Value* v) {
+ ev->CheckStack();
+ v_ = Value::NewExpr(v_, Value::NewLiteral(" "), v);
+}
+
+StringPiece RecursiveVar::String() const {
+ return orig_;
+}
+
+string RecursiveVar::DebugString() const {
+ return Value::DebugString(v_);
+}
+
+UndefinedVar::UndefinedVar() {}
+
+void UndefinedVar::Eval(Evaluator*, string*) const {
+ // Nothing to do.
+}
+
+StringPiece UndefinedVar::String() const {
+ return StringPiece("");
+}
+
+string UndefinedVar::DebugString() const {
+ return "*undefined*";
+}
+
+Vars::~Vars() {
+ for (auto p : *this) {
+ delete p.second;
+ }
+}
+
+void Vars::add_used_env_vars(Symbol v) {
+ used_env_vars_.insert(v);
+}
+
+Var* Vars::Lookup(Symbol name) const {
+ auto found = find(name);
+ if (found == end())
+ return Var::Undefined();
+ Var* v = found->second;
+ if (v->Origin() == VarOrigin::ENVIRONMENT ||
+ v->Origin() == VarOrigin::ENVIRONMENT_OVERRIDE) {
+ used_env_vars_.insert(name);
+ }
+ return v;
+}
+
+Var* Vars::Peek(Symbol name) const {
+ auto found = find(name);
+ return found == end() ? Var::Undefined() : found->second;
+}
+
+void Vars::Assign(Symbol name, Var* v, bool* readonly) {
+ *readonly = false;
+ auto p = emplace(name, v);
+ if (!p.second) {
+ Var* orig = p.first->second;
+ if (orig->ReadOnly()) {
+ *readonly = true;
+ return;
+ }
+ if (orig->Origin() == VarOrigin::OVERRIDE ||
+ orig->Origin() == VarOrigin::ENVIRONMENT_OVERRIDE) {
+ return;
+ }
+ if (orig->Origin() == VarOrigin::AUTOMATIC) {
+ ERROR("overriding automatic variable is not implemented yet");
+ }
+ if (orig->IsDefined())
+ delete p.first->second;
+ p.first->second = v;
+ }
+}
+
+SymbolSet Vars::used_env_vars_;
+
+ScopedVar::ScopedVar(Vars* vars, Symbol name, Var* var)
+ : vars_(vars), orig_(NULL) {
+ auto p = vars->emplace(name, var);
+ iter_ = p.first;
+ if (!p.second) {
+ orig_ = iter_->second;
+ iter_->second = var;
+ }
+}
+
+ScopedVar::~ScopedVar() {
+ if (orig_) {
+ iter_->second = orig_;
+ } else {
+ vars_->erase(iter_);
+ }
+}
diff --git a/src/var.h b/src/var.h
new file mode 100644
index 0000000..cf6dd2d
--- /dev/null
+++ b/src/var.h
@@ -0,0 +1,180 @@
+// Copyright 2015 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.
+
+#ifndef VAR_H_
+#define VAR_H_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "eval.h"
+#include "expr.h"
+#include "log.h"
+#include "stmt.h"
+#include "string_piece.h"
+#include "symtab.h"
+
+using namespace std;
+
+class Evaluator;
+class Value;
+
+enum struct VarOrigin : char {
+ UNDEFINED,
+ DEFAULT,
+ ENVIRONMENT,
+ ENVIRONMENT_OVERRIDE,
+ FILE,
+ COMMAND_LINE,
+ OVERRIDE,
+ AUTOMATIC,
+};
+
+const char* GetOriginStr(VarOrigin origin);
+
+class Var : public Evaluable {
+ public:
+ virtual ~Var();
+
+ virtual const char* Flavor() const = 0;
+
+ VarOrigin Origin() { return origin_; }
+ virtual bool IsDefined() const { return true; }
+
+ virtual void AppendVar(Evaluator* ev, Value* v);
+
+ virtual StringPiece String() const = 0;
+
+ virtual string DebugString() const = 0;
+
+ bool ReadOnly() const { return readonly_; }
+ void SetReadOnly() { readonly_ = true; }
+
+ bool Deprecated() const { return deprecated_; }
+ void SetDeprecated(const StringPiece& msg);
+
+ bool Obsolete() const { return obsolete_; }
+ void SetObsolete(const StringPiece& msg);
+
+ const string& DeprecatedMessage() const;
+
+ // This variable was used (either written or read from)
+ void Used(Evaluator* ev, const Symbol& sym) const;
+
+ AssignOp op() const { return assign_op_; }
+ void SetAssignOp(AssignOp op) { assign_op_ = op; }
+
+ static Var* Undefined();
+
+ protected:
+ Var();
+ explicit Var(VarOrigin origin);
+
+ private:
+ const VarOrigin origin_;
+ AssignOp assign_op_;
+ bool readonly_ : 1;
+ bool deprecated_ : 1;
+ bool obsolete_ : 1;
+
+ const char* diagnostic_message_text() const;
+
+ static unordered_map<const Var*, string> diagnostic_messages_;
+};
+
+class SimpleVar : public Var {
+ public:
+ explicit SimpleVar(VarOrigin origin);
+ SimpleVar(const string& v, VarOrigin origin);
+ SimpleVar(VarOrigin, Evaluator* ev, Value* v);
+
+ virtual const char* Flavor() const override { return "simple"; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override;
+
+ virtual void AppendVar(Evaluator* ev, Value* v) override;
+
+ virtual StringPiece String() const override;
+
+ virtual string DebugString() const override;
+
+ private:
+ string v_;
+};
+
+class RecursiveVar : public Var {
+ public:
+ RecursiveVar(Value* v, VarOrigin origin, StringPiece orig);
+
+ virtual const char* Flavor() const override { return "recursive"; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override;
+
+ virtual void AppendVar(Evaluator* ev, Value* v) override;
+
+ virtual StringPiece String() const override;
+
+ virtual string DebugString() const override;
+
+ private:
+ Value* v_;
+ StringPiece orig_;
+};
+
+class UndefinedVar : public Var {
+ public:
+ UndefinedVar();
+
+ virtual const char* Flavor() const override { return "undefined"; }
+ virtual bool IsDefined() const override { return false; }
+
+ virtual void Eval(Evaluator* ev, string* s) const override;
+
+ virtual StringPiece String() const override;
+
+ virtual string DebugString() const override;
+};
+
+class Vars : public unordered_map<Symbol, Var*> {
+ public:
+ ~Vars();
+
+ Var* Lookup(Symbol name) const;
+ Var* Peek(Symbol name) const;
+
+ void Assign(Symbol name, Var* v, bool* readonly);
+
+ static void add_used_env_vars(Symbol v);
+
+ static const SymbolSet used_env_vars() { return used_env_vars_; }
+
+ private:
+ static SymbolSet used_env_vars_;
+};
+
+class ScopedVar {
+ public:
+ // Does not take ownerships of arguments.
+ ScopedVar(Vars* vars, Symbol name, Var* var);
+ ~ScopedVar();
+
+ private:
+ Vars* vars_;
+ Var* orig_;
+ Vars::iterator iter_;
+};
+
+#endif // VAR_H_
diff --git a/src/version.h b/src/version.h
new file mode 100644
index 0000000..4235985
--- /dev/null
+++ b/src/version.h
@@ -0,0 +1,20 @@
+// Copyright 2015 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.
+
+#ifndef VERSION_H_
+#define VERSION_H_
+
+extern const char* kGitVersion;
+
+#endif // VERSION_H_
diff --git a/src/version_unknown.cc b/src/version_unknown.cc
new file mode 100644
index 0000000..4ce90f5
--- /dev/null
+++ b/src/version_unknown.cc
@@ -0,0 +1,17 @@
+// Copyright 2016 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.
+
+// +build ignore
+
+const char* kGitVersion = "unknown";