func: Add support for .SHELLSTATUS .SHELLSTATUS is a variable that's set with the return status of the last $(shell) command. GNU Make has this feature, but kati had been missing it.
diff --git a/src/command.cc b/src/command.cc index 9178e9e..5268b9e 100644 --- a/src/command.cc +++ b/src/command.cc
@@ -189,6 +189,7 @@ std::vector<Command> result; ev_->set_loc(n.loc); ev_->set_current_scope(n.rule_vars); + ev_->SetEvaluatingCommand(true); current_dep_node_ = &n; for (Value* v : n.cmds) { ev_->set_loc(v->Location()); @@ -238,6 +239,7 @@ } ev_->set_current_scope(NULL); + ev_->SetEvaluatingCommand(false); return result; }
diff --git a/src/eval.cc b/src/eval.cc index 58d8d5b..7f9dc6f 100644 --- a/src/eval.cc +++ b/src/eval.cc
@@ -883,4 +883,12 @@ fclose(jsonfile); } +bool Evaluator::IsEvaluatingCommand() const { + return is_evaluating_command_; +} + +void Evaluator::SetEvaluatingCommand(bool evaluating_command) { + is_evaluating_command_ = evaluating_command; +} + SymbolSet Evaluator::used_undefined_vars_;
diff --git a/src/eval.h b/src/eval.h index c677f99..75038ef 100644 --- a/src/eval.h +++ b/src/eval.h
@@ -46,7 +46,7 @@ FUNCALL, // Evaluating a function call (not its result) STATEMENT, // Denotes individual statements for better location reporting DEPENDENCY, // Dependency analysis. += requires variable expansion here. - EXEC, // Execution phase. Expansoin of = and rule-specific variables. + EXEC, // Execution phase. Expansion of = and rule-specific variables. NINJA, // Ninja file generation }; @@ -216,6 +216,9 @@ profiled_files_.emplace_back(mk.as_string()); } + bool IsEvaluatingCommand() const; + void SetEvaluatingCommand(bool evaluating_command); + private: Var* EvalRHS(Symbol lhs, Value* rhs, @@ -280,6 +283,8 @@ vector<string> profiled_files_; static SymbolSet used_undefined_vars_; + + bool is_evaluating_command_ = false; }; #endif // EVAL_H_
diff --git a/src/func.cc b/src/func.cc index 8c298a7..bd8d78d 100644 --- a/src/func.cc +++ b/src/func.cc
@@ -494,12 +494,12 @@ return false; } -static void ShellFuncImpl(const string& shell, - const string& shellflag, - const string& cmd, - const Loc& loc, - string* s, - FindCommand** fc) { +static int 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 @@ -515,7 +515,7 @@ } #else if (FindEmulator::Get()->HandleFind(cmd, **fc, loc, s)) { - return; + return 0; } #endif } @@ -524,7 +524,7 @@ } COLLECT_STATS_WITH_SLOW_REPORT("func shell time", cmd.c_str()); - RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, s); + int status = RunCommand(shell, shellflag, cmd, RedirectStderr::NONE, s); FormatForCommandSubstitution(s); #ifdef TEST_FIND_EMULATOR @@ -535,6 +535,11 @@ } } #endif + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } + return 1; } static vector<CommandResult*> g_command_results; @@ -577,7 +582,7 @@ string out; FindCommand* fc = NULL; - ShellFuncImpl(shell, shellflag, cmd, ev->loc(), &out, &fc); + int returnCode = ShellFuncImpl(shell, shellflag, cmd, ev->loc(), &out, &fc); if (ShouldStoreCommandResult(cmd)) { CommandResult* cr = new CommandResult(); cr->op = (fc == NULL) ? CommandOp::SHELL : CommandOp::FIND, @@ -590,6 +595,7 @@ g_command_results.push_back(cr); } *s += out; + ShellStatusVar::SetValue(returnCode); } void CallFunc(const vector<Value*>& args, Evaluator* ev, string* s) {
diff --git a/src/symtab.cc b/src/symtab.cc index d46f4e9..69895f7 100644 --- a/src/symtab.cc +++ b/src/symtab.cc
@@ -129,6 +129,8 @@ kEmptySym = Intern(""); kShellSym = Intern("SHELL"); + Symbol shellStatusSym = Intern(".SHELLSTATUS"); + shellStatusSym.SetGlobalVar(new ShellStatusVar(), false, nullptr); kAllowRulesSym = Intern(".KATI_ALLOW_RULES"); kKatiReadonlySym = Intern(".KATI_READONLY"); kVariablesSym = Intern(".VARIABLES");
diff --git a/src/var.cc b/src/var.cc index 4bc9bbb..e51aa6b 100644 --- a/src/var.cc +++ b/src/var.cc
@@ -248,6 +248,51 @@ return "*VariableNamesVar*"; } +bool ShellStatusVar::is_set_ = false; +int ShellStatusVar::shell_status_ = 0; + +ShellStatusVar::ShellStatusVar() { + SetReadOnly(); + SetAssignOp(AssignOp::COLON_EQ); +} + +void ShellStatusVar::SetValue(int newShellStatus) { + shell_status_ = newShellStatus; + is_set_ = true; +} + +bool ShellStatusVar::IsDefined() const { + return is_set_; +} + +bool ShellStatusVar::IsFunc(Evaluator*) const { + return false; +} + +void ShellStatusVar::Eval(Evaluator* ev, string* s) const { + if (ev->IsEvaluatingCommand()) { + ev->Error("Kati does not support using .SHELLSTATUS inside of a rule"); + } + + if (!is_set_) { + return; + } + + *s += std::to_string(shell_status_); +} + +StringPiece ShellStatusVar::String() const { + if (!is_set_) { + return ""; + } + + return std::to_string(shell_status_); +} + +string ShellStatusVar::DebugString() const { + return "*ShellStatusVar*"; +} + Vars::~Vars() { for (auto p : *this) { delete p.second;
diff --git a/src/var.h b/src/var.h index b091ffb..67b26f6 100644 --- a/src/var.h +++ b/src/var.h
@@ -195,6 +195,29 @@ void ConcatVariableNames(Evaluator* ev, string* s) const; }; +// The built-in .SHELLSTATUS variable +class ShellStatusVar : public Var { + public: + ShellStatusVar(); + + static void SetValue(int newShellStatus); + + virtual const char* Flavor() const override { return "simple"; } + virtual bool IsDefined() const override; + + virtual bool IsFunc(Evaluator* ev) const override; + + virtual void Eval(Evaluator* ev, string* s) const override; + + virtual StringPiece String() const override; + + virtual string DebugString() const override; + + private: + static bool is_set_; + static int shell_status_; +}; + class Vars : public unordered_map<Symbol, Var*> { public: ~Vars();
diff --git a/testcase/shellstatus.mk b/testcase/shellstatus.mk new file mode 100644 index 0000000..d4faf3a --- /dev/null +++ b/testcase/shellstatus.mk
@@ -0,0 +1,23 @@ +# Check the value of .SHELLSTATUS before $(shell) has run +A := $(.SHELLSTATUS) + +$(shell exit 0) +B := $(.SHELLSTATUS) + +$(shell exit 1) +C := $(.SHELLSTATUS) + +# .SHELLSTATUS is global across makefiles +$(file >nested.mk,$$(shell exit 2)) +include nested.mk +D := $(.SHELLSTATUS) + +ruletest: temp := $(shell exit 3) +ruletest: E := $(.SHELLSTATUS) +ruletest: + @echo $(E) + +$(shell exit 0) + +test: ruletest + echo $(A) $(B) $(C) $(D) $(flavor .SHELLSTATUS)
diff --git a/testcase/shellstatus_in_rule.mk b/testcase/shellstatus_in_rule.mk new file mode 100644 index 0000000..bfc0842 --- /dev/null +++ b/testcase/shellstatus_in_rule.mk
@@ -0,0 +1,15 @@ +$(shell exit 5) + +ifdef KATI +NAMEWORKAROUND := .SHELLSTATUS +testTargetWithShellCommand: + @echo $(shell exit 7) + @echo $($(NAMEWORKAROUND)) +else +testTargetWithShellCommand: + @echo "Makefile:7: Kati does not support using .SHELLSTATUS inside of a rule" +endif + +test: testTargetWithShellCommand + @# Suppress the "Nothing to be done for "test"." message + @:
diff --git a/testcase/shellstatus_in_rule_in_eval.mk b/testcase/shellstatus_in_rule_in_eval.mk new file mode 100644 index 0000000..954b8ac --- /dev/null +++ b/testcase/shellstatus_in_rule_in_eval.mk
@@ -0,0 +1,17 @@ +$(shell exit 5) + +define rule-template +NAMEWORKAROUND := .SHELLSTATUS +testTargetWithShellCommand: + @echo $(shell exit 7) + @echo $($(NAMEWORKAROUND)) + @echo "Rule ran!" +endef + +$(eval $(rule-template)) + +$(warning $(.SHELLSTATUS)) + +test: testTargetWithShellCommand + @# Suppress the "Nothing to be done for "test"." message + @:
diff --git a/testcase/shellstatus_readonly.mk b/testcase/shellstatus_readonly.mk new file mode 100644 index 0000000..592150f --- /dev/null +++ b/testcase/shellstatus_readonly.mk
@@ -0,0 +1,12 @@ +ifdef KATI +# Test that you can't bypass the readonlyness of .SHELLSTATUS +NAMEWORKAROUND := .SHELLSTATUS +$(NAMEWORKAROUND) := 5 +$(info $(.SHELLSTATUS)) +else +$(info Makefile:4: *** cannot assign to readonly variable: .SHELLSTATUS) +endif + +test: + @# Suppress the "Nothing to be done for "test"." message + @: