rules_uv
Bazel rules for uv (Astral's Python package manager)
| Latest | 0.7.4 |
|---|---|
| Versions | 12 |
| Category | Bazel rules |
| Maintainers | Matt Marshall |
| Registry | https://registry.tbzl.dev/modules/rules_uv/ |
| Source | github.com/tomato-bazel/rules_uv |
bazel_dep(name = "rules_uv", version = "0.7.4")
View source & releases on GitHub ↗
Bazel rules for uv, Astral’s
high-speed Python package + project manager. Two pieces:
-
@uv//:binary— the uv CLI, built from source inside Bazel viarules_rust’scargo_bootstrap_repository(so the binary is pinned to the same Rust toolchain + uv source revision across every machine in the org). -
@rules_uv//pip:pip.parse— auv.lock→@pipmodule extension. Same shape as rules_python’spip_parse, but driven by uv’s resolver output: one Bazel-fetched repo per package, an aggregating hub repo with arequirement("<name>")macro, and transitive deps wired up by the lockfile.
Status: v0.7
What v0.7 adds on top of v0.6:
- Editable workspace roots —
uv.lockentries withsource = { editable = "." }(uv’s standard pattern for a workspace’s own project) are now skipped rather than rejected. Their attacheddev-dependenciestable is mined for PEP 735 dependency groups. - PEP 735 dependency groups — new
group(name)macro in the hub’srequirements.bzlreturns labels for every package in a named group. Markers + extras per edge are honoured.
load("@pip//:requirements.bzl", "requirement", "group")
py_test(
name = "tests",
deps = [requirement("my_lib")] + group("dev"),
)
What ships:
uvbinary, two interchangeable paths:source = "build"(default) — built from astral-sh/uv source via rules_rust’scargo_bootstrap_repository. ~12-min cold build; cached after. Highest hermeticity.source = "prebuilt"— fetches the official release asset for the host platform (darwin_aarch64,darwin_x86_64,linux_aarch64,linux_x86_64). Seconds to fetch.
pip.parsereadsuv.lockand materializes:- Pure-Python wheels (
py3-none-any) — http_archive unpacks the wheel as a zip. - Native wheels (
manylinux_*,macosx_*_arm64, …) — PEP 425 / PEP 600 tag scoring against the host triple +python_version. Best-matching wheel wins. - Sdists — shells to
@uv//:uv(uv pip install --target=. --no-deps) at repo-rule time. Builds C extensions if the sdist has any. Choose betweenpython = "host"(usespython3on PATH) andpython = "uv"(usesuv python install <version>). - Git sources (
source = { git = "…", rev = "…" }) — fetched vianew_git_repositorywith the BUILD wrapper. - Path sources (
source = { path = "…" }) — symlinked into a Bazel repo via a thinnew_local_repository-shaped rule. - Editable sources — explicitly rejected with a clear error.
- Pure-Python wheels (
- Extras (
requirement("pkg[extra]")) — per-extra Bazel sub-targets generated from each package’s[package.optional-dependencies]table. The extra target re-exports:pkgplus the extra’s deps. - Markers (
marker = "python_full_version < '3.11'", etc.) — evaluated at extension time against the configuredpython_version- host platform. Edges whose markers fail are filtered out. PEP
508 subset:
python_version,python_full_version,os_name,sys_platform,platform_system,platform_machine,extra; comparisons +and/or/not/in/not in/grouping.
- host platform. Edges whose markers fail are filtered out. PEP
508 subset:
- Cross-platform wheels + pure-Python sdists (
pip.parse(platforms = [...])) — when the consumer opts into multi-platform mode:- Packages with platform-divergent native wheels fan out into
per-platform repos behind a selector that
alias-select()s on@platforms//os+@platforms//cpu. - Pure-Python wheels stay single-repo.
- Pure-Python sdists (v0.6) install once on the host with a
native-extension check; pure-Python results become a single
repo serving every target platform. Sdists that build native
code still fail loudly (cross-arch reuse of a host-built
.so/.dylib/.pydis unsafe). - Git + path sources remain host-only and fail loudly under a cross-platform build.
- Packages with platform-divergent native wheels fan out into
per-platform repos behind a selector that
- End-to-end smoke tests:
examples/smoke/— host-only build with all five lockfile-feature paths: pure wheel, native wheel, sdist install,certifi[bundle]extra, marker-gated dep filtered out.examples/multiplatform/— multi-platform lockfile + py_test that resolves a native wheel through the per-platform selector.
Deferred to v0.7+ (see docs/ROADMAP.md):
- Migration to rules_python’s
uv_toolchainonce it leaves experimental. - Native-extension sdists in multi-platform mode (cross-compile).
- musl + Windows platform tag tables in
pip/private/platform.bzl.
Architecture
//uv uv binary + toolchain
defs.bzl user-facing rule: uv_run
toolchains.bzl uv_toolchain rule
extensions.bzl module extension: fetch source + cargo_bootstrap
private/known_versions.bzl pinned uv versions + sha256s
//pip uv.lock → @pip
extensions.bzl module extension: pip.parse
private/uvlock_to_json.py TOML → JSON shim (uses py 3.11 stdlib)
private/wheel_selection.bzl pure-wheel-first artifact picker
private/pip_package.BUILD.tpl per-package BUILD template
//examples/smoke end-to-end smoke test
Install
.bazelrc:
common --registry=https://registry.fastverk.com/
common --registry=https://bcr.bazel.build/
MODULE.bazel:
bazel_dep(name = "rules_uv", version = "0.5.0")
bazel_dep(name = "rules_python", version = "1.7.0")
uv = use_extension("@rules_uv//uv:extensions.bzl", "uv")
# Optional — omit the tag for the default `source = "build"` path.
uv.toolchain(source = "prebuilt")
use_repo(uv, "uv", "uv_source")
register_toolchains("@rules_uv//uv:uv_toolchain_def")
pip = use_extension("@rules_uv//pip:extensions.bzl", "pip")
pip.parse(
hub_name = "pip",
lock = "//:uv.lock",
python_version = "3.12", # used for wheel tag matching
python = "host", # "host" (python3 on PATH) | "uv"
)
use_repo(pip, "pip")
pip.parse
In a BUILD file:
load("@pip//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_library")
py_library(
name = "app",
srcs = ["app.py"],
deps = [
requirement("idna"),
requirement("certifi"),
],
)
requirement(name) is case-insensitive and accepts the same
spellings PyPI does (_, -, . are folded together per PEP 503).
uv_run
bazel run-able wrapper around uv <subcommand> against the live
workspace source (escapes the sandbox so uv lock, uv pip sync,
etc. can write into the user’s tree):
load("@rules_uv//uv:defs.bzl", "uv_run")
uv_run(
name = "lock",
subcommand = "lock",
)
uv_run(
name = "sync",
subcommand = "pip",
args = ["sync", "requirements.txt"],
)
bazel run //:lock
bazel run //:sync -- --refresh
Versioning
rules_uv versions track its own surface, not uv’s. The pinned uv
version lives in uv/private/known_versions.bzl;
override with uv.toolchain(version = "<new>") in your MODULE.bazel.
Usage#
Real usage, taken from the module’s examples/.
examples/multiplatform/BUILD.bazel
load("@multipip//:requirements.bzl", "requirement")
load("@rules_python//python:defs.bzl", "py_test")
py_test(
name = "multiplatform_test",
srcs = ["multiplatform_test.py"],
deps = [
requirement("idna"), # pure wheel: single repo, no select
requirement("markupsafe"), # native wheel: per-platform select
requirement("six"), # pure-python sdist-only: single repo via host install (v0.6)
],
)
examples/smoke/BUILD.bazel
load("@pip//:requirements.bzl", "group", "requirement")
load("@rules_python//python:defs.bzl", "py_test")
load("@rules_uv//uv:defs.bzl", "uv_run")
py_test(
name = "smoke_test",
srcs = ["smoke_test.py"],
deps = [
# Base targets.
requirement("idna"),
requirement("markupsafe"),
# Per-extra target: certifi's synthetic `bundle` extra
# re-exports `:pkg` and adds a dep on idna. We pull this
# instead of plain `requirement("certifi")` to exercise
# the `pkg[extra]` resolution path.
requirement("certifi[bundle]"),
# PEP 735 dependency-group expansion (v0.7). The smoke lockfile's
# editable root carries `[package.dev-dependencies] dev = [{name = "iniconfig"}]`
# — `group("dev")` returns `[Label("@pip//:iniconfig")]`.
] + group("dev"),
)
# Smoke target for `uv_run` — `bazel run //examples/smoke:uv_version`
# prints the uv version (via the sandbox-escaping wrapper).
uv_run(
name = "uv_version",
subcommand = "--version",
)Rules & providers#
Generated with Stardoc from the module's .bzl sources.
from docs/ROADMAP.md
rules_uv roadmap
v0.1
-
@uv//:binarybuilt from source viacargo_bootstrap_repository. -
uv_runmacro: sandbox-escapingbazel runwrapper. -
pip.parsemodule extension:uv.lock→@piphub + per-package repos. - Pure-Python wheel materialization (
py3-none-any). - Sdist fallback (raw download; no build step yet).
- End-to-end smoke test in
examples/smoke.
v0.2 (this release)
- Prebuilt-uv toolchain alternative.
uv.toolchain(source = "prebuilt")fetches the official release asset for the host platform fromastral-sh/uvreleases. Supported hosts today:darwin_{aarch64,x86_64}andlinux_{aarch64,x86_64}. musl + 32-bit + Windows triples are intentionally omitted until someone needs them — pinning shas we never test is security theater. - Unified target shape. Both
buildandprebuiltproduce@uv//:binaryas aFile;uv_toolchainaccepts the file directly (no more:installrust_binary indirection).
v0.3 (this release)
- Native wheel selection. PEP 425 / PEP 600 tag scoring in
pip/private/wheel_selection.bzl: parse wheel filenames, fan out compressed tag fields, score against a host-specific ordered tag list (pip/private/platform.bzl). MVP covers the 4 fastverk hosts (darwin_{aarch64,x86_64},linux_{aarch64,x86_64}); rules_python’swhl_target_platformsis more thorough and will be the backing implementation once their internals stabilize. - Sdist installation via uv.
sdist_install_repo(pip/private/sdist_install.bzl): downloads the sdist, shells to@uv//:uv(uv pip install --target=. --no-deps) at repo-rule time. Python interpreter viapython = "host"(python3on PATH) orpython = "uv"(uv python installinto a per-repo scratch dir). -
python_version+pythonattrs onpip.parse. Wheel tag matching consultspython_version; sdist install dispatches onpython.
v0.4 (this release)
- Extras:
requirement("pkg[extra]")resolves to a per-extra Bazel target that re-exports:pkgplus the extra’s deps. Generated from each package’s[package.optional-dependencies]table. - Markers: PEP 508 subset evaluated at extension time
against
python_version+ host platform. Edges whose markers fail are filtered out. Cross-platformselect()is v0.5. - Git sources (
source = { git = "…", rev = "…" }):new_git_repositorywith the BUILD wrapper. - Path sources (
source = { path = "…" }):new_local_repository-style symlink rule. - Editable sources: explicit failure with a clear message (editable installs don’t translate to Bazel).
- Hermetic uv invocation:
--no-configon alluv pipanduv python installcalls so the user’s~/.config/uv/uv.toml(which on many machines points at a private index) doesn’t leak into sandbox builds.
v0.5 (this release)
- Cross-platform wheels.
pip.parse(platforms = [...])opts the hub into multi-platform mode. Packages with platform-divergent native wheels fan out into per-platform repos (@<hub>__<pkg>__<platform>) behind a selector repo that emitsalias(name = "pkg", actual = select(...))over@platforms//os+@platforms//cpuconstraint values. Non-host platform repos are declared but lazy-fetched — they only land on disk when Bazel’s configuration triggers that branch of theselect(). - Multi-platform smoke (
examples/multiplatform/): pure-python wheel (idna) flows through the single-repo path; native wheel (markupsafe) flows through the per-platform select.
v0.6 (next)
Smoke fixtures for git + path sources
v0.4 wires git/path source materialization, but no smoke fixture exercises either. A fixture that lock-files a tiny pure-Python package from a pinned GitHub commit + a sibling local path package would catch regressions.
Sdist install in multi-platform mode
Today sdist install is host-only — if a multi-platform lockfile
references an sdist-only package, the extension fails fast rather
than silently producing a broken cross-platform target. v0.6 could
support per-platform sdist installs by running uv pip install --target once per requested platform (each producing its own
per-platform repo). Requires either cross-compilation toolchains
on the host (rare) or Bazel platform-transition magic.
musl + Windows platform tag tables
pip/private/platform.bzl ships tag tables for the four fastverk
hosts only. Adding musllinux + Windows entries (with
@platforms//os:windows and a musl libc constraint) is mechanical
once a consumer needs them.
Marker evaluator: spot tests
pip/private/markers.bzl is a hand-rolled PEP 508 subset parser.
A skylib unittest suite covering operators, precedence, and the
python_full_version vs python_version edge cases would lock
the behavior down.
Beyond v0.6
uv_pip_compile:bazel run-able workflow to regeneraterequirements.txtfrom apyproject.toml(analogous to rules_uv upstream’s compile workflow).- Cross-platform wheels: support emitting
select()deps when a package has multiple platform wheels but the consumer wants to target several configurations from one tree. - Stardoc-generated reference in
/docs.
Delete uv/ when rules_python’s uv is stable
rules_python ships its own experimental uv toolchain primitive at
@rules_python//python/uv:uv_toolchain.bzl and a binary-fetching
module extension at @rules_python//python/uv:uv.bzl. Both are
marked EXPERIMENTAL: This is experimental and may be removed without notice, so today rules_uv carries its own toolchain +
fetch + build paths.
When rules_python promotes these out of experimental, rules_uv’s
uv/ directory becomes pure duplication and should be removed:
- Drop
uv/extensions.bzl,uv/toolchains.bzl,uv/private/known_versions.bzl,uv/private/uv_source.BUILD.bazel. - Replace our
uv_runmacro with one that resolves through rules_python’suv_toolchain_type. - The pip extension keeps using
@uv//:binaryat repo-rule time (just pointing at whichever target rules_python’s extension materializes by then).
This trims rules_uv down to its actual reason for existing: the
uv.lock TOML → @pip materializer. Track upstream status at
https://github.com/bazelbuild/rules_python/issues/ (search for
“uv toolchain experimental”).
from docs/pip_extensions.md
pip_parse module extension — uv.lock → @
Counterpart to rules_python’s pip_parse, but driven by uv.lock
instead of requirements.txt. For each package the lockfile resolves
to, we create a Bazel-fetched repo containing the unpacked wheel
(or installed sdist, or fetched git/path source). A hub repo
aggregates these and exposes a requirement("<name>") macro plus
pre-aliased @<hub>//<name>:pkg labels.
Consumer:
pip = use_extension("@rules_uv//pip:extensions.bzl", "pip")
pip.parse(
hub_name = "pip",
lock = "//:uv.lock",
python_version = "3.12",
)
use_repo(pip, "pip")
Extras are exposed as additional sub-targets on the package repo:
load("@pip//:requirements.bzl", "requirement")
py_library(
name = "app",
deps = [
requirement("requests"), # base package
requirement("requests[security]"), # base + extra deps
],
)
Markers (e.g. marker = "python_version < '3.11'") are evaluated
at extension time against the configured python_version + host
platform. Edges whose markers fail are silently dropped from the
generated BUILD — keeping the host-only view simple. Cross-platform
select() is v0.5.
pip
pip = use_extension("@rules_uv//pip:extensions.bzl", "pip")
pip.parse(hub_name, lock, platforms, python, python_version, uv)
Materialize @
TAG CLASSES
parse
Attributes
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| hub_name | Name of the hub repo (the @<hub_name>//… namespace). | String | optional | "pip" |
| lock | Label pointing at a uv.lock file. | Label | required | |
| platforms | Optional list of <os>_<arch> platforms this lockfile should support. Default is host-only (the v0.4 behavior — select() is not introduced). Supported entries: darwin_aarch64, darwin_x86_64, linux_aarch64, linux_x86_64. Packages with platform-divergent native wheels fan out into per-platform repos behind a select() alias; sdist/git/path packages remain host-only and the build will fail loudly if a non-host platform tries to resolve them. | List of strings | optional | [] |
| python | How to find a Python interpreter for sdist install. host uses python3 on PATH; uv runs uv python install <python_version> per package. | String | optional | "host" |
| python_version | Python major.minor used for wheel-tag matching and (when python = “uv”) the uv-managed interpreter. | String | optional | "3.12" |
| uv | Label of the uv binary used to install sdists. | Label | optional | "@uv" |
from docs/uv_defs.md
User-facing rules for rules_uv.
uv_run— sh_binary macro:bazel run //path:NAMEinvokesuv <subcommand>against the live workspace source. Intentionally non-hermetic (escapes the runfiles sandbox) for the dev loop (uv pip sync,uv lock,uv run …).
Lockfile-driven Python repo materialization lives in
@rules_uv//pip:extensions.bzl (pip_parse), which is the rules_uv
analogue of rules_python’s pip_parse but reads uv.lock rather
than requirements.txt.
uv_run
load("@rules_uv//uv:defs.bzl", "uv_run")
uv_run(name, subcommand, args, **kwargs)
bazel run-able wrapper around uv <subcommand>.
Escapes the runfiles sandbox via BUILD_WORKSPACE_DIRECTORY so uv
operates on the user’s source tree (uv lock, uv pip sync …
both need to write into the workspace).
PARAMETERS
from docs/uv_toolchains.md
Toolchain wrapper for the uv binary.
UvToolchainInfo.uv is a File for the uv executable. Consumers
resolve it via ctx.toolchains["@rules_uv//uv:toolchain_type"].
The attr uses allow_single_file = True rather than
executable = True because the bootstrapped binary at @uv//:binary
is an alias to a source File (cargo_bootstrap_repository’s output)
— Bazel rejects source files as executable attr inputs, so we
accept the file and let the consuming rule mark it executable
itself.
uv_toolchain
load("@rules_uv//uv:toolchains.bzl", "uv_toolchain")
uv_toolchain(name, uv)
Declares a uv toolchain.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| uv | Label of the uv binary (either built via cargo_bootstrap_repository or fetched as a prebuilt release asset). | Label | required |
UvToolchainInfo
load("@rules_uv//uv:toolchains.bzl", "UvToolchainInfo")
UvToolchainInfo(uv)
Information about a uv toolchain.
FIELDS
| Name | Description |
|---|---|
| uv | File pointing at the uv executable. |
Conformance#
2 findings across 1 invariant. 10 contested atoms. See how gating works or the full report.
| version | toolchain |
|---|---|
0.7.4 | //uv:uv_toolchain_def |
0.7.4 | @rust_toolchains//:all |
Contested atoms
Third-party modules where this module resolves a different version than others do. Not a violation of anything this module did — it is the actionable form of a registry-level convergence finding, and the sentence a maintainer can act on.
| Atom | Resolved here | Elsewhere |
|---|---|---|
apple_support | 1.24.2 | 2.2.0 ×1 |
bazel_skylib | 1.8.2 | 1.9.0 ×2 |
gazelle | 0.36.0 | 0.30.0 ×50.44.0 ×10.51.0 ×3 |
nlohmann_json | 3.6.1 | 3.12.0.bcr.1 ×1 |
protobuf | 33.4 | 34.0.bcr.1 ×2 |
rules_go | 0.60.0 | 0.39.1 ×5 |
rules_jvm_external | 6.7 | 6.8 ×4 |
rules_python | 1.7.0 | 2.0.1 ×1 |
rules_swift | 3.1.2 | 3.6.1 ×1 |
upb | 0.0.0-20220923-a547704 | 0.0.0-20230516-61a97ef ×1 |
Dependencies#
Depends on
Used by (2 in the registry)
Versions#
12 published versions, newest first. Each resolves to an immutable, integrity-checked archive.
| Version | Integrity (sha256) | Source archive |
|---|---|---|
0.7.4 latest | vlPl/TGBHaNF+cbw… | tag archive ↗ |
0.7.3 | 3zI+FgC7T2Lts14X… | tag archive ↗ |
0.7.2 | BlIFBfKwGbxUpHp1… | tag archive ↗ |
0.7.1 | zEr82psXnaxkSUt4… | tag archive ↗ |
0.7.0 | LakkT3oy5Q8U0L10… | tag archive ↗ |
0.6.0 | lbx67u5jTIBi+qnr… | tag archive ↗ |
0.5.1 | Jilgp1SIwiUNjMqE… | tag archive ↗ |
0.5.0 | eG6oYlgcwSWU0Gdw… | tag archive ↗ |
0.4.0 | Q/pI4oqdtIcGC+iy… | tag archive ↗ |
0.3.0 | 0hLHwQt6YEefJneQ… | tag archive ↗ |
0.2.0 | 0nv8x5Jqf8R8tbon… | tag archive ↗ |
0.1.0 | eFUd4ga9sCLmgF34… | tag archive ↗ |
Changelog#
All notable changes to rules_uv. The format is loosely Keep a Changelog — version headers mirror the published bazel-registry entries.
0.7.4 — extras wiring + nested extras + 3.11 shim
Three fixes to pip.parse’s extras handling, surfaced wiring deep
pypi tools (e.g. awslabs.aws-api-mcp-server) hermetically:
- Lock shim interpreter: prefer a versioned
python3.13/3.12/3.11for theuv.lock→ JSON step. A barepython3is frequently the system 3.9 on macOS, which lacks stdlibtomllib. - Extras-edge key: the shim projects requested extras under
extras(a list), but the extension readextra— sopkg[extra]dependency edges were silently never wired to the dep’s per-extra target. A consumer offoo[bar]now depends on@hub__foo//:bar. - Nested extras: the shim preserves each extra member’s own
extras, and per-extra targets wire them recursively through the
target graph — e.g.
fastmcp-slim[client,server]→py-key-value-aio[filetree,keyring,memory]→cachetools.
0.7.3 — uv resolution-markers
uv’s resolver can emit multiple [[package]] entries with the
same package name, each gated by a resolution-markers array
that scopes it to a subset of envs (most commonly per-Python-
minor variants — selectsmart-dsi 2026.1.dev53787 for
python_full_version == '3.12.*' alongside 2026.1.dev53830 for
>= '3.13'). Pre-v0.7.3, we treated all entries as candidates
and the extension crashed with “A repo named X is already
generated by this module extension”.
v0.7.3: uvlock_to_json.py projects the resolution-markers
array; _pip_extension_impl filters package entries up front,
keeping only the env-matching variant. Empty lists (the
unconditional common case) are unaffected. The reachability
walk + materialize loop see a deduplicated package list.
0.7.2 — graceful skip for platform-incompatible packages
Real-world lockfiles often pin packages whose wheels only cover
some platforms (Datadog’s ddtrace, the SAVVI private mirror’s
build of ddtrace ships Linux + Windows wheels only; macOS arm64
local dev hits a wall). The reachability filter walks every edge
without a marker, so these packages get visited and v0.7.1 then
failed pip.parse for the entire lockfile.
v0.7.2:
select_artifact()returnsNoneinstead offail()-ing when no host-compatible wheel exists and no sdist fallback is available._make_pkg_reporeturns the sentinel"skipped:no-artifact"for these; the caller drops them from the hub’srequirement()set. Consumer-siderequirement("ddtrace")then fails with “unknown package” + the known-packages list — the right surface for that error vs. a top-level pip.parse explosion.- Multi-platform mode (
pip.parse(platforms = [...])) keeps the fail-loudly behavior; cross-platform builds genuinely need a resolvable artifact on every requested platform.
0.7.1 — real-world lockfile bug fixes
Surfaced when wiring selectsmart-engine (an 89-package uv workspace
lockfile) through rules_uv end-to-end:
- Starlark
{!r}format strings: Starlark’sstr.format()doesn’t support Python’s{!r}repr conversion. We had ~10 instances in error-path code (markers.bzl,wheel_selection.bzl,pip/extensions.bzl) that crashed withError in format: Missing argument '!r'whenever they fired — masking the actual upstream errors. Replaced with explicit'{}'quoting. - PEP 508 environment variables: added
implementation_name = "cpython"andplatform_python_implementation = "CPython"to the marker env. selectsmart-engine’s lockfile carriesmarker = "implementation_name == 'pypy'"and similar on a few edges; without these vars the marker parser failed loudly. rules_uv is CPython-only today, so the values are constants. - Reachability filter: lockfiles list every package the resolver
considered, including platform-gated ones like
pywin32(only reached viasys_platform == 'win32'edges). On hosts where those edges fail, the packages have no host-compatible artifact and we used to hard-fail trying to materialize them. v0.7.1 walks the dependency graph from each root (editable/virtual entries + dep-groups), filtering edges by host markers, and only materializes the reachable set. Extras are pulled in transitively. - PEP 425 abi3 stable-ABI relaxation: a wheel tagged
cp38-abi3-<plat>(e.g. tornado, cryptography) is installable on any CPython ≥ 3.8 with the same platform. Our wheel selector required exactcpXYmatches and rejected the wheel. Fixed by building anabi3_compatible_py_tags()helper that returns[cp{host_minor}, cp{host_minor-1}, …, cp30]and using it for py-tag scoring only when the wheel declaresabi3in its abi field.
No API changes; existing fixtures still pass.
0.7.0 — editable workspace roots + PEP 735 dependency groups
-
Editable workspace roots are now skipped, not rejected. uv writes
source = { editable = "." }on the project’s own entry inuv.lock; v0.5 explicitly failed with “convert to a path source”. v0.7 skips the entry silently — the consumer’s source already lives in their own Bazel targets — but still mines the attacheddev-dependenciestable for dep-groups (see below). Closes the v0.6 compatibility gap that blocked uv workspaces. -
PEP 735 dependency groups are now first-class. The lockfile parser (
uvlock_to_json.py) hoists[package.dev-dependencies](which despite the name carries every named group —dev,test,docs, …) to a top-level map. Per-edge markers and extras are honoured. The hub repo emits a newgroup(name)macro alongsiderequirement():load("@pip//:requirements.bzl", "requirement", "group") py_test( name = "tests", deps = [requirement("my_lib")] + group("dev"), )ALL_GROUPSconstant lists every defined group name for introspection. Unknown group name fails the load with the known set. -
Smoke fixture now exercises both: lockfile’s
smokeentry iseditable = "."with adev = [{name = "iniconfig"}]group;examples/smoke:smoke_testpulls iniconfig viagroup("dev").
0.6.0 — pure-Python sdists in multi-platform mode
- Pure-Python sdists are now allowed in
pip.parse(platforms = ...)lockfiles. The extension installs the sdist once on the host withsdist_install_repo(forbid_native_extensions = True); the result is a single platform-agnostic repo shared by every target platform (same shape as a pure-Python wheel — no selector needed). v0.5 had this case fail loudly; the v0.6 path closes the gap for the common case (one-file utility packages that never bothered shipping wheels —six1.4.1 is the smoke- test case inexamples/multiplatform/). - Sdists that produce native extensions on the host install
(
.so/.pyd/.dylib/.dllfiles in the repo root) still fail loudly — cross-arch reuse of a host-built native extension would silently link wrong-arch binaries into other platforms. The error message points at the alternatives (pin a wheel, or drop the unsupported platform fromplatforms). - New
sdist_install_repo(forbid_native_extensions)attr; defaultFalseso single-platform host-only installs work exactly like before.
0.5.1 — docs + CI infrastructure
- Stardoc-generated reference docs in
docs/foruv_run,uv_toolchain+UvToolchainInfo, and thepipmodule extension.bazel run //docs:updateregenerates; CI gates the committed copies viadiff_test. - GitHub Actions CI:
bazel test //...on ubuntu + macos, plus a buildifier lint job. CHANGELOG.md(this file).
0.5.0 — cross-platform wheel select()
pip.parse(platforms = [<os>_<arch>, ...])opts a hub into multi-platform mode. Packages with platform-divergent native wheels fan out into per-platform repos (@<hub>__<pkg>__<platform>) behind a selector repo that emitsalias(actual = select({...}))over@platforms//os+@platforms//cpu. Non-host platform repos are declared but lazy-fetched.- Pure-Python wheels stay single-repo (platform-agnostic). Sdist +
git + path sources stay host-only, and the extension fails
loudly if a multi-platform lockfile points at an sdist-only
package (sdist install is host-only — running
uv pip install --targetonce per requested platform is on the v0.6 roadmap). - New
examples/multiplatform/end-to-end fixture. - Default
platforms = []keeps v0.4 host-only behavior intact — zero behavior change for existing consumers.
0.4.0 — extras, markers, git/path sources
- Extras:
requirement("pkg[extra]")resolves to a Bazel sub-target generated from each package’s[package.optional-dependencies]. The extra re-exports:pkgplus the extra’s filtered dep set. - Markers: PEP 508 subset evaluator
(
pip/private/markers.bzl) — recursive-descent parser covering==/!=/</<=/>/>=/in/not in,and/or/not, grouping. Evaluated at extension time againstpython_version+ host; edges whose markers fail are filtered out. - Git sources (
source = { git = "...", rev = "..." }): fetched vianew_git_repository. - Path sources (
source = { path = "..." }): symlinked via a thin_path_reporule. - Editable sources: explicit failure with a clear message.
- Hermetic uv invocation:
--no-configon everyuv pip installanduv python installcall so the developer’s~/.config/uv/uv.tomlcan’t leak into sandbox builds.
0.3.0 — native wheel selection + sdist install via uv
- Native wheel selection
(
pip/private/wheel_selection.bzl,pip/private/platform.bzl): PEP 425/600 tag scoring against a host-specific ordered tag list. Picks the bestmanylinux_*/macosx_*-tagged wheel for the host. - Sdist install (
pip/private/sdist_install.bzl):sdist_install_reporepository_rule that downloads the sdist and shells to@uv//:uv(uv pip install --target=. --no-deps) at repo-rule time. - New
pip.parseattrs:python_version(3.12default, wheel tag matching) andpython(host|uv, sdist install interpreter source).
0.2.0 — prebuilt-uv toolchain alternative
uv.toolchain(source = "prebuilt")fetches the official astral-sh/uv release asset for the host platform from GitHub Releases. Skips the ~12-min source-build cold path.- Supported hosts at pin:
darwin_{aarch64,x86_64},linux_{aarch64,x86_64}. uv_toolchain.uvnow takes aFilelabel (allow_single_file = True) so bothbuildandprebuiltmodes satisfy it via@uv//:binaryuniformly. The:installrust_binary indirection is gone.
0.1.0 — initial release
@uv//:binarybuilt from astral-sh/uv source via rules_rust’scargo_bootstrap_repository.uv_runmacro: sandbox-escapingbazel runwrapper.pip.parsemodule extension:uv.lock→@piphub with per-package repos and arequirement("<name>")macro.- Pure-Python wheel materialization (
py3-none-any) + raw-sdist fallback (no build step). - End-to-end smoke test in
examples/smoke/.