| // 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); | |
| for (StringPiece tok : WordScanner(text)) { | |
| AppendString(tok, s); | |
| return; | |
| } | |
| } | |
| 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; | |
| 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; | |
| 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())); | |
| } | |
| } | |
| 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); | |
| } | |
| 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}, | |
| }; | |
| 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; | |
| } |