rules_uv
API reference, generated from the module’s .bzl docstrings (stardoc).
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”).
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" |
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
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