Skip to content

fix(uv): consolidate per-wheel metadata + namespace/regular-span venv merge#1092

Draft
jbedard wants to merge 2 commits into
mainfrom
oai-azure-3-5
Draft

fix(uv): consolidate per-wheel metadata + namespace/regular-span venv merge#1092
jbedard wants to merge 2 commits into
mainfrom
oai-azure-3-5

Conversation

@jbedard

@jbedard jbedard commented Jun 7, 2026

Copy link
Copy Markdown
Member

Schema foundation: wheel metadata (top_levels, namespace_top_levels, console_scripts) is keyed per wheel by file basename instead of unioned across platform wheels, and the whl_install build rule selects the entry for the wheel the select chain resolved to for the active config. A union leaked an inactive wheel's surface into the active one (another platform's C-extension suffix as a dangling symlink, a win32-only console script wrapper everywhere). A lookup miss (sbuild fallback, failed extraction) emits no PyWheelsInfo and consumers fall back to .pth resolution.

Namespace packages: PEP 420 namespace packages are now materialised CONCRETELY in site-packages via per-entry symlinks under a real namespace directory, from the new per-wheel namespace_entries metadata, so tools that inspect site-packages directly (mypy, pyright) see the packages and their py.typed markers — runtime .pth resolution alone left them invisible. Wheels lacking entry metadata (hand-written py_unpacked_wheel) keep the historical .pth-only fallback.

Regular packages spanning wheels: a regular package split across wheels (azure-core owns azure/core/ while azure-core-tracing-opentelemetry grafts azure/core/tracing/ext/... into it) can't be merged by symlinks or .pth — Python locks a regular package's path to one directory. The new per-wheel namespace_dirs / regular_roots metadata is cross-referenced across wheels to detect the overlap, and the conflicted subtree is physically merged by a new site_merge tool run under the (optional) exec-tools toolchain.

The two assembly mechanisms coexist in _resolve_wheel_collisions: an all-namespace top-level with a spanning regular package goes to the physical merge + .pth (azure); a pure PEP 420 namespace goes to the per-entry concrete symlinks. They never declare conflicting outputs for the same path.

All metadata fields are now attr.string_list_dict keyed by wheel basename. e2e: pth-namespace-547 (concrete namespace, mypy-visible), azure-core-tracing-overlap (regular package spanning wheels), and the cffi abi3 snapshot (per-wheel surface) all pass; snapshots regenerated.


Changes are visible to end-users: no

Test plan

  • Covered by existing test cases
  • New test cases added

@aspect-workflows

aspect-workflows Bot commented Jun 7, 2026

Copy link
Copy Markdown

✨ Aspect Workflows Tasks

📅 Fri Jun 12 20:54:41 UTC 2026

✅ 7 successful tasks

  • ✅ buildifier · ⏱ 18.5s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle · ⏱ 23.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ test (test-e2e-bazel-8) · ⏱ 2m 22s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (144/144 passed)
  • ✅ test (test-e2e-bazel-9) · ⏱ 3m 31s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (139/139 passed)
  • ✅ test (test-examples-uv_pip_compile-bazel-8) · ⏱ 32.5s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test (test-root-bazel-8) · ⏱ 2m 8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (191/191 passed)
  • ✅ test (test-root-bazel-9) · ⏱ 2m · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (190/190 passed)

⏱ Last updated Fri Jun 12 20:58:25 UTC 2026 · 📊 GitHub API quota 1,499/15,000 (10% used, resets in 36m)
🚀 Powered by Aspect CLI (v2026.24.11)  |  Aspect Build · X · LinkedIn · YouTube

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

py_binary startup benchmark

Version Mean (ms) Median (ms) ± stddev vs BCR vs main Build (s)
BCR 1.11.5 (baseline) 315.620 314.989 ±8.893 40.78
HEAD main 61.266 61.685 ±1.494 -80.6% 12.86
This PR 59.659 59.805 ±1.623 -81.1% -2.6% 10.95

Measured with hyperfine --warmup 5 --runs 50 on Linux
Gate: PR vs HEAD main (threshold: 10%). BCR is shown only as a historical baseline.
Build time: cold bazel build //:bench with isolated output base, no disk cache.

