Skip to content

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

Open
sallustfire wants to merge 1 commit into
aspect-build:mainfrom
sallustfire:gazelle-manifest-platform-parents
Open

sallustfire wants to merge 1 commit into
aspect-build:mainfrom
sallustfire:gazelle-manifest-platform-parents

Conversation

@sallustfire

Copy link
Copy Markdown
Contributor

AI written, human reviewed.

Fixes #1416.

Per venv, gazelle_python_manifest declares a throwaway platform() whose flags force --@<hub>//dep_group=<venv>, then pushes the hub's gazelle_index_whls filegroup through platform_transition_filegroup. That synthetic platform hardcodes parents = ["@platforms//host"], and the transition replaces --platforms wholesale, so everything that has to build under the transitioned configuration resolves toolchains against a bare os+cpu platform. Two real setups break:

  1. Custom --host_platform / hermetic toolchains. With common --host_platform=@host_platform//:platform (a platform carrying libc/kernel/etc. constraints that hermetic cc toolchains gate on) and --repo_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1, PEP 517 sdist builds in the hub (python-ldap, PyMuPDF, ...) fail analysis under the transition in our build:

    ERROR: While resolving toolchains for target
    @@aspect_rules_py++uv+sdist_build__diskover__python_ldap__3_4_5//:whl:
    No matching toolchains found for types:
      @@bazel_tools//tools/cpp:toolchain_type
    

Those sdists are the packages the manifest exists to index (python-ldap imports as ldap/ldapurl/ldif, PyMuPDF as fitz), so filtering them out isn't an option.

  1. Cross-compilation ([Bug]: gazelle_python_manifest hard-codes @platforms//host, breaks cross-platform builds #1416). Building with --platforms=<linux target> from a darwin host, the transition pulls wheel selection back to the darwin host platform and fails with No matching toolchains found for types: @@aspect_rules_py+//py/private/toolchain:native_build_toolchain_type.

The fix

A platform_parents parameter, threaded into the synthetic platform's parents. The default preserves current behavior exactly.

  • The list must hold exactly one label, since platform() accepts at most one parent, and the macro fails eagerly at load time with a message naming the parameter otherwise. That also rejects the empty list, whose constraint-free platform would quietly degrade wheel selection to sdist fallbacks. The list shape mirrors the native parents attribute so callers can lift a parents list off an existing platform() unchanged.
  • The default is built as Label("@platforms//host") in the defining module, so consumers need no direct bazel_dep on platforms to use it. Previously the hardcoded string resolved against the caller's repo mapping.

Why parameterization and not something smarter

  • The platform-with-flags mechanism can't become a plain Starlark transition. A transition setting --@<hub>//dep_group would have to statically declare that flag label as a transition output, but the hub name is a macro parameter, unknown at rule-definition time. platform(flags = ...) is data, constructible per hub at load time.
  • platform() parents must be a static label; there's no "inherit the incoming platform". Deriving the parent from the incoming --platforms isn't implementable from a macro, and it would make the checked-in manifest depend on whatever --platforms a developer happened to pass. The explicit parameter keeps bazel run :gazelle_python_manifest.update reproducible, and covers the case where the desired parent is a target platform rather than the current one.

Test coverage

New snapshot case e2e/cases/gazelle-platform-parents-1416: builds a manifest over the existing pypi_uv_deps_650 hub's extras venv with a caller-supplied parent carrying a case-local constraint, and pins two goldens, the manifest YAML (built end-to-end) and a probe genrule whose select() keys on that constraint, transitioned through the macro-generated synthetic platform. If the macro ever ignores platform_parents, the probe golden flips from custom-parent to default-parent and the diff test fails (verified by reverting the fix).


  • 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_parents, 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.

Test plan

  • New e2e snapshot case as described above; bazel run //:snapshots plus all 36 snapshot diff tests pass.
  • bazel build //gazelle-platform-parents-1416:all and the unmodified //uv-gazelle-778:gazelle_python_manifest (default-parent path through the new Label() substitution) both build.
  • Verified on macOS (darwin_arm64, Bazel 8.6.0) in addition to Linux: bazel run //:snapshots reproduces both goldens byte-identically, and bazel query --output=build shows the synthetic platform's parents is the custom platform in the new case and @platforms//host for existing callers.
  • bazel test //uv/private/gazelle_manifest/tests:test passes; buildifier clean on the changed files.

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 aspect-build#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 aspect-build#1416

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

coderabbitai Bot commented Aug 19, 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: 25e4d698-98a4-4e20-9aec-3031952237cb

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.

@xangcastle

Copy link
Copy Markdown
Member

Thanks for doing this @sallustfire this will be landed on #1470 while I am keeping your authoring.
@jbedard this is a change in the public API

xangcastle added a commit that referenced this pull request Aug 20, 2026
…#1470)

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_setting`s 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.

---

- [x] Changes are visible to end-users: yes
- [x] Searched for relevant documentation and updated as needed: yes
(`docs/uv.md` Gazelle section)
- [x] Breaking change (forces users to change their own code or config):
no
- [x] 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](https://claude.com/claude-code)

---------

Co-authored-by: Connor McEntee <sallustfire@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@jbedard
jbedard force-pushed the main branch 2 times, most recently from b25e5ec to 25d3b44 Compare August 25, 2026 18:24
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

2 participants