Make the system bazelrc configurable.

The master bazelrc is now defined by preprocessor macro at (Bazel's) compile time. The default is still /etc/bazel.bazelrc for most platforms, but windows now has a %ProgramData% relative default value as well. Users wishing to change this default when building Bazel for a new platform should edit BAZEL_SYSTEM_BAZELRC_PATH in src/main/cpp/BUILD.

Part of https://github.com/bazelbuild/bazel/issues/4502, relevant to the duplicate issue #4809.

TESTED: default settings were tested manually, since they cannot be tested in a sandbox

RELNOTES: Windows default system bazelrc is read from the user's ProgramData if present.
PiperOrigin-RevId: 201423446
diff --git a/src/main/cpp/BUILD b/src/main/cpp/BUILD
index 73bbf5d..f6b3110 100644
--- a/src/main/cpp/BUILD
+++ b/src/main/cpp/BUILD
@@ -122,6 +122,20 @@
         "option_processor.h",
         "option_processor-internal.h",
     ],
+    # The system bazelrc can be voided by setting BAZEL_SYSTEM_BAZELRC_PATH to
+    # /dev/null.
+    copts = select({
+        # We need to escape for multiple levels, here, this becomes
+        # /DBAZEL_SYSTEM_BAZELRC_PATH="%%ProgramData%%/bazel.bazelrc"',
+        # and the double % get reduced down to 1 by the compiler. A forward
+        # slash is used because \b is a special character, backspace.
+        "//src/conditions:windows": [
+            "/DBAZEL_SYSTEM_BAZELRC_PATH#\\\"%%ProgramData%%/bazel.bazelrc\\\"",
+        ],
+        "//conditions:default": [
+            "-DBAZEL_SYSTEM_BAZELRC_PATH=\\\"/etc/bazel.bazelrc\\\"",
+        ],
+    }),
     visibility = [
         "//src:__pkg__",
         "//src/test/cpp:__pkg__",
diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h
index 078e271..29596df 100644
--- a/src/main/cpp/blaze_util_platform.h
+++ b/src/main/cpp/blaze_util_platform.h
@@ -61,9 +61,6 @@
 // On Linux/macOS, this is $HOME. On Windows this is %USERPROFILE%.
 std::string GetHomeDir();
 
-// Returns the location of the global bazelrc file if it exists, otherwise "".
-std::string FindSystemWideBlazerc();
-
 // Warn about dubious filesystem types, such as NFS, case-insensitive (?).
 void WarnFilesystemType(const std::string& output_base);
 
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index 156b021..2398e17 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -142,14 +142,6 @@
 
 string GetHomeDir() { return GetEnv("HOME"); }
 
-string FindSystemWideBlazerc() {
-  string path = "/etc/bazel.bazelrc";
-  if (blaze_util::CanReadFile(path)) {
-    return path;
-  }
-  return "";
-}
-
 string GetJavaBinaryUnderJavabase() { return "bin/java"; }
 
 // NB: execve() requires pointers to non-const char arrays but .c_str() returns
diff --git a/src/main/cpp/option_processor-internal.h b/src/main/cpp/option_processor-internal.h
index 8ebfb64..47f0df4 100644
--- a/src/main/cpp/option_processor-internal.h
+++ b/src/main/cpp/option_processor-internal.h
@@ -30,6 +30,8 @@
 std::vector<std::string> DedupeBlazercPaths(
     const std::vector<std::string>& paths);
 
+std::string FindSystemWideRc();
+
 std::string FindRcAlongsideBinary(const std::string& cwd,
                                   const std::string& path_to_binary);
 
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index 582833a..11da52a 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -47,6 +47,10 @@
 constexpr char WorkspaceLayout::WorkspacePrefix[];
 static std::vector<std::string> GetProcessedEnv();
 
+// Path to the system-wide bazelrc configuration file.
+// This is a mutable global for testing purposes only.
+const char* system_bazelrc_path = BAZEL_SYSTEM_BAZELRC_PATH;
+
 OptionProcessor::OptionProcessor(
     const WorkspaceLayout* workspace_layout,
     std::unique_ptr<StartupOptions> default_startup_options)
@@ -187,6 +191,20 @@
   return result;
 }
 
