From 023493df2337aa6a950472a7e6ee40ebd061a683 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Mon, 26 Oct 2026 08:12:38 -0700 Subject: [PATCH] docs(uv): document uv.lock support in locking and download docs `rules_python` supports `uv.lock` in addition to `requirements.txt`, leaving documentation that states only `requirements.txt` is supported outdated. Update the locking and download guides and `lock` rule docstrings to document both `uv.lock` generation via `uv lock` and `requirements.txt` generation via `uv pip compile`. --- docs/pypi/download.md | 2 ++ docs/pypi/lock.md | 56 ++++++++++++++++++++++++++++++++------ python/uv/lock.bzl | 11 +++++--- python/uv/private/lock.bzl | 27 ++++++++++-------- 4 files changed, 72 insertions(+), 24 deletions(-) diff --git a/docs/pypi/download.md b/docs/pypi/download.md index b9904d4071..cc8b7890aa 100644 --- a/docs/pypi/download.md +++ b/docs/pypi/download.md @@ -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") diff --git a/docs/pypi/lock.md b/docs/pypi/lock.md index c944938efa..b1c59eeccd 100644 --- a/docs/pypi/lock.md +++ b/docs/pypi/lock.md @@ -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. ::: @@ -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 ` to `uv pip compile`. This causes `uv` to read +`--project ` 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). @@ -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`. diff --git a/python/uv/lock.bzl b/python/uv/lock.bzl index 7fd50082ea..ddb6b9078a 100644 --- a/python/uv/lock.bzl +++ b/python/uv/lock.bzl @@ -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") @@ -37,6 +38,7 @@ load("@rules_python//python/uv:lock.bzl", "lock") lock( name = "requirements", srcs = ["pyproject.toml"], + out = "uv.lock", ) native_test( @@ -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. @@ -63,6 +65,7 @@ lock( "pyproject.toml", "requirements.in", ], + out = "requirements_lock.txt", project = "subproject", ) ``` diff --git a/python/uv/private/lock.bzl b/python/uv/private/lock.bzl index a75c54dde2..c0ad8bf8d3 100644 --- a/python/uv/private/lock.bzl +++ b/python/uv/private/lock.bzl @@ -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 @@ -629,12 +629,12 @@ 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 @@ -642,9 +642,10 @@ def lock( 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 @@ -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 @@ -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]`