Merge pull request #121 from danw/implicit_outputs

Include implicit outputs in the DepNode graph
diff --git a/dep.cc b/dep.cc
index fec36b4..16aeeec 100644
--- a/dep.cc
+++ b/dep.cc
@@ -144,14 +144,39 @@
 
 struct RuleMerger {
   vector<const Rule*> rules;
+  vector<pair<Symbol, RuleMerger*>> implicit_outputs;
   const Rule* primary_rule;
+  const RuleMerger* parent;
+  Symbol parent_sym;
   bool is_double_colon;
 
   RuleMerger()
       : primary_rule(nullptr),
+        parent(nullptr),
+        parent_sym(Symbol::IsUninitialized()),
         is_double_colon(false) {
   }
 
+  void AddImplicitOutput(Symbol output, RuleMerger *merger) {
+    implicit_outputs.push_back(make_pair(output, merger));
+  }
+
+  void SetImplicitOutput(Symbol output, Symbol p, const RuleMerger* merger) {
+    if (!merger->primary_rule) {
+      ERROR("*** implicit output `%s' on phony target `%s'", output.c_str(), p.c_str());
+    }
+    if (parent) {
+      ERROR_LOC(merger->primary_rule->cmd_loc(), "*** implicit output `%s' of `%s' was already defined by `%s' at %s:%d",
+                output.c_str(), p.c_str(), parent_sym.c_str(), parent->primary_rule->cmd_loc());
+    }
+    if (primary_rule) {
+      ERROR_LOC(primary_rule->cmd_loc(), "*** implicit output `%s' may not have commands",
+                output.c_str());
+    }
+    parent = merger;
+    parent_sym = p;
+  }
+
   void AddRule(Symbol output, const Rule* r) {
     if (rules.empty()) {
       is_double_colon = r->is_double_colon;
@@ -226,6 +251,13 @@
       if (n->loc.filename == NULL)
         n->loc = r->loc;
     }
+
+    for (auto& implicit_output : implicit_outputs) {
+      n->implicit_outputs.push_back(implicit_output.first);
+      for (const Rule* r : implicit_output.second->rules) {
+        FillDepNodeFromRule(output, r, n);
+      }
+    }
   }
 };
 
@@ -239,7 +271,6 @@
       is_restat(r),
       rule_vars(NULL),
       depfile_var(NULL),
-      implicit_outputs_var(NULL),
       ninja_pool_var(NULL),
       output_pattern(Symbol::IsUninitialized()) {
   g_dep_node_pool->push_back(this);
@@ -388,6 +419,25 @@
     for (auto& p : suffix_rules_) {
       reverse(p.second.begin(), p.second.end());
     }
+    for (auto& p : rules_) {
+      auto vars = LookupRuleVars(p.first);
+      if (!vars) {
+        continue;
+      }
+      auto var = vars->Lookup(implicit_outputs_var_name_);
+      if (!var->IsDefined()) {
+        continue;
+      }
+
+      string implicit_outputs;
+      var->Eval(ev_, &implicit_outputs);
+
+      for (StringPiece output : WordScanner(implicit_outputs)) {
+        Symbol sym = Intern(TrimLeadingCurdir(output));
+        rules_[sym].SetImplicitOutput(sym, p.first, &p.second);
+        p.second.AddImplicitOutput(sym, &rules_[sym]);
+      }
+    }
   }
 
   bool PopulateSuffixRule(const Rule* rule, Symbol output) {
@@ -519,8 +569,13 @@
     Vars* vars = LookupRuleVars(output);
     *out_rule_merger = rule_merger;
     *out_var = vars;
-    if (rule_merger && rule_merger->primary_rule)
+    if (rule_merger && rule_merger->primary_rule) {
+      for (auto implicit_output : rule_merger->implicit_outputs) {
+        vars = MergeImplicitRuleVars(implicit_output.first, vars);
+      }
+      *out_var = vars;
       return true;
+    }
 
     vector<const Rule*> irules;
     implicit_rules_->Get(output.str(), &irules);
@@ -586,6 +641,14 @@
     if (!PickRule(output, n, &rule_merger, &pattern_rule, &vars)) {
       return n;
     }
+    if (rule_merger && rule_merger->parent) {
+      output = rule_merger->parent_sym;
+      done_[output] = n;
+      n->output = output;
+      if (!PickRule(output, n, &rule_merger, &pattern_rule, &vars)) {
+        return n;
+      }
+    }
     if (rule_merger)
       rule_merger->FillDepNode(output, pattern_rule.get(), n);
     else
@@ -619,7 +682,6 @@
         if (name == depfile_var_name_) {
           n->depfile_var = new_var;
         } else if (name == implicit_outputs_var_name_) {
-          n->implicit_outputs_var = new_var;
         } else if (name == ninja_pool_var_name_) {
           n->ninja_pool_var = new_var;
         } else {
@@ -628,6 +690,10 @@
       }
     }
 