+string FindSystemWideRc() {
+  // MakeAbsoluteAndResolveWindowsEnvvars will standardize the form of the
+  // provided path. This also means we accept relative paths, which is
+  // is convenient for testing.
+  const string path = blaze_util::MakeAbsoluteAndResolveWindowsEnvvars(
+      system_bazelrc_path);
+  if (blaze_util::CanReadFile(path)) {
+    return path;
+  }
+  BAZEL_LOG(INFO) << "Looked for a system bazelrc at path '" << path
+                  << "', but none was found.";
+  return "";
+}
+
 string FindRcAlongsideBinary(const string& cwd, const string& path_to_binary) {
   const string path = blaze_util::IsAbsolute(path_to_binary)
                           ? path_to_binary
@@ -231,9 +249,13 @@
   if (SearchNullaryOption(cmd_line->startup_args, "master_bazelrc", true)) {
     const string workspace_rc =
         workspace_layout->GetWorkspaceRcPath(workspace, cmd_line->startup_args);
+    // TODO(b/36168162): Remove the alongside-binary rc file. (Part of GitHub
+    // issue #4502)
     const string binary_rc =
         internal::FindRcAlongsideBinary(cwd, cmd_line->path_to_binary);
-    const string system_rc = FindSystemWideBlazerc();
+    // TODO(b/36168162): This is not the desired order, see
+    // https://github.com/bazelbuild/bazel/issues/4502#issuecomment-372697374.
+    const string system_rc = internal::FindSystemWideRc();
     BAZEL_LOG(INFO)
         << "Looking for master bazelrcs in the following three paths: "
         << workspace_rc << ", " << binary_rc << ", " << system_rc;
diff --git a/src/main/cpp/workspace_layout.cc b/src/main/cpp/workspace_layout.cc
index b1d3ff2..d781993 100644
--- a/src/main/cpp/workspace_layout.cc
+++ b/src/main/cpp/workspace_layout.cc
@@ -61,6 +61,9 @@
 std::string WorkspaceLayout::GetWorkspaceRcPath(
     const std::string &workspace,
     const std::vector<std::string> &startup_args) const {
+  // TODO(b/36168162): Rename and remove the tools/ prefix. See
+  // https://github.com/bazelbuild/bazel/issues/4502#issuecomment-372697374
+  // for the final set of bazelrcs we want to have.
   return blaze_util::JoinPath(workspace, "tools/bazel.rc");
 }
 
diff --git a/src/test/cpp/rc_file_test.cc b/src/test/cpp/rc_file_test.cc
index f10287d..e46d039 100644
--- a/src/test/cpp/rc_file_test.cc
+++ b/src/test/cpp/rc_file_test.cc
@@ -22,6 +22,7 @@
 #include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/file_platform.h"
 #include "src/main/cpp/util/path.h"
+#include "src/main/cpp/util/path_platform.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "googletest/include/gtest/gtest.h"
@@ -36,6 +37,8 @@
 constexpr const char* kNullDevice = "/dev/null";
 #endif
 
+extern const char* system_bazelrc_path;
+
 class RcFileTest : public ::testing::Test {
  protected:
   RcFileTest()
@@ -45,11 +48,34 @@
         binary_dir_(
             blaze_util::JoinPath(blaze::GetEnv("TEST_TMPDIR"), "bazeldir")),
         binary_path_(blaze_util::JoinPath(binary_dir_, "bazel")),
-        workspace_layout_(new WorkspaceLayout()) {}
+        workspace_layout_(new WorkspaceLayout()),
+        old_system_bazelrc_path_(system_bazelrc_path) {}
 
   void SetUp() override {
+    // We modify the global system_bazelrc_path to be a relative path.
+    // This test allows us to verify that the global bazelrc is read correctly,
+    // in the right order relative to the other files.
+    //
+    // However, this does not test the default path of this file, nor does it
+    // test that absolute paths are accepted properly. This is an unfortunate
+    // limitation of our testing - within the sandboxed environment of a test,
+    // we cannot place a file in arbitrary locations.
+    system_bazelrc_path = "bazel.bazelrc";
+
     ASSERT_TRUE(blaze_util::MakeDirectories(workspace_, 0755));
     ASSERT_TRUE(blaze_util::MakeDirectories(cwd_, 0755));
+    ASSERT_TRUE(blaze_util::ChangeDirectory(cwd_));
+#if defined(_WIN32) || defined(__CYGWIN__)
+    // GetCwd returns a short path on Windows, so we store this expectation now
+    // to keep assertions sane in the tests.
+    std::string short_cwd;
+    std::string error;
+    ASSERT_TRUE(blaze_util::AsShortWindowsPath(cwd_, &short_cwd, &error))
+        << error;
+    cwd_ = short_cwd;
+
+#endif
+
     ASSERT_TRUE(blaze_util::MakeDirectories(binary_dir_, 0755));
     option_processor_.reset(new OptionProcessor(
         workspace_layout_.get(),
@@ -75,15 +101,21 @@
     for (const std::string& file : files) {
       blaze_util::UnlinkPath(file);
     }
+    system_bazelrc_path = old_system_bazelrc_path_.c_str();
   }
 
-  // We only test 2 of the 3 master bazelrc locations in this test. The third
-  // masterrc that should be loaded is the system wide bazelrc path,
-  // /etc/bazel.bazelrc, which we do not mock within this test because it is not
-  // within the sandbox. It may or may not exist on the system running the test,
-  // so we do not check for it.
-  // TODO(#4502): Make the system-wide master bazelrc location configurable and
-  // add test coverage for it.
+  bool SetUpGlobalRcFile(const std::string& contents,
+                         std::string* rcfile_path) const {
+    const std::string global_rc_path =
+        blaze_util::ConvertPath(blaze_util::JoinPath(cwd_, "bazel.bazelrc"));
+
+    if (blaze_util::WriteFile(contents, global_rc_path, 0755)) {
+      *rcfile_path = global_rc_path;
+      return true;
+    }
+    return false;
+  }
+
   bool SetUpMasterRcFileInWorkspace(const std::string& contents,
                                     std::string* rcfile_path) const {
     const std::string tools_dir = blaze_util::JoinPath(workspace_, "tools");
@@ -121,10 +153,11 @@
   }
 
   const std::string workspace_;
-  const std::string cwd_;
+  std::string cwd_;
   const std::string binary_dir_;
   const std::string binary_path_;
   const std::unique_ptr<WorkspaceLayout> workspace_layout_;
+  const std::string old_system_bazelrc_path_;
   std::unique_ptr<OptionProcessor> option_processor_;
 };
 
@@ -135,6 +168,8 @@
   ASSERT_TRUE(SetUpMasterRcFileInWorkspace("", &workspace_rc));
   std::string binary_rc;
   ASSERT_TRUE(SetUpMasterRcFileAlongsideBinary("", &binary_rc));
+  std::string global_rc;
+  ASSERT_TRUE(SetUpGlobalRcFile("", &global_rc));
 
   const CommandLine cmd_line =
       CommandLine(binary_path_, {"--bazelrc=/dev/null"}, "build", {});
@@ -146,19 +181,17 @@
   EXPECT_EQ(blaze_exit_code::SUCCESS, exit_code);
   EXPECT_EQ("check that this string is not modified", error);
 
-  // There should be 3-4 rc files, "/dev/null" does in some sense count as a
-  // file, and there's an optional /etc/ file the test environment cannot
-  // control. The first 2 rcs parsed should be the two rc files we expect, and
-  // the last file is the user-provided /dev/null.
-  ASSERT_LE(3, parsed_rcs.size());
-  ASSERT_GE(4, parsed_rcs.size());
+  // There should be 4 rc files, since "/dev/null" does count along with the 3
+  // master rcs.
+  ASSERT_EQ(4, parsed_rcs.size());
   const std::deque<std::string> expected_workspace_rc_que = {workspace_rc};
   const std::deque<std::string> expected_binary_rc_que = {binary_rc};
+  const std::deque<std::string> expected_global_rc_que = {global_rc};
   const std::deque<std::string> expected_user_rc_que = {kNullDevice};
   EXPECT_EQ(expected_workspace_rc_que, parsed_rcs[0].get()->sources());
   EXPECT_EQ(expected_binary_rc_que, parsed_rcs[1].get()->sources());
-  EXPECT_EQ(expected_user_rc_que,
-            parsed_rcs[parsed_rcs.size() - 1].get()->sources());
+  EXPECT_EQ(expected_global_rc_que, parsed_rcs[2].get()->sources());
+  EXPECT_EQ(expected_user_rc_que, parsed_rcs[3].get()->sources());
 }
 
 TEST_F(GetRcFileTest, GetRcFilesRespectsNoMasterBazelrc) {
@@ -166,6 +199,8 @@
   ASSERT_TRUE(SetUpMasterRcFileInWorkspace("", &workspace_rc));
   std::string binary_rc;
   ASSERT_TRUE(SetUpMasterRcFileAlongsideBinary("", &binary_rc));
+  std::string global_rc;
+  ASSERT_TRUE(SetUpGlobalRcFile("", &global_rc));
 
   const CommandLine cmd_line = CommandLine(
       binary_path_, {"--nomaster_bazelrc", "--bazelrc=/dev/null"}, "build", {});
@@ -242,6 +277,8 @@
   std::string binary_rc;
   ASSERT_TRUE(SetUpMasterRcFileAlongsideBinary("startup --binarymasterfoo",
                                                &binary_rc));
+  std::string global_rc;
+  ASSERT_TRUE(SetUpGlobalRcFile("startup --globalmasterfoo", &global_rc));
 
   const std::vector<std::string> args = {binary_path_, "--ignore_all_rc_files",
                                          "build"};
@@ -295,6 +332,8 @@
   std::string binary_rc;
   ASSERT_TRUE(SetUpMasterRcFileAlongsideBinary("startup --binarymasterfoo",
                                                &binary_rc));
+  std::string global_rc;
+  ASSERT_TRUE(SetUpGlobalRcFile("startup --globalmasterfoo", &global_rc));
   const std::string cmdline_rc_path =
       blaze_util::JoinPath(workspace_, "mybazelrc");
   ASSERT_TRUE(
@@ -382,6 +421,8 @@
        IncorrectMasterBazelrcIgnoredWhenNoMasterBazelrcIsPresent) {
   std::string workspace_rc;
   ASSERT_TRUE(SetUpMasterRcFileInWorkspace("startup --foo", &workspace_rc));
+  std::string global_rc;
+  ASSERT_TRUE(SetUpGlobalRcFile("startup --globalfoo", &global_rc));
 
   const std::vector<std::string> args = {binary_path_, "--nomaster_bazelrc",
                                          "build"};