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
+	@: