Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
8d3d897
add project-qualified hub labels @hub//project/<stamp>:<pkg>
xangcastle Jun 4, 2026
06ce478
Merge branch 'main' into xangcastle/quilified-labels
xangcastle Jun 26, 2026
58ce530
fix(py): exclude Tcl/Tk runtime libraries when exclude_feature=tkinte…
xangcastle Jul 6, 2026
15c3c88
fix(uv): deduplicate whl_install wheel file labels (#1232)
jbedard Jul 6, 2026
085595a
refactor(uv): remove redundant is_* indirection from ABI config setti…
jbedard Jul 6, 2026
928e9f4
fix: unformatted error message (#1237)
jbedard Jul 6, 2026
6e01a43
refactor: remove dead code (#1236)
jbedard Jul 6, 2026
42751ca
perf(uv): use index cursors in PEP 508 tokenizer and parser (#1239)
jbedard Jul 6, 2026
1d74a22
refactor: remove unused load()s in generated code (#1241)
jbedard Jul 6, 2026
704e16c
refactor(py): remove dead code and unreachable paths (#1248)
jbedard Jul 7, 2026
1834c16
chore: remove orphaned tools/e2e devserver (#1249)
jbedard Jul 7, 2026
b1725c7
fix(uv): correct GitHub archive URLs for git ref sources (#1238)
jbedard Jul 7, 2026
5204fc2
refactor(uv): warn instead of silently skipping pyproject parsing wit…
jbedard Jul 7, 2026
c876f4c
fix: invalid error message on failure (#1245)
jbedard Jul 7, 2026
b9f3a3f
ci: use 32core for rules-proto-grpc-python e2e test (#1251)
jbedard Jul 7, 2026
690804f
test: cover PEP 508 ~=/===/partial eval, dep_groups, and pytest_shard…
jbedard Jul 7, 2026
a631d3a
chore: upgrade aspect-cli to 2026.28.2
jbedard Jul 7, 2026
c54b612
refactor(uv): remove the unresolved uv toolchain type (#1250)
jbedard Jul 7, 2026
0b6d7e8
fix(py_image_layer): wire PY_TOOLCHAIN through py_venv_exec so interp…
xangcastle Jul 7, 2026
025c748
fix(uv): select C++ compiler companions (#1213)
tamird Jul 7, 2026
79db931
test: cover py_pex_binary shebang, inherit_path, and constraint attrs…
jbedard Jul 7, 2026
c76bdc8
fix: correct two broken CLI error paths found in the dead-code audit …
jbedard Jul 7, 2026
dbb5287
refactor(uv): simplify no-op logic in whl_install repo rule (#1259)
jbedard Jul 7, 2026
7515417
feat: Analysis benchmark (#1264)
xangcastle Jul 7, 2026
06ce9b3
perf(uv): name uv_project targets without pure-Starlark sha1 (#1255)
jbedard Jul 7, 2026
f585d9c
refactor(uv): remove dead code and unreachable paths (#1260)
jbedard Jul 7, 2026
31d4d94
chore: remove the unconsumed root requirements lockfile loop (#1266)
jbedard Jul 7, 2026
c9242bb
refactor(py_venv): dedupe winner-selection walk in collision resoluti…
jbedard Jul 7, 2026
4071c50
refactor(py_venv): share provider construction between rule variants …
jbedard Jul 7, 2026
547fda2
test: assert the freethreaded venv layout in freethreaded-805 (#1258)
jbedard Jul 7, 2026
ffabde1
test(uv): unit-cover parse_whl_name and version_util.is_decimal (#1261)
jbedard Jul 7, 2026
e587410
perf(uv): collapse abi tag config_setting_groups into config_settings…
jbedard Jul 7, 2026
9771104
refactor(uv): alias cp* python-version constraints to their py* twins…
jbedard Jul 7, 2026
2669911
refactor(uv): consolidate indent/url_basename/hash helpers (#1263)
jbedard Jul 7, 2026
31e8616
perf(uv): derive SCC ids without hashing full SCC content (#1253)
jbedard Jul 7, 2026
1763f00
chore: config hygiene — drop no-op flags, dead ci.bazelrc stanzas, tr…
jbedard Jul 7, 2026
2ba1ea4
chore: remove orphaned test fixtures (#1272)
jbedard Jul 7, 2026
92d2213
fix(pytest): expose main() from py_pytest_main-generated entrypoints …
jbedard Jul 7, 2026
8364c55
fix: resolve py_image_layer.platform relative to the calling repo (#1…
jbedard Jul 7, 2026
bc9a851
perf(py_venv): precompute per-wheel collision claims in the wheel rec…
jbedard Jul 8, 2026
60dcaf9
refactor(uv): make SCC computation linear and single-pass (#1246)
jbedard Jul 8, 2026
279543d
fix(py_venv): merge colliding package directories (#1205)
tamird Jul 8, 2026
6400ae4
chore: remove unused bazel/defs.bzl and empty bazel/patches (#1280)
jbedard Jul 8, 2026
8212791
chore: remove unused tools scripts (#1282)
jbedard Jul 8, 2026
f227521
chore: delete unused code (#1281)
jbedard Jul 8, 2026
4211c23
chore: checkin MODULE.bazel.lock files (#1278)
jbedard Jul 8, 2026
54a69a8
perf(uv): expand wheel configurations once per distinct tag triple (#…
jbedard Jul 8, 2026
0f0a215
perf(py_venv): build symlink target paths from a cached per-wheel pre…
jbedard Jul 8, 2026
ac3087e
refactor: simplify _resolve_wheel_collisions (#1283)
acozzette Jul 9, 2026
400409f
add project-qualified hub labels @hub//project/<stamp>:<pkg>
xangcastle Jun 4, 2026
8642c38
refactor: unify namespace entry merging across both all-namespace bra…
acozzette Jul 9, 2026
16db162
Merge remote-tracking branch 'origin/xangcastle/quilified-labels' int…
xangcastle Jul 10, 2026
b036884
add project-qualified hub labels @hub//project/<stamp>:<pkg>
xangcastle Jun 4, 2026
e96964c
Merge remote-tracking branch 'origin/xangcastle/quilified-labels' int…
xangcastle Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions docs/uv.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,10 @@ py_library(
## A mental model

```
@pypi # Your UV built hub repository
@pypi//requests:requests # The library for a requirement
@pypi//jinja2-cli/entrypoints:jinja2-cli # A requirement's declared entrypoint
@pypi # Your UV built hub repository
@pypi//requests:requests # The library for a requirement
@pypi//jinja2-cli/entrypoints:jinja2-cli # A requirement's declared entrypoint
@pypi//project/my_project:requests # Project-qualified label (see below)
```

This central hub wraps "spoke" internal dependency group repos. For instance if you have two
Expand All @@ -286,6 +287,24 @@ over the dependency group targets in which that requirement is defined.
Hub requirement targets are _incompatible_ with dependency group configurations in which the
requirement in question is not defined.

### Project-qualified labels

In a multi-project hub where two projects both provide the same package, the unqualified label
can produce a Bazel "multiple keys match" error when the active dep_group name is shared across
projects. The project-qualified form scopes the `select()` to a single project, eliminating
the ambiguity:

```python
# Unqualified — works unless two projects have the same package + same dep_group name
deps = ["@pypi//requests"]

# Project-qualified — always routes to exactly one project's resolution
deps = ["@pypi//project/my_project:requests"]
```

The `<stamp>` in `//project/<stamp>` is the [PEP 503-normalised](https://peps.python.org/pep-0503/#normalized-names)
form of the `[project].name` declared in that project's `pyproject.toml`.

Each dependency group requirement is backed by a `whl_install` rule which chooses among
prebuilt wheels listed in the lockfile to produce the equivalent of a
`py_library`.
Expand Down
29 changes: 29 additions & 0 deletions e2e/cases/multi-project-hub/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,32 @@ py_test(
"@pypi_multi//cowsay",
],
)

# Project-qualified labels: each project exposes its packages under
# @hub//project/<stamp>:<pkg>, scoping the select to that project only.
py_test(
name = "alpha_qualified",
srcs = ["__test__.py"],
dep_group = "dev",
main = "__test__.py",
python_version = "3.11",
deps = ["@pypi_multi//project/alpha:cowsay"],
)

py_test(
name = "beta_qualified",
srcs = ["__test__.py"],
dep_group = "beta",
main = "__test__.py",
python_version = "3.11",
deps = ["@pypi_multi//project/beta:cowsay"],
)

py_test(
name = "gamma_qualified",
srcs = ["__test__.py"],
dep_group = "gamma",
main = "__test__.py",
python_version = "3.11",
deps = ["@pypi_multi//project/gamma:cowsay"],
)
12 changes: 12 additions & 0 deletions e2e/cases/single-project-hub/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ py_test(
"@pypi_single//cowsay",
],
)

# Verifies @hub//project/<stamp>:<pkg> resolves correctly.
py_test(
name = "single_project_hub_qualified_label",
srcs = ["__test__.py"],
dep_group = "single_project_hub",
main = "__test__.py",
python_version = "3.11",
deps = [
"@pypi_single//project/single_project_hub:cowsay",
],
)
50 changes: 50 additions & 0 deletions uv/private/uv_hub/repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,56 @@ exports_files(

repository_ctx.file(package_name + "/BUILD.bazel", content = "\n".join(content))

################################################################################
# Lay down //project/<stamp>/BUILD.bazel — project-qualified labels
# (@hub//project/<stamp>:<pkg> scopes the select to one project, avoiding
# "multiple keys match" in multi-project hubs with shared package names.)
per_project = {}
for pkg_name, cfgs in packages.items():
for cfg, target in cfgs.items():
proj_id = target[1:].split("//")[0] # "@project__<stamp>//:pkg" → "project__<stamp>"
per_project.setdefault(proj_id, {}).setdefault(pkg_name, {})[cfg] = target

for proj_id, proj_pkgs in sorted(per_project.items()):
stamp = proj_id[len("project__"):]
proj_cfg_set = {}
for pkg_cfgs in proj_pkgs.values():
for cfg in pkg_cfgs.keys():
proj_cfg_set[cfg] = 1
proj_cfg_names = sorted(proj_cfg_set.keys())

p_content = [
"""\
load("@aspect_rules_py//py:defs.bzl", "py_library")
load("//:defs.bzl", "compatible_with")
""",
]

for pkg_name, pkg_cfgs in sorted(proj_pkgs.items()):
select_spec = {
"//dep_group:{}".format(cfg): tgt
for cfg, tgt in pkg_cfgs.items()
}
p_content.append(
"""
alias(
name = "{name}",
actual = select({select}),
target_compatible_with = select(compatible_with({compat})),
visibility = ["//visibility:public"],
)
""".format(
name = pkg_name,
select = indent(pprint(select_spec), " ").lstrip(),
compat = repr(proj_cfg_names),
),
)

repository_ctx.file(
"project/{}/BUILD.bazel".format(stamp),

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.

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...

"\n".join(p_content),
)

################################################################################
# Lay down //:defs.bzl
content = [
Expand Down