+    for (Symbol output : n->implicit_outputs) {
+      done_[output] = n;
+    }
+
     for (Symbol input : n->actual_inputs) {
       DepNode* c = BuildPlan(input, output);
       n->deps.push_back(c);
diff --git a/dep.h b/dep.h
index b6f25e1..c42b380 100644
--- a/dep.h
+++ b/dep.h
@@ -41,11 +41,11 @@
   bool is_default_target;
   bool is_phony;
   bool is_restat;
+  vector<Symbol> implicit_outputs;
   vector<Symbol> actual_inputs;
   vector<Symbol> actual_order_only_inputs;
   Vars* rule_vars;
   Var* depfile_var;
-  Var* implicit_outputs_var;
   Var* ninja_pool_var;
   Symbol output_pattern;
   Loc loc;
diff --git a/ninja.cc b/ninja.cc
index 35eb789..0d7acc9 100644
--- a/ninja.cc
+++ b/ninja.cc
@@ -547,17 +547,10 @@
     const DepNode* node = nn->node;
     string target = EscapeBuildTarget(node->output);
     *o << "build " << target;
-    if (node->implicit_outputs_var) {
-      string implicit_outputs;
-      node->implicit_outputs_var->Eval(ev_, &implicit_outputs);
-
-      bool first = true;
-      for (StringPiece output : WordScanner(implicit_outputs)) {
-        if (first) {
-          *o << " |";
-          first = false;
-        }
-        *o << " " << EscapeNinja(output.as_string()).c_str();
+    if (!node->implicit_outputs.empty()) {
+      *o << " |";
+      for (Symbol output : node->implicit_outputs) {
+        *o << " " << EscapeBuildTarget(output);
       }
     }
     *o << ": " << rule_name;
diff --git a/testcase/ninja_implicit_output_var.sh b/testcase/ninja_implicit_output_var.sh
new file mode 100644
index 0000000..63ef6b4
--- /dev/null
+++ b/testcase/ninja_implicit_output_var.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+#
+# 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.
+
+set -e
+
+mk="$@"
+
+cat <<EOF >Makefile
+all: a
+
+a:
+	if ! [ -z "\$(VAR)" ]; then echo \$(VAR); fi
+a: .KATI_IMPLICIT_OUTPUTS := b
+b: VAR := OK
+EOF
+
+${mk} -j1
+if [ -e ninja.sh ]; then
+  ./ninja.sh -j1 -w dupbuild=err;
+else
+  echo OK
+fi
diff --git a/testcase/ninja_implicit_outputs.sh b/testcase/ninja_implicit_outputs.sh
index eb9ef13..43c91a0 100644
--- a/testcase/ninja_implicit_outputs.sh
+++ b/testcase/ninja_implicit_outputs.sh
@@ -24,14 +24,17 @@
 a b:
 	touch A
 	echo 1 >>A
+d: a
 c: .KATI_IMPLICIT_OUTPUTS := d
 c:
 	touch C
 	echo 1 >>C
+
+c d: b
 EOF
 
-$@ -j1 all c
-if [ -e ninja.sh ]; then ./ninja.sh -j1 all d; fi
+${mk} -j1 all d c
+if [ -e ninja.sh ]; then ./ninja.sh -j1 -w dupbuild=err all d; fi
 
 echo "A:"
 cat A