You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1006 lines
27 KiB
1006 lines
27 KiB
// 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; |
|
}
|
|
|