From df7ca20ae4953a1efc51b725a61ca6e8581cb69c Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 4 Oct 2026 21:03:53 -0700 Subject: [PATCH 1/7] refactor(zipapp): add dev-only toolchain for the Rust exe_zip_maker PR #4151 added a Rust implementation of `exe_zip_maker`, but nothing used it. This wires it in for rules_python development only, leaving downstream users on the Python implementation. A new optional toolchain type, `//python/private/toolchain_types:exe_zip_maker`, is consulted by `py_binary`/`py_test` and `py_zipapp_*` when creating self-executable zips. If no toolchain is resolved (or it provides no tool), the rules fall back to the existing `_exe_zip_maker` attribute, so users and WORKSPACE mode need no new registration and see no behavior change. When the toolchain's tool is used, the action is bound to that toolchain type so it runs on the exec platform the tool was built for. A dev-only toolchain in `dev/dev_only_toolchains/` points at `//crates/exe_zip_maker` and is registered with `dev_dependency = True`. The `toolchain()` and its implementation live in separate packages so registration doesn't load the implementation. It is gated behind `--//dev/dev_only_toolchains:use_rust_exe_zip_maker`, which defaults to off. Analysis tests verify the Rust tool is selected when the flag is on and the Python fallback is used when off. Work towards #4151 --- .../plans/dev-only-exe-zip-maker-toolchain.md | 110 ++++++++++++++++ MODULE.bazel | 6 + dev/dev_only_toolchains/BUILD.bazel | 30 +++++ dev/dev_only_toolchains/impls/BUILD.bazel | 15 +++ python/private/BUILD.bazel | 1 + python/private/py_executable.bzl | 8 +- python/private/toolchain_types.bzl | 1 + python/private/toolchain_types/BUILD.bazel | 16 +++ python/private/zipapp/BUILD.bazel | 11 ++ .../zipapp/py_exe_zip_maker_toolchain.bzl | 75 +++++++++++ python/private/zipapp/py_zipapp_rule.bzl | 12 +- tests/exe_zip_maker_toolchain/BUILD.bazel | 5 + .../exe_zip_maker_toolchain_tests.bzl | 118 ++++++++++++++++++ tests/exe_zip_maker_toolchain/main.py | 1 + 14 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 .agents/plans/dev-only-exe-zip-maker-toolchain.md create mode 100644 dev/dev_only_toolchains/BUILD.bazel create mode 100644 dev/dev_only_toolchains/impls/BUILD.bazel create mode 100644 python/private/toolchain_types/BUILD.bazel create mode 100644 python/private/zipapp/py_exe_zip_maker_toolchain.bzl create mode 100644 tests/exe_zip_maker_toolchain/BUILD.bazel create mode 100644 tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl create mode 100644 tests/exe_zip_maker_toolchain/main.py diff --git a/.agents/plans/dev-only-exe-zip-maker-toolchain.md b/.agents/plans/dev-only-exe-zip-maker-toolchain.md new file mode 100644 index 0000000000..c7b6138085 --- /dev/null +++ b/.agents/plans/dev-only-exe-zip-maker-toolchain.md @@ -0,0 +1,110 @@ +# Plan: dev-only toolchain for the Rust `exe_zip_maker` + +## Background + +PR #4151 added a Rust implementation of `exe_zip_maker` under +`//crates/exe_zip_maker`. It is built with `rules_rust`, which is a +`dev_dependency` of rules_python. Users of rules_python therefore cannot +build it, and in WORKSPACE mode `@rules_rust` is a stub that produces empty +`filegroup`s. + +Today, `py_binary`/`py_test` (via `py_executable.bzl`) and `py_zipapp` +(via `py_zipapp_rule.bzl`) invoke the Python `//tools/zipapp:exe_zip_maker` +(a `py_interpreter_program`) through a private `_exe_zip_maker` attribute and +`actions_run()`. + +## Goal + +Use the Rust tool when developing rules_python itself (bzlmod only), while +leaving downstream users on the Python implementation with no behavior change. + +## Design + +Introduce a dedicated, optional toolchain type for the `exe_zip_maker` tool: + +* `//python/private/toolchain_types:exe_zip_maker` (`toolchain_type`), in a + new `python/private/toolchain_types/` package dedicated to internal + toolchain types. +* No separate provider: the toolchain is tool-specific, so + `ToolchainInfo(exe_zip_maker = Target | None)` is sufficient. +* `py_exe_zip_maker_toolchain` rule + (`python/private/zipapp/py_exe_zip_maker_toolchain.bzl`) that returns the + `ToolchainInfo` above. Its `exe_zip_maker` attr uses `cfg = "exec"` so the + tool is built for the exec platform. +* Add `EXE_ZIP_MAKER_TOOLCHAIN_TYPE` to `python/private/toolchain_types.bzl`. +* Rules that create executable zips declare the toolchain as + `mandatory = False`. When resolved and `exe_zip_maker` is set, use it; + otherwise fall back to the existing `_exe_zip_maker` attribute (the Python + tool). This keeps users and WORKSPACE mode unchanged: no new toolchain + registration is required. +* Gated by a flag, disabled by default: + `--//dev/dev_only_toolchains:use_rust_exe_zip_maker` (a `bool_flag` + + `config_setting` used as the `toolchain()`'s `target_settings`). +* Dev-only registration: the `toolchain()` lives in + `dev/dev_only_toolchains/` and its `py_exe_zip_maker_toolchain` + implementation in `dev/dev_only_toolchains/impls/`, kept in separate + packages so registration does not load the implementation. The + implementation points at `//crates/exe_zip_maker`. It is registered in + `MODULE.bazel` with `register_toolchains(..., dev_dependency = True)`. + Nothing is registered in WORKSPACE mode (where the Rust binary is a stub). + +Why a new toolchain type instead of extending `py_exec_tools_toolchain`? +The exec tools toolchain is created per-Python-version inside the hermetic +runtime repos and is coupled to the interpreter; overriding it just for dev +would require duplicating that setup. A small, separate toolchain type is +simpler. + +Naming: tool-specific (`exe_zip_maker`). Future tools (zipper, +zip_main_maker) get their own toolchain types in the same package. + +## Work items + +- [x] Research current wiring (`py_executable.bzl`, `py_zipapp_rule.bzl`, + `actions_run`, `py_exec_tools_toolchain`). +- [x] Add `EXE_ZIP_MAKER_TOOLCHAIN_TYPE` to `toolchain_types.bzl`. +- [x] Add `toolchain_type(name = "exe_zip_maker")` in + `python/private/toolchain_types/BUILD.bazel`. +- [x] ~~Provider file~~ (dropped; tool-specific toolchain needs none). +- [x] Add `py_exe_zip_maker_toolchain.bzl` (`py_exe_zip_maker_toolchain` rule) + + `bzl_library`. +- [x] Add helper `get_exe_zip_maker(ctx)` (in `py_exe_zip_maker_toolchain.bzl`) + that prefers the toolchain and falls back to `ctx.attr._exe_zip_maker`. +- [x] Wire into `py_executable.bzl`: declare optional toolchain, use helper. +- [x] Wire into `py_zipapp_rule.bzl`: declare optional toolchain, use helper. +- [x] Define dev toolchain in `dev/dev_only_toolchains/BUILD.bazel` + (`py_exe_zip_maker_toolchain` + `toolchain`). +- [x] Register dev toolchain in `MODULE.bazel` with `dev_dependency = True`. +- [x] Update `bzl_library` deps in `python/private/BUILD.bazel` and + `python/private/zipapp/BUILD.bazel`. +- [x] Add analysis tests (`tests/exe_zip_maker_toolchain/`): Rust tool used + for `py_binary` and `py_zipapp` when the flag is on; Python fallback + when off. +- [x] Add `use_rust_exe_zip_maker` flag (default off) gating the toolchain. +- [x] Verify: `bazel build //crates/...`, `bazel test --config=fast-tests` + on `//tests/exe_zip_maker_toolchain/... //tests/py_zipapp/... + //tests/exe_zip_maker/... //tests/tools/zipapp/... //tests/base_rules/...` + (98 pass, 4 skipped), `bazel build //docs:docs` (ok), buildifier (ok). +- [x] Verify WORKSPACE mode falls back to the Python tool. Bazel 9 has no + WORKSPACE, so checked with `USE_BAZEL_VERSION=8.x` + `--noenable_bzlmod + --enable_workspace`: both `PyBuildExecutableZip` and + `PyZipAppCreateExecutableZip` invoke `tools/zipapp/exe_zip_maker_.py`. +- [x] News entry: skipped; dev-only, no user-visible behavior change + (same as PR #4151). +- [ ] Create PR (pending user go-ahead). + +## Progress log + +* 2026-10-04: Plan written. +* 2026-10-04: Implementation done; bzlmod `aquery` shows both zip actions + executing `bazel-out/.../crates/exe_zip_maker/exe_zip_maker`. +* 2026-10-04: Tests, docs, buildifier, WORKSPACE fallback all verified. +* 2026-10-04: Per review, renamed to be tool-specific + (`//python/private/toolchain_types:exe_zip_maker`, + `py_exe_zip_maker_toolchain`) and dropped the generic provider. Tests + re-run and pass. + +## Open questions / follow-ups + +* Should the default (Python) tool also be delivered via a registered + toolchain in a later change, so the attribute fallback can be removed? +* Later: add `zipper`/`zip_main_maker` toolchain types alongside. diff --git a/MODULE.bazel b/MODULE.bazel index 3ce0c80632..18a5196952 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -112,6 +112,12 @@ rust_crates.spec( rust_crates.from_specs() use_repo(rust_crates, "crates") +# Use the Rust-based build tools when developing rules_python itself. +register_toolchains( + "//dev/dev_only_toolchains:exe_zip_maker_toolchain", + dev_dependency = True, +) + bazel_dep(name = "other", version = "0", dev_dependency = True) bazel_dep(name = "another_module", version = "0", dev_dependency = True) diff --git a/dev/dev_only_toolchains/BUILD.bazel b/dev/dev_only_toolchains/BUILD.bazel new file mode 100644 index 0000000000..ec5281c6fd --- /dev/null +++ b/dev/dev_only_toolchains/BUILD.bazel @@ -0,0 +1,30 @@ +# Toolchains that are only registered when developing rules_python itself +# (see `register_toolchains(..., dev_dependency = True)` in MODULE.bazel). +# They are not registered in WORKSPACE mode. +# +# Only `toolchain()` declarations belong here; implementations live in +# `impls/` so that registering the toolchains doesn't load the implementations. + +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +package(default_visibility = ["//visibility:private"]) + +# Whether to use the Rust implementation of exe_zip_maker. Disabled by +# default; enable with `--//dev/dev_only_toolchains:use_rust_exe_zip_maker`. +bool_flag( + name = "use_rust_exe_zip_maker", + build_setting_default = False, + visibility = ["//tests:__subpackages__"], +) + +config_setting( + name = "is_rust_exe_zip_maker_enabled", + flag_values = {":use_rust_exe_zip_maker": "true"}, +) + +toolchain( + name = "exe_zip_maker_toolchain", + target_settings = [":is_rust_exe_zip_maker_enabled"], + toolchain = "//dev/dev_only_toolchains/impls:exe_zip_maker", + toolchain_type = "//python/private/toolchain_types:exe_zip_maker", +) diff --git a/dev/dev_only_toolchains/impls/BUILD.bazel b/dev/dev_only_toolchains/impls/BUILD.bazel new file mode 100644 index 0000000000..881dd74314 --- /dev/null +++ b/dev/dev_only_toolchains/impls/BUILD.bazel @@ -0,0 +1,15 @@ +# Toolchain implementations for the dev-only toolchains. +# +# These are kept in a separate package from the `toolchain()` declarations so +# that toolchain resolution only needs to load this package (and the tool's +# dependencies) when a toolchain is actually selected. + +load("//python/private/zipapp:py_exe_zip_maker_toolchain.bzl", "py_exe_zip_maker_toolchain") # buildifier: disable=bzl-visibility + +package(default_visibility = ["//dev/dev_only_toolchains:__pkg__"]) + +# Use the Rust implementation of exe_zip_maker instead of the Python one. +py_exe_zip_maker_toolchain( + name = "exe_zip_maker", + exe_zip_maker = "//crates/exe_zip_maker", +) diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index b88fb724a4..35dd1d0347 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -560,6 +560,7 @@ bzl_library( ":toolchain_types", ":transition_labels", ":venv_runfiles", + "//python/private/zipapp:py_exe_zip_maker_toolchain", "@bazel_skylib//lib:dicts", "@bazel_skylib//lib:paths", "@bazel_skylib//lib:structs", diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 62742d334c..66b09a0908 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -19,6 +19,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("//python/private/zipapp:py_exe_zip_maker_toolchain.bzl", "get_exe_zip_maker") load(":attr_builders.bzl", "attrb") load( ":attributes.bzl", @@ -68,7 +69,7 @@ load(":py_internal.bzl", "py_internal") load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG") load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo") load(":rule_builders.bzl", "ruleb") -load(":toolchain_types.bzl", "CC_TOOLCHAIN_TYPE", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") +load(":toolchain_types.bzl", "CC_TOOLCHAIN_TYPE", "EXEC_TOOLS_TOOLCHAIN_TYPE", "EXE_ZIP_MAKER_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") load(":transition_labels.bzl", "TRANSITION_LABELS") load(":venv_runfiles.bzl", "create_venv_app_files") @@ -1122,9 +1123,11 @@ def _create_executable_zip_file( args.add(prelude) args.add(zip_file) args.add(output) + exe_zip_maker = get_exe_zip_maker(ctx) actions_run( ctx, - executable = ctx.attr._exe_zip_maker, + executable = exe_zip_maker.executable, + toolchain = exe_zip_maker.toolchain, arguments = [args], inputs = depset([prelude, zip_file]), outputs = [output], @@ -2236,6 +2239,7 @@ def create_executable_rule_builder(implementation, **kwargs): toolchains = [ ruleb.ToolchainType(TOOLCHAIN_TYPE), ruleb.ToolchainType(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False), + ruleb.ToolchainType(EXE_ZIP_MAKER_TOOLCHAIN_TYPE, mandatory = False), ruleb.ToolchainType(CC_TOOLCHAIN_TYPE, mandatory = False), ] + ([ruleb.ToolchainType(LAUNCHER_MAKER_TOOLCHAIN_TYPE)] if rp_config.bazel_9_or_later else []), cfg = dict( diff --git a/python/private/toolchain_types.bzl b/python/private/toolchain_types.bzl index a3a52409a6..a4e51248f1 100644 --- a/python/private/toolchain_types.bzl +++ b/python/private/toolchain_types.bzl @@ -20,6 +20,7 @@ implementation of the toolchain. TARGET_TOOLCHAIN_TYPE = Label("//python:toolchain_type") EXEC_TOOLS_TOOLCHAIN_TYPE = Label("//python:exec_tools_toolchain_type") +EXE_ZIP_MAKER_TOOLCHAIN_TYPE = Label("//python/private/toolchain_types:exe_zip_maker") PY_CC_TOOLCHAIN_TYPE = Label("//python/cc:toolchain_type") LAUNCHER_MAKER_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/launcher:launcher_maker_toolchain_type") CC_TOOLCHAIN_TYPE = Label("@bazel_tools//tools/cpp:toolchain_type") diff --git a/python/private/toolchain_types/BUILD.bazel b/python/private/toolchain_types/BUILD.bazel new file mode 100644 index 0000000000..33c7f1e5bc --- /dev/null +++ b/python/private/toolchain_types/BUILD.bazel @@ -0,0 +1,16 @@ +# Toolchain types used internally by rules_python. +# +# These are in a separate package so toolchain() registrations and rules can +# depend on the type without loading the toolchain implementation. + +package(default_visibility = ["//:__subpackages__"]) + +licenses(["notice"]) + +# Toolchain providing the tool that creates self-executable zip files +# (` `). It is optional: when no toolchain is +# registered, rules fall back to the Python `//tools/zipapp:exe_zip_maker`. +# For now, only rules_python itself registers one (a Rust build, dev-only). +toolchain_type( + name = "exe_zip_maker", +) diff --git a/python/private/zipapp/BUILD.bazel b/python/private/zipapp/BUILD.bazel index 395e1d242a..6da278a504 100644 --- a/python/private/zipapp/BUILD.bazel +++ b/python/private/zipapp/BUILD.bazel @@ -33,6 +33,7 @@ bzl_library( name = "py_zipapp_rule", srcs = ["py_zipapp_rule.bzl"], deps = [ + ":py_exe_zip_maker_toolchain", "//python/private:attributes", "//python/private:builders", "//python/private:common", @@ -46,3 +47,13 @@ bzl_library( "@rules_python_internal//:rules_python_config", ], ) + +bzl_library( + name = "py_exe_zip_maker_toolchain", + srcs = ["py_exe_zip_maker_toolchain.bzl"], + deps = [ + "//python/private:py_interpreter_program", + "//python/private:sentinel_impl", + "//python/private:toolchain_types", + ], +) diff --git a/python/private/zipapp/py_exe_zip_maker_toolchain.bzl b/python/private/zipapp/py_exe_zip_maker_toolchain.bzl new file mode 100644 index 0000000000..fb8efe9b90 --- /dev/null +++ b/python/private/zipapp/py_exe_zip_maker_toolchain.bzl @@ -0,0 +1,75 @@ +"""Toolchain for the tool that creates self-executable zip files.""" + +load("//python/private:py_interpreter_program.bzl", "PyInterpreterProgramInfo") +load("//python/private:sentinel_impl.bzl", "SentinelInfo") +load("//python/private:toolchain_types.bzl", "EXE_ZIP_MAKER_TOOLCHAIN_TYPE") + +def _py_exe_zip_maker_toolchain_impl(ctx): + exe_zip_maker = ctx.attr.exe_zip_maker + if exe_zip_maker != None and SentinelInfo in exe_zip_maker: + exe_zip_maker = None + + return [ + platform_common.ToolchainInfo( + exe_zip_maker = exe_zip_maker, + ), + ] + +py_exe_zip_maker_toolchain = rule( + implementation = _py_exe_zip_maker_toolchain_impl, + doc = """Provides the tool used to create self-executable zip files. + +This provides `ToolchainInfo` with the following attributes: +* `exe_zip_maker`: {type}`Target | None`. Invoked with three positional + arguments: ` `. Must provide either + `PyInterpreterProgramInfo` or `DefaultInfo.files_to_run`. If `None`, the + rules fall back to their built-in implementation. +""", + attrs = { + "exe_zip_maker": attr.label( + # NOTE: This is an executable, but can't use `executable = True` + # because the `//python:none` sentinel isn't executable. + # `allow_files = True` (not `allow_single_file`) because binary + # targets can have multiple files in `DefaultInfo.files`. + allow_files = True, + cfg = "exec", + doc = """ +The tool to create self-executable zip files. + +To indicate no tool, specify the special target {obj}`//python:none`. +""", + ), + }, +) + +def get_exe_zip_maker(ctx): + """Returns how to run the tool for creating self-executable zips. + + Prefers the tool from the `exe_zip_maker` toolchain, if one is resolved + and provides it. Otherwise, falls back to the rule's `_exe_zip_maker` + attribute. + + Args: + ctx: The rule context. The rule must declare + `EXE_ZIP_MAKER_TOOLCHAIN_TYPE` as an optional toolchain and have + an `_exe_zip_maker` attribute. + + Returns: + {type}`struct` with fields: + * `executable`: {type}`Target` to pass to `actions_run()`. + * `toolchain`: {type}`Label | None` the toolchain type to pass to + `actions_run()` so the action runs on the exec platform the tool + was built for. `None` when the tool is a `py_interpreter_program` + (`actions_run` then uses the exec tools toolchain) or when falling + back to the attribute. + """ + toolchain = ctx.toolchains[EXE_ZIP_MAKER_TOOLCHAIN_TYPE] + if toolchain and toolchain.exe_zip_maker: + executable = toolchain.exe_zip_maker + if PyInterpreterProgramInfo in executable: + return struct(executable = executable, toolchain = None) + return struct( + executable = executable, + toolchain = EXE_ZIP_MAKER_TOOLCHAIN_TYPE, + ) + return struct(executable = ctx.attr._exe_zip_maker, toolchain = None) diff --git a/python/private/zipapp/py_zipapp_rule.bzl b/python/private/zipapp/py_zipapp_rule.bzl index b664c1f628..ebf083c069 100644 --- a/python/private/zipapp/py_zipapp_rule.bzl +++ b/python/private/zipapp/py_zipapp_rule.bzl @@ -18,8 +18,9 @@ load("//python/private:common_labels.bzl", "labels") load("//python/private:py_executable_info.bzl", "PyExecutableInfo") load("//python/private:py_internal.bzl", "py_internal") load("//python/private:py_runtime_info.bzl", "PyRuntimeInfo") -load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE") +load("//python/private:toolchain_types.bzl", "EXEC_TOOLS_TOOLCHAIN_TYPE", "EXE_ZIP_MAKER_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE") load("//python/private:transition_labels.bzl", "TRANSITION_LABELS") +load(":py_exe_zip_maker_toolchain.bzl", "get_exe_zip_maker") def _is_symlink(f): if hasattr(f, "is_symlink"): @@ -225,9 +226,11 @@ def _create_self_executable_zip(ctx, preamble, zip_file): args.add(preamble) args.add(zip_file) args.add(pyz) + exe_zip_maker = get_exe_zip_maker(ctx) actions_run( ctx, - executable = ctx.attr._exe_zip_maker, + executable = exe_zip_maker.executable, + toolchain = exe_zip_maker.toolchain, arguments = [args], inputs = depset([preamble, zip_file]), outputs = [pyz], @@ -396,7 +399,10 @@ Whether the output should be an executable zip file. ), } if not rp_config.bazel_9_or_later else {}) -_TOOLCHAINS = [EXEC_TOOLS_TOOLCHAIN_TYPE] + ([LAUNCHER_MAKER_TOOLCHAIN_TYPE] if rp_config.bazel_9_or_later else []) +_TOOLCHAINS = [ + EXEC_TOOLS_TOOLCHAIN_TYPE, + config_common.toolchain_type(EXE_ZIP_MAKER_TOOLCHAIN_TYPE, mandatory = False), +] + ([LAUNCHER_MAKER_TOOLCHAIN_TYPE] if rp_config.bazel_9_or_later else []) _COMMON_RULE_DOC = """ diff --git a/tests/exe_zip_maker_toolchain/BUILD.bazel b/tests/exe_zip_maker_toolchain/BUILD.bazel new file mode 100644 index 0000000000..57f2b55b6f --- /dev/null +++ b/tests/exe_zip_maker_toolchain/BUILD.bazel @@ -0,0 +1,5 @@ +load(":exe_zip_maker_toolchain_tests.bzl", "exe_zip_maker_toolchain_test_suite") + +exe_zip_maker_toolchain_test_suite( + name = "exe_zip_maker_toolchain_tests", +) diff --git a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl new file mode 100644 index 0000000000..cb17cadf00 --- /dev/null +++ b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl @@ -0,0 +1,118 @@ +"""Tests that the dev-only build tools toolchain is used.""" + +load("@rules_testing//lib:analysis_test.bzl", "analysis_test") +load("@rules_testing//lib:test_suite.bzl", "test_suite") +load("@rules_testing//lib:truth.bzl", "matching") +load("@rules_testing//lib:util.bzl", rt_util = "util") +load("//python:py_binary.bzl", "py_binary") +load("//python/private:common.bzl", "maybe_builtin_build_python_zip") # buildifier: disable=bzl-visibility +load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility +load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") +load("//tests/support:support.bzl", "SUPPORTS_BZLMOD") + +_tests = [] + +# When developing rules_python (bzlmod), MODULE.bazel registers a toolchain +# that points to the Rust implementation, gated behind a flag. These tests +# verify the rules pick it up when enabled, and use the Python fallback +# otherwise. +# +# The dev toolchain is only registered under bzlmod; in WORKSPACE mode, +# rules_rust is a stub and the Python fallback is used, so skip there. +_RUST_EXE_ZIP_MAKER_SUFFIX = "crates/exe_zip_maker/exe_zip_maker" +_PYTHON_EXE_ZIP_MAKER_SUFFIX = "tools/zipapp/exe_zip_maker_.py" +_USE_RUST_FLAG = str(Label("//dev/dev_only_toolchains:use_rust_exe_zip_maker")) + +def _test_py_binary_uses_toolchain_exe_zip_maker(name): + rt_util.helper_target( + py_binary, + name = name + "_subject", + srcs = ["main.py"], + main = "main.py", + ) + analysis_test( + name = name, + impl = _test_py_binary_uses_toolchain_exe_zip_maker_impl, + target = name + "_subject", + config_settings = { + labels.BUILD_PYTHON_ZIP: True, + _USE_RUST_FLAG: True, + } | maybe_builtin_build_python_zip("true"), + attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, + ) + +def _test_py_binary_uses_toolchain_exe_zip_maker_impl(env, target): + action = env.expect.that_target(target).action_named( + "PyBuildExecutableZip", + ) + action.argv().contains_predicate( + matching.str_endswith(_RUST_EXE_ZIP_MAKER_SUFFIX), + ) + +_tests.append(_test_py_binary_uses_toolchain_exe_zip_maker) + +def _test_py_binary_flag_disabled_uses_python_exe_zip_maker(name): + rt_util.helper_target( + py_binary, + name = name + "_subject", + srcs = ["main.py"], + main = "main.py", + ) + analysis_test( + name = name, + impl = _test_py_binary_flag_disabled_uses_python_exe_zip_maker_impl, + target = name + "_subject", + config_settings = { + labels.BUILD_PYTHON_ZIP: True, + _USE_RUST_FLAG: False, + } | maybe_builtin_build_python_zip("true"), + attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, + ) + +def _test_py_binary_flag_disabled_uses_python_exe_zip_maker_impl(env, target): + action = env.expect.that_target(target).action_named( + "PyBuildExecutableZip", + ) + action.argv().contains_predicate( + matching.str_endswith(_PYTHON_EXE_ZIP_MAKER_SUFFIX), + ) + +_tests.append(_test_py_binary_flag_disabled_uses_python_exe_zip_maker) + +def _test_py_zipapp_uses_toolchain_exe_zip_maker(name): + rt_util.helper_target( + py_binary, + name = name + "_bin", + srcs = ["main.py"], + main = "main.py", + ) + rt_util.helper_target( + py_zipapp_binary, + name = name + "_subject", + binary = name + "_bin", + ) + analysis_test( + name = name, + impl = _test_py_zipapp_uses_toolchain_exe_zip_maker_impl, + target = name + "_subject", + config_settings = { + _USE_RUST_FLAG: True, + }, + attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, + ) + +def _test_py_zipapp_uses_toolchain_exe_zip_maker_impl(env, target): + action = env.expect.that_target(target).action_named( + "PyZipAppCreateExecutableZip", + ) + action.argv().contains_predicate( + matching.str_endswith(_RUST_EXE_ZIP_MAKER_SUFFIX), + ) + +_tests.append(_test_py_zipapp_uses_toolchain_exe_zip_maker) + +def exe_zip_maker_toolchain_test_suite(name): + test_suite( + name = name, + tests = _tests, + ) diff --git a/tests/exe_zip_maker_toolchain/main.py b/tests/exe_zip_maker_toolchain/main.py new file mode 100644 index 0000000000..11b15b1a45 --- /dev/null +++ b/tests/exe_zip_maker_toolchain/main.py @@ -0,0 +1 @@ +print("hello") From 73695f3cd2c6c38c82cbf2a5f085fd1ba564acca Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 4 Oct 2026 23:41:42 -0700 Subject: [PATCH 2/7] refactor(zipapp): make dev exe_zip_maker flag yes/no/auto Use a string flag so `auto` can later let rules_python decide; for now `auto` behaves as `no`. Also wrap a long load() line. --- .../plans/dev-only-exe-zip-maker-toolchain.md | 6 ++++-- dev/dev_only_toolchains/BUILD.bazel | 21 +++++++++++++------ python/private/py_executable.bzl | 9 +++++++- .../exe_zip_maker_toolchain_tests.bzl | 6 +++--- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/.agents/plans/dev-only-exe-zip-maker-toolchain.md b/.agents/plans/dev-only-exe-zip-maker-toolchain.md index c7b6138085..51e2b89840 100644 --- a/.agents/plans/dev-only-exe-zip-maker-toolchain.md +++ b/.agents/plans/dev-only-exe-zip-maker-toolchain.md @@ -38,7 +38,8 @@ Introduce a dedicated, optional toolchain type for the `exe_zip_maker` tool: tool). This keeps users and WORKSPACE mode unchanged: no new toolchain registration is required. * Gated by a flag, disabled by default: - `--//dev/dev_only_toolchains:use_rust_exe_zip_maker` (a `bool_flag` + + `--//dev/dev_only_toolchains:use_rust_exe_zip_maker=yes|no|auto` (a + `string_flag`; `auto` currently behaves as `no`) + `config_setting` used as the `toolchain()`'s `target_settings`). * Dev-only registration: the `toolchain()` lives in `dev/dev_only_toolchains/` and its `py_exe_zip_maker_toolchain` @@ -90,7 +91,8 @@ zip_main_maker) get their own toolchain types in the same package. `PyZipAppCreateExecutableZip` invoke `tools/zipapp/exe_zip_maker_.py`. - [x] News entry: skipped; dev-only, no user-visible behavior change (same as PR #4151). -- [ ] Create PR (pending user go-ahead). +- [x] Create PR: draft #4215 + (https://github.com/bazel-contrib/rules_python/pull/4215). ## Progress log diff --git a/dev/dev_only_toolchains/BUILD.bazel b/dev/dev_only_toolchains/BUILD.bazel index ec5281c6fd..242fa19deb 100644 --- a/dev/dev_only_toolchains/BUILD.bazel +++ b/dev/dev_only_toolchains/BUILD.bazel @@ -5,21 +5,30 @@ # Only `toolchain()` declarations belong here; implementations live in # `impls/` so that registering the toolchains doesn't load the implementations. -load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +load("@bazel_skylib//rules:common_settings.bzl", "string_flag") package(default_visibility = ["//visibility:private"]) -# Whether to use the Rust implementation of exe_zip_maker. Disabled by -# default; enable with `--//dev/dev_only_toolchains:use_rust_exe_zip_maker`. -bool_flag( +# Whether to use the Rust implementation of exe_zip_maker. +# * `yes`: use it. +# * `no`: use the built-in Python implementation. +# * `auto`: let rules_python decide. Currently the same as `no`. +# +# Enable with `--//dev/dev_only_toolchains:use_rust_exe_zip_maker=yes`. +string_flag( name = "use_rust_exe_zip_maker", - build_setting_default = False, + build_setting_default = "auto", + values = [ + "auto", + "no", + "yes", + ], visibility = ["//tests:__subpackages__"], ) config_setting( name = "is_rust_exe_zip_maker_enabled", - flag_values = {":use_rust_exe_zip_maker": "true"}, + flag_values = {":use_rust_exe_zip_maker": "yes"}, ) toolchain( diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index 66b09a0908..dc56e83f78 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -69,7 +69,14 @@ load(":py_internal.bzl", "py_internal") load(":py_runtime_info.bzl", "DEFAULT_STUB_SHEBANG") load(":reexports.bzl", "BuiltinPyInfo", "BuiltinPyRuntimeInfo") load(":rule_builders.bzl", "ruleb") -load(":toolchain_types.bzl", "CC_TOOLCHAIN_TYPE", "EXEC_TOOLS_TOOLCHAIN_TYPE", "EXE_ZIP_MAKER_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE") +load( + ":toolchain_types.bzl", + "CC_TOOLCHAIN_TYPE", + "EXEC_TOOLS_TOOLCHAIN_TYPE", + "EXE_ZIP_MAKER_TOOLCHAIN_TYPE", + "LAUNCHER_MAKER_TOOLCHAIN_TYPE", + TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE", +) load(":transition_labels.bzl", "TRANSITION_LABELS") load(":venv_runfiles.bzl", "create_venv_app_files") diff --git a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl index cb17cadf00..d8db9d907e 100644 --- a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl +++ b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl @@ -36,7 +36,7 @@ def _test_py_binary_uses_toolchain_exe_zip_maker(name): target = name + "_subject", config_settings = { labels.BUILD_PYTHON_ZIP: True, - _USE_RUST_FLAG: True, + _USE_RUST_FLAG: "yes", } | maybe_builtin_build_python_zip("true"), attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, ) @@ -64,7 +64,7 @@ def _test_py_binary_flag_disabled_uses_python_exe_zip_maker(name): target = name + "_subject", config_settings = { labels.BUILD_PYTHON_ZIP: True, - _USE_RUST_FLAG: False, + _USE_RUST_FLAG: "no", } | maybe_builtin_build_python_zip("true"), attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, ) @@ -96,7 +96,7 @@ def _test_py_zipapp_uses_toolchain_exe_zip_maker(name): impl = _test_py_zipapp_uses_toolchain_exe_zip_maker_impl, target = name + "_subject", config_settings = { - _USE_RUST_FLAG: True, + _USE_RUST_FLAG: "yes", }, attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, ) From 6b65c4a7f608e8819d277668ae808b807f766d34 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Sun, 4 Oct 2026 23:44:37 -0700 Subject: [PATCH 3/7] refactor(zipapp): rename config_setting to is_rust_exe_zip_maker_yes --- dev/dev_only_toolchains/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/dev_only_toolchains/BUILD.bazel b/dev/dev_only_toolchains/BUILD.bazel index 242fa19deb..b713295f99 100644 --- a/dev/dev_only_toolchains/BUILD.bazel +++ b/dev/dev_only_toolchains/BUILD.bazel @@ -27,13 +27,13 @@ string_flag( ) config_setting( - name = "is_rust_exe_zip_maker_enabled", + name = "is_rust_exe_zip_maker_yes", flag_values = {":use_rust_exe_zip_maker": "yes"}, ) toolchain( name = "exe_zip_maker_toolchain", - target_settings = [":is_rust_exe_zip_maker_enabled"], + target_settings = [":is_rust_exe_zip_maker_yes"], toolchain = "//dev/dev_only_toolchains/impls:exe_zip_maker", toolchain_type = "//python/private/toolchain_types:exe_zip_maker", ) From e72743ea17f8ffb890169dcb9ad8491e3255644d Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 5 Oct 2026 00:17:16 -0700 Subject: [PATCH 4/7] refactor(zipapp): remove agent plan file from the PR --- .../plans/dev-only-exe-zip-maker-toolchain.md | 112 ------------------ 1 file changed, 112 deletions(-) delete mode 100644 .agents/plans/dev-only-exe-zip-maker-toolchain.md diff --git a/.agents/plans/dev-only-exe-zip-maker-toolchain.md b/.agents/plans/dev-only-exe-zip-maker-toolchain.md deleted file mode 100644 index 51e2b89840..0000000000 --- a/.agents/plans/dev-only-exe-zip-maker-toolchain.md +++ /dev/null @@ -1,112 +0,0 @@ -# Plan: dev-only toolchain for the Rust `exe_zip_maker` - -## Background - -PR #4151 added a Rust implementation of `exe_zip_maker` under -`//crates/exe_zip_maker`. It is built with `rules_rust`, which is a -`dev_dependency` of rules_python. Users of rules_python therefore cannot -build it, and in WORKSPACE mode `@rules_rust` is a stub that produces empty -`filegroup`s. - -Today, `py_binary`/`py_test` (via `py_executable.bzl`) and `py_zipapp` -(via `py_zipapp_rule.bzl`) invoke the Python `//tools/zipapp:exe_zip_maker` -(a `py_interpreter_program`) through a private `_exe_zip_maker` attribute and -`actions_run()`. - -## Goal - -Use the Rust tool when developing rules_python itself (bzlmod only), while -leaving downstream users on the Python implementation with no behavior change. - -## Design - -Introduce a dedicated, optional toolchain type for the `exe_zip_maker` tool: - -* `//python/private/toolchain_types:exe_zip_maker` (`toolchain_type`), in a - new `python/private/toolchain_types/` package dedicated to internal - toolchain types. -* No separate provider: the toolchain is tool-specific, so - `ToolchainInfo(exe_zip_maker = Target | None)` is sufficient. -* `py_exe_zip_maker_toolchain` rule - (`python/private/zipapp/py_exe_zip_maker_toolchain.bzl`) that returns the - `ToolchainInfo` above. Its `exe_zip_maker` attr uses `cfg = "exec"` so the - tool is built for the exec platform. -* Add `EXE_ZIP_MAKER_TOOLCHAIN_TYPE` to `python/private/toolchain_types.bzl`. -* Rules that create executable zips declare the toolchain as - `mandatory = False`. When resolved and `exe_zip_maker` is set, use it; - otherwise fall back to the existing `_exe_zip_maker` attribute (the Python - tool). This keeps users and WORKSPACE mode unchanged: no new toolchain - registration is required. -* Gated by a flag, disabled by default: - `--//dev/dev_only_toolchains:use_rust_exe_zip_maker=yes|no|auto` (a - `string_flag`; `auto` currently behaves as `no`) + - `config_setting` used as the `toolchain()`'s `target_settings`). -* Dev-only registration: the `toolchain()` lives in - `dev/dev_only_toolchains/` and its `py_exe_zip_maker_toolchain` - implementation in `dev/dev_only_toolchains/impls/`, kept in separate - packages so registration does not load the implementation. The - implementation points at `//crates/exe_zip_maker`. It is registered in - `MODULE.bazel` with `register_toolchains(..., dev_dependency = True)`. - Nothing is registered in WORKSPACE mode (where the Rust binary is a stub). - -Why a new toolchain type instead of extending `py_exec_tools_toolchain`? -The exec tools toolchain is created per-Python-version inside the hermetic -runtime repos and is coupled to the interpreter; overriding it just for dev -would require duplicating that setup. A small, separate toolchain type is -simpler. - -Naming: tool-specific (`exe_zip_maker`). Future tools (zipper, -zip_main_maker) get their own toolchain types in the same package. - -## Work items - -- [x] Research current wiring (`py_executable.bzl`, `py_zipapp_rule.bzl`, - `actions_run`, `py_exec_tools_toolchain`). -- [x] Add `EXE_ZIP_MAKER_TOOLCHAIN_TYPE` to `toolchain_types.bzl`. -- [x] Add `toolchain_type(name = "exe_zip_maker")` in - `python/private/toolchain_types/BUILD.bazel`. -- [x] ~~Provider file~~ (dropped; tool-specific toolchain needs none). -- [x] Add `py_exe_zip_maker_toolchain.bzl` (`py_exe_zip_maker_toolchain` rule) + - `bzl_library`. -- [x] Add helper `get_exe_zip_maker(ctx)` (in `py_exe_zip_maker_toolchain.bzl`) - that prefers the toolchain and falls back to `ctx.attr._exe_zip_maker`. -- [x] Wire into `py_executable.bzl`: declare optional toolchain, use helper. -- [x] Wire into `py_zipapp_rule.bzl`: declare optional toolchain, use helper. -- [x] Define dev toolchain in `dev/dev_only_toolchains/BUILD.bazel` - (`py_exe_zip_maker_toolchain` + `toolchain`). -- [x] Register dev toolchain in `MODULE.bazel` with `dev_dependency = True`. -- [x] Update `bzl_library` deps in `python/private/BUILD.bazel` and - `python/private/zipapp/BUILD.bazel`. -- [x] Add analysis tests (`tests/exe_zip_maker_toolchain/`): Rust tool used - for `py_binary` and `py_zipapp` when the flag is on; Python fallback - when off. -- [x] Add `use_rust_exe_zip_maker` flag (default off) gating the toolchain. -- [x] Verify: `bazel build //crates/...`, `bazel test --config=fast-tests` - on `//tests/exe_zip_maker_toolchain/... //tests/py_zipapp/... - //tests/exe_zip_maker/... //tests/tools/zipapp/... //tests/base_rules/...` - (98 pass, 4 skipped), `bazel build //docs:docs` (ok), buildifier (ok). -- [x] Verify WORKSPACE mode falls back to the Python tool. Bazel 9 has no - WORKSPACE, so checked with `USE_BAZEL_VERSION=8.x` + `--noenable_bzlmod - --enable_workspace`: both `PyBuildExecutableZip` and - `PyZipAppCreateExecutableZip` invoke `tools/zipapp/exe_zip_maker_.py`. -- [x] News entry: skipped; dev-only, no user-visible behavior change - (same as PR #4151). -- [x] Create PR: draft #4215 - (https://github.com/bazel-contrib/rules_python/pull/4215). - -## Progress log - -* 2026-10-04: Plan written. -* 2026-10-04: Implementation done; bzlmod `aquery` shows both zip actions - executing `bazel-out/.../crates/exe_zip_maker/exe_zip_maker`. -* 2026-10-04: Tests, docs, buildifier, WORKSPACE fallback all verified. -* 2026-10-04: Per review, renamed to be tool-specific - (`//python/private/toolchain_types:exe_zip_maker`, - `py_exe_zip_maker_toolchain`) and dropped the generic provider. Tests - re-run and pass. - -## Open questions / follow-ups - -* Should the default (Python) tool also be delivered via a registered - toolchain in a later change, so the attribute fallback can be removed? -* Later: add `zipper`/`zip_main_maker` toolchain types alongside. From cf3e42c4d8ff5a1af510b5637e0dde58c860bb2a Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 5 Oct 2026 00:43:49 -0700 Subject: [PATCH 5/7] refactor(zipapp): only consult exe_zip_maker toolchain from py_zipapp The `--build_python_zip` path in py_executable is deprecated, so there's no need to wire the toolchain into py_binary/py_test. Restrict the toolchain lookup to the py_zipapp rules and adjust the tests to cover both flag states through py_zipapp instead. --- python/private/BUILD.bazel | 1 - python/private/py_executable.bzl | 7 +- .../exe_zip_maker_toolchain_tests.bzl | 77 ++++++------------- 3 files changed, 23 insertions(+), 62 deletions(-) diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel index 35dd1d0347..b88fb724a4 100644 --- a/python/private/BUILD.bazel +++ b/python/private/BUILD.bazel @@ -560,7 +560,6 @@ bzl_library( ":toolchain_types", ":transition_labels", ":venv_runfiles", - "//python/private/zipapp:py_exe_zip_maker_toolchain", "@bazel_skylib//lib:dicts", "@bazel_skylib//lib:paths", "@bazel_skylib//lib:structs", diff --git a/python/private/py_executable.bzl b/python/private/py_executable.bzl index dc56e83f78..29500f9ad2 100644 --- a/python/private/py_executable.bzl +++ b/python/private/py_executable.bzl @@ -19,7 +19,6 @@ 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("//python/private/zipapp:py_exe_zip_maker_toolchain.bzl", "get_exe_zip_maker") load(":attr_builders.bzl", "attrb") load( ":attributes.bzl", @@ -73,7 +72,6 @@ load( ":toolchain_types.bzl", "CC_TOOLCHAIN_TYPE", "EXEC_TOOLS_TOOLCHAIN_TYPE", - "EXE_ZIP_MAKER_TOOLCHAIN_TYPE", "LAUNCHER_MAKER_TOOLCHAIN_TYPE", TOOLCHAIN_TYPE = "TARGET_TOOLCHAIN_TYPE", ) @@ -1130,11 +1128,9 @@ def _create_executable_zip_file( args.add(prelude) args.add(zip_file) args.add(output) - exe_zip_maker = get_exe_zip_maker(ctx) actions_run( ctx, - executable = exe_zip_maker.executable, - toolchain = exe_zip_maker.toolchain, + executable = ctx.attr._exe_zip_maker, arguments = [args], inputs = depset([prelude, zip_file]), outputs = [output], @@ -2246,7 +2242,6 @@ def create_executable_rule_builder(implementation, **kwargs): toolchains = [ ruleb.ToolchainType(TOOLCHAIN_TYPE), ruleb.ToolchainType(EXEC_TOOLS_TOOLCHAIN_TYPE, mandatory = False), - ruleb.ToolchainType(EXE_ZIP_MAKER_TOOLCHAIN_TYPE, mandatory = False), ruleb.ToolchainType(CC_TOOLCHAIN_TYPE, mandatory = False), ] + ([ruleb.ToolchainType(LAUNCHER_MAKER_TOOLCHAIN_TYPE)] if rp_config.bazel_9_or_later else []), cfg = dict( diff --git a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl index d8db9d907e..c9aabb43f8 100644 --- a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl +++ b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl @@ -1,12 +1,10 @@ -"""Tests that the dev-only build tools toolchain is used.""" +"""Tests that the dev-only exe_zip_maker toolchain is used.""" load("@rules_testing//lib:analysis_test.bzl", "analysis_test") load("@rules_testing//lib:test_suite.bzl", "test_suite") load("@rules_testing//lib:truth.bzl", "matching") load("@rules_testing//lib:util.bzl", rt_util = "util") load("//python:py_binary.bzl", "py_binary") -load("//python/private:common.bzl", "maybe_builtin_build_python_zip") # buildifier: disable=bzl-visibility -load("//python/private:common_labels.bzl", "labels") # buildifier: disable=bzl-visibility load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") load("//tests/support:support.bzl", "SUPPORTS_BZLMOD") @@ -23,93 +21,62 @@ _RUST_EXE_ZIP_MAKER_SUFFIX = "crates/exe_zip_maker/exe_zip_maker" _PYTHON_EXE_ZIP_MAKER_SUFFIX = "tools/zipapp/exe_zip_maker_.py" _USE_RUST_FLAG = str(Label("//dev/dev_only_toolchains:use_rust_exe_zip_maker")) -def _test_py_binary_uses_toolchain_exe_zip_maker(name): +def _define_py_zipapp_subject(name): rt_util.helper_target( py_binary, - name = name + "_subject", + name = name + "_bin", srcs = ["main.py"], main = "main.py", ) - analysis_test( - name = name, - impl = _test_py_binary_uses_toolchain_exe_zip_maker_impl, - target = name + "_subject", - config_settings = { - labels.BUILD_PYTHON_ZIP: True, - _USE_RUST_FLAG: "yes", - } | maybe_builtin_build_python_zip("true"), - attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, - ) - -def _test_py_binary_uses_toolchain_exe_zip_maker_impl(env, target): - action = env.expect.that_target(target).action_named( - "PyBuildExecutableZip", - ) - action.argv().contains_predicate( - matching.str_endswith(_RUST_EXE_ZIP_MAKER_SUFFIX), - ) - -_tests.append(_test_py_binary_uses_toolchain_exe_zip_maker) - -def _test_py_binary_flag_disabled_uses_python_exe_zip_maker(name): rt_util.helper_target( - py_binary, + py_zipapp_binary, name = name + "_subject", - srcs = ["main.py"], - main = "main.py", + binary = name + "_bin", ) + +def _test_py_zipapp_uses_toolchain_exe_zip_maker(name): + _define_py_zipapp_subject(name) analysis_test( name = name, - impl = _test_py_binary_flag_disabled_uses_python_exe_zip_maker_impl, + impl = _test_py_zipapp_uses_toolchain_exe_zip_maker_impl, target = name + "_subject", config_settings = { - labels.BUILD_PYTHON_ZIP: True, - _USE_RUST_FLAG: "no", - } | maybe_builtin_build_python_zip("true"), + _USE_RUST_FLAG: "yes", + }, attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, ) -def _test_py_binary_flag_disabled_uses_python_exe_zip_maker_impl(env, target): +def _test_py_zipapp_uses_toolchain_exe_zip_maker_impl(env, target): action = env.expect.that_target(target).action_named( - "PyBuildExecutableZip", + "PyZipAppCreateExecutableZip", ) action.argv().contains_predicate( - matching.str_endswith(_PYTHON_EXE_ZIP_MAKER_SUFFIX), + matching.str_endswith(_RUST_EXE_ZIP_MAKER_SUFFIX), ) -_tests.append(_test_py_binary_flag_disabled_uses_python_exe_zip_maker) +_tests.append(_test_py_zipapp_uses_toolchain_exe_zip_maker) -def _test_py_zipapp_uses_toolchain_exe_zip_maker(name): - rt_util.helper_target( - py_binary, - name = name + "_bin", - srcs = ["main.py"], - main = "main.py", - ) - rt_util.helper_target( - py_zipapp_binary, - name = name + "_subject", - binary = name + "_bin", - ) +def _test_py_zipapp_flag_disabled_uses_python_exe_zip_maker(name): + _define_py_zipapp_subject(name) analysis_test( name = name, - impl = _test_py_zipapp_uses_toolchain_exe_zip_maker_impl, + impl = _test_py_zipapp_flag_disabled_uses_python_exe_zip_maker_impl, target = name + "_subject", config_settings = { - _USE_RUST_FLAG: "yes", + _USE_RUST_FLAG: "no", }, attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, ) -def _test_py_zipapp_uses_toolchain_exe_zip_maker_impl(env, target): +def _test_py_zipapp_flag_disabled_uses_python_exe_zip_maker_impl(env, target): action = env.expect.that_target(target).action_named( "PyZipAppCreateExecutableZip", ) action.argv().contains_predicate( - matching.str_endswith(_RUST_EXE_ZIP_MAKER_SUFFIX), + matching.str_endswith(_PYTHON_EXE_ZIP_MAKER_SUFFIX), ) -_tests.append(_test_py_zipapp_uses_toolchain_exe_zip_maker) +_tests.append(_test_py_zipapp_flag_disabled_uses_python_exe_zip_maker) def exe_zip_maker_toolchain_test_suite(name): test_suite( From 9308be7ac2d3772c9c9cc3e744c6bcee29fe34cf Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 5 Oct 2026 01:17:39 -0700 Subject: [PATCH 6/7] fix(zipapp): add distribution target to toolchain_types package `//python/private:distribution` auto-discovers subpackages and expects each to declare a `:distribution` filegroup. The new `toolchain_types` package lacked one, causing loading-phase errors in every CI job that builds `//...`. --- python/private/toolchain_types/BUILD.bazel | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/private/toolchain_types/BUILD.bazel b/python/private/toolchain_types/BUILD.bazel index 33c7f1e5bc..b223730374 100644 --- a/python/private/toolchain_types/BUILD.bazel +++ b/python/private/toolchain_types/BUILD.bazel @@ -3,10 +3,16 @@ # These are in a separate package so toolchain() registrations and rules can # depend on the type without loading the toolchain implementation. +load("//python/private:distribution_filegroup.bzl", "distribution_filegroup") + package(default_visibility = ["//:__subpackages__"]) licenses(["notice"]) +distribution_filegroup( + name = "distribution", +) + # Toolchain providing the tool that creates self-executable zip files # (` `). It is optional: when no toolchain is # registered, rules fall back to the Python `//tools/zipapp:exe_zip_maker`. From 489aa2e8c327fceef7cb101b25bc83b2aef267b0 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 5 Oct 2026 01:31:45 -0700 Subject: [PATCH 7/7] fix(zipapp): skip exe_zip_maker toolchain tests on Windows On Windows, py_zipapp emits the Bazel launcher instead of a self-executable zip, so the PyZipAppCreateExecutableZip action the tests assert on never exists there. --- .../exe_zip_maker_toolchain_tests.bzl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl index c9aabb43f8..bcf019bfa9 100644 --- a/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl +++ b/tests/exe_zip_maker_toolchain/exe_zip_maker_toolchain_tests.bzl @@ -6,7 +6,7 @@ load("@rules_testing//lib:truth.bzl", "matching") load("@rules_testing//lib:util.bzl", rt_util = "util") load("//python:py_binary.bzl", "py_binary") load("//python/zipapp:py_zipapp_binary.bzl", "py_zipapp_binary") -load("//tests/support:support.bzl", "SUPPORTS_BZLMOD") +load("//tests/support:support.bzl", "NOT_WINDOWS", "SUPPORTS_BZLMOD") _tests = [] @@ -17,6 +17,10 @@ _tests = [] # # The dev toolchain is only registered under bzlmod; in WORKSPACE mode, # rules_rust is a stub and the Python fallback is used, so skip there. +# +# On Windows, py_zipapp uses the Bazel launcher instead of a self-executable +# zip, so the exe_zip_maker toolchain is never used; skip there too. +_COMPATIBLE_WITH = SUPPORTS_BZLMOD + NOT_WINDOWS _RUST_EXE_ZIP_MAKER_SUFFIX = "crates/exe_zip_maker/exe_zip_maker" _PYTHON_EXE_ZIP_MAKER_SUFFIX = "tools/zipapp/exe_zip_maker_.py" _USE_RUST_FLAG = str(Label("//dev/dev_only_toolchains:use_rust_exe_zip_maker")) @@ -43,7 +47,7 @@ def _test_py_zipapp_uses_toolchain_exe_zip_maker(name): config_settings = { _USE_RUST_FLAG: "yes", }, - attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, + attr_values = {"target_compatible_with": _COMPATIBLE_WITH}, ) def _test_py_zipapp_uses_toolchain_exe_zip_maker_impl(env, target): @@ -65,7 +69,7 @@ def _test_py_zipapp_flag_disabled_uses_python_exe_zip_maker(name): config_settings = { _USE_RUST_FLAG: "no", }, - attr_values = {"target_compatible_with": SUPPORTS_BZLMOD}, + attr_values = {"target_compatible_with": _COMPATIBLE_WITH}, ) def _test_py_zipapp_flag_disabled_uses_python_exe_zip_maker_impl(env, target):