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