Skip to content

fix(whl_install): use rules_python exec tools toolchain for pyc compilation#903

Merged
gregmagolan merged 1 commit into
mainfrom
fix/whl-install-exec-tools-toolchain
Apr 4, 2026
Merged

fix(whl_install): use rules_python exec tools toolchain for pyc compilation#903
gregmagolan merged 1 commit into
mainfrom
fix/whl-install-exec-tools-toolchain

Conversation

@gregmagolan
Copy link
Copy Markdown
Member

@gregmagolan gregmagolan commented Apr 4, 2026

Summary

  • Bumps rules_python (and rules_python_gazelle_plugin) from 1.0.0 to 1.9.0
  • Replaces the resolved_py_toolchain workaround in whl_install with EXEC_TOOLS_TOOLCHAIN_TYPE from rules_python, using PyExecToolsInfo.exec_runtime to get an exec-platform interpreter for compile_pyc actions
  • Removes the now-unnecessary resolved_py_toolchain rule and its BUILD target from py/private/toolchain/
  • Generates exec_tools_toolchain_type registrations in rules_py's interpreter provisioning so EXEC_TOOLS_TOOLCHAIN_TYPE resolves for all PBS-provisioned interpreters

Possible breaking change

Minimum rules_python version is now 1.9.0 (previously 1.0.0). Users must update their MODULE.bazel:

bazel_dep(name = "rules_python", version = "1.9.0")

rules_python 1.9.0 should be generally compatible with 1.0.0 but we're calling this out here just incase it is not for some users.

Background

When compile_pyc = True, whl_install needs to run compileall as a build action, which requires an interpreter that executes on the build host, not the target platform. In cross-arch builds (e.g. x86 host → arm64 target), ctx.toolchains[PY_TOOLCHAIN] gives the arm64 interpreter, which can't run on the host.

