fix: Avoid C++ toolchain requirement if possible (#2919)
By making use of the new `launcher_maker_toolchain` in Bazel 9,
rules_python can avoid the requirement for a C++ toolchain targeting the
target platform if that platform isn't Windows.
For example, this makes it possible to cross-compile pure Python targets
from one Unix to another. Since Java targets have a dependency on Python
targets through the `proguard_allowlister`, this also allows Java
targets to be built without any C++ toolchain.
diff --git a/python/private/internal_config_repo.bzl b/python/private/internal_config_repo.bzl
index d5192ec..b208037 100644
--- a/python/private/internal_config_repo.bzl
+++ b/python/private/internal_config_repo.bzl
@@ -32,6 +32,7 @@
enable_pystar = True,
enable_pipstar = {enable_pipstar},
enable_deprecation_warnings = {enable_deprecation_warnings},
+ bazel_9_or_later = {bazel_9_or_later},
BuiltinPyInfo = getattr(getattr(native, "legacy_globals", None), "PyInfo", {builtin_py_info_symbol}),
BuiltinPyRuntimeInfo = getattr(getattr(native, "legacy_globals", None), "PyRuntimeInfo", {builtin_py_runtime_info_symbol}),
BuiltinPyCcLinkParamsProvider = getattr(getattr(native, "legacy_globals", None), "PyCcLinkParamsProvider", {builtin_py_cc_link_params_provider}),
@@ -87,7 +88,10 @@
"""
def _internal_config_repo_impl(rctx):
- if not native.bazel_version or int(native.bazel_version.split(".")[0]) >= 8:
+ # An empty version signifies a development build, which is treated as
+ # the latest version.
+ bazel_major_version = int(native.bazel_version.split(".")[0]) if native.bazel_version else 99999
+ if bazel_major_version >= 8:
builtin_py_info_symbol = "None"
builtin_py_runtime_info_symbol = "None"
builtin_py_cc_link_params_provider = "None"
@@ -103,6 +107,7 @@
builtin_py_info_symbol = builtin_py_info_symbol,
builtin_py_runtime_info_symbol = builtin_py_runtime_info_symbol,
builtin_py_cc_link_params_provider = builtin_py_cc_link_params_provider,
+ bazel_9_or_later = str(bazel_major_version >= 9),
))
shim_content = _PY_INTERNAL_SHIM
diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl
index 669951e..99a3dff 100644
--- a/python/private/py_executable.bzl
+++ b/python/private/py_executable.bzl
@@ -18,6 +18,7 @@
load("@bazel_skylib//lib:structs.bzl", "structs")
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
load(":attr_builders.bzl", "attrb")
load(
":attributes.bzl",
@@ -69,6 +70,7 @@
_py_builtins = py_internal
_EXTERNAL_PATH_PREFIX = "external"
_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
+_LAUNCHER_MAKER_TOOLCHAIN_TYPE = "@bazel_tools//tools/launcher:launcher_maker_toolchain_type"
# Non-Google-specific attributes for executables
# These attributes are for rules that accept Python sources.
@@ -228,17 +230,19 @@
"@platforms//os:windows",
],
),
- "_windows_launcher_maker": lambda: attrb.Label(
- default = "@bazel_tools//tools/launcher:launcher_maker",
- cfg = "exec",
- executable = True,
- ),
"_zipper": lambda: attrb.Label(
cfg = "exec",
executable = True,
default = "@bazel_tools//tools/zip:zipper",
),
},
+ {
+ "_windows_launcher_maker": lambda: attrb.Label(
+ default = "@bazel_tools//tools/launcher:launcher_maker",
+ cfg = "exec",
+ executable = True,
+ ),
+ } if not rp_config.bazel_9_or_later else {},
)
def convert_legacy_create_init_to_int(kwargs):
@@ -777,6 +781,11 @@
is_executable = True,
)
+def _find_launcher_maker(ctx):
+ if rp_config.bazel_9_or_later:
+ return (ctx.toolchains[_LAUNCHER_MAKER_TOOLCHAIN_TYPE].binary, _LAUNCHER_MAKER_TOOLCHAIN_TYPE)
+ return (ctx.executable._windows_launcher_maker, None)
+
def _create_windows_exe_launcher(
ctx,
*,
@@ -796,8 +805,9 @@
launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s")
launcher = ctx.attr._launcher[DefaultInfo].files_to_run.executable
+ executable, toolchain = _find_launcher_maker(ctx)
ctx.actions.run(
- executable = ctx.executable._windows_launcher_maker,
+ executable = executable,
arguments = [launcher.path, launch_info, output.path],
inputs = [launcher],
outputs = [output],
@@ -805,6 +815,7 @@
progress_message = "Creating launcher for %{label}",
# Needed to inherit PATH when using non-MSVC compilers like MinGW
use_default_shell_env = True,
+ toolchain = toolchain,
)
def _create_zip_file(ctx, *, output, zip_main, runfiles):
@@ -1838,7 +1849,7 @@
ruleb.ToolchainType(TOOLCHAIN_TYPE),
ruleb.ToolchainType(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False),
ruleb.ToolchainType("@bazel_tools//tools/cpp:toolchain_type", mandatory = False),
- ],
+ ] + ([ruleb.ToolchainType(_LAUNCHER_MAKER_TOOLCHAIN_TYPE)] if rp_config.bazel_9_or_later else []),
cfg = dict(
implementation = _transition_executable_impl,
inputs = TRANSITION_LABELS + [labels.PYTHON_VERSION],
diff --git a/tests/base_rules/py_executable_base_tests.bzl b/tests/base_rules/py_executable_base_tests.bzl
index e41bc2c..ed1a550 100644
--- a/tests/base_rules/py_executable_base_tests.bzl
+++ b/tests/base_rules/py_executable_base_tests.bzl
@@ -14,6 +14,7 @@
"""Tests common to py_binary and py_test (executable rules)."""
load("@rules_python//python:py_runtime_info.bzl", RulesPythonPyRuntimeInfo = "PyRuntimeInfo")
+load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
load("@rules_testing//lib:truth.bzl", "matching")
load("@rules_testing//lib:util.bzl", rt_util = "util")
@@ -114,6 +115,29 @@
_tests.append(_test_basic_zip)
+def _test_cross_compile_to_unix(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ main_module = "dummy",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_cross_compile_to_unix_impl,
+ target = name + "_subject",
+ # Cross-compilation of py_test fails since the default test toolchain
+ # requires an execution platform that matches the target platform.
+ config_settings = {
+ "//command_line_option:platforms": [platform_targets.EXOTIC_UNIX],
+ } if rp_config.bazel_9_or_later and not "py_test" in str(config.rule) else {},
+ expect_failure = True,
+ )
+
+def _test_cross_compile_to_unix_impl(_env, _target):
+ pass
+
+_tests.append(_test_cross_compile_to_unix)
+
def _test_executable_in_runfiles(name, config):
rt_util.helper_target(
config.rule,
diff --git a/tests/support/platforms/BUILD.bazel b/tests/support/platforms/BUILD.bazel
index 41d7936..eeb7ccb 100644
--- a/tests/support/platforms/BUILD.bazel
+++ b/tests/support/platforms/BUILD.bazel
@@ -75,3 +75,11 @@
"@platforms//cpu:aarch64",
],
)
+
+platform(
+ name = "exotic_unix",
+ constraint_values = [
+ "@platforms//os:linux",
+ "@platforms//cpu:s390x",
+ ],
+)
diff --git a/tests/support/platforms/platforms.bzl b/tests/support/platforms/platforms.bzl
index af049f2..92a1d61 100644
--- a/tests/support/platforms/platforms.bzl
+++ b/tests/support/platforms/platforms.bzl
@@ -10,4 +10,8 @@
WINDOWS = Label("//tests/support/platforms:windows"),
WINDOWS_AARCH64 = Label("//tests/support/platforms:windows_aarch64"),
WINDOWS_X86_64 = Label("//tests/support/platforms:windows_x86_64"),
+
+ # Unspecified Unix platform that is unlikely to be the host platform in CI,
+ # but still provides a Python toolchain.
+ EXOTIC_UNIX = Label("//tests/support/platforms:exotic_unix"),
)