aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml5
-rw-r--r--AUTHORS3
-rw-r--r--Android.bp13
-rw-r--r--CONTRIBUTORS1
-rw-r--r--Makefile.ckati3
-rw-r--r--eval.cc58
-rw-r--r--eval.h2
-rw-r--r--exec.cc7
-rw-r--r--fileutil.cc25
-rw-r--r--fileutil.h4
-rw-r--r--fileutil_bench.cc41
-rw-r--r--find.cc16
-rw-r--r--find.h2
-rw-r--r--flags.cc11
-rw-r--r--flags.h1
-rw-r--r--func.cc139
-rw-r--r--func.h11
-rw-r--r--ninja.cc40
-rw-r--r--regen.cc99
-rw-r--r--rule.cc27
-rw-r--r--rule.h8
-rw-r--r--strutil.cc95
-rw-r--r--strutil_test.cc48
-rw-r--r--symtab.cc15
-rw-r--r--symtab.h2
-rwxr-xr-xtestcase/cmdline_var.sh27
-rwxr-xr-xtestcase/cmdline_var_makeflags.sh36
-rwxr-xr-xtestcase/cmdline_var_modify.sh27
-rwxr-xr-xtestcase/cmdline_var_override.sh27
-rw-r--r--testcase/empty_target_specific_var.mk7
-rw-r--r--testcase/empty_target_specific_var2.mk11
-rwxr-xr-xtestcase/file_func.sh47
-rwxr-xr-xtestcase/ninja_regen_filefunc_read.sh74
-rwxr-xr-xtestcase/ninja_regen_filefunc_write.sh46
-rw-r--r--testcase/phony.mk3
-rw-r--r--testcase/readonly_global.sh47
-rw-r--r--testcase/readonly_global_missing.sh32
-rw-r--r--testcase/readonly_rule.sh48
-rw-r--r--testcase/readonly_rule_missing.sh32
-rw-r--r--var.cc9
-rw-r--r--var.h8
42 files changed, 1001 insertions, 158 deletions
diff --git a/.gitignore b/.gitignore
index b907c3d..19d2723 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,8 +11,10 @@ repo/maloader/
testcase_parse_benchmark_test.go
bench-old.out
bench-new.out
+find_test
ninja_test
string_piece_test
+strutil_bench
strutil_test
go_src_stamp
version.cc
diff --git a/.travis.yml b/.travis.yml
index f045dd3..2fd6fa8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,10 @@ before_script:
- sudo apt-get install -y libstdc++-4.8-dev clang-3.5 ninja-build realpath
script:
- - make -j4 ckati
+ - make -j4 ckati ckati_tests
- ruby runtest.rb -c
- ruby runtest.rb -c -n
- ruby runtest.rb -c -n -a
+ - ./ninja_test
+ - ./string_piece_test
+ - ./strutil_test
diff --git a/AUTHORS b/AUTHORS
index 787d767..3ca8fe5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,5 +8,6 @@
#
# Please keep the list sorted.
-Kouhei Sutou <kou@cozmixng.org>
Google Inc.
+Kouhei Sutou <kou@cozmixng.org>
+Po Hu <hupo1985@gmail.com>
diff --git a/Android.bp b/Android.bp
index 91c2972..5dd8ef5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -79,3 +79,16 @@ cc_test_host {
},
},
}
+
+cc_benchmark_host {
+ name: "ckati_fileutil_bench",
+ srcs: [
+ "fileutil_bench.cc",
+ ],
+ whole_static_libs: ["libckati"],
+ target: {
+ linux: {
+ host_ldlibs: ["-lrt", "-lpthread"],
+ },
+ },
+}
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 1d81fb2..c153787 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -26,6 +26,7 @@ Colin Cross <ccross@google.com>
Dan Willemsen <dwillemsen@google.com>
Fumitoshi Ukai <ukai@google.com>
Kouhei Sutou <kou@cozmixng.org>
+Po Hu <hupo1985@gmail.com>
Ryo Hashimoto <hashimoto@google.com>
Shinichiro Hamaji <hamaji@google.com>
Stefan Becker <stefanb@gpartner-nvidia.com>
diff --git a/Makefile.ckati b/Makefile.ckati
index df4c6a5..e4067bb 100644
--- a/Makefile.ckati
+++ b/Makefile.ckati
@@ -57,7 +57,8 @@ KATI_CXX_GENERATED_SRCS := \
KATI_CXX_SRCS := $(addprefix $(KATI_SRC_PATH)/,$(KATI_CXX_SRCS))
KATI_CXX_TEST_SRCS := \
$(wildcard $(KATI_SRC_PATH)/*_test.cc) \
- $(wildcard $(KATI_SRC_PATH)/*_bench.cc)
+ $(filter-out $(KATI_SRC_PATH)/fileutil_bench.cc,\
+ $(wildcard $(KATI_SRC_PATH)/*_bench.cc))
KATI_CXX_OBJS := $(patsubst $(KATI_SRC_PATH)/%.cc,$(KATI_INTERMEDIATES_PATH)/%.o,\
$(KATI_CXX_SRCS))
diff --git a/eval.cc b/eval.cc
index 5032913..4b56f1f 100644
--- a/eval.cc
+++ b/eval.cc
@@ -36,7 +36,8 @@ Evaluator::Evaluator()
avoid_io_(false),
eval_depth_(0),
posix_sym_(Intern(".POSIX")),
- is_posix_(false) {
+ is_posix_(false),
+ kati_readonly_(Intern(".KATI_READONLY")) {
}
Evaluator::~Evaluator() {
@@ -69,6 +70,8 @@ Var* Evaluator::EvalRHS(Symbol lhs, Value* rhs_v, StringPiece orig_rhs,
Var* prev = LookupVarInCurrentScope(lhs);
if (!prev->IsDefined()) {
rhs = new RecursiveVar(rhs_v, origin, orig_rhs);
+ } else if (prev->ReadOnly()) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
} else {
prev->AppendVar(this, rhs_v);
rhs = prev;
@@ -101,11 +104,31 @@ void Evaluator::EvalAssign(const AssignStmt* stmt) {
Symbol lhs = stmt->GetLhsSymbol(this);
if (lhs.empty())
Error("*** empty variable name.");
+
+ if (lhs == kati_readonly_) {
+ 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;
+ }
+
Var* rhs = EvalRHS(lhs, stmt->rhs, stmt->orig_rhs, stmt->op,
stmt->directive == AssignDirective::OVERRIDE);
- if (rhs)
+ if (rhs) {
+ bool readonly;
lhs.SetGlobalVar(rhs,
- stmt->directive == AssignDirective::OVERRIDE);
+ stmt->directive == AssignDirective::OVERRIDE,
+ &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
+ }
+ }
}
void Evaluator::EvalRule(const RuleStmt* stmt) {
@@ -122,7 +145,10 @@ void Evaluator::EvalRule(const RuleStmt* stmt) {
Rule* rule;
RuleVarAssignment rule_var;
- ParseRule(loc_, expr, stmt->term, &rule, &rule_var);
+ function<string()> after_term_fn = [this, stmt](){
+ return stmt->after_term ? stmt->after_term->Eval(this) : "";
+ };
+ ParseRule(loc_, expr, stmt->term, after_term_fn, &rule, &rule_var);
if (rule) {
if (stmt->term == ';') {
@@ -164,9 +190,29 @@ void Evaluator::EvalRule(const RuleStmt* stmt) {
}
current_scope_ = p.first->second;
+
+ if (lhs == kati_readonly_) {
+ string rhs_value;
+ rhs->Eval(this, &rhs_value);
+ for (auto const& name : WordScanner(rhs_value)) {
+ Var* var = current_scope_->Lookup(Intern(name));
+ if (!var->IsDefined()) {
+ Error(StringPrintf("*** unknown variable: %s", name.as_string().c_str()));
+ }
+ var->SetReadOnly();
+ }
+ current_scope_ = NULL;
+ continue;
+ }
+
Var* rhs_var = EvalRHS(lhs, rhs, StringPiece("*TODO*"), rule_var.op);
- if (rhs_var)
- current_scope_->Assign(lhs, new RuleVar(rhs_var, rule_var.op));
+ if (rhs_var) {
+ bool readonly;
+ current_scope_->Assign(lhs, new RuleVar(rhs_var, rule_var.op), &readonly);
+ if (readonly) {
+ Error(StringPrintf("*** cannot assign to readonly variable: %s", lhs.c_str()));
+ }
+ }
current_scope_ = NULL;
}
}
diff --git a/eval.h b/eval.h
index bf8c98a..c0f27b9 100644
--- a/eval.h
+++ b/eval.h
@@ -125,6 +125,8 @@ class Evaluator {
bool is_posix_;
static unordered_set<Symbol> used_undefined_vars_;
+
+ Symbol kati_readonly_;
};
#endif // EVAL_H_
diff --git a/exec.cc b/exec.cc
index 6065169..1569519 100644
--- a/exec.cc
+++ b/exec.cc
@@ -46,7 +46,8 @@ class Executor {
explicit Executor(Evaluator* ev)
: ce_(ev),
num_commands_(0) {
- shell_ = ev->GetShellAndFlag();
+ shell_ = ev->GetShell();
+ shellflag_ = ev->GetShellFlag();
}
double ExecNode(DepNode* n, DepNode* needed_by) {
@@ -106,7 +107,8 @@ class Executor {
}
if (!g_flags.is_dry_run) {
string out;
- int result = RunCommand(shell_, command->cmd.c_str(),
+ int result = RunCommand(shell_, shellflag_,
+ command->cmd.c_str(),
RedirectStderr::STDOUT,
&out);
printf("%s", out.c_str());
@@ -136,6 +138,7 @@ class Executor {
CommandEvaluator ce_;
unordered_map<Symbol, double> done_;
string shell_;
+ string shellflag_;
uint64_t num_commands_;
};
diff --git a/fileutil.cc b/fileutil.cc
index 0d3c2d6..4cf653b 100644
--- a/fileutil.cc
+++ b/fileutil.cc
@@ -60,15 +60,24 @@ double GetTimestamp(StringPiece filename) {
return GetTimestampFromStat(st);
}
-int RunCommand(const string& shell, const string& cmd,
- RedirectStderr redirect_stderr,
+int RunCommand(const string& shell, const string& shellflag,
+ const string& cmd, RedirectStderr redirect_stderr,
string* s) {
- string cmd_escaped = cmd;
- EscapeShell(&cmd_escaped);
- string cmd_with_shell = shell + " \"" + cmd_escaped + "\"";
- const char* argv[] = {
- "/bin/sh", "-c", cmd_with_shell.c_str(), NULL
- };
+ 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)
diff --git a/fileutil.h b/fileutil.h
index 7cc3c8e..264d9e1 100644
--- a/fileutil.h
+++ b/fileutil.h
@@ -36,8 +36,8 @@ enum struct RedirectStderr {
DEV_NULL,
};
-int RunCommand(const string& shell, const string& cmd,
- RedirectStderr redirect_stderr,
+int RunCommand(const string& shell, const string& shellflag,
+ const string& cmd, RedirectStderr redirect_stderr,
string* out);
void GetExecutablePath(string* path);
diff --git a/fileutil_bench.cc b/fileutil_bench.cc
new file mode 100644
index 0000000..4b1f033
--- /dev/null
+++ b/fileutil_bench.cc
@@ -0,0 +1,41 @@
+// 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.
+
+#include "fileutil.h"
+#include <benchmark/benchmark.h>
+#include <cstdio>
+
+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/find.cc b/find.cc
index 71cc77c..2539212 100644
--- a/find.cc
+++ b/find.cc
@@ -585,7 +585,7 @@ class FindCommandParser {
} else if (tok.find_first_of("|;&><*'\"") != string::npos) {
return false;
} else {
- fc_->finddirs.push_back(tok);
+ fc_->finddirs.push_back(tok.as_string());
}
}
}
@@ -594,7 +594,7 @@ class FindCommandParser {
fc_->type = FindCommandType::FINDLEAVES;
fc_->follows_symlinks = true;
StringPiece tok;
- vector<StringPiece> findfiles;
+ vector<string> findfiles;
while (true) {
if (!GetNextToken(&tok))
return false;
@@ -604,13 +604,13 @@ class FindCommandParser {
if (findfiles.size() < 2)
return false;
fc_->finddirs.swap(findfiles);
- fc_->print_cond.reset(new NameCond(fc_->finddirs.back().as_string()));
+ 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.as_string());
+ FindCond* cond = new NameCond(file);
if (fc_->print_cond.get()) {
cond = new OrCond(fc_->print_cond.release(), cond);
}
@@ -640,12 +640,12 @@ class FindCommandParser {
fc_->mindepth = d;
} else if (HasPrefix(tok, "--dir=")) {
StringPiece dir= tok.substr(strlen("--dir="));
- fc_->finddirs.push_back(dir);
+ fc_->finddirs.push_back(dir.as_string());
} else if (HasPrefix(tok, "--")) {
WARN("Unknown flag in findleaves.py: %.*s", SPF(tok));
return false;
} else {
- findfiles.push_back(tok);
+ findfiles.push_back(tok.as_string());
}
}
}
@@ -788,7 +788,7 @@ class FindEmulatorImpl : public FindEmulator {
}
const size_t orig_out_size = out->size();
- for (StringPiece finddir : fc.finddirs) {
+ for (const string& finddir : fc.finddirs) {
const string dir = ConcatDir(fc.chdir, finddir);
if (!CanHandle(dir)) {
@@ -813,7 +813,7 @@ class FindEmulatorImpl : public FindEmulator {
continue;
}
- string path = finddir.as_string();
+ string path = finddir;
unordered_map<const DirentNode*, string> cur_read_dirs;
if (!base->RunFind(fc, 0, &path, &cur_read_dirs, out)) {
LOG("FindEmulator: RunFind failed: %s", cmd.c_str());
diff --git a/find.h b/find.h
index ccd50e0..ab92e67 100644
--- a/find.h
+++ b/find.h
@@ -41,7 +41,7 @@ struct FindCommand {
FindCommandType type;
string chdir;
string testdir;
- vector<StringPiece> finddirs;
+ vector<string> finddirs;
bool follows_symlinks;
unique_ptr<FindCond> print_cond;
unique_ptr<FindCond> prune_cond;
diff --git a/flags.cc b/flags.cc
index 876e855..3ad8e3c 100644
--- a/flags.cc
+++ b/flags.cc
@@ -16,6 +16,7 @@
#include "flags.h"
+#include <stdlib.h>
#include <unistd.h>
#include "log.h"
@@ -52,6 +53,13 @@ void Flags::Parse(int argc, char** argv) {
num_jobs = num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
const char* num_jobs_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;
@@ -78,10 +86,13 @@ void Flags::Parse(int argc, char** argv) {
} 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")) {
diff --git a/flags.h b/flags.h
index a0c6a3b..d22d0cd 100644
--- a/flags.h
+++ b/flags.h
@@ -36,6 +36,7 @@ struct Flags {
bool is_silent_mode;
bool is_syntax_check_only;
bool regen;
+ bool regen_debug;
bool regen_ignoring_kati_binary;
bool use_find_emulator;
const char* goma_dir;
diff --git a/func.cc b/func.cc
index 9aaa9a2..716cb2b 100644
--- a/func.cc
+++ b/func.cc
@@ -17,9 +17,11 @@
#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>
@@ -491,8 +493,8 @@ static bool HasNoIoInShellScript(const string& cmd) {
return false;
}
-static void ShellFuncImpl(const string& shell, const string& cmd,
- string* s, FindCommand** fc) {
+static void ShellFuncImpl(const string& shell, const string& shellflag,
+ const string& cmd, string* s, FindCommand** fc) {
LOG("ShellFunc: %s", cmd.c_str());
#ifdef TEST_FIND_EMULATOR
@@ -517,7 +519,7 @@ static void ShellFuncImpl(const string& shell, const string& cmd,
}
COLLECT_STATS_WITH_SLOW_REPORT("func shell time", cmd.c_str());
- RunCommand(shell, cmd, RedirectStderr::NONE, s);
+ RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, s);
FormatForCommandSubstitution(s);
#ifdef TEST_FIND_EMULATOR
@@ -533,7 +535,9 @@ static void ShellFuncImpl(const string& shell, const string& cmd,
static vector<CommandResult*> g_command_results;
bool ShouldStoreCommandResult(StringPiece cmd) {
- if (HasWord(cmd, "date") || HasWord(cmd, "echo"))
+ // 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);
@@ -562,14 +566,17 @@ void ShellFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
return;
}
- const string&& shell = ev->GetShellAndFlag();
+ const string&& shell = ev->GetShell();
+ const string&& shellflag = ev->GetShellFlag();
string out;
FindCommand* fc = NULL;
- ShellFuncImpl(shell, cmd, &out, &fc);
+ ShellFuncImpl(shell, shellflag, cmd, &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;
@@ -684,6 +691,124 @@ void ErrorFunc(const vector<Value*>& args, Evaluator* ev, string*) {
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;
+ 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;
+ 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;
+ 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()));
+ }
+}
+
FuncInfo g_func_infos[] = {
{ "patsubst", &PatsubstFunc, 3, 3, false, false },
{ "strip", &StripFunc, 1, 1, false, false },
@@ -725,6 +850,8 @@ FuncInfo g_func_infos[] = {
{ "info", &InfoFunc, 1, 1, false, false },
{ "warning", &WarningFunc, 1, 1, false, false },
{ "error", &ErrorFunc, 1, 1, false, false },
+
+ { "file", &FileFunc, 2, 1, false, false },
};
unordered_map<StringPiece, FuncInfo*>* g_func_info_map;
diff --git a/func.h b/func.h
index e78deb7..4ba03b6 100644
--- a/func.h
+++ b/func.h
@@ -41,8 +41,19 @@ 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;
diff --git a/ninja.cc b/ninja.cc
index acf5293..05d0ad1 100644
--- a/ninja.cc
+++ b/ninja.cc
@@ -483,6 +483,9 @@ class NinjaGenerator {
string rule_name = "phony";
bool use_local_pool = false;
+ if (node->output.get(0) == '.') {
+ return;
+ }
if (g_flags.enable_debug) {
*o << "# " << (node->loc.filename ? node->loc.filename : "(null)")
<< ':' << node->loc.lineno << "\n";
@@ -733,31 +736,28 @@ class NinjaGenerator {
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);
- if (!cr->find.get()) {
- // Always re-run this command.
- DumpInt(fp, 0);
- continue;
- }
-
- DumpInt(fp, 1);
- 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);
- }
+ 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->read_dirs->size());
- for (StringPiece s : *cr->find->read_dirs) {
- 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));
+ }
}
}
diff --git a/regen.cc b/regen.cc
index c72bfb6..01bacb7 100644
--- a/regen.cc
+++ b/regen.cc
@@ -23,6 +23,7 @@
#include "fileutil.h"
#include "find.h"
+#include "func.h"
#include "io.h"
#include "log.h"
#include "ninja.h"
@@ -52,12 +53,13 @@ class StampChecker {
};
struct ShellResult {
+ CommandOp op;
string shell;
+ string shellflag;
string cmd;
string result;
vector<string> missing_dirs;
vector<string> read_dirs;
- bool has_condition;
};
public:
@@ -132,7 +134,7 @@ class StampChecker {
const string& stamp_filename = GetNinjaStampFilename();
FILE* fp = fopen(stamp_filename.c_str(), "rb");
if (!fp) {
- if (g_flags.dump_kati_stamp)
+ if (g_flags.regen_debug)
printf("%s: %s\n", stamp_filename.c_str(), strerror(errno));
return true;
}
@@ -145,7 +147,7 @@ class StampChecker {
fprintf(stderr, "incomplete kati_stamp, regenerating...\n");
RETURN_TRUE;
}
- if (g_flags.dump_kati_stamp)
+ if (g_flags.regen_debug)
printf("Generated time: %f\n", gen_time);
string s, s2;
@@ -163,7 +165,7 @@ class StampChecker {
}
}
if (ShouldIgnoreDirty(s)) {
- if (g_flags.dump_kati_stamp)
+ if (g_flags.regen_debug)
printf("file %s: ignored (%f)\n", s.c_str(), ts);
continue;
}
@@ -231,22 +233,23 @@ class StampChecker {
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);
- sr->has_condition = LOAD_INT(fp);
- if (!sr->has_condition)
- continue;
- 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_read_dirs = LOAD_INT(fp);
- for (int j = 0; j < num_read_dirs; j++) {
- LOAD_STRING(fp, &s);
- sr->read_dirs.push_back(s);
+ 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_read_dirs = LOAD_INT(fp);
+ for (int j = 0; j < num_read_dirs; j++) {
+ LOAD_STRING(fp, &s);
+ sr->read_dirs.push_back(s);
+ }
}
}
@@ -292,7 +295,7 @@ class StampChecker {
}
bool ShouldRunCommand(const ShellResult* sr) {
- if (!sr->has_condition)
+ if (sr->op != CommandOp::FIND)
return true;
COLLECT_STATS("stat time (regen)");
@@ -324,8 +327,56 @@ class StampChecker {
}
bool CheckShellResult(const ShellResult* sr, string* err) {
- if (!ShouldRunCommand(sr)) {
+ 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;
}
@@ -340,7 +391,7 @@ class StampChecker {
COLLECT_STATS_WITH_SLOW_REPORT("shell time (regen)", sr->cmd.c_str());
string result;
- RunCommand(sr->shell, sr->cmd, RedirectStderr::DEV_NULL, &result);
+ RunCommand(sr->shell, sr->shellflag, sr->cmd, RedirectStderr::DEV_NULL, &result);
FormatForCommandSubstitution(&result);
if (sr->result != result) {
if (g_flags.dump_kati_stamp) {
@@ -351,7 +402,7 @@ class StampChecker {
//*err += StringPrintf("%s => %s\n", expected.c_str(), result.c_str());
}
return true;
- } else if (g_flags.dump_kati_stamp) {
+ } else if (g_flags.regen_debug) {
printf("shell %s: clean (rerun)\n", sr->cmd.c_str());
}
return false;
@@ -375,8 +426,8 @@ class StampChecker {
}
});
- for (ShellResult* sr : commands_) {
- tp->Submit([this, sr]() {
+ tp->Submit([this]() {
+ for (ShellResult* sr : commands_) {
string err;
if (CheckShellResult(sr, &err)) {
unique_lock<mutex> lock(mu_);
@@ -385,8 +436,8 @@ class StampChecker {
msg_ = err;
}
}
- });
- }
+ }
+ });
tp->Wait();
if (needs_regen_) {
diff --git a/rule.cc b/rule.cc
index f3f5203..68ec7f5 100644
--- a/rule.cc
+++ b/rule.cc
@@ -54,6 +54,7 @@ Rule::Rule()
}
void ParseRule(Loc& loc, StringPiece line, char term,
+ function<string()> after_term_fn,
Rule** out_rule, RuleVarAssignment* rule_var) {
size_t index = line.find(':');
if (index == string::npos) {
@@ -84,15 +85,31 @@ void ParseRule(Loc& loc, StringPiece line, char term,
StringPiece rest = line.substr(index);
size_t term_index = rest.find_first_of("=;");
+ string buf;
if ((term_index != string::npos && rest[term_index] == '=') ||
(term_index == string::npos && term == '=')) {
if (term_index == string::npos)
term_index = rest.size();
- rule_var->outputs.swap(outputs);
- ParseAssignStatement(rest, term_index,
- &rule_var->lhs, &rule_var->rhs, &rule_var->op);
- *out_rule = NULL;
- return;
+ // "test: =foo" is questionable but a valid rule definition (not a
+ // target specific variable).
+ // See https://github.com/google/kati/issues/83
+ if (term_index == 0) {
+ KATI_WARN("%s:%d: defining a target which starts with `=', "
+ "which is not probably what you meant", LOCF(loc));
+ buf = line.as_string();
+ if (term)
+ buf += term;
+ buf += after_term_fn();
+ line = buf;
+ rest = line.substr(index);
+ term_index = string::npos;
+ } else {
+ rule_var->outputs.swap(outputs);
+ ParseAssignStatement(rest, term_index,
+ &rule_var->lhs, &rule_var->rhs, &rule_var->op);
+ *out_rule = NULL;
+ return;
+ }
}
Rule* rule = new Rule();
diff --git a/rule.h b/rule.h
index 2a67368..4d72bb7 100644
--- a/rule.h
+++ b/rule.h
@@ -15,6 +15,8 @@
#ifndef RULE_H_
#define RULE_H_
+#include <functional>
+#include <string>
#include <vector>
#include "loc.h"
@@ -58,8 +60,12 @@ struct RuleVarAssignment {
AssignOp op;
};
-// If |rule| is not NULL, |rule_var| is filled.
+// If |rule| is not NULL, |rule_var| is filled. If the expression
+// after the terminator |term| is needed (this happens only when
+// |term| is '='), |after_term_fn| will be called to obtain the right
+// hand side.
void ParseRule(Loc& loc, StringPiece line, char term,
+ function<string()> after_term_fn,
Rule** rule, RuleVarAssignment* rule_var);
#endif // RULE_H_
diff --git a/strutil.cc b/strutil.cc
index d3c14d8..1b99dff 100644
--- a/strutil.cc
+++ b/strutil.cc
@@ -21,6 +21,7 @@
#include <unistd.h>
#include <algorithm>
+#include <functional>
#include <stack>
#include <utility>
@@ -38,8 +39,9 @@ static bool isSpace(char c) {
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;
- do {
+ while (i < len) {
__m128i b16 = _mm_loadu_si128((const __m128i*)(s + i));
int r = _mm_cmpestri(
ranges16, ranges_size, b16, len - i,
@@ -48,11 +50,26 @@ static int SkipUntilSSE42(const char* s, int len,
return i + r;
}
i += 16;
- } while (i < len);
+ }
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++) {
@@ -66,17 +83,11 @@ WordScanner::Iterator& WordScanner::Iterator::operator++() {
return *this;
}
-#ifdef __SSE4_2__
static const char ranges[] = "\x09\x0d ";
- i = s;
- i += SkipUntilSSE42(in->data() + s, len - s, ranges, 4);
-#else
- for (i = s; i < len; i++) {
- if (isSpace((*in)[i]))
- break;
- }
-#endif
-
+ // 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;
}
@@ -423,10 +434,10 @@ size_t FindThreeOutsideParen(StringPiece s, char c1, char c2, char c3) {
}
size_t FindEndOfLine(StringPiece s, size_t e, size_t* lf_cnt) {
-#ifdef __SSE4_2__
static const char ranges[] = "\0\0\n\n\\\\";
while (e < s.size()) {
- e += SkipUntilSSE42(s.data() + e, s.size() - e, ranges, 6);
+ 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;
@@ -452,24 +463,6 @@ size_t FindEndOfLine(StringPiece s, size_t e, size_t* lf_cnt) {
}
}
return e;
-#else
- bool prev_backslash = false;
- for (; e < s.size(); e++) {
- char c = s[e];
- if (c == '\\') {
- prev_backslash = !prev_backslash;
- } else if (c == '\n') {
- ++*lf_cnt;
- if (!prev_backslash) {
- return e;
- }
- prev_backslash = false;
- } else if (c != '\r') {
- prev_backslash = false;
- }
- }
- return e;
-#endif
}
StringPiece TrimLeadingCurdir(StringPiece s) {
@@ -528,11 +521,14 @@ string EchoEscape(const string str) {
return buf;
}
+static bool NeedsShellEscape(char c) {
+ return c == 0 || c == '"' || c == '$' || c == '\\' || c == '`';
+}
+
void EscapeShell(string* s) {
-#ifdef __SSE4_2__
static const char ranges[] = "\0\0\"\"$$\\\\``";
size_t prev = 0;
- size_t i = SkipUntilSSE42(s->c_str(), s->size(), ranges, 10);
+ size_t i = SkipUntil(s->c_str(), s->size(), ranges, 10, NeedsShellEscape);
if (i == s->size())
return;
@@ -550,37 +546,8 @@ void EscapeShell(string* s) {
r += c;
i++;
prev = i;
- i += SkipUntilSSE42(s->c_str() + i, s->size() - i, ranges, 10);
+ i += SkipUntil(s->c_str() + i, s->size() - i, ranges, 10, NeedsShellEscape);
}
StringPiece(*s).substr(prev).AppendToString(&r);
s->swap(r);
-#else
- if (s->find_first_of("$`\\\"") == string::npos)
- return;
- string r;
- bool last_dollar = false;
- for (char c : *s) {
- switch (c) {
- case '$':
- if (last_dollar) {
- r += c;
- last_dollar = false;
- } else {
- r += '\\';
- r += c;
- last_dollar = true;
- }
- break;
- case '`':
- case '"':
- case '\\':
- r += '\\';
- // fall through.
- default:
- r += c;
- last_dollar = false;
- }
- }
- s->swap(r);
-#endif
}
diff --git a/strutil_test.cc b/strutil_test.cc
index a89786f..7a5a47d 100644
--- a/strutil_test.cc
+++ b/strutil_test.cc
@@ -17,6 +17,8 @@
#include "strutil.h"
#include <assert.h>
+#include <sys/mman.h>
+#include <unistd.h>
#include <string>
#include <vector>
@@ -138,6 +140,50 @@ void TestFindEndOfLine() {
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);
+}
+
} // namespace
int main() {
@@ -150,5 +196,7 @@ int main() {
TestNormalizePath();
TestEscapeShell();
TestFindEndOfLine();
+ TestWordScannerInvalidAccess();
+ TestFindEndOfLineInvalidAccess();
assert(!g_failed);
}
diff --git a/symtab.cc b/symtab.cc
index b47522b..0e51c21 100644
--- a/symtab.cc
+++ b/symtab.cc
@@ -59,16 +59,29 @@ Var* Symbol::GetGlobalVar() const {
return v;
}
-void Symbol::SetGlobalVar(Var* v, bool is_override) const {
+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");
}
diff --git a/symtab.h b/symtab.h
index e7e71d5..7e39be9 100644
--- a/symtab.h
+++ b/symtab.h
@@ -56,7 +56,7 @@ class Symbol {
bool IsValid() const { return v_ >= 0; }
Var* GetGlobalVar() const;
- void SetGlobalVar(Var* v, bool is_override = false) const;
+ void SetGlobalVar(Var* v, bool is_override = false, bool* readonly = nullptr) const;
private:
explicit Symbol(int v);
diff --git a/testcase/cmdline_var.sh b/testcase/cmdline_var.sh
new file mode 100755
index 0000000..b892736
--- /dev/null
+++ b/testcase/cmdline_var.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+cat <<EOF > Makefile
+CLVAR := FAIL
+all:
+ @echo \$(CLVAR)
+EOF
+
+${mk} CLVAR:=PASS 2> /dev/null
diff --git a/testcase/cmdline_var_makeflags.sh b/testcase/cmdline_var_makeflags.sh
new file mode 100755
index 0000000..6e38a2d
--- /dev/null
+++ b/testcase/cmdline_var_makeflags.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+cat <<EOF > Makefile
+CLVAR := FAIL
+MFVAR := FAIL
+FILEVAR := PASS
+all:
+ @echo \$(ENVVAR) \$(origin ENVVAR)
+ @echo \$(MFVAR) \$(origin MFVAR)
+ @echo \$(CLVAR) \$(origin CLVAR)
+ @echo \$(FILEVAR) \$(origin FILEVAR)
+EOF
+
+export ENVVAR=PASS
+export FILEVAR=FAIL
+export MAKEFLAGS="MFVAR=PASS CLVAR=FAIL"
+
+${mk} CLVAR=PASS 2> /dev/null
diff --git a/testcase/cmdline_var_modify.sh b/testcase/cmdline_var_modify.sh
new file mode 100755
index 0000000..9616a2b
--- /dev/null
+++ b/testcase/cmdline_var_modify.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+cat <<EOF > Makefile
+CLVAR := FAIL
+all:
+ @echo \$(CLVAR)
+EOF
+
+${mk} CLVAR:=P CLVAR+=A CLVAR+=SS CLVAR?=FAIL 2> /dev/null
diff --git a/testcase/cmdline_var_override.sh b/testcase/cmdline_var_override.sh
new file mode 100755
index 0000000..0a5b3c1
--- /dev/null
+++ b/testcase/cmdline_var_override.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+cat <<EOF > Makefile
+override CLVAR := PASS
+all:
+ @echo \$(CLVAR)
+EOF
+
+${mk} CLVAR:=FAIL 2> /dev/null
diff --git a/testcase/empty_target_specific_var.mk b/testcase/empty_target_specific_var.mk
new file mode 100644
index 0000000..fd9e6c1
--- /dev/null
+++ b/testcase/empty_target_specific_var.mk
@@ -0,0 +1,7 @@
+# TODO(go): https://github.com/google/kati/issues/83
+
+test: =foo
+
+var==foo
+$(var):
+ echo PASS
diff --git a/testcase/empty_target_specific_var2.mk b/testcase/empty_target_specific_var2.mk
new file mode 100644
index 0000000..6defb52
--- /dev/null
+++ b/testcase/empty_target_specific_var2.mk
@@ -0,0 +1,11 @@
+# TODO(go): https://github.com/google/kati/issues/83
+
+define var
+VAR:=1
+endef
+
+$(call var)
+
+eq_one:==1
+$(eq_one):
+ echo PASS
diff --git a/testcase/file_func.sh b/testcase/file_func.sh
new file mode 100755
index 0000000..72935dc
--- /dev/null
+++ b/testcase/file_func.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+echo "PASS" >testfile
+
+cat <<EOF > Makefile
+ifdef KATI
+SUPPORTS_FILE := 1
+endif
+ifneq (,\$(filter 4.2%,\$(MAKE_VERSION)))
+SUPPORTS_FILE := 1
+endif
+
+ifdef SUPPORTS_FILE
+ \$(file >testwrite,PASS)
+ \$(info Read not found: \$(if \$(file <notfound),FAIL,PASS))
+ \$(info Read: \$(file < testfile))
+ \$(info Read back: \$(file <testwrite))
+else
+ # Make <4 does not support \$(file ...)
+ \$(info Read not found: PASS)
+ \$(info Read: PASS)
+ \$(info Read back: PASS)
+endif
+
+.PHONY: all
+all:
+EOF
+
+${mk} 2>&1
diff --git a/testcase/ninja_regen_filefunc_read.sh b/testcase/ninja_regen_filefunc_read.sh
new file mode 100755
index 0000000..7fc3a9f
--- /dev/null
+++ b/testcase/ninja_regen_filefunc_read.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+log=/tmp/log
+mk="$@"
+
+sleep_if_necessary() {
+ if [ x$(uname) != x"Linux" -o x"${TRAVIS}" != x"" ]; then
+ sleep "$@"
+ fi
+}
+
+cat <<EOF > Makefile
+A := \$(file <file_a)
+all:
+ echo foo
+EOF
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ ./ninja.sh
+fi
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if grep regenerating ${log}; then
+ echo 'Should not be regenerated'
+ fi
+ ./ninja.sh
+fi
+
+echo regen >file_a
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if ! grep regenerating ${log} >/dev/null; then
+ echo 'Should be regenerated'
+ fi
+ ./ninja.sh
+fi
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if grep regenerating ${log}; then
+ echo 'Should not be regenerated'
+ fi
+ ./ninja.sh
+fi
+
+sleep_if_necessary 1
+echo regen >>file_a
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if ! grep regenerating ${log} >/dev/null; then
+ echo 'Should be regenerated'
+ fi
+ ./ninja.sh
+fi
diff --git a/testcase/ninja_regen_filefunc_write.sh b/testcase/ninja_regen_filefunc_write.sh
new file mode 100755
index 0000000..cb438cc
--- /dev/null
+++ b/testcase/ninja_regen_filefunc_write.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# 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.
+
+set -e
+
+log=/tmp/log
+mk="$@"
+
+cat <<EOF > Makefile
+\$(file >file_a,test)
+all:
+ echo foo
+EOF
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if [ ! -f file_a ]; then
+ echo 'file_a does not exist'
+ fi
+ ./ninja.sh
+ rm file_a
+fi
+
+${mk} 2> ${log}
+if [ -e ninja.sh ]; then
+ if grep regenerating ${log}; then
+ echo 'Should not be regenerated'
+ fi
+ if [ ! -f file_a ]; then
+ echo 'file_a does not exist'
+ fi
+ ./ninja.sh
+fi
diff --git a/testcase/phony.mk b/testcase/phony.mk
index 59fb911..64a67bb 100644
--- a/testcase/phony.mk
+++ b/testcase/phony.mk
@@ -11,9 +11,6 @@ baz: foo
test1: foo bar baz
echo PASS test1 from foo bar baz
-# Actually, you can use .PHONY!
-test2: .PHONY
-
test3:
touch test4
diff --git a/testcase/readonly_global.sh b/testcase/readonly_global.sh
new file mode 100644
index 0000000..33695f2
--- /dev/null
+++ b/testcase/readonly_global.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# 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.
+
+set -u
+
+mk="$@"
+
+function build() {
+ cat <<EOF > Makefile
+FOO $1 bar
+.KATI_READONLY $2 FOO
+FOO $3 baz
+all:
+EOF
+
+ echo "Testcase: $1 $2 $3"
+ if echo "${mk}" | grep -q "^make"; then
+ # Make doesn't support .KATI_READONLY
+ echo "Makefile:3: *** cannot assign to readonly variable: FOO"
+ else
+ ${mk} 2>&1 && echo "Clean exit"
+ fi
+}
+
+build "=" "=" "="
+build "=" "+=" "="
+build "=" ":=" "="
+
+build "=" ":=" ":="
+build "=" ":=" "+="
+
+build ":=" ":=" ":="
+build ":=" ":=" "+="
+build ":=" ":=" "="
diff --git a/testcase/readonly_global_missing.sh b/testcase/readonly_global_missing.sh
new file mode 100644
index 0000000..cab86f2
--- /dev/null
+++ b/testcase/readonly_global_missing.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# 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.
+
+set -u
+
+mk="$@"
+
+cat <<EOF > Makefile
+all: FOO = bar
+.KATI_READONLY = FOO
+all:
+EOF
+
+if echo "${mk}" | grep -q "^make"; then
+ # Make doesn't support .KATI_READONLY
+ echo "Makefile:2: *** unknown variable: FOO"
+else
+ ${mk} 2>&1 && echo "Clean exit"
+fi
diff --git a/testcase/readonly_rule.sh b/testcase/readonly_rule.sh
new file mode 100644
index 0000000..676d639
--- /dev/null
+++ b/testcase/readonly_rule.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+#
+# 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.
+
+set -u
+
+mk="$@"
+
+function build() {
+ cat <<EOF > Makefile
+all: FOO $1 bar
+all: .KATI_READONLY $2 FOO
+FOO $3 foo
+all: FOO $3 baz
+all:
+EOF
+
+ echo "Testcase: $1 $2 $3"
+ if echo "${mk}" | grep -q "^make"; then
+ # Make doesn't support .KATI_READONLY
+ echo "Makefile:4: *** cannot assign to readonly variable: FOO"
+ else
+ ${mk} 2>&1 && echo "Clean exit"
+ fi
+}
+
+#build "=" "=" "="
+#build "=" "+=" "="
+#build "=" ":=" "="
+#
+#build "=" ":=" ":="
+#build "=" ":=" "+="
+#
+#build ":=" ":=" ":="
+build ":=" ":=" "+="
+#build ":=" ":=" "="
diff --git a/testcase/readonly_rule_missing.sh b/testcase/readonly_rule_missing.sh
new file mode 100644
index 0000000..6940438
--- /dev/null
+++ b/testcase/readonly_rule_missing.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# 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.
+
+set -u
+
+mk="$@"
+
+cat <<EOF > Makefile
+FOO = bar
+all: .KATI_READONLY = FOO
+all:
+EOF
+
+if echo "${mk}" | grep -q "^make"; then
+ # Make doesn't support .KATI_READONLY
+ echo "Makefile:2: *** unknown variable: FOO"
+else
+ ${mk} 2>&1 && echo "Clean exit"
+fi
diff --git a/var.cc b/var.cc
index 4b67c4f..8f46ff8 100644
--- a/var.cc
+++ b/var.cc
@@ -37,7 +37,7 @@ const char* GetOriginStr(VarOrigin origin) {
return "*** broken origin ***";
}
-Var::Var() {
+Var::Var() : readonly_(false) {
}
Var::~Var() {
@@ -130,10 +130,15 @@ Var* Vars::Lookup(Symbol name) const {
return v;
}
-void Vars::Assign(Symbol name, Var* v) {
+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;
diff --git a/var.h b/var.h
index 5fc09fa..75653de 100644
--- a/var.h
+++ b/var.h
@@ -56,8 +56,14 @@ class Var : public Evaluable {
virtual string DebugString() const = 0;
+ bool ReadOnly() const { return readonly_; }
+ void SetReadOnly() { readonly_ = true; }
+
protected:
Var();
+
+ private:
+ bool readonly_;
};
class SimpleVar : public Var {
@@ -177,7 +183,7 @@ class Vars : public unordered_map<Symbol, Var*> {
Var* Lookup(Symbol name) const;
- void Assign(Symbol name, Var* v);
+ void Assign(Symbol name, Var* v, bool* readonly);
static void add_used_env_vars(Symbol v);