Windows, Bazel client: detect Bash early enough

Detect Bash's path early enough to compute the
--windows_unix_root host JVM flag's value.

Now Bazel forwards Bash's location to the Bazel
server even if BAZEL_SH is undefined, and no
longer crashes when --compiler=msys-gcc but
BAZEL_SH is undefined.

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

Closes #7289.

PiperOrigin-RevId: 231931801
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index e62b821..267fc30 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -1537,10 +1537,12 @@
   // Must be done before command line parsing.
   ComputeWorkspace(workspace_layout);
 
+#if defined(_WIN32) || defined(__CYGWIN__)
   // Must be done before command line parsing.
   // ParseOptions already populate --client_env, so detect bash before it
   // happens.
   DetectBashOrDie();
+#endif  // if defined(_WIN32) || defined(__CYGWIN__)
 
   globals->binary_path = CheckAndGetBinaryPath(argv[0]);
   ParseOptions(argc, argv);
diff --git a/src/main/cpp/blaze_util_platform.h b/src/main/cpp/blaze_util_platform.h
index 2e13acf..7ed5373 100644
--- a/src/main/cpp/blaze_util_platform.h
+++ b/src/main/cpp/blaze_util_platform.h
@@ -260,7 +260,10 @@
 // raised; false otherwise.
 bool UnlimitCoredumps();
 
+#if defined(_WIN32) || defined(__CYGWIN__)
+std::string DetectBashAndExportBazelSh();
 void DetectBashOrDie();
+#endif  // if defined(_WIN32) || defined(__CYGWIN__)
 
 // This function has no effect on Unix platforms.
 // On Windows, this function looks into PATH to find python.exe, if python
diff --git a/src/main/cpp/blaze_util_posix.cc b/src/main/cpp/blaze_util_posix.cc
index 11de625..1398efb 100644
--- a/src/main/cpp/blaze_util_posix.cc
+++ b/src/main/cpp/blaze_util_posix.cc
@@ -830,10 +830,6 @@
   return UnlimitResource(RLIMIT_CORE, true);
 }
 
-void DetectBashOrDie() {
-  // do nothing.
-}
-
 void EnsurePythonPathOption(vector<string>* options) {
   // do nothing.
 }
diff --git a/src/main/cpp/blaze_util_windows.cc b/src/main/cpp/blaze_util_windows.cc
index a8ccacb..038ad50 100644
--- a/src/main/cpp/blaze_util_windows.cc
+++ b/src/main/cpp/blaze_util_windows.cc
@@ -1458,12 +1458,15 @@
   return result;
 }
 
