diff options
| author | Dan Willemsen <dwillemsen@google.com> | 2020-06-26 18:46:21 -0700 |
|---|---|---|
| committer | Dan Willemsen <dwillemsen@google.com> | 2020-06-26 18:52:06 -0700 |
| commit | 979e7ae6e417ae4ee45e835104b66191ae16a14c (patch) | |
| tree | 6b5075e832cbdf2a7996a25a26659363527b6e4c /src | |
| parent | 003cf51e9b6da48063c90cf4c6710fde103c9c4a (diff) | |
| download | platform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.tar.gz platform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.tar.bz2 platform_build_kati-979e7ae6e417ae4ee45e835104b66191ae16a14c.zip | |
Refactor source tree into directories
Now instead of almost every file in the top level, move the old go code
into its own directory 'golang', and the C++ code into it's own 'src'
Also removes a few obsolete scripts that were used to work on Android
before Android fully switched to Kati.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Android.bp | 102 | ||||
| -rw-r--r-- | src/affinity.cc | 66 | ||||
| -rw-r--r-- | src/affinity.h | 21 | ||||
| -rw-r--r-- | src/command.cc | 240 | ||||
| -rw-r--r-- | src/command.h | 46 | ||||
| -rw-r--r-- | src/dep.cc | 923 | ||||
| -rw-r--r-- | src/dep.h | 69 | ||||
| -rw-r--r-- | src/eval.cc | 556 | ||||
| -rw-r--r-- | src/eval.h | 173 | ||||
| -rw-r--r-- | src/exec.cc | 151 | ||||
| -rw-r--r-- | src/exec.h | 26 | ||||
| -rw-r--r-- | src/expr.cc | 591 | ||||
| -rw-r--r-- | src/expr.h | 77 | ||||
| -rw-r--r-- | src/file.cc | 62 | ||||
| -rw-r--r-- | src/file.h | 48 | ||||
| -rw-r--r-- | src/file_cache.cc | 65 | ||||
| -rw-r--r-- | src/file_cache.h | 40 | ||||
| -rw-r--r-- | src/fileutil.cc | 211 | ||||
| -rw-r--r-- | src/fileutil.h | 62 | ||||
| -rw-r--r-- | src/fileutil_bench.cc | 45 | ||||
| -rw-r--r-- | src/find.cc | 1168 | ||||
| -rw-r--r-- | src/find.h | 79 | ||||
| -rw-r--r-- | src/find_test.cc | 192 | ||||
| -rw-r--r-- | src/flags.cc | 197 | ||||
| -rw-r--r-- | src/flags.h | 85 | ||||
| -rw-r--r-- | src/func.cc | 1022 | ||||
| -rw-r--r-- | src/func.h | 66 | ||||
| -rw-r--r-- | src/io.cc | 49 | ||||
| -rw-r--r-- | src/io.h | 45 | ||||
| -rw-r--r-- | src/loc.h | 32 | ||||
| -rw-r--r-- | src/log.cc | 62 | ||||
| -rw-r--r-- | src/log.h | 107 | ||||
| -rw-r--r-- | src/main.cc | 370 | ||||
| -rw-r--r-- | src/ninja.cc | 854 | ||||
| -rw-r--r-- | src/ninja.h | 43 | ||||
| -rw-r--r-- | src/ninja_test.cc | 87 | ||||
| -rw-r--r-- | src/parser.cc | 625 | ||||
| -rw-r--r-- | src/parser.h | 45 | ||||
| -rw-r--r-- | src/regen.cc | 485 | ||||
| -rw-r--r-- | src/regen.h | 24 | ||||
| -rw-r--r-- | src/regen_dump.cc | 224 | ||||
| -rw-r--r-- | src/rule.cc | 122 | ||||
| -rw-r--r-- | src/rule.h | 65 | ||||
| -rw-r--r-- | src/stats.cc | 145 | ||||
| -rw-r--r-- | src/stats.h | 74 | ||||
| -rw-r--r-- | src/stmt.cc | 180 | ||||
| -rw-r--r-- | src/stmt.h | 167 | ||||
| -rw-r--r-- | src/string_piece.cc | 239 | ||||
| -rw-r--r-- | src/string_piece.h | 224 | ||||
| -rw-r--r-- | src/string_piece_test.cc | 37 | ||||
| -rw-r--r-- | src/stringprintf.cc | 39 | ||||
| -rw-r--r-- | src/stringprintf.h | 24 | ||||
| -rw-r--r-- | src/strutil.cc | 556 | ||||
| -rw-r--r-- | src/strutil.h | 149 | ||||
| -rw-r--r-- | src/strutil_bench.cc | 42 | ||||
| -rw-r--r-- | src/strutil_test.cc | 231 | ||||
| -rw-r--r-- | src/symtab.cc | 190 | ||||
| -rw-r--r-- | src/symtab.h | 228 | ||||
| -rw-r--r-- | src/testutil.h | 38 | ||||
| -rw-r--r-- | src/thread_pool.cc | 90 | ||||
| -rw-r--r-- | src/thread_pool.h | 35 | ||||
| -rw-r--r-- | src/timeutil.cc | 43 | ||||
| -rw-r--r-- | src/timeutil.h | 30 | ||||
| -rw-r--r-- | src/var.cc | 230 | ||||
| -rw-r--r-- | src/var.h | 180 | ||||
| -rw-r--r-- | src/version.h | 20 | ||||
| -rw-r--r-- | src/version_unknown.cc | 17 |
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"; |