The previous fix (#881) worked around this with a resolved_py_toolchain indirection rule — a cfg="exec" label attr that re-exported the Python toolchain in exec configuration. This was a correct workaround, but rules_python already has the proper solution: EXEC_TOOLS_TOOLCHAIN_TYPE (//python:exec_tools_toolchain_type), which is registered with exec_compatible_with set to the platform constraints so toolchain resolution naturally selects the host-runnable interpreter.

PyExecToolsInfo.exec_runtime (the RBE-compatible field we need) was added in rules_python #3539, which landed in 1.9.0.

rules_py's own interpreter provisioning (py/private/interpreter/) previously only generated TARGET_TOOLCHAIN_TYPE registrations. This PR also generates exec_tools_toolchain_type toolchains for each PBS-provisioned interpreter so the new whl_install dependency resolves correctly.

Test plan

  • New crossbuild_compile_pyc_test in e2e/cases/uv-deps-650/crossbuild/ builds arm64 wheels with compile_pyc=True on the host platform — fails with "Exec format error" if the wrong interpreter is selected
  • Existing cross-arch test cases cover the broader cross-compilation path

@gregmagolan gregmagolan force-pushed the fix/whl-install-exec-tools-toolchain branch from 3a4b9a0 to da49db2 Compare April 4, 2026 18:26
@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented Apr 4, 2026

Bazel 8 (Test)

All tests were cache hits

109 tests (100.0%) were fully cached saving 40s.


Bazel 9 (Test)

All tests were cache hits

109 tests (100.0%) were fully cached saving 39s.


Bazel 8 (Test)

e2e

All tests were cache hits

47 tests (100.0%) were fully cached saving 51s.


Bazel 9 (Test)

e2e

All tests were cache hits

47 tests (100.0%) were fully cached saving 48s.


Bazel 8 (Test)

examples/uv_pip_compile

All tests were cache hits

1 test (100.0%) was fully cached saving 444ms.

Comment thread uv/private/whl_install/rule.bzl Outdated
@gregmagolan gregmagolan force-pushed the fix/whl-install-exec-tools-toolchain branch 3 times, most recently from 2cd9e85 to d4016ef Compare April 4, 2026 20:32
…lation

Replace the `resolved_py_toolchain` workaround (a cfg=exec label attr that
re-exported the Python toolchain) with the proper `EXEC_TOOLS_TOOLCHAIN_TYPE`
from rules_python, whose `exec_runtime` field provides an RBE-compatible
exec-platform interpreter via correct toolchain resolution.

This required bumping rules_python to 1.9.0, which introduced
`PyExecToolsInfo.exec_runtime` in aspect-build/rules_python#3539.

The old workaround and the new approach solve the same cross-compilation
problem: `ctx.toolchains[PY_TOOLCHAIN]` resolves the Python interpreter for
the *target* platform, which isn't executable on the build host. The exec
tools toolchain is registered with `exec_compatible_with` set to the platform
constraints (rather than `target_compatible_with`), so toolchain resolution
selects the host-runnable interpreter automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@gregmagolan gregmagolan force-pushed the fix/whl-install-exec-tools-toolchain branch from d4016ef to 073a9f2 Compare April 4, 2026 20:46
@gregmagolan gregmagolan merged commit 32e8ce3 into main Apr 4, 2026
4 checks passed
@gregmagolan gregmagolan deleted the fix/whl-install-exec-tools-toolchain branch April 4, 2026 20:56
gregmagolan added a commit that referenced this pull request Apr 4, 2026
UNPACK_TOOLCHAIN is registered in repo.bzl with exec_compatible_with
(via the cfg="exec" TOOL_CFGS entry), so ctx.toolchains[UNPACK_TOOLCHAIN]
already resolves the exec-platform binary without any indirection.
The resolved_unpack_toolchain rule and its cfg="exec" _unpack attr in
whl_install were an unnecessary workaround, analogous to resolved_py_toolchain
which was removed in #903.

Remove resolved_unpack_toolchain from tools.bzl and BUILD.bazel, and replace
the ctx.attr._unpack indirection in rule.bzl with a direct toolchain lookup.

Add an analysis-time regression test (exec_toolchain_resolution_test) that
confirms UNPACK_TOOLCHAIN resolves the exec-platform binary even when the
target platform is arm64. The test writes the resolved binary path to a file
for both native and arm64-transitioned targets and asserts they are identical,
proving exec-platform resolution is honoured throughout platform transitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gregmagolan added a commit that referenced this pull request Apr 5, 2026
UNPACK_TOOLCHAIN is registered in repo.bzl with exec_compatible_with
(via the cfg="exec" TOOL_CFGS entry), so ctx.toolchains[UNPACK_TOOLCHAIN]
already resolves the exec-platform binary without any indirection.
The resolved_unpack_toolchain rule and its cfg="exec" _unpack attr in
whl_install were an unnecessary workaround, analogous to resolved_py_toolchain
which was removed in #903.

Remove resolved_unpack_toolchain from tools.bzl and BUILD.bazel, and replace
the ctx.attr._unpack indirection in rule.bzl with a direct toolchain lookup.

Add an analysis-time regression test (exec_toolchain_resolution_test) that
confirms UNPACK_TOOLCHAIN resolves the exec-platform binary even when the
target platform is arm64. The test writes the resolved binary path to a file
for both native and arm64-transitioned targets and asserts they are identical,
proving exec-platform resolution is honoured throughout platform transitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gregmagolan added a commit that referenced this pull request Apr 5, 2026
UNPACK_TOOLCHAIN is registered in repo.bzl with exec_compatible_with
(via the cfg="exec" TOOL_CFGS entry), so ctx.toolchains[UNPACK_TOOLCHAIN]
already resolves the exec-platform binary without any indirection.
The resolved_unpack_toolchain rule and its cfg="exec" _unpack attr in
whl_install were an unnecessary workaround, analogous to resolved_py_toolchain
which was removed in #903.

Remove resolved_unpack_toolchain from tools.bzl and BUILD.bazel, and replace
the ctx.attr._unpack indirection in rule.bzl with a direct toolchain lookup.

Add an analysis-time regression test (exec_toolchain_resolution_test) that
confirms UNPACK_TOOLCHAIN resolves the exec-platform binary even when the
target platform is arm64. The test writes the resolved binary path to a file
for both native and arm64-transitioned targets and asserts they are identical,
proving exec-platform resolution is honoured throughout platform transitions.
gregmagolan added a commit that referenced this pull request Apr 5, 2026
UNPACK_TOOLCHAIN is registered in repo.bzl with exec_compatible_with
(via the cfg="exec" TOOL_CFGS entry), so ctx.toolchains[UNPACK_TOOLCHAIN]
already resolves the exec-platform binary without any indirection.
The resolved_unpack_toolchain rule and its cfg="exec" _unpack attr in
whl_install were an unnecessary workaround, analogous to resolved_py_toolchain
which was removed in #903.

Remove resolved_unpack_toolchain from tools.bzl and BUILD.bazel, and replace
the ctx.attr._unpack indirection in rule.bzl with a direct toolchain lookup.

Add an analysis-time regression test (exec_toolchain_resolution_test) that
confirms UNPACK_TOOLCHAIN resolves the exec-platform binary even when the
target platform is arm64. The test writes the resolved binary path to a file
for both native and arm64-transitioned targets and asserts they are identical,
proving exec-platform resolution is honoured throughout platform transitions.
gregmagolan added a commit that referenced this pull request Apr 5, 2026
UNPACK_TOOLCHAIN is registered in repo.bzl with exec_compatible_with
(via the cfg="exec" TOOL_CFGS entry), so ctx.toolchains[UNPACK_TOOLCHAIN]
already resolves the exec-platform binary without any indirection.
The resolved_unpack_toolchain rule and its cfg="exec" _unpack attr in
whl_install were an unnecessary workaround, analogous to resolved_py_toolchain
which was removed in #903.

Remove resolved_unpack_toolchain from tools.bzl and BUILD.bazel, and replace
the ctx.attr._unpack indirection in rule.bzl with a direct toolchain lookup.

Add an analysis-time regression test (exec_toolchain_resolution_test) that
confirms UNPACK_TOOLCHAIN resolves the exec-platform binary even when the
target platform is arm64. The test writes the resolved binary path to a file
for both native and arm64-transitioned targets and asserts they are identical,
proving exec-platform resolution is honoured throughout platform transitions.
gregmagolan added a commit that referenced this pull request Apr 5, 2026
)

