From 25ff96d2e7331f4ce517f2c6b1ac911b242ab197 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Mon, 18 May 2026 10:48:35 -0500 Subject: [PATCH 1/3] fix(windows): place bootstrap stub beside the .exe launcher --- CHANGELOG.md | 4 ++++ python/private/py_executable.bzl | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3feaebf428..1d0037c9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,10 @@ END_UNRELEASED_TEMPLATE * (pypi) Fix `importlib.metadata.files` by ensuring `RECORD` is included in installed wheel targets, except when built from sdist ([#3024](https://github.com/bazel-contrib/rules_python/issues/3024)). +* (windows) Fix `py_test`/`py_binary` failure when the target name contains + path separators; the bootstrap stub is now declared as a sibling of the + `.exe` launcher + ([#3789](https://github.com/bazel-contrib/rules_python/issues/3789)). {#v0-0-0-added} diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 9c21e5d274..965ed536cc 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -424,7 +424,7 @@ WARNING: Target: {} # On Windows, the main executable has an "exe" extension, so # here we re-use the un-extensioned name for the bootstrap output. - bootstrap_output = ctx.actions.declare_file(base_executable_name) + bootstrap_output = ctx.actions.declare_file(base_executable_name, sibling = executable) # The launcher looks for the non-zip executable next to # itself, so add it to the default outputs. From 33284f1aaa28b0493b3ddba96bdfe56c662bf779 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Mon, 18 May 2026 11:07:55 -0500 Subject: [PATCH 2/3] add test --- tests/base_rules/py_executable_base_tests.bzl | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl index d6b5aedf0c..d5ac6df43d 100644 --- a/tests/base_rules/py_executable_base_tests.bzl +++ b/tests/base_rules/py_executable_base_tests.bzl @@ -65,6 +65,35 @@ def _test_basic_windows_impl(env, target): _tests.append(_test_basic_windows) +def _test_windows_target_with_path_separators(name, config): + rt_util.helper_target( + config.rule, + name = name + "/nested_subject", + srcs = ["main.py"], + main = "main.py", + ) + analysis_test( + name = name, + impl = _test_windows_target_with_path_separators_impl, + target = name + "/nested_subject", + config_settings = { + "//command_line_option:cpu": "windows_x86_64", + "//command_line_option:crosstool_top": CROSSTOOL_TOP, + "//command_line_option:extra_execution_platforms": [platform_targets.WINDOWS_X86_64], + "//command_line_option:extra_toolchains": [CC_TOOLCHAIN], + "//command_line_option:platforms": [platform_targets.WINDOWS_X86_64], + }, + attr_values = {}, + ) + +def _test_windows_target_with_path_separators_impl(env, target): + target = env.expect.that_target(target) + target.runfiles().contains_predicate(matching.str_endswith( + target.meta.format_str("/{name}"), + )) + +_tests.append(_test_windows_target_with_path_separators) + def _test_basic_zip(name, config): target_compatible_with = select({ # Disable the new test on windows because we have _test_basic_windows. From 7e61cd75015175c1168113008a62aff4d93f1b55 Mon Sep 17 00:00:00 2001 From: Titus Fortner Date: Mon, 18 May 2026 19:21:25 -0500 Subject: [PATCH 3/3] test: move windows path-separator test to alphabetical position Addresses review feedback on PR #3790. --- tests/base_rules/py_executable_base_tests.bzl | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl index d5ac6df43d..dff0399bc2 100644 --- a/tests/base_rules/py_executable_base_tests.bzl +++ b/tests/base_rules/py_executable_base_tests.bzl @@ -65,35 +65,6 @@ def _test_basic_windows_impl(env, target): _tests.append(_test_basic_windows) -def _test_windows_target_with_path_separators(name, config): - rt_util.helper_target( - config.rule, - name = name + "/nested_subject", - srcs = ["main.py"], - main = "main.py", - ) - analysis_test( - name = name, - impl = _test_windows_target_with_path_separators_impl, - target = name + "/nested_subject", - config_settings = { - "//command_line_option:cpu": "windows_x86_64", - "//command_line_option:crosstool_top": CROSSTOOL_TOP, - "//command_line_option:extra_execution_platforms": [platform_targets.WINDOWS_X86_64], - "//command_line_option:extra_toolchains": [CC_TOOLCHAIN], - "//command_line_option:platforms": [platform_targets.WINDOWS_X86_64], - }, - attr_values = {}, - ) - -def _test_windows_target_with_path_separators_impl(env, target): - target = env.expect.that_target(target) - target.runfiles().contains_predicate(matching.str_endswith( - target.meta.format_str("/{name}"), - )) - -_tests.append(_test_windows_target_with_path_separators) - def _test_basic_zip(name, config): target_compatible_with = select({ # Disable the new test on windows because we have _test_basic_windows. @@ -525,6 +496,35 @@ def _test_py_runtime_info_provided_impl(env, target): _tests.append(_test_py_runtime_info_provided) +def _test_windows_target_with_path_separators(name, config): + rt_util.helper_target( + config.rule, + name = name + "/nested_subject", + srcs = ["main.py"], + main = "main.py", + ) + analysis_test( + name = name, + impl = _test_windows_target_with_path_separators_impl, + target = name + "/nested_subject", + config_settings = { + "//command_line_option:cpu": "windows_x86_64", + "//command_line_option:crosstool_top": CROSSTOOL_TOP, + "//command_line_option:extra_execution_platforms": [platform_targets.WINDOWS_X86_64], + "//command_line_option:extra_toolchains": [CC_TOOLCHAIN], + "//command_line_option:platforms": [platform_targets.WINDOWS_X86_64], + }, + attr_values = {}, + ) + +def _test_windows_target_with_path_separators_impl(env, target): + target = env.expect.that_target(target) + target.runfiles().contains_predicate(matching.str_endswith( + target.meta.format_str("/{name}"), + )) + +_tests.append(_test_windows_target_with_path_separators) + # ===== # You were gonna add a test at the end, weren't you? # Nope. Please keep them sorted; put it in its alphabetical location.