Skip to content

feat(uv): parameterize the platform parent in gazelle_python_manifest - #1470

Merged
xangcastle merged 4 commits into
mainfrom
xangcastle/gazelle_platform_parents
Aug 20, 2026
Merged

xangcastle merged 4 commits into
mainfrom
xangcastle/gazelle_platform_parents

Conversation

@xangcastle

Copy link
Copy Markdown
Member

Fixes #1416. Supersedes #1468, carrying @sallustfire's commit unchanged (cherry-picked, authorship preserved) plus three follow-up commits.

Base change (from #1468, by @sallustfire)

gazelle_python_manifest's synthetic per-venv platforms hardcoded parents = ["@platforms//host"], so the platform_transition_filegroup over the hub's wheel index replaced the caller's --platforms wholesale. That broke custom --host_platform setups (hermetic cc toolchains) and cross-compilation. The fix parameterizes the parent platform; the default preserves current behavior and is resolved as a Label in rules_py's own repository, so consumers need no bazel_dep on platforms.

Follow-up commits

platform_parent is a single label, not a list. The list form failed at load time on any length other than one — promising plurality the underlying platform() rule does not support. Renamed and made singular before the API ships. The load-time check now rejects any non-string, non-Label value (lists, selects) with a message naming the parameter.

Wheel selection is pinned under a real cross-compilation parent. The e2e case's parent is now a fully-specified linux_x86_64 platform carrying the platform_libc/platform_version flags — issue #1416's actual scenario, and host-invariant. The flags ride on the platform because their defaults are host-detected: without them, manylinux config_settings never match off-linux and native packages silently degrade to sdist fallbacks (the other half of the config chimera the issue describes; this is also the documented recipe for real cross-compilation parents). A new analysis-time probe snapshots which wheel variant each hub dependency resolves to (distribution: platform-class lines — no versions or tag spellings, so lock bumps don't churn the golden). Native packages pin to linux_x86_64, pure ones to any, and sdist fallbacks surface explicitly. The manifest YAML golden is replaced by a build_test: the YAML content is platform-independent, so it only re-pinned another case's hub state; building the manifest end-to-end from linux-selected wheels is the actual signal.

Gazelle manifest tooling moved to a tools/ subpackage. generate.py and update.sh move out of the package holding the public defs.bzl into //uv/private/gazelle_manifest/tools with absolute labels; implicit-attribute visibility is checked against the defining .bzl's package, so :generator stays private.

Test plan

  • 37/37 e2e/cases tests pass (36 snapshot diff tests + the new manifest_build_test); goldens regenerated via bazel run //:snapshots on darwin_arm64 — the wheel-selection golden shows linux_x86_64 wheels selected from a mac host.
  • Regression detection verified by simulating the revert (macro ignoring platform_parent): the constraint probe flips to default-parent and the wheel probe flips to macosx_* wheels.
  • //uv/private/gazelle_manifest/tests:test passes; buildifier clean on changed files.

  • Changes are visible to end-users: yes
  • Searched for relevant documentation and updated as needed: yes (docs/uv.md Gazelle section)
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

gazelle_python_manifest now accepts platform_parent, letting the manifest's per-venv wheel selection parent onto a custom platform. Needed for repos with a custom --host_platform (hermetic C++ toolchains) and for cross-compilation; in the cross-compilation case, carry the platform_libc/platform_version flags on the parent platform.

🤖 Generated with Claude Code

The per-venv synthetic platform gazelle_python_manifest generates
hardcoded parents = ["@platforms//host"], and the dep_group transition
replaces --platforms wholesale. Anything building under the transitioned
configuration therefore resolved toolchains against a bare os+cpu
platform, which breaks builds with a custom --host_platform (hermetic cc
toolchains gating on extra constraints fail sdist builds in the hub at
analysis) and cross-compilation (wheel selection snaps back to the host
platform, issue #1416).

Add a platform_parents parameter, defaulting to the current behavior so
existing callers are unaffected, and thread it into the synthetic
platform's parents. The list must hold exactly one label (platform()
accepts at most one parent); the macro fails eagerly with a message
naming the parameter otherwise. The default is built as
Label("@platforms//host") in the defining module, so callers need no
direct bazel_dep on platforms to use it.

A new e2e snapshot case builds a manifest under a caller-supplied parent
and pins a probe transitioned through the macro-generated platform,
selecting on a constraint only the custom parent carries.

Fixes #1416

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8fcb2f5f-7763-452e-bd51-3af4f70e1085

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@aspect-workflows

aspect-workflows Bot commented Aug 20, 2026

Copy link
Copy Markdown

✨ Aspect Workflows Tasks

📅 Thu Aug 20 03:08:38 UTC 2026

✅ 44 successful tasks

  • ✅ buildifier · ⏱ 19.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle · ⏱ 16.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ test-e2e-bazel-8 [test] · ⏱ 7m 32s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (304/304 passed)
  • ✅ test-e2e-bazel-9 [test] · ⏱ 5m 53s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (294/294 passed)
  • ✅ test-e2e-crossbuild-bazel-8 [test] · ⏱ 1m 2s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (10/10 passed)
  • ✅ test-e2e-crossbuild-bazel-9 [test] · ⏱ 1m 14s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (10/10 passed)
  • ✅ test-e2e-interpreter-build-config-bazel-8 [test] · ⏱ 18.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-interpreter-build-config-bazel-9 [test] · ⏱ 1m 4s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-interpreter-input-validation-bazel-8 [test] · ⏱ 14.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-interpreter-input-validation-bazel-9 [test] · ⏱ 1m 4s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-interpreter-runtime-metadata-bazel-8 [test] · ⏱ 22.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (2/2 passed)
  • ✅ test-e2e-interpreter-runtime-metadata-bazel-9 [test] · ⏱ 1m 25s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (2/2 passed)
  • ✅ test-e2e-interpreter-toolchain-settings-bazel-8 [test] · ⏱ 21.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-interpreter-toolchain-settings-bazel-9 [test] · ⏱ 1m 14s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-rules-proto-grpc-python-bazel-8 [test] · ⏱ 1m 34s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-rules-proto-grpc-python-bazel-9 [test] · ⏱ 1m 8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-e2e-rules-python-interop-bazel-8 [test] · ⏱ 49s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (15/15 passed)
  • ✅ test-e2e-rules-python-interop-bazel-9 [test] · ⏱ 1m 13s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (15/15 passed)
  • ✅ test-e2e-rules-python-provider-compat-bazel-8 [test] · ⏱ 25.5s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (4/4 passed)
  • ✅ test-e2e-rules-python-provider-compat-bazel-9 [test] · ⏱ 50.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (4/4 passed)
  • ✅ test-examples-debugger-bazel-8 [test] · ⏱ 30.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-debugger-bazel-9 [test] · ⏱ 1m 20s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-dev_deps-bazel-8 [test] · ⏱ 32.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-dev_deps-bazel-9 [test] · ⏱ 44.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-django-bazel-8 [test] · ⏱ 24.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-django-bazel-9 [test] · ⏱ 1m 32s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-multi_version-bazel-8 [test] · ⏱ 30.4s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (2/2 passed)
  • ✅ test-examples-multi_version-bazel-9 [test] · ⏱ 1m 1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (2/2 passed)
  • ✅ test-examples-protobuf-bazel-8 [test] · ⏱ 1m 24s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-protobuf-bazel-9 [test] · ⏱ 1m 34s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-py_binary-bazel-8 [test] · ⏱ 26s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-py_binary-bazel-9 [test] · ⏱ 1m 16s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-py_pex_binary-bazel-8 [test] · ⏱ 24.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-py_pex_binary-bazel-9 [test] · ⏱ 40.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-examples-py_venv-bazel-8 [test] · ⏱ 28.6s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (3/3 passed · 3 cached)
  • ✅ test-examples-py_venv-bazel-9 [test] · ⏱ 42.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (3/3 passed)
  • ✅ test-examples-pytest-bazel-8 [test] · ⏱ 45.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (11/11 passed)
  • ✅ test-examples-pytest-bazel-9 [test] · ⏱ 1m 15s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (11/11 passed)
  • ✅ test-examples-uv_pip_compile-bazel-8 [test] · ⏱ 31.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-uv_pip_compile-bazel-9 [test] · ⏱ 39.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-virtual_deps-bazel-8 [test] · ⏱ 22.9s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-examples-virtual_deps-bazel-9 [test] · ⏱ 39s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)
  • ✅ test-root-bazel-8 [test] · ⏱ 2m 20s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (300/300 passed)
  • ✅ test-root-bazel-9 [test] · ⏱ 3m 1s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (299/299 passed)

⏱ Last updated Thu Aug 20 03:16:02 UTC 2026 · 📊 GitHub API quota 2,524/15,000 (17% used, resets in 10m)
🚀 Powered by Aspect CLI (v2026.28.2)  |  Aspect Build · X · LinkedIn · YouTube

@xangcastle
xangcastle force-pushed the xangcastle/gazelle_platform_parents branch from 3a7a7fb to 58cf020 Compare August 20, 2026 03:02
Moves generate.py and update.sh out of the package that holds the public
defs.bzl into //uv/private/gazelle_manifest/tools with its own BUILD, and
switches the _generator implicit attribute and the update label to absolute
labels. Implicit-attribute visibility is checked against the .bzl's package,
so the generator stays private to //uv/private/gazelle_manifest.
…abel

Replaces the platform_parents single-element-list parameter (not yet
released) with a singular platform_parent label before the API ships. A
list that fails on any length other than one promises plurality the
underlying platform() rule does not support. The load-time check now
rejects any non-string, non-Label value (lists, selects) with a message
naming the parameter, instead of dying inside len() or native.platform.

Also documents the cross-compilation trade-off: with platform_parent set to
a target platform, <name>.update builds sdist fallbacks for that platform,
which needs a capable execution platform (e.g. RBE) unless everything
resolves to wheels.
Strengthens the gazelle-platform-parents-1416 case to cover the behavior
issue #1416 actually reported — wheel selection following the caller's
platform — not just constraint inheritance:

- parent_platform is now a fully-specified linux_x86_64 platform carrying
  the platform_libc/platform_version flags, making the case the real
  cross-compilation scenario and host-invariant. The flags ride on the
  platform because their defaults are host-detected: without them manylinux
  config_settings never match off-linux and native packages silently
  degrade to sdist fallbacks.
- A new analysis-time probe snapshots which wheel variant each hub
  dependency resolves to under the macro-generated platform
  (distribution: platform-class lines, no versions or tag spellings, so
  lock bumps don't churn the golden). Native packages pin to linux_x86_64,
  pure ones to any, and any sdist fallback shows up explicitly. Verified by
  simulating the regression: the golden flips to macosx wheels on a darwin
  host.
- The manifest YAML golden is replaced by a build_test: the YAML content is
  platform-independent, so the golden only re-pinned another case's hub
  state; building the manifest end-to-end from the linux-selected wheels is
  the actual signal.
@github-actions

Copy link
Copy Markdown

py_binary startup benchmark

Version Mean (ms) Median (ms) ± stddev vs BCR vs main Build (s)
BCR 1.11.7 (baseline) 157.941 157.764 ±1.010 1.67
HEAD main 55.430 55.307 ±0.758 -64.9% 2.02
This PR 55.230 55.150 ±0.582 -65.0% -0.3% 1.52

Measured with hyperfine --warmup 5 --runs 50 --shell=none on Linux
Gate: PR vs HEAD main median (threshold: 10%, and must exceed the 2×SE noise floor, here 0.5%). BCR is shown only as a historical baseline.
Build time: cold bazel build //:bench with isolated output base, no disk cache; external repos prefetched so network is excluded.

sys.path quality

Version sys.path entries distinct site-packages roots duplicate realpaths
BCR 1.11.7 (baseline) 6 1 0
HEAD main 7 2 0
This PR 7 2 0

sys.path quality measured by bench_syspath inside the assembled venv. Duplicate realpaths indicate symlink redundancy; many distinct site-packages roots suggest an inefficient venv layout.

Bazel analysis benchmark

Version Mean (ms) Median (ms) ± stddev vs BCR vs main Targets Actions
BCR 2.0.0-alpha.6 (baseline) 1096.899 1028.354 ±146.792 304 13746
HEAD main 1099.973 1037.167 ±177.348 +0.9% 304 13546
This PR 1131.387 1050.757 ±217.106 +2.2% +1.3% 304 13546

Measured with hyperfine --warmup 1 --runs 10 on Linux
Gate: PR vs HEAD main median (threshold: 10%, and must exceed the 2×SE noise floor, here 17.1%). BCR is shown only as a historical baseline.
Command: warm-server bazel build --nobuild //workspace/..., analysis cache discarded each run via a fresh --action_env value; no disk cache.

py_image_layer benchmark

Scenario Version Mean (s) Median (s) ± stddev Actions vs BCR vs main
Analysis BCR 2.0.0-alpha.6 (baseline) 0.70 0.63 ±0.16 747
Analysis HEAD main 0.68 0.65 ±0.15 725 +3.6%
Analysis This PR 0.67 0.61 ±0.16 725 -2.7% -6.1%
1p Source Change BCR 2.0.0-alpha.6 (baseline) 0.66 0.65 ±0.04 4
1p Source Change HEAD main 0.65 0.64 ±0.03 4 -1.7%
1p Source Change This PR 0.64 0.63 ±0.03 4 -2.2% -0.5%
3p Source Change BCR 2.0.0-alpha.6 (baseline) 0.81 0.80 ±0.05 5
3p Source Change HEAD main 0.80 0.80 ±0.02 5 +0.3%
3p Source Change This PR 0.80 0.80 ±0.02 5 +0.1% -0.1%

Measured with hyperfine on Linux, building //workspace:image_layers (10 binaries, ~30-wheel dep pool, grouped first-party/pip/interpreter tier) with isolated output base, no disk cache.
Scenarios: analysis = warm-server bazel build --nobuild, re-analyzed each run via a fresh --action_env value; incrementals run against a built state with warm analysis: source = append to the last package's lib.py, wheel = rewrite click post_install_patches content.
Actions: for Analysis, the total action count behind the image target from aquery deps(...); for incrementals, actions re-executed for the mutation, from a single instrumented run's BEP build metrics (deterministic; per-mnemonic breakdown in the *-actions.json artifacts). Informational only, not gated.
Gate: PR vs HEAD main median per scenario (threshold: 10%, and must exceed the 2×SE noise floor). BCR is shown only as a historical baseline.

@xangcastle
xangcastle marked this pull request as ready for review August 20, 2026 03:20
@xangcastle
xangcastle requested a review from jbedard August 20, 2026 03:20
@xangcastle

Copy link
Copy Markdown
Member Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@xangcastle

Copy link
Copy Markdown
Member Author

@codex

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 201d27afaa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread e2e/cases/gazelle-platform-parents-1416/wheel_selection_probe.bzl
@xangcastle
xangcastle merged commit 15a3bb2 into main Aug 20, 2026
98 checks passed
@xangcastle
xangcastle deleted the xangcastle/gazelle_platform_parents branch August 20, 2026 20:42
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.

[Bug]: gazelle_python_manifest hard-codes @platforms//host, breaks cross-platform builds

3 participants