-void DetectBashOrDie() {
-  if (!blaze::GetEnv("BAZEL_SH").empty()) return;
+string DetectBashAndExportBazelSh() {
+  string bash = blaze::GetEnv("BAZEL_SH");
+  if (!bash.empty()) {
+    return bash;
+  }
 
   uint64_t start = blaze::GetMillisecondsMonotonic();
 
-  string bash = LocateBash();
+  bash = LocateBash();
   uint64_t end = blaze::GetMillisecondsMonotonic();
   BAZEL_LOG(INFO) << "BAZEL_SH detection took " << end - start
                   << " msec, found " << bash.c_str();
@@ -1471,7 +1474,13 @@
   if (!bash.empty()) {
     // Set process environment variable.
     blaze::SetEnv("BAZEL_SH", bash);
-  } else {
+  }
+  return bash;
+}
+
+void DetectBashOrDie() {
+  string bash = DetectBashAndExportBazelSh();
+  if (bash.empty()) {
     // TODO(bazel-team) should this be printed to stderr? If so, it should use
     // BAZEL_LOG(ERROR)
     printf(
diff --git a/src/main/cpp/startup_options.cc b/src/main/cpp/startup_options.cc
index c259bb6..9fc35fb 100644
--- a/src/main/cpp/startup_options.cc
+++ b/src/main/cpp/startup_options.cc
@@ -112,7 +112,7 @@
   }
 
 #if defined(_WIN32) || defined(__CYGWIN__)
-  string windows_unix_root = WindowsUnixRoot(blaze::GetEnv("BAZEL_SH"));
+  string windows_unix_root = DetectBashAndExportBazelSh();
   if (!windows_unix_root.empty()) {
     host_jvm_args.push_back(string("-Dbazel.windows_unix_root=") +
                             windows_unix_root);
@@ -593,32 +593,4 @@
   return blaze_exit_code::SUCCESS;
 }
 
-#if defined(_WIN32) || defined(__CYGWIN__)
-// Extract the Windows path of "/" from $BAZEL_SH.
-// $BAZEL_SH usually has the form `<prefix>/usr/bin/bash.exe` or
-// `<prefix>/bin/bash.exe`, and this method returns that `<prefix>` part.
-// If $BAZEL_SH doesn't end with "usr/bin/bash.exe" or "bin/bash.exe" then this
-// method returns an empty string.
-string StartupOptions::WindowsUnixRoot(const string &bazel_sh) {
-  if (bazel_sh.empty()) {
-    return string();
-  }
-  std::pair<string, string> split = blaze_util::SplitPath(bazel_sh);
-  if (blaze_util::AsLower(split.second) != "bash.exe") {
-    return string();
-  }
-  split = blaze_util::SplitPath(split.first);
-  if (blaze_util::AsLower(split.second) != "bin") {
-    return string();
-  }
-
-  std::pair<string, string> split2 = blaze_util::SplitPath(split.first);
-  if (blaze_util::AsLower(split2.second) == "usr") {
-    return split2.first;
-  } else {
-    return split.first;
-  }
-}
-#endif  // defined(_WIN32) || defined(__CYGWIN__)
-
 }  // namespace blaze
diff --git a/src/main/cpp/startup_options.h b/src/main/cpp/startup_options.h
index 07f641c..23105e3 100644
--- a/src/main/cpp/startup_options.h
+++ b/src/main/cpp/startup_options.h
@@ -326,10 +326,6 @@
   std::string default_server_javabase_;
   // Contains the collection of startup flags that Bazel accepts.
   std::set<std::unique_ptr<StartupFlag>> valid_startup_flags;
-
-#if defined(_WIN32) || defined(__CYGWIN__)
-  static std::string WindowsUnixRoot(const std::string &bazel_sh);
-#endif
 };
 
 }  // namespace blaze
