Flags: warn for duplicate --bazelrc flag

If multiple --bazelrc flags are given, Bazel only
uses the first one.

This commit adds a warning to make that more
clear.

Rationale: https://github.com/bazelbuild/bazel/issues/7489#issuecomment-515952490

Fixes https://github.com/bazelbuild/bazel/issues/7489

PiperOrigin-RevId: 262118102
diff --git a/src/main/cpp/blaze_util.cc b/src/main/cpp/blaze_util.cc
index ce48149..ca25bba 100644
--- a/src/main/cpp/blaze_util.cc
+++ b/src/main/cpp/blaze_util.cc
@@ -75,24 +75,66 @@
 }
 
 const char* SearchUnaryOption(const vector<string>& args,
-                              const char *key) {
+                              const char *key, bool warn_if_dupe) {
   if (args.empty()) {
     return NULL;
   }
 
+  const char* value = nullptr;
+  bool found_dupe = false;  // true if 'key' was found twice
   vector<string>::size_type i = 0;
+
+  // Examine the first N-1 arguments. (N-1 because we examine the i'th and
+  // i+1'th together, in case a flag is defined "--name value" style and not
+  // "--name=value" style.)
   for (; i < args.size() - 1; ++i) {
     if (args[i] == "--") {
-      return NULL;
+      // If the current argument is "--", all following args are target names.
+      // If 'key' was not found, 'value' is nullptr and we can return that.
+      // If 'key' was found exactly once, then 'value' has the value and again
+      // we can return that.
+      // If 'key' was found more than once then we could not have reached this
+      // line, because we would have broken out of the loop when 'key' was found
+      // the second time.
+      return value;
     }
     const char* result = GetUnaryOption(args[i].c_str(),
                                         args[i + 1].c_str(),
                                         key);
     if (result != NULL) {
-      return result;
+      // 'key' was found and 'result' has its value.
+      if (value) {
+        // 'key' was found once before, because 'value' is not empty.
+        found_dupe = true;
+        break;
+      } else {
+        // 'key' was not found before, so store the value in 'value'.
+        value = result;
+      }
     }
   }
-  return GetUnaryOption(args[i].c_str(), NULL, key);
+
+  if (value) {
+    // 'value' is not empty, so 'key' was found at least once in the first N-1
+    // arguments.
+    if (warn_if_dupe) {
+      if (!found_dupe) {
+        // We did not find a duplicate in the first N-1 arguments. Examine the
+        // last argument, it may be a duplicate.
+        found_dupe = (GetUnaryOption(args[i].c_str(), NULL, key) != nullptr);
+      }
+      if (found_dupe) {
+        BAZEL_LOG(WARNING) << key << " is given more than once, "
+                           << "only the first occurrence is used";
+      }
+    }
+    return value;
+  } else {
+    // 'value' is empty, so 'key' was not yet found in the first N-1 arguments.
+    // If 'key' is in the last argument, we'll parse and return the value from
+    // that, and if it isn't, we'll return NULL.
+    return GetUnaryOption(args[i].c_str(), NULL, key);
+  }
 }
 
 bool SearchNullaryOption(const vector<string>& args,
diff --git a/src/main/cpp/blaze_util.h b/src/main/cpp/blaze_util.h
index b0b5538..123ead9 100644
--- a/src/main/cpp/blaze_util.h
+++ b/src/main/cpp/blaze_util.h
@@ -44,10 +44,12 @@
 
 // Searches for 'key' in 'args' using GetUnaryOption. Arguments found after '--'
 // are omitted from the search.
+// When 'warn_if_dupe' is true, the method checks if 'key' is specified more
+// than once and prints a warning if so.
 // Returns the value of the 'key' flag iff it occurs in args.
 // Returns NULL otherwise.
 const char* SearchUnaryOption(const std::vector<std::string>& args,
-                              const char* key);
+                              const char* key, bool warn_if_dupe);
 
 // Searches for '--flag_name' and '--noflag_name' in 'args' using
 // GetNullaryOption. Arguments found after '--' are omitted from the search.
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index 41c6db8..8f8f156 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -195,7 +195,8 @@
     candidate_bazelrc_paths = {workspace_rc, binary_rc, system_bazelrc_path};
   }
   string user_bazelrc_path = internal::FindLegacyUserBazelrc(
-      SearchUnaryOption(startup_args, "--bazelrc"), workspace);
+      SearchUnaryOption(startup_args, "--bazelrc", /* warn_if_dupe */ true),
+      workspace);
   if (!user_bazelrc_path.empty()) {
     candidate_bazelrc_paths.push_back(user_bazelrc_path);
   }
