Windows native launcher: support launching with 8dot3 Windows path

Windows native launcher finds the shell/python script to launch based on the first command line argument (the path name of itself). But it could be a shortened 8dot3 windows path. Eg. when `bazel test` runs a test, if the test binary path is too long (> 260), Bazel will convert it to a short path. The native launcher should convert it back to its full path before using it to find the script to launch.

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

Closes #11047.

PiperOrigin-RevId: 303994833
diff --git a/src/test/py/bazel/launcher_test.py b/src/test/py/bazel/launcher_test.py
index 41f4332..8541787 100644
--- a/src/test/py/bazel/launcher_test.py
+++ b/src/test/py/bazel/launcher_test.py
@@ -19,6 +19,10 @@
 import unittest
 from src.test.py.bazel import test_base
 
+# pylint: disable=g-import-not-at-top
+if os.name == 'nt':
+  import win32api
+
 
 class LauncherTest(test_base.TestBase):
 
@@ -630,18 +634,33 @@
     exit_code, stdout, stderr = self.RunProgram([long_binary_path], shell=True)
     self.AssertExitCode(exit_code, 0, stderr)
     self.assertEqual('helloworld', ''.join(stdout))
+    # Make sure we can launch the binary with a shortened Windows 8dot3 path
+    short_binary_path = win32api.GetShortPathName(long_binary_path)
+    exit_code, stdout, stderr = self.RunProgram([short_binary_path], shell=True)
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertEqual('helloworld', ''.join(stdout))
 
     long_binary_path = os.path.abspath(long_dir_path + '/bin_sh.exe')
     # subprocess doesn't support long path without shell=True
     exit_code, stdout, stderr = self.RunProgram([long_binary_path], shell=True)
     self.AssertExitCode(exit_code, 0, stderr)
     self.assertEqual('helloworld', ''.join(stdout))
+    # Make sure we can launch the binary with a shortened Windows 8dot3 path
+    short_binary_path = win32api.GetShortPathName(long_binary_path)
+    exit_code, stdout, stderr = self.RunProgram([short_binary_path], shell=True)
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertEqual('helloworld', ''.join(stdout))
 
     long_binary_path = os.path.abspath(long_dir_path + '/bin_py.exe')
     # subprocess doesn't support long path without shell=True
     exit_code, stdout, stderr = self.RunProgram([long_binary_path], shell=True)
     self.AssertExitCode(exit_code, 0, stderr)
     self.assertEqual('helloworld', ''.join(stdout))
+    # Make sure we can launch the binary with a shortened Windows 8dot3 path
+    short_binary_path = win32api.GetShortPathName(long_binary_path)
+    exit_code, stdout, stderr = self.RunProgram([short_binary_path], shell=True)
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertEqual('helloworld', ''.join(stdout))
 
   def AssertRunfilesManifestContains(self, manifest, entry):
     with open(manifest, 'r') as f:
diff --git a/src/tools/launcher/bash_launcher.cc b/src/tools/launcher/bash_launcher.cc
index 67f0a5d..56b7727 100644
--- a/src/tools/launcher/bash_launcher.cc
+++ b/src/tools/launcher/bash_launcher.cc
@@ -52,7 +52,11 @@
 
   vector<wstring> origin_args = this->GetCommandlineArguments();
   wostringstream bash_command;
-  wstring bash_main_file = GetBinaryPathWithoutExtension(origin_args[0]);
+  // In case the given binary path is a shortened Windows 8dot3 path, we need to
+  // convert it back to its long path form before using it to find the bash main
+  // file.
+  wstring full_binary_path = GetWindowsLongPath(origin_args[0]);
+  wstring bash_main_file = GetBinaryPathWithoutExtension(full_binary_path);
   bash_command << BashEscapeArg(bash_main_file);
   for (int i = 1; i < origin_args.size(); i++) {
     bash_command << L' ';
diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc
index d96f208..cd856d9 100644
--- a/src/tools/launcher/python_launcher.cc
+++ b/src/tools/launcher/python_launcher.cc
@@ -56,10 +56,14 @@
   vector<wstring> args = this->GetCommandlineArguments();
   wstring use_zip_file = this->GetLaunchInfoByKey(USE_ZIP_FILE);
   wstring python_file;
+  // In case the given binary path is a shortened Windows 8dot3 path, we need to
+  // convert it back to its long path form before using it to find the python
+  // file.
+  wstring full_binary_path = GetWindowsLongPath(args[0]);
   if (use_zip_file == L"1") {
-    python_file = GetBinaryPathWithoutExtension(args[0]) + L".zip";
+    python_file = GetBinaryPathWithoutExtension(full_binary_path) + L".zip";
   } else {
-    python_file = GetBinaryPathWithoutExtension(args[0]);
+    python_file = GetBinaryPathWithoutExtension(full_binary_path);
   }
 
   // Replace the first argument with python file path
diff --git a/src/tools/launcher/util/launcher_util.cc b/src/tools/launcher/util/launcher_util.cc
index 31d7091..8a709d3 100644
--- a/src/tools/launcher/util/launcher_util.cc
+++ b/src/tools/launcher/util/launcher_util.cc
@@ -25,11 +25,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+
 #include <algorithm>
 #include <sstream>
 #include <string>
 
 #include "src/main/cpp/util/path_platform.h"
+#include "src/main/native/windows/file.h"
 #include "src/tools/launcher/util/launcher_util.h"
 
 namespace bazel {
@@ -125,6 +127,17 @@
   return s.size() >= 4 && _wcsnicmp(s.c_str() + s.size() - 4, L".exe", 4) == 0;
 }
 
+wstring GetWindowsLongPath(const wstring& path) {
+  std::unique_ptr<WCHAR[]> result;
+  wstring abs_path = AsAbsoluteWindowsPath(path.c_str());
+  wstring error = bazel::windows::GetLongPath(abs_path.c_str(), &result);
+  if (!error.empty()) {
+    PrintError(L"Failed to get long path for %s: %s", path.c_str(),
+               error.c_str());
+  }
+  return wstring(blaze_util::RemoveUncPrefixMaybe(result.get()));
+}
+
 wstring GetBinaryPathWithoutExtension(const wstring& binary) {
   return EndsWithExe(binary) ? binary.substr(0, binary.size() - 4) : binary;
 }
diff --git a/src/tools/launcher/util/launcher_util.h b/src/tools/launcher/util/launcher_util.h
index 7353612..b62267c 100644
--- a/src/tools/launcher/util/launcher_util.h
+++ b/src/tools/launcher/util/launcher_util.h
@@ -31,6 +31,12 @@
 // Prints the specified error message.
 void PrintError(const wchar_t* format, ...) PRINTF_ATTRIBUTE(1, 2);
 
+// Converts the specified path (Windows 8dot3 style short path) to its long form
+//
+// eg. C:\FO~1\BAR\B~1 -> C:\Foooo\Bar\bin.exe
+// Note that: the given path must be an existing path.
+std::wstring GetWindowsLongPath(const std::wstring& path);
+
 // Strip the .exe extension from binary path.
 //
 // On Windows, if the binary path is foo/bar/bin.exe then return foo/bar/bin