Python launcher: don’t assume that argv[0] is the program name.

This should fix https://github.com/bazelbuild/bazel/issues/14343.

PiperOrigin-RevId: 419401678
diff --git a/src/test/py/bazel/launcher_test.py b/src/test/py/bazel/launcher_test.py
index 2c34722..c65ba74 100644
--- a/src/test/py/bazel/launcher_test.py
+++ b/src/test/py/bazel/launcher_test.py
@@ -398,6 +398,33 @@
 
     self._buildAndCheckArgumentPassing('foo', 'bin')
 
+  def testPyBinaryLauncherWithDifferentArgv0(self):
+    """Test for https://github.com/bazelbuild/bazel/issues/14343."""
+    self.CreateWorkspaceWithDefaultRepos('WORKSPACE')
+    self.ScratchFile('foo/BUILD', [
+        'py_binary(',
+        '  name = "bin",',
+        '  srcs = ["bin.py"],',
+        ')',
+    ])
+    self.ScratchFile('foo/bin.py', ['print("Hello world")'])
+
+    exit_code, stdout, stderr = self.RunBazel(['info', 'bazel-bin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+    bazel_bin = stdout[0]
+
+    # Verify that the build of our py_binary succeeds.
+    exit_code, _, stderr = self.RunBazel(['build', '//foo:bin'])
+    self.AssertExitCode(exit_code, 0, stderr)
+
+    # Try to run the built py_binary.
+    binary_suffix = '.exe' if self.IsWindows() else ''
+    foo_bin = os.path.join(bazel_bin, 'foo', 'bin%s' % binary_suffix)
+    args = [r'C:\Invalid.exe' if self.IsWindows() else '/invalid']
+    exit_code, stdout, stderr = self.RunProgram(args, executable=foo_bin)
+    self.AssertExitCode(exit_code, 0, stderr)
+    self.assertEqual(stdout[0], 'Hello world')
+
   def testWindowsJavaExeLauncher(self):
     # Skip this test on non-Windows platforms
     if not self.IsWindows():
diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py
index f980f8f..653a737 100644
--- a/src/test/py/bazel/test_base.py
+++ b/src/test/py/bazel/test_base.py
@@ -450,7 +450,8 @@
                  env_add=None,
                  shell=False,
                  cwd=None,
-                 allow_failure=True):
+                 allow_failure=True,
+                 executable=None):
     """Runs a program (args[0]), waits for it to exit.
 
     Args:
@@ -464,6 +465,8 @@
       cwd: string; the current working dirctory, will be self._test_cwd if not
         specified.
       allow_failure: bool; if false, the function checks the return code is 0
+      executable: string or None; executable program to run; use args[0]
+        if None
     Returns:
       (int, [string], [string]) tuple: exit code, stdout lines, stderr lines
     """
@@ -471,6 +474,7 @@
       with tempfile.TemporaryFile(dir=self._test_cwd) as stderr:
         proc = subprocess.Popen(
             args,
+            executable=executable,
             stdout=stdout,
             stderr=stderr,
             cwd=(str(cwd) if cwd else self._test_cwd),
diff --git a/src/tools/launcher/launcher_main.cc b/src/tools/launcher/launcher_main.cc
index 5e0e142..e2fc473 100644
--- a/src/tools/launcher/launcher_main.cc
+++ b/src/tools/launcher/launcher_main.cc
@@ -12,7 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#ifndef STRICT
+#define STRICT
+#endif
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#ifndef UNICODE
+#define UNICODE
+#endif
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
 #include <memory>
+#include <string>
 
 #include "src/tools/launcher/bash_launcher.h"
 #include "src/tools/launcher/java_launcher.h"
@@ -33,11 +52,21 @@
 using std::make_unique;
 using std::unique_ptr;
 
-int wmain(int argc, wchar_t* argv[]) {
-  LaunchDataParser::LaunchInfo launch_info;
+static std::wstring GetExecutableFileName() {
+  // https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
+  constexpr std::wstring::size_type maximum_file_name_length = 0x8000;
+  std::wstring buffer(maximum_file_name_length, L'\0');
+  DWORD length = GetModuleFileNameW(nullptr, &buffer.front(), buffer.size());
+  if (length == 0 || length >= buffer.size()) {
+    die(L"Failed to obtain executable filename");
+  }
+  return buffer.substr(0, length);
+}
 
-  if (!LaunchDataParser::GetLaunchInfo(GetBinaryPathWithExtension(argv[0]),
-                                       &launch_info)) {
+int wmain(int argc, wchar_t* argv[]) {
+  const std::wstring executable_file = GetExecutableFileName();
+  LaunchDataParser::LaunchInfo launch_info;
+  if (!LaunchDataParser::GetLaunchInfo(executable_file, &launch_info)) {
     die(L"Failed to parse launch info.");
   }
 
@@ -49,8 +78,8 @@
   unique_ptr<BinaryLauncherBase> binary_launcher;
 
   if (result->second == L"Python") {
-    binary_launcher =
-        make_unique<PythonBinaryLauncher>(launch_info, argc, argv);
+    binary_launcher = make_unique<PythonBinaryLauncher>(
+        launch_info, executable_file, argc, argv);
   } else if (result->second == L"Bash") {
     binary_launcher = make_unique<BashBinaryLauncher>(launch_info, argc, argv);
   } else if (result->second == L"Java") {
diff --git a/src/tools/launcher/python_launcher.cc b/src/tools/launcher/python_launcher.cc
index cd856d9..ebdd219 100644
--- a/src/tools/launcher/python_launcher.cc
+++ b/src/tools/launcher/python_launcher.cc
@@ -59,7 +59,7 @@
   // 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]);
+  wstring full_binary_path = GetWindowsLongPath(executable_file_);
   if (use_zip_file == L"1") {
     python_file = GetBinaryPathWithoutExtension(full_binary_path) + L".zip";
   } else {
diff --git a/src/tools/launcher/python_launcher.h b/src/tools/launcher/python_launcher.h
index e0d5c54..eb1ca6d 100644
--- a/src/tools/launcher/python_launcher.h
+++ b/src/tools/launcher/python_launcher.h
@@ -15,6 +15,9 @@
 #ifndef BAZEL_SRC_TOOLS_LAUNCHER_PYTHON_LAUNCHER_H_
 #define BAZEL_SRC_TOOLS_LAUNCHER_PYTHON_LAUNCHER_H_
 
+#include <string>
+#include <utility>
+
 #include "src/tools/launcher/launcher.h"
 
 namespace bazel {
@@ -23,10 +26,14 @@
 class PythonBinaryLauncher : public BinaryLauncherBase {
  public:
   PythonBinaryLauncher(const LaunchDataParser::LaunchInfo& launch_info,
-                       int argc, wchar_t* argv[])
-      : BinaryLauncherBase(launch_info, argc, argv) {}
+                       std::wstring executable_file, int argc, wchar_t* argv[])
+      : BinaryLauncherBase(launch_info, argc, argv),
+        executable_file_(std::move(executable_file)) {}
   ~PythonBinaryLauncher() override = default;
   ExitCode Launch() override;
+
+ private:
+  std::wstring executable_file_;
 };
 
 }  // namespace launcher