sys.path quality

Version sys.path entries distinct site-packages roots duplicate realpaths
BCR 1.11.5 (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.

jbedard and others added 2 commits June 12, 2026 13:53
… merge

Consolidates three previously-independent branches (oai-5, oai-3,
oai-azure) that each reshaped the same wheel-metadata schema and venv
site-packages assembly in mutually-incompatible ways. Landing them
separately would have had oai-3 and oai-azure reintroduce the exact
cross-wheel metadata leak oai-5 fixes. This unifies them onto one
per-wheel-keyed schema.

Schema foundation (was oai-5): wheel metadata (top_levels,
namespace_top_levels, console_scripts) is keyed per wheel by file
basename instead of unioned across platform wheels, and the whl_install
build rule selects the entry for the wheel the select chain resolved to
for the active config. A union leaked an inactive wheel's surface into
the active one (another platform's C-extension suffix as a dangling
symlink, a win32-only console script wrapper everywhere). A lookup miss
(sbuild fallback, failed extraction) emits no PyWheelsInfo and consumers
fall back to .pth resolution.

Namespace packages (was oai-3): PEP 420 namespace packages are now
materialised CONCRETELY in site-packages via per-entry symlinks under a
real namespace directory, from the new per-wheel `namespace_entries`
metadata, so tools that inspect site-packages directly (mypy, pyright)
see the packages and their py.typed markers — runtime .pth resolution
alone left them invisible. Wheels lacking entry metadata (hand-written
py_unpacked_wheel) keep the historical .pth-only fallback.

Regular packages spanning wheels (was oai-azure): a regular package
split across wheels (azure-core owns `azure/core/` while
azure-core-tracing-opentelemetry grafts `azure/core/tracing/ext/...`
into it) can't be merged by symlinks or .pth — Python locks a regular
package's __path__ to one directory. The new per-wheel `namespace_dirs`
/ `regular_roots` metadata is cross-referenced across wheels to detect
the overlap, and the conflicted subtree is physically merged by a new
site_merge tool run under the (optional) exec-tools toolchain.

The two assembly mechanisms coexist in _resolve_wheel_collisions: an
all-namespace top-level with a spanning regular package goes to the
physical merge + .pth (azure); a pure PEP 420 namespace goes to the
per-entry concrete symlinks (oai-3). They never declare conflicting
outputs for the same path.

All metadata fields are now `attr.string_list_dict` keyed by wheel
basename. e2e: pth-namespace-547 (concrete namespace, mypy-visible),
azure-core-tracing-overlap (regular package spanning wheels), and the
cffi abi3 snapshot (per-wheel surface) all pass; snapshots regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thing

  When wheels share a PEP 420 namespace, the concrete per-entry merge
  previously required entry metadata from EVERY claimant: a single
  entryless contributor (a hand-written py_unpacked_wheel that omits
  namespace_entries) dragged the whole group back to the .pth fallback,
  re-hiding the well-formed wheels from tools that inspect site-packages
  directly (mypy, pyright).

  Merge the claimants that HAVE entries concretely and route only the
  entryless ones to .pth. A concrete site-packages/<tl>/ directory (no
  __init__.py) and a .pth/addsitedir portion both contribute to the same
  namespace at runtime, so the entried wheels stay statically visible
  while the entryless wheel still imports. Only when NO claimant has
  entries do we keep the historical .pth-only fallback for the group.

  Also documents two known limitations of the heuristic:
    * the nested-namespace prefix collision where the shallower entry is a
      regular package (surfaced via package_collisions, not mis-merged);
    * wheels shipping namespace contributions under *.data/{purelib,platlib}/,
      which aren't discovered as top-levels/entries.

  Test: e2e/cases/pth-namespace-547/test_partial_merge — jaraco.classes
  (entried uv whl_install) stays concrete + py.typed-visible while
  jaraco.functools (entryless hand-written py_unpacked_wheel) merges via
  .pth, and jaraco stays a namespace package with both portions on
  __path__.
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.

1 participant