@@ -363,7 +364,8 @@
   // Get the command-line provided rc, passed as --bazelrc or nothing if the
   // flag is absent.
   const char* cmd_line_rc_file =
-      SearchUnaryOption(cmd_line->startup_args, "--bazelrc");
+      SearchUnaryOption(cmd_line->startup_args, "--bazelrc",
+                        /* warn_if_dupe */ true);
   if (cmd_line_rc_file != nullptr) {
     string absolute_cmd_line_rc = blaze::AbsolutePathFromFlag(cmd_line_rc_file);
     // Unlike the previous 3 paths, where we ignore it if the file does not
diff --git a/src/test/cpp/blaze_util_test.cc b/src/test/cpp/blaze_util_test.cc
index 03b43c0..dda7bb51 100644
--- a/src/test/cpp/blaze_util_test.cc
+++ b/src/test/cpp/blaze_util_test.cc
@@ -23,11 +23,13 @@
 #include "src/main/cpp/blaze_util.h"
 #include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/util/file.h"
+#include "googlemock/include/gmock/gmock.h"
 #include "googletest/include/gtest/gtest.h"
 
 namespace blaze {
 
 using std::string;
+using ::testing::HasSubstr;
 
 class BlazeUtilTest : public ::testing::Test {
  protected:
@@ -113,7 +115,7 @@
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryEmptyCase) {
-  ASSERT_STREQ(nullptr, SearchUnaryOption({}, "--flag"));
+  ASSERT_STREQ(nullptr, SearchUnaryOption({}, "--flag", false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchNullaryForEmpty) {
@@ -165,50 +167,71 @@
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryForEmpty) {
-  ASSERT_STREQ(nullptr, SearchUnaryOption({"bazel", "build", ":target"}, ""));
+  ASSERT_STREQ(nullptr, SearchUnaryOption({"bazel", "build", ":target"}, "",
+                                          false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryFlagNotPresent) {
   ASSERT_STREQ(nullptr,
-               SearchUnaryOption({"bazel", "build", ":target"}, "--flag"));
+               SearchUnaryOption({"bazel", "build", ":target"}, "--flag",
+                                 false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryStartupOptionWithEquals) {
   ASSERT_STREQ("value",
                SearchUnaryOption({"bazel", "--flag=value", "build", ":target"},
-                                 "--flag"));
+                                 "--flag", false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryStartupOptionWithoutEquals) {
   ASSERT_STREQ("value",
                SearchUnaryOption(
-                   {"bazel", "--flag", "value", "build", ":target"}, "--flag"));
+                   {"bazel", "--flag", "value", "build", ":target"}, "--flag",
+                   false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryCommandOptionWithEquals) {
   ASSERT_STREQ("value",
                SearchUnaryOption(
-                   {"bazel", "build", ":target", "--flag", "value"}, "--flag"));
+                   {"bazel", "build", ":target", "--flag", "value"}, "--flag",
+                   false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnaryCommandOptionWithoutEquals) {
   ASSERT_STREQ("value",
                SearchUnaryOption(
-                   {"bazel", "build", ":target", "--flag=value"}, "--flag"));
+                   {"bazel", "build", ":target", "--flag=value"}, "--flag",
+                   false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnarySkipsAfterDashDashWithEquals) {
   ASSERT_STREQ(nullptr,
                SearchUnaryOption(
                    {"bazel", "build", ":target", "--", "--flag", "value"},
-                   "--flag"));
+                   "--flag", false));
 }
 
 TEST_F(BlazeUtilTest, TestSearchUnarySkipsAfterDashDashWithoutEquals) {
   ASSERT_STREQ(nullptr,
                SearchUnaryOption(
                    {"bazel", "build", ":target", "--", "--flag=value"},
-                   "--flag"));
+                   "--flag", false));
+}
+
+TEST_F(BlazeUtilTest, TestSearchUnaryCommandOptionWarnsAboutDuplicates) {
+  testing::internal::CaptureStderr();
+  for (int i = 0; i < 2; ++i) {
+    bool warn_if_dupe = (i == 0);
+    ASSERT_STREQ("v1",
+                 SearchUnaryOption(
+                     {"foo", "--flag", "v1", "bar", "--flag=v2"}, "--flag",
+                     warn_if_dupe));
+
+    if (warn_if_dupe) {
+      std::string stderr_output = testing::internal::GetCapturedStderr();
+      EXPECT_THAT(stderr_output, HasSubstr("--flag is given more than once"));
+    }
+  }
 }
 
 }  // namespace blaze