diff --git a/src/test/cpp/option_processor_test.cc b/src/test/cpp/option_processor_test.cc
index f008d03..461b425 100644
--- a/src/test/cpp/option_processor_test.cc
+++ b/src/test/cpp/option_processor_test.cc
@@ -28,11 +28,11 @@
 
 class OptionProcessorTest : public ::testing::Test {
  protected:
-  OptionProcessorTest() :
-      workspace_(
-          blaze_util::JoinPath(blaze::GetEnv("TEST_TMPDIR"), "testdir")),
-      cwd_("cwd"),
-      workspace_layout_(new WorkspaceLayout()) {}
+  OptionProcessorTest()
+      : workspace_(
+            blaze_util::JoinPath(blaze::GetEnv("TEST_TMPDIR"), "testdir")),
+        cwd_("cwd"),
+        workspace_layout_(new WorkspaceLayout()) {}
 
   ~OptionProcessorTest() override {}
 
@@ -98,21 +98,32 @@
 };
 
 TEST_F(OptionProcessorTest, CanParseOptions) {
-  const std::vector<std::string> args =
-      {"bazel",
-       "--host_jvm_args=MyParam", "--nobatch",
-       "command",
-       "--flag", "//my:target", "--flag2=42"};
+  const std::vector<std::string> args = {"bazel",     "--host_jvm_args=MyParam",
+                                         "--nobatch", "command",
+                                         "--flag",    "//my:target",
+                                         "--flag2=42"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
             option_processor_->ParseOptions(args, workspace_, cwd_, &error))
-                << error;
+      << error;
 
   ASSERT_EQ("", error);
+#if defined(_WIN32) || defined(__CYGWIN__)
+  ASSERT_EQ(size_t(2),
+            option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
+  const std::string win_unix_root("-Dbazel.windows_unix_root=");
+  const std::string host_jvm_args_0 =
+      option_processor_->GetParsedStartupOptions()->host_jvm_args[0];
+  EXPECT_EQ(host_jvm_args_0.find(win_unix_root), 0) << host_jvm_args_0;
+  EXPECT_GT(host_jvm_args_0.size(), win_unix_root.size());
+  EXPECT_EQ("MyParam",
+            option_processor_->GetParsedStartupOptions()->host_jvm_args[1]);
+#else   // ! (defined(_WIN32) || defined(__CYGWIN__))
   ASSERT_EQ(size_t(1),
             option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
   EXPECT_EQ("MyParam",
             option_processor_->GetParsedStartupOptions()->host_jvm_args[0]);
+#endif  // defined(_WIN32) || defined(__CYGWIN__)
   EXPECT_FALSE(option_processor_->GetParsedStartupOptions()->batch);
 
   EXPECT_EQ("command", option_processor_->GetCommand());
@@ -122,21 +133,32 @@
 }
 
 TEST_F(OptionProcessorTest, CanParseHelpCommandSurroundedByOtherArgs) {
-  const std::vector<std::string> args =
-      {"bazel",
-       "--host_jvm_args=MyParam", "--nobatch",
-       "help",
-       "--flag", "//my:target", "--flag2=42"};
+  const std::vector<std::string> args = {"bazel",     "--host_jvm_args=MyParam",
+                                         "--nobatch", "help",
+                                         "--flag",    "//my:target",
+                                         "--flag2=42"};
   std::string error;
   ASSERT_EQ(blaze_exit_code::SUCCESS,
             option_processor_->ParseOptions(args, workspace_, cwd_, &error))
-                << error;
+      << error;
 
   ASSERT_EQ("", error);
+#if defined(_WIN32) || defined(__CYGWIN__)
+  ASSERT_EQ(size_t(2),
+            option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
+  const std::string win_unix_root("-Dbazel.windows_unix_root=");
+  const std::string host_jvm_args_0 =
+      option_processor_->GetParsedStartupOptions()->host_jvm_args[0];
+  EXPECT_EQ(host_jvm_args_0.find(win_unix_root), 0) << host_jvm_args_0;
+  EXPECT_GT(host_jvm_args_0.size(), win_unix_root.size());
+  EXPECT_EQ("MyParam",
+            option_processor_->GetParsedStartupOptions()->host_jvm_args[1]);
+#else   // ! (defined(_WIN32) || defined(__CYGWIN__))
   ASSERT_EQ(size_t(1),
             option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
   EXPECT_EQ("MyParam",
             option_processor_->GetParsedStartupOptions()->host_jvm_args[0]);
+#endif  // defined(_WIN32) || defined(__CYGWIN__)
   EXPECT_FALSE(option_processor_->GetParsedStartupOptions()->batch);
 
   EXPECT_EQ("help", option_processor_->GetCommand());
@@ -181,12 +203,26 @@
                 << error;
   ASSERT_EQ("", error);
 
+#if defined(_WIN32) || defined(__CYGWIN__)
+  ASSERT_EQ(size_t(3),
+            option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
+  const std::string win_unix_root("-Dbazel.windows_unix_root=");
+  const std::string host_jvm_args_0 =
+      option_processor_->GetParsedStartupOptions()->host_jvm_args[0];
+  EXPECT_EQ(host_jvm_args_0.find(win_unix_root), 0) << host_jvm_args_0;
+  EXPECT_GT(host_jvm_args_0.size(), win_unix_root.size());
+  EXPECT_EQ("MyParam",
+            option_processor_->GetParsedStartupOptions()->host_jvm_args[1]);
+  EXPECT_EQ("42",
+            option_processor_->GetParsedStartupOptions()->host_jvm_args[2]);
+#else   // ! (defined(_WIN32) || defined(__CYGWIN__))
   ASSERT_EQ(size_t(2),
             option_processor_->GetParsedStartupOptions()->host_jvm_args.size());
   EXPECT_EQ("MyParam",
             option_processor_->GetParsedStartupOptions()->host_jvm_args[0]);
   EXPECT_EQ("42",
             option_processor_->GetParsedStartupOptions()->host_jvm_args[1]);
+#endif  // defined(_WIN32) || defined(__CYGWIN__)
 
   EXPECT_EQ("", option_processor_->GetCommand());