Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions docs/pypi/download.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pip.parse(
hub_name = "my_deps",
python_version = "3.13",
requirements_lock = "//:requirements_lock_3_11.txt",
# Or use a uv.lock file:
# uv_lock = "//:uv.lock",
)

use_repo(pip, "my_deps")
Expand Down
56 changes: 47 additions & 9 deletions docs/pypi/lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# Lock

:::{note}
Currently `rules_python` only supports `requirements.txt` format.
`rules_python` supports `requirements.txt` and `uv.lock` (bzlmod only)
formats.

#{gh-issue}`2787` tracks `pylock.toml` support.
:::
Expand Down Expand Up @@ -86,13 +87,13 @@ lock(

#### `[tool.uv]` settings from pyproject.toml

When a `pyproject.toml` file is among the {attr}`lock.srcs`, the
When a `pyproject.toml` file is among the {arg}`lock.srcs`, the
{obj}`lock` rule auto-detects the project directory and passes
`--project <dir>` to `uv pip compile`. This causes `uv` to read
`--project <dir>` to `uv`. This causes `uv` to read
`[tool.uv]` settings from that `pyproject.toml`, such as
`no-build-isolation`, `exclude-dependencies`, and workspace members.

If multiple `pyproject.toml` files are in {attr}`lock.srcs`, the one
If multiple `pyproject.toml` files are in {arg}`lock.srcs`, the one
with the shortest directory path is selected (this heuristic works for
typical uv workspace layouts where the root configuration is at the
shortest path).
Expand All @@ -112,11 +113,48 @@ lock(
:::{warning}
**Known limitations of auto-detection**

1. **Workspace heuristic** — the shortest-path selection may incorrectly assume the upper-most
workspace `pyproject.toml` is the correct one. For monorepos with multiple independent
sub-projects, you must set `project` explicitly for each {obj}`lock` target.
1. **No test target** — unlike {obj}`compile_pip_requirements`, no test target is auto-created; see
the {obj}`lock` docs for how to add one manually using `diff_test` from `bazel_skylib`.
1. **Workspace heuristic** — the shortest-path selection may incorrectly assume
the upper-most workspace `pyproject.toml` is the correct one. For monorepos
with multiple independent sub-projects, you must set `project` explicitly
for each {obj}`lock` target.
1. **No test target** — unlike {obj}`compile_pip_requirements`, no test target
is auto-created; see the {obj}`lock` docs for how to add one manually using
`diff_test` from `bazel_skylib`.
:::

## uv.lock (bzlmod only)

The {obj}`lock` rule also supports generating cross-platform `uv.lock` files
using `uv lock` when {arg}`lock.out` ends with `.lock` (such as `uv.lock`).

### Example usage

In `BUILD.bazel`:

```starlark
load("@rules_python//python/uv:lock.bzl", "lock")

lock(
name = "uv_lock",
srcs = ["pyproject.toml"],
out = "uv.lock",
)
```

Run `bazel run //:uv_lock.update` to generate or update `uv.lock`, then pass it
to {obj}`pip.parse` in `MODULE.bazel` via {attr}`pip.parse.uv_lock`:

```starlark
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
hub_name = "my_deps",
python_version = "3.13",
uv_lock = "//:uv.lock",
)
use_repo(pip, "my_deps")
```

`[tool.uv]` settings auto-detection and the {arg}`lock.project` attribute work
the same way as described in the `uv pip compile` section above.

For more documentation see {obj}`lock`.
11 changes: 7 additions & 4 deletions python/uv/lock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ Differences with the legacy {obj}`compile_pip_requirements` rule:
- Supports transitions out of the box.

Note, this does not provide a test target like {obj}`compile_pip_requirements` does.
The `uv pip compile` command is not hermetic and thus a test based on it would most likely be flaky:
The `uv lock` and `uv pip compile` commands are not hermetic and thus a test
based on them would most likely be flaky:
- It may require auth injected into it, so most likely it requires a local tag added so that the bazel action runs without sandboxing.
- It requires network access.

Given those points, a test target should be an explicit and properly documented target and not a hidden implicit target.
If, you would like to add a test target that always does the locking automatically to ensure that the
`requirements.txt` file is up-to-date, add something similar to:
lock file is up-to-date, add something similar to:

```starlark
load("@bazel_skylib//rules:native_binary.bzl", "native_test")
Expand All @@ -37,6 +38,7 @@ load("@rules_python//python/uv:lock.bzl", "lock")
lock(
name = "requirements",
srcs = ["pyproject.toml"],
out = "uv.lock",
)

native_test(
Expand All @@ -47,8 +49,8 @@ native_test(

### `[tool.uv]` settings support

When a `pyproject.toml` file is included in {attr}`lock.srcs`, the
`--project` flag is automatically passed to `uv pip compile` using the
When a `pyproject.toml` file is included in {arg}`lock.srcs`, the
`--project` flag is automatically passed to `uv` using the
directory of the shortest-path `pyproject.toml`. This causes `uv` to
read `[tool.uv]` settings such as `no-build-isolation`,
`exclude-dependencies`, and `[tool.uv.workspace]` from that file.
Expand All @@ -63,6 +65,7 @@ lock(
"pyproject.toml",
"requirements.in",
],
out = "requirements_lock.txt",
project = "subproject",
)
```
Expand Down
27 changes: 16 additions & 11 deletions python/uv/private/lock.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ modifications and the locking is not done from scratch.
),
"project": attr.string(
doc = """\
Overrides the `--project` directory passed to `uv pip compile`.
Overrides the `--project` directory passed to `uv`.
If not set, the project directory is auto-detected: when
`pyproject.toml` files are in {obj}`lock.srcs`, the one with the
shortest directory path is selected. This makes `uv` read
Expand Down Expand Up @@ -629,22 +629,23 @@ def lock(
"""Pin the requirements based on the src files.

This macro creates the following targets:
- `name`: the target that creates the requirements.txt file in a build
action. This target will have `no-cache` and `requires-network` added
to its tags.
- `name`: the target that creates the lock file (`uv.lock` or
`requirements.txt`) in a build action. This target will have `no-cache`
and `requires-network` added to its tags.
- `name.run`: a runnable target that can be used to pass extra parameters
to the same command that would be run in the `name` action. This will
update the source copy of the requirements file. You can customize the
update the source copy of the lock file. You can customize the
args via the command line, but it requires being able to run `uv` (and
possibly `python`) directly on your host. This target uses the `uv`
cache and inherits its cache settings from the environment, including
`UV_CACHE_DIR` and `UV_NO_CACHE`. Pass `--refresh` to refresh cached
data or `--no-cache` to disable caching. Build actions, including those
used by `name.update`, disable the `uv` cache.
- `name.update`: a target that can be run to update the source-tree version
of the requirements lock file. The output can be fed to the
{obj}`pip.parse` bzlmod extension tag class. Note, you can use
`native_test` to wrap this target to make a test. You can't customize the
of the lock file. The output can be fed to the {obj}`pip.parse` bzlmod
extension tag class via {attr}`pip.parse.uv_lock` or
{attr}`pip.parse.requirements_lock`. Note, you can use `native_test` to
wrap this target to make a test. You can't customize the
args via command line, but you can use RBE to generate requirements
(offload execution and run for different platforms). Note, that for RBE
to be usable, one needs to ensure that the nodes running the action have
Expand All @@ -662,8 +663,12 @@ def lock(
Args:
name: {type}`str` The prefix of all targets created by this macro.
srcs: {type}`list[Label]` The sources that will be used. Add all of the
files that would be passed as srcs to the `uv pip compile` command.
out: {type}`str` The output file relative to the package.
files that would be passed as srcs to `uv pip compile`, or the
`pyproject.toml` files when generating `uv.lock`.
out: {type}`str` The output file relative to the package. If the file
name ends with `.lock` (e.g. `uv.lock`), `uv lock` is used to
generate a `uv.lock` file; otherwise `uv pip compile` is used to
generate a `requirements.txt`-formatted file.
args: {type}`list[str]` The list of args to pass to uv. Note, these are
written into the runnable `name.run` target.
env: {type}`dict[str, str]` the environment variables to set. Note, this
Expand All @@ -683,7 +688,7 @@ def lock(
are compiling the requirements file for using it as a constraints
file. Defaults to `False`.
project: {type}`str | None` overrides the `--project` directory
passed to `uv pip compile`. By default the project directory
passed to `uv`. By default the project directory
is auto-detected: when {obj}`lock.srcs` contains
`pyproject.toml` files, the one with the shortest directory
path is selected. This causes `uv` to read `[tool.uv]`
Expand Down
Loading