feat: add project-qualified hub labels @hub//project/<stamp>:<pkg>#1079
feat: add project-qualified hub labels @hub//project/<stamp>:<pkg>#1079xangcastle wants to merge 54 commits into
Conversation
✨ Aspect Workflows Tasks📅 Fri Jul 10 17:31:48 UTC 2026 ✅ 35 successful tasks
⏱ Last updated Fri Jul 10 17:35:28 UTC 2026 · 📊 GitHub API quota 2,443/15,000 (16% used, resets in 20m) |
bc6a04a to
8d3d897
Compare
| ) | ||
|
|
||
| repository_ctx.file( | ||
| "project/{}/BUILD.bazel".format(stamp), |
There was a problem hiding this comment.
I still don't like this magic hardcoded "project/" keyword and think this is an indication of a bad API. Is this bad API simply aligning with a python/pip/uv API and the alignment is more important then it being bad? Or is there a better way to do this without hardcoding a magci "project/" keyword?
@gregmagolan and I had a similar discussion in #977 where he was able to find a way to avoid such a keyword...
# Conflicts: # docs/uv.md
py_binary startup benchmark
sys.path quality
✅ No regression detected (PR is -1.8% vs HEAD main) |
…r is set (#1234) Recent python-build-standalone releases ship Tcl/Tk as separate runtime shared libraries and support directories, so excluding only the Python tkinter package and _tkinter extension module leaves libtcl*, libtk*, tcl*/, tk*/, itcl*/, and thread*/ behind in the interpreter image. This change extends the tkinter feature definition in `py/private/interpreter/exclude_feature.bzl` to also remove those files, adds an e2e/cases/interpreter-features-tkinter regression case that pins the aspect_rules_py interpreter and verifies the resulting OCI layer contents and regenerates the layer snapshot so that `--exclude_feature=tkinter` once again produces a Tcl/Tk-free output. fix #1203 --- ### Changes are visible to end-users: yes/no <!-- If no, please delete this section. --> - Searched for relevant documentation and updated as needed: yes/no - Breaking change (forces users to change their own code or config): yes/no - Suggested release notes appear below: yes/no ### Test plan <!-- Delete any which do not apply --> - Covered by existing test cases - New test cases added - Manual testing; please provide instructions so we can reproduce:
Close #1225 ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added --------- Co-authored-by: Jimmy Tanner <jimmyt857@gmail.com>
…ngs (#1240) ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - Manual testing
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
The tokenizer consumed its input via `marker = marker[1:]`, copying the remaining string for every character (O(n²)), and the expression parser re-sliced the token list (`tokens[1:]` / `tokens[3:]`) on every step. Both now walk their input with a position cursor instead, making tokenization O(n). This runs at analysis time in every `_decide_marker` target — one per marker-conditioned dependency edge, per configuration — so the cost scales with lockfile size. No behavior or API change; `evaluate`'s signature is untouched. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
- drop the Bazel-6-era interpreter_version_info hasattr guard - make assemble_venv params required; drop its never-taken branches - drop always-true conjuncts in py_image_layer - remove the python_version_transition alias - derive exclude_feature validation from INTERPRETER_FEATURES - remove unused resolutions.empty, **kwargs swallows, struct exports, TOOLCHAIN_PLATFORMS fields, and pytest_shard/__init__.py ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
…hout tomllib (#1247) detect_native silently returned empty [build-system] requires on pre-3.11 interpreters, misdetecting modern sdists. Warn on stderr when pyproject.toml is present but tomllib is not, keep the rest of the tool (native detection, setup.cfg/setup.py parsing) working, and pin the unit test to the 3.13 toolchain the fetch-time inspection actually uses. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…#1252) ### Changes are visible to end-users: no ### Test plan - New test cases added
No rule ever resolves //uv/private/toolchain:toolchain_type — only the
@uv//:uv binary alias is consumed. Drop UvToolInfo, the generated
per-platform toolchain() targets, and the inert register_toolchains
calls; update docs.
### Changes are visible to end-users: yes
- Searched for relevant documentation and updated as needed: yes
- Breaking change (forces users to change their own code or config): yes
- Suggested release notes appear below: yes
@uv//:all no longer exists: the uv toolchain type and its per-platform
toolchain() targets are removed since no rule ever consumed them, so
drop any register_toolchains("@uv//:all") line (stale ones are a
harmless no-op) — the @uv//:uv binary alias is unaffected.
### Test plan
- Covered by existing test cases
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…reter layers are emitted (#1235) `py_image_layer` builds the interpreter tar from the Python toolchain aspect, but it reads that tar back from ctx.rule.toolchains on the binary target. py_venv_exec is the rule that the py_binary macro ultimately attaches the layer aspect to, yet it only declared the launcher toolchains, not `PY_TOOLCHAIN`. Because the toolchain was invisible to the aspect, the interpreter was silently dropped into the default source layer instead of a dedicated layer. Adding `PY_TOOLCHAIN` to `py_venv_exec` and `py_venv_exec_test` exposes the toolchain info so the aspect can route interpreter files to their own layer as intended. Fix #1217 ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
Native PEP 517 wheel builds export the selected C compiler as both CC and CXX. With clang/clang++ or gcc/g++, C++ extensions can link through the C driver and omit the C++ runtime. This recognizes only exact or numeric-suffixed clang/gcc names, selects the same-directory companion already in the toolchain inputs, and preserves the existing same-driver fallback when no companion is declared. It also rejects near-miss tool names such as clang-cl and gcc-ar. The unit and analysis coverage covers exact, versioned, near-miss, and missing-companion cases. The native-sdist e2e patches python-geohash with std::string so Linux CI must link a real C++ runtime. Local compiler-driver and toolchain-env tests, canonical snapshots, snapshot tests, Buildifier, and diff check pass. Linux runtime e2e is left to CI on this macOS host.
…#1244) ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
…1256) - pex main.py: -o/--output-file is required in practice (build(None) would crash) — declare it required and drop the help text describing a run-immediately mode that doesn't exist - pytest_shard: the shard-bounds ValueError printed a literal template (missing f-prefix, nonexistent variable names) ### Changes are visible to end-users: no ### Test plan - Manual testing
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
- stop emitting the unused py_library load in generated hub BUILDs - drop unused to_string/_STRING_REPLACEMENTS and semver.to_dict - simplify _parse_hubs, the select_arms disjunct, and an exact-else elif - remove the stale pprint bzl_library dep and generate.py's stale header/unused default ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
Nothing consumes root requirements.txt since the uv clean-sheet (#650): the only pip.parse uses the django test's own requirements, and the pypi hub / gazelle manifest run off uv.lock + pyproject.toml. Drop the lockfile, its compile_pip_requirements target (and implicit diff test), the check-requirements-lock pre-commit hook, and renovate's pip-compile manager. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…on (#1242) ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…1254) ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - New test cases added
### Changes are visible to end-users: no ### Test plan - New test cases added
…#1262) The abi constraints package generated a skylib config_setting_group for each of its 1344 abi tags, and match_all expands every group into a chain of intermediate targets. All five conditions per tag are flag checks (the _python_version_at_least feature flag plus four interpreter bool_flags), and a native config_setting already ANDs its flag_values — so each tag is now one target checking all five flags directly. Same labels and match semantics; the python version flag targets gain constraints-subpackage visibility, and the now-unreferenced per-feature enabled/disabled settings are dropped. Loading the abi package falls from 210ms to 20ms in the Starlark CPU profile, paid on every build. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…#1265) The generated settings check only the interpreter version, so cp312 and py312 were identical config_settings (each with a private flag rule behind it). Generate real settings for the generic py prefix and aliases for the rest. Also make is_python_version_at_least's version/ visibility params mandatory: the version-defaults-to-name fallback was unreachable, and the visibility default was the .bzl builtin function. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
- indent() was defined identically in three repository rules; move it to //uv/private/pprint, which all three already load - _format_arms hand-rolled what pprint does; generated BUILDs change in whitespace only, arm order preserved - extract _dist_identifier (repo-name id) and _dist_sha256 (http_file checksum) for the four copy-pasted hash-extraction sites. The sdist http_file path gains the sha256: algorithm guard the bdist path had, instead of slicing a garbage checksum from non-sha256 hashes - collect_configurations now uses url_basename (moved to lockfile.bzl) so query/fragment-suffixed wheel URLs parse consistently ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
collect_sccs() computed each SCC's id as a pure-Starlark SHA-1 over the repr of its members plus all external deps and markers — often multi-KB per package, ~94% of the function's CPU (830ms -> 70ms on a 500-package graph). A lone base package (the common case; dependency graphs are DAGs) now gets the readable `<name>__<version>`; a real cycle hashes only its member list. An id_state dict shared across a project's dependency groups interns ids by full content, so identical SCCs keep one id and SCCs differing only in deps/markers stay distinct — the property the content hash previously provided for the cross-group aggregation. SCC ids only name targets inside the generated project repos, so the new spelling (visible in the regenerated snapshots) is not a breaking change — and makes the generated BUILD files debuggable. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…acked generated/empty files (#1267) - --incompatible_enable_cc_toolchain_resolution is default-true since Bazel 7; the repo runs 8/9 only - ci.bazelrc: its only consumer is startup-benchmark.yml's build commands (which pass --disk_cache=), so the test stanzas, build:release, and disk_cache lines never take effect - benchmark/startup/MODULE.bazel is generated with a machine-local local_path_override and regenerated by CI; untrack + gitignore it - .prettierignore was a tracked empty file - fix stale comments in e2e/cases/.bazelrc ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- py/tests/py-internal-venv/test_env_vars.py: its py_test was removed with the #635 cherry-pick; a maintained copy lives in py/tests/py-test - py/tests/internal-deps/expected: never referenced since #29 (2022) - e2e uv-legacy-deps-750/main.py: BUILD lists only __test__.py - e2e uv-patching-829 pre_build.patch: case only exercises post_install_patches; pre-build patching is covered by uv-invalid-build-overrides - py/tests/virtual/django/uv.lock: case runs on requirements.in/txt; nothing references this lockfile ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…1269) By de-duplicating `py/private/pytest.py.tmpl` and `py/private/pytest_main.py` which were already diverging (the added test case). ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added
…ord (#1274) Collision resolution re-derived per-wheel-invariant data for every consuming binary: namespace-entry splits, dist-info classification of every top_level, and console-script parsing, allocating fresh claim structs each time. On a 200-binary x 240-wheel fixture this was 25% of all analysis-phase Starlark CPU; precomputing cuts _resolve_wheel_collisions from 0.98s to 0.63s there. PyWheelsInfo wheel records are now built by make_wheel_record, which derives tl_claims, metadata_top_levels, and cs_claims once per wheel. The record contract requires the derived fields, so venv assembly drops its getattr fallbacks for optional fields. <!-- Delete this comment! Include a summary of your changes, links to related issue(s), relevant motivation and context for why you made the change, how you arrived at this design, or alternatives considered. For repositories that use a squash merge strategy, the pull request description may also be used as the landed commit description ensuring that useful information ends up in the git log. --> --- ### Changes are visible to end-users: yes/no <!-- If no, please delete this section. --> - Searched for relevant documentation and updated as needed: yes/no - Breaking change (forces users to change their own code or config): yes/no - Suggested release notes appear below: yes/no ### Test plan <!-- Delete any which do not apply --> - Covered by existing test cases - New test cases added - Manual testing; please provide instructions so we can reproduce:
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
Permissive directory collisions currently select one whole-tree symlink. When two regular wheels contribute unique children to one package, Python binds the selected directory and hides the other wheel. Carry RECORD-derived top_level_dirs and native_roots through whl_install. Relocatable colliding directories merge in provider order. A native root keeps a direct wheel-relative projection because copying it can break origin-relative sibling lookup. Distinct losing distributions stay on fallback so a regular package that extends __path__ can still see a native graft. A prior duplicate-metadata loser is suppressed because a fallback copy would remain visible to importlib.metadata. Post-install patches that change package classification or native descendants fail before stale RECORD metadata can drive venv assembly. The real e2e reproductions use PyJWT plus jwt for regular-directory union and google plus protobuf for a native namespace graft. Focused tests cover regular, mixed, nested native, duplicate-metadata, and patch-time cases.
The munge() Bazel 7->8 label shim has no consumers, and the version_extension use_repo in MODULE.bazel existed only to feed it. bazel/patches has been an empty package since #1044. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…1276) collect_configurations parsed every wheel filename in the lock and ran the tag cross-product per wheel, but configurations depend only on the trailing {python}-{abi}-{platform} triple — thousands of wheel files share a few dozen triples. Dedupe on the triple before parsing: 230ms to 60ms per evaluation of a ~730-package lock. Also drops three accumulator dicts that were written but never read. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
…fix (#1277) The per-top-level symlink loop runs per (binary x top-level) and re-formatted every path component each iteration. Concatenate from a per-wheel target prefix cached across the loop instead: the loop's string formatting drops from 0.98s to 0.17s on a 200-binary x 240-wheel fixture. ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases
Behavior-preserving cleanup (all venv actions are bit-identical on the existing test fixtures): * Compute duplicate-metadata losers with a _distinct_claimants comprehension per metadata entry, replacing the manual winner/seen loop. * Add _under/_shallowest helpers, replacing five hand-rolled path-prefix / minimal-root loops. * Merge ns_covered_per_wheel into covered_per_wheel; pass 3 consumed them identically. * Derive the distinct-claimant chain from distinct_sp directly: dict keys already preserve first-claim order with last-claim values. * Drop the console-script singleton special case; _complain_chain is a no-op on a single claimant. * Replace the native-winner scan and pass 2a's manual dedup with comprehensions over precomputed distinct orders. Also add a regression test pinning the per-entry scope of the duplicate-metadata dedup: a distinct-metadata native claimant that loses to a regular claimant must keep its .pth fallback even when a third wheel follows it in metadata-claimant order. --- ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases - New test cases added --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…nches (#1285) The conflicted-root and pure-namespace branches built entry_owner with the same last-distinct-claimant-wins takeover logic, but only the pure branch marked the losing claimant skipped. Extract a shared _own_entries helper with the pure branch's semantics so both branches apply one policy, and rename the awkward `entried` to `claimants_with_entries`. This carries one slight behavior change, which we do not expect to affect anyone in practice: a wheel that loses a sibling-entry takeover inside a conflicted-root top-level now stays on the .pth fallback instead of being silently counted as covered. Import resolution is unchanged for well-formed wheels (the winner's projected copy shadows the loser's either way); the loser's tree just remains reachable as a safety net, matching the pure-namespace branch's documented behavior. The shape requires two wheels shipping the same file under a PEP 420 namespace alongside a regular package spanning wheels, which healthy dependency sets don't produce. --- ### Changes are visible to end-users: no ### Test plan - Covered by existing test cases Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…o xangcastle/quilified-labels
…o xangcastle/quilified-labels
py_binary startup benchmark
sys.path quality
Bazel analysis benchmark
Auxiliary metrics
|
Each hub now generates a
//project/<stamp>/subdirectory with aliases for every package in that project.@hub//project/my_project:requests # qualified
@hub//requests # unqualified (unchanged)
Both forms resolve to the same target in single-project hubs. The qualified form becomes useful in multi-project hubs: when two projects provide the same package under the same dep_group name, the unqualified alias raises Bazel's "multiple keys match" error. The qualified alias scopes the
select()to a single project, resolving unambiguously.The
//project/<stamp>/BUILD.bazelis derived entirely from data already available inuv_hub— no changes to the extension tag API or theuv_hubrule attributes are required.Changes are visible to end-users: yes
Test plan