## Summary

- `UNPACK_TOOLCHAIN` is registered with `exec_compatible_with` (via
`cfg="exec"` in `TOOL_CFGS`), so `ctx.toolchains[UNPACK_TOOLCHAIN]`
already resolves the exec-platform binary — no indirection needed.
- Remove `resolved_unpack_toolchain` rule from `tools.bzl` and
`BUILD.bazel`.
- Replace the `cfg="exec"` `_unpack` attr +
`ctx.attr._unpack[ToolchainInfo].bin.bin` pattern in
`whl_install/rule.bzl` with a direct
`ctx.toolchains[UNPACK_TOOLCHAIN].bin.bin` lookup.
- Add `exec_toolchain_resolution_test` to the `uv-deps-650/crossbuild`
e2e case: writes the resolved unpack binary path for both a native
target and an arm64-transitioned target, then asserts the paths are
identical (proving exec-platform resolution is preserved across
target-platform transitions).

This is a follow-on cleanup to #903, which removed the analogous
`resolved_py_toolchain` workaround.

## Test plan

- [x] Existing e2e suite passes (crossbuild_compile_pyc_test still
green)
- [x] New `exec_toolchain_resolution_test` passes on Linux CI (arm64 and
amd64 transitions resolve the same exec-platform binary)
- [x] No references to `resolved_unpack_toolchain` remain
return """\
load("@rules_python//python:py_runtime.bzl", "py_runtime")
load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
load("@rules_python//python/private:py_exec_tools_toolchain.bzl", "py_exec_tools_toolchain")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not be importing private stuff from rules_python, isn't this also exposed publicly?

gregmagolan added a commit that referenced this pull request Apr 5, 2026
…, drop min version to 1.0.0 (#913)

## Summary

#903 bumped the minimum `rules_python` version from `1.0.0` to `1.9.0`
to gain access to `PyExecToolsInfo.exec_runtime` (added in rules_python
1.9.0) and loaded `py_exec_tools_toolchain` from `rules_python`'s
private API. This PR reverses the version bump by hoisting the necessary
code into rules_py and removes all imports from `rules_python/private`.

## Changes

**New `py/private/exec_tools/defs.bzl`** — hoisted from rules_python:
- `PyExecToolsInfo` provider with only an `exec_runtime` field. We own
the provider so we can include `exec_runtime` (the field added in 1.9.0)
regardless of which rules_python version the user has.
- `py_exec_tools_toolchain` rule — instantiated in each PBS interpreter
repo; derives `exec_runtime` from
`exec_interpreter[ToolchainInfo].py3_runtime` (the pre-1.9.0
equivalent).
- `current_interpreter_executable` rule — resolves `PY_TOOLCHAIN` to
create an exec-configured interpreter symlink; defaults for the above
rule.

**`py/private/toolchain/BUILD.bazel`** — added
`exec_tools_toolchain_type` toolchain type. We declare our own type
rather than reusing `@rules_python//python:exec_tools_toolchain_type`,
so our `PyExecToolsInfo` never needs to satisfy rules_python's internal
interface (e.g. the `precompiler` field that `maybe_precompile` checks
for).

**`py/private/toolchain/types.bzl`** — `EXEC_TOOLS_TOOLCHAIN` now points
to `@aspect_rules_py//py/private/toolchain:exec_tools_toolchain_type`.

**`py/private/interpreter/repository.bzl`** — generated interpreter
BUILD files now load `py_exec_tools_toolchain` from
`@aspect_rules_py//py/private/exec_tools:defs.bzl` and register under
our own toolchain type instead of
`@rules_python//python:exec_tools_toolchain_type`.

**`uv/private/whl_install/rule.bzl`** — `EXEC_TOOLS_TOOLCHAIN` now
loaded from our own `types.bzl`.

**`MODULE.bazel` / `e2e/MODULE.bazel`** — `rules_python` and
`rules_python_gazelle_plugin` dropped back to `1.0.0`.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: aspect-marvin[bot] <marvin@aspect.build>
gregmagolan added a commit that referenced this pull request Apr 6, 2026
…oolchain (#924)

## Problem

The BCR presubmit fails on Bazel 9 for
`//cases/uv-deps-650/crossbuild:crossbuild_compile_pyc_test`:

```
While resolving toolchains for target //cases/uv-deps-650/crossbuild:app_bin:
No matching toolchains found for types:
  @@bazel_tools//tools/python:toolchain_type
```

The toolchain resolution debug log shows why — when evaluating `app_bin`
for an arm64 target on an x86_64 exec host, the arm64 Python interpreter
toolchain is rejected with:

```
Rejected toolchain python_3_12_aarch64_unknown_linux_gnu; mismatching values: aarch64
```

This is Bazel 9 enforcing `exec_compatible_with` strictly. The aarch64
Python interpreter was registered with `exec_compatible_with =
["@platforms//cpu:aarch64", ...]`, which doesn't match the x86_64 exec
platform. Bazel 8 ignored this mismatch; Bazel 9 enforces it.

## Root cause

Since the original
[python-build-standalone](https://github.com/astral-sh/python-build-standalone)
(PBS) interpreter commit (`b2feee5`), every Python interpreter toolchain
has been registered with:

```python
toolchain(
    name = "python_3_12_aarch64_unknown_linux_gnu",
    exec_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"],  # WRONG
    target_compatible_with = ["@platforms//os:linux", "@platforms//cpu:aarch64"],
    ...
    toolchain_type = "@bazel_tools//tools/python:toolchain_type",
)
```

Setting `exec_compatible_with` on a
`@bazel_tools//tools/python:toolchain_type` toolchain is semantically
wrong. The Python interpreter here is a **runtime dependency** — it ends
up inside the virtualenv and runs on the **target machine**, not on the
build host. The exec platform constraint is irrelevant and actively
harmful during cross-compilation.

This is distinct from `exec_tools_toolchain_type`, which represents the
interpreter that runs **build actions** (e.g. `compileall` in
`whl_install`). That toolchain correctly uses `exec_compatible_with` —
it must run on the exec host, and #903 was specifically designed to
handle this case properly. This fix does not touch that path.

## Fix

Remove `exec_compatible_with` from the
`@bazel_tools//tools/python:toolchain_type` registration.
`target_compatible_with` alone is sufficient to select the right
interpreter for the target platform.

## Test plan
- [x] All e2e tests continue to pass (native builds unaffected — target
constraint still selects the right interpreter)
- [x] `//cases/uv-deps-650/crossbuild:crossbuild_compile_pyc_test` still
passes (whl_install exec interpreter still resolved via
`exec_tools_toolchain_type`)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants