Repository: ninja-build/ninja (C++)

Issue:
Turn duplicate rules for the same target into an error
If multiple build edges generate the same output, ninja currently warns with

```
ninja: warning: multiple rules generate $name. build will not be correct; continuing anyway
```

Internally, ninja just ignores all but the last rule. In practice, this usually happens if people accidentally repeat a rule in their meta-buildsystem. But for rules with multiple targets, ninja's current handling of this can lead to inconsistent build graphs that can lead to crashes (see e.g. #867). Also, some people mistake this diagnostic for a bug in ninja instead of a bug in their build files (e.g. #543 last comment). Arguably, ninja could try to check if the two rules are exactly the same and not complain in that case, but
1. That's something the generator could do
2. It's not always obvious – the commands could be `cd ../.. && foo/bar.sh` and `cd ../../.. && baz/foo/bar.sh`, or they could set slightly different env vars (which happen to not make a difference in practice, but ninja can't know that)

So this should be an error, not a warning. However, this is something that happens in practice every now and then (examples: http://reviews.llvm.org/D6166#109619 http://crbug.com/351052), so the rollout for this should look something like this:
1. [x] Add a flag to turn this warning into an error, but don't set it by default (`--dupeedge=[warn|error]`?), make a release with this. (Mention the flag name in the warning text.)
2. [x] File bugs with all known generators to reject duplicate rules for the same target (up to generators to allow this if the two rules effectively are the same – generators are in a better position to make that decision. If they opt to do that, they still should only write one of these rules). Wait for this to be implemented everywhere, and to make it into releases.
3. [x] Flip the flag default to error, but keep the flag around to switch back to warning, keep mentioning the flag in the error message, and in --help output. Make another release.
4. [x] Remove flag from error message and help output, but keep honoring it. Make another release.
5. [x] Eventually, remove the flag.

Not sure if we ever actually want to do 5, but the rest all seem like Obviously Good steps.



Discussion: #940 was another bug in this area.

RfC to make this err by default from a while ago (no concerns): https://groups.google.com/forum/#!topic/ninja-build/ob5vhaGvvTc



Fix (patch):
diff --git a/src/manifest_parser.cc b/src/manifest_parser.cc
index 103c365ebf..c4b2980164 100644
--- a/src/manifest_parser.cc
+++ b/src/manifest_parser.cc
@@ -336,20 +336,9 @@ bool ManifestParser::ParseEdge(string* err) {
       return lexer_.Error("empty path", err);
     uint64_t slash_bits;
     CanonicalizePath(&path, &slash_bits);
-    if (!state_->AddOut(edge, path, slash_bits)) {
-      if (options_.dupe_edge_action_ == kDupeEdgeActionError) {
-        lexer_.Error("multiple rules generate " + path, err);
-        return false;
-      } else {
-        if (!quiet_) {
-          Warning(
-              "multiple rules generate %s. builds involving this target will "
-              "not be correct; continuing anyway",
-              path.c_str());
-        }
-        if (e - i <= static_cast<size_t>(implicit_outs))
-          --implicit_outs;
-      }
+    if (!state_->AddOut(edge, path, slash_bits, err)) {
+      lexer_.Error(std::string(*err), err);
+      return false;
     }
   }
 
diff --git a/src/manifest_parser.h b/src/manifest_parser.h
index 954cf467d5..db6812dce4 100644
--- a/src/manifest_parser.h
+++ b/src/manifest_parser.h
@@ -31,11 +31,7 @@ enum PhonyCycleAction {
 };
 
 struct ManifestParserOptions {
-  ManifestParserOptions()
-      : dupe_edge_action_(kDupeEdgeActionWarn),
-        phony_cycle_action_(kPhonyCycleActionWarn) {}
-  DupeEdgeAction dupe_edge_action_;
-  PhonyCycleAction phony_cycle_action_;
+  PhonyCycleAction phony_cycle_action_ = kPhonyCycleActionWarn;
 };
 
 /// Parses .ninja files.
diff --git a/src/ninja.cc b/src/ninja.cc
index c011be1356..839e9e6fce 100644
--- a/src/ninja.cc
+++ b/src/ninja.cc
@@ -77,9 +77,6 @@ struct Options {
   /// Tool to run rather than building.
   const Tool* tool;
 
-  /// Whether duplicate rules for one target should warn or print an error.
-  bool dupe_edges_should_err;
-
   /// Whether phony cycles should warn or print an error.
   bool phony_cycle_should_err;
 };
@@ -1210,12 +1207,6 @@ bool WarningEnable(const string& name, Options* options) {
 "  phonycycle={err,warn}  phony build statement references itself\n"
     );
     return false;
-  } else if (name == "dupbuild=err") {
-    options->dupe_edges_should_err = true;
-    return true;
-  } else if (name == "dupbuild=warn") {
-    options->dupe_edges_should_err = false;
-    return true;
   } else if (name == "phonycycle=err") {
     options->phony_cycle_should_err = true;
     return true;
@@ -1227,9 +1218,8 @@ bool WarningEnable(const string& name, Options* options) {
     Warning("deprecated warning 'depfilemulti'");
     return true;
   } else {
-    const char* suggestion =
-        SpellcheckString(name.c_str(), "dupbuild=err", "dupbuild=warn",
-                         "phonycycle=err", "phonycycle=warn", NULL);
+    const char* suggestion = SpellcheckString(name.c_str(), "phonycycle=err",
+                                              "phonycycle=warn", nullptr);
     if (suggestion) {
       Error("unknown warning flag '%s', did you mean '%s'?",
             name.c_str(), suggestion);
@@ -1525,7 +1515,6 @@ NORETURN void real_main(int argc, char** argv) {
   BuildConfig config;
   Options options = {};
   options.input_file = "build.ninja";
-  options.dupe_edges_should_err = true;
 
   setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
   const char* ninja_command = argv[0];
@@ -1562,9 +1551,6 @@ NORETURN void real_main(int argc, char** argv) {
     NinjaMain ninja(ninja_command, config);
 
     ManifestParserOptions parser_opts;
-    if (options.dupe_edges_should_err) {
-      parser_opts.dupe_edge_action_ = kDupeEdgeActionError;
-    }
     if (options.phony_cycle_should_err) {
       parser_opts.phony_cycle_action_ = kPhonyCycleActionError;
     }
diff --git a/src/state.cc b/src/state.cc
index 0a68f2163a..d4b9a71a50 100644
--- a/src/state.cc
+++ b/src/state.cc
@@ -133,10 +133,17 @@ void State::AddIn(Edge* edge, StringPiece path, uint64_t slash_bits) {
   node->AddOutEdge(edge);
 }
 
-bool State::AddOut(Edge* edge, StringPiece path, uint64_t slash_bits) {
+bool State::AddOut(Edge* edge, StringPiece path, uint64_t slash_bits,
+                   std::string* err) {
   Node* node = GetNode(path, slash_bits);
-  if (node->in_edge())
+  if (Edge* other = node->in_edge()) {
+    if (other == edge) {
+      *err = path.AsString() + " is defined as an output multiple times";
+    } else {
+      *err = "multiple rules generate " + path.AsString();
+    }
     return false;
+  }
   edge->outputs_.push_back(node);
   node->set_in_edge(edge);
   node->set_generated_by_dep_loader(false);
diff --git a/src/state.h b/src/state.h
index 886b78f765..29bed56193 100644
--- a/src/state.h
+++ b/src/state.h
@@ -109,7 +109,7 @@ struct State {
   /// ensures that the generated_by_dep_loader() flag for all these nodes
   /// is set to false, to indicate that they come from the input manifest.
   void AddIn(Edge* edge, StringPiece path, uint64_t slash_bits);
-  bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits);
+  bool AddOut(Edge* edge, StringPiece path, uint64_t slash_bits, std::string* err);
   void AddValidation(Edge* edge, StringPiece path, uint64_t slash_bits);
   bool AddDefault(StringPiece path, std::string* error);