Skip to content

Commit 4cb8412

Browse files
authored
feat(uv): parse the dist-manifest.json to not hardcode sha256 in rules_python (#2578)
Finalize the `uv` extension interface employing a builder pattern so that the users can specify the exact version that needs to be registered. This also moves the registration of the actual toolchain to `rules_python` itself and ensures that an incompatible noop toolchain is registered if nothing is configured. This ensures that the `register_toolchains("@uv//:all")` never fails. If the `url/sha256` values are not specified, this is falling back to using the `dist-manifest.json` on the GH releases page so that we can get the expected `sha256` value of each available file and download all of the usable archives. This means that `rules_python` no longer needs to be updated for `uv` version bumps. The remaining bits for closing the ticket: - [ ] Finalize the `lock` interface. - [ ] Add the locking target to the `pip.parse` hub repo if `pyproject.toml` is passed in. - [ ] Add a rule/target for `venv` creation. Work towards #1975.
1 parent 52712b9 commit 4cb8412

17 files changed

+1371
-284
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ Unreleased changes template.
8080
* {obj}`//python/bin:python`: convenience target for directly running an
8181
interpreter. {obj}`--//python/bin:python_src` can be used to specify a
8282
binary whose interpreter to use.
83+
* (uv) Now the extension can be fully configured via `bzlmod` APIs without the
84+
need to patch `rules_python`. The documentation has been added to `rules_python`
85+
docs but usage of the extension may result in your setup breaking without any
86+
notice. What is more, the URLs and SHA256 values will be retrieved from the
87+
GitHub releases page metadata published by the `uv` project.
8388
* (pypi) An extra argument to add the interpreter lib dir to `LDFLAGS` when
8489
building wheels from `sdist`.
8590

MODULE.bazel

+75-8
Original file line numberDiff line numberDiff line change
@@ -174,16 +174,83 @@ use_repo(
174174
"build_bazel_bazel_self",
175175
)
176176

177-
# EXPERIMENTAL: This is experimental and may be removed without notice
178-
uv = use_extension(
177+
# TODO @aignas 2025-01-27: should this be moved to `//python/extensions:uv.bzl` or should
178+
# it stay as it is? I think I may prefer to move it.
179+
uv = use_extension("//python/uv:uv.bzl", "uv")
180+
181+
# Here is how we can define platforms for the `uv` binaries - this will affect
182+
# all of the downstream callers because we are using the extension without
183+
# `dev_dependency = True`.
184+
uv.default(
185+
base_url = "https://github.com/astral-sh/uv/releases/download",
186+
manifest_filename = "dist-manifest.json",
187+
version = "0.6.3",
188+
)
189+
uv.default(
190+
compatible_with = [
191+
"@platforms//os:macos",
192+
"@platforms//cpu:aarch64",
193+
],
194+
platform = "aarch64-apple-darwin",
195+
)
196+
uv.default(
197+
compatible_with = [
198+
"@platforms//os:linux",
199+
"@platforms//cpu:aarch64",
200+
],
201+
platform = "aarch64-unknown-linux-gnu",
202+
)
203+
uv.default(
204+
compatible_with = [
205+
"@platforms//os:linux",
206+
"@platforms//cpu:ppc",
207+
],
208+
platform = "powerpc64-unknown-linux-gnu",
209+
)
210+
uv.default(
211+
compatible_with = [
212+
"@platforms//os:linux",
213+
"@platforms//cpu:ppc64le",
214+
],
215+
platform = "powerpc64le-unknown-linux-gnu",
216+
)
217+
uv.default(
218+
compatible_with = [
219+
"@platforms//os:linux",
220+
"@platforms//cpu:s390x",
221+
],
222+
platform = "s390x-unknown-linux-gnu",
223+
)
224+
uv.default(
225+
compatible_with = [
226+
"@platforms//os:macos",
227+
"@platforms//cpu:x86_64",
228+
],
229+
platform = "x86_64-apple-darwin",
230+
)
231+
uv.default(
232+
compatible_with = [
233+
"@platforms//os:windows",
234+
"@platforms//cpu:x86_64",
235+
],
236+
platform = "x86_64-pc-windows-msvc",
237+
)
238+
uv.default(
239+
compatible_with = [
240+
"@platforms//os:linux",
241+
"@platforms//cpu:x86_64",
242+
],
243+
platform = "x86_64-unknown-linux-gnu",
244+
)
245+
use_repo(uv, "uv")
246+
247+
register_toolchains("@uv//:all")
248+
249+
uv_dev = use_extension(
179250
"//python/uv:uv.bzl",
180251
"uv",
181252
dev_dependency = True,
182253
)
183-
uv.toolchain(uv_version = "0.4.25")
184-
use_repo(uv, "uv_toolchains")
185-
186-
register_toolchains(
187-
"@uv_toolchains//:all",
188-
dev_dependency = True,
254+
uv_dev.configure(
255+
version = "0.6.2",
189256
)

examples/bzlmod/MODULE.bazel

+9-6
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,15 @@ python.single_version_platform_override(
101101
# rules based on the `python_version` arg values.
102102
use_repo(python, "python_3_10", "python_3_9", "python_versions", "pythons_hub")
103103

104-
# EXPERIMENTAL: This is experimental and may be removed without notice
105-
uv = use_extension("@rules_python//python/uv:uv.bzl", "uv")
106-
uv.toolchain(uv_version = "0.4.25")
107-
use_repo(uv, "uv_toolchains")
108-
109-
register_toolchains("@uv_toolchains//:all")
104+
# EXPERIMENTAL: This is experimental and may be changed or removed without notice
105+
uv = use_extension(
106+
"@rules_python//python/uv:uv.bzl",
107+
"uv",
108+
# Use `dev_dependency` so that the toolchains are not defined pulled when your
109+
# module is used elsewhere.
110+
dev_dependency = True,
111+
)
112+
uv.configure(version = "0.6.2")
110113

111114
# This extension allows a user to create modifications to how rules_python
112115
# creates different wheel repositories. Different attributes allow the user

python/uv/private/BUILD.bazel

+7-14
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,19 @@ bzl_library(
4747
name = "uv_bzl",
4848
srcs = ["uv.bzl"],
4949
visibility = ["//python/uv:__subpackages__"],
50-
deps = [":uv_repositories_bzl"],
51-
)
52-
53-
bzl_library(
54-
name = "uv_repositories_bzl",
55-
srcs = ["uv_repositories.bzl"],
56-
visibility = ["//python/uv:__subpackages__"],
5750
deps = [
5851
":toolchain_types_bzl",
52+
":uv_repository_bzl",
5953
":uv_toolchains_repo_bzl",
60-
":versions_bzl",
6154
],
6255
)
6356

57+
bzl_library(
58+
name = "uv_repository_bzl",
59+
srcs = ["uv_repository.bzl"],
60+
visibility = ["//python/uv:__subpackages__"],
61+
)
62+
6463
bzl_library(
6564
name = "uv_toolchain_bzl",
6665
srcs = ["uv_toolchain.bzl"],
@@ -82,9 +81,3 @@ bzl_library(
8281
"//python/private:text_util_bzl",
8382
],
8483
)
85-
86-
bzl_library(
87-
name = "versions_bzl",
88-
srcs = ["versions.bzl"],
89-
visibility = ["//python/uv:__subpackages__"],
90-
)

python/uv/private/lock.bzl

+5-3
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ def lock(*, name, srcs, out, upgrade = False, universal = True, args = [], **kwa
3030
"""Pin the requirements based on the src files.
3131
3232
Differences with the current {obj}`compile_pip_requirements` rule:
33-
- This is implemented in shell and uv.
33+
- This is implemented in shell and `uv`.
3434
- This does not error out if the output file does not exist yet.
3535
- Supports transitions out of the box.
36+
- The execution of the lock file generation is happening inside of a build
37+
action in a `genrule`.
3638
3739
Args:
3840
name: The name of the target to run for updating the requirements.
@@ -41,8 +43,8 @@ def lock(*, name, srcs, out, upgrade = False, universal = True, args = [], **kwa
4143
upgrade: Tell `uv` to always upgrade the dependencies instead of
4244
keeping them as they are.
4345
universal: Tell `uv` to generate a universal lock file.
44-
args: Extra args to pass to `uv`.
45-
**kwargs: Extra kwargs passed to the {obj}`py_binary` rule.
46+
args: Extra args to pass to the rule.
47+
**kwargs: Extra kwargs passed to the binary rule.
4648
"""
4749
pkg = native.package_name()
4850
update_target = name + ".update"

python/uv/private/toolchains_hub.bzl

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2025 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""A macro used from the uv_toolchain hub repo."""
16+
17+
load(":toolchain_types.bzl", "UV_TOOLCHAIN_TYPE")
18+
19+
def toolchains_hub(
20+
*,
21+
name,
22+
toolchains,
23+
implementations,
24+
target_compatible_with,
25+
target_settings):
26+
"""Define the toolchains so that the lexicographical order registration is deterministic.
27+
28+
TODO @aignas 2025-03-09: see if this can be reused in the python toolchains.
29+
30+
Args:
31+
name: The prefix to all of the targets, which goes after a numeric prefix.
32+
toolchains: The toolchain names for the targets defined by this macro.
33+
The earlier occurring items take precedence over the later items if
34+
they match the target platform and target settings.
35+
implementations: The name to label mapping.
36+
target_compatible_with: The name to target_compatible_with list mapping.
37+
target_settings: The name to target_settings list mapping.
38+
"""
39+
if len(toolchains) != len(implementations):
40+
fail("Each name must have an implementation")
41+
42+
# We are defining the toolchains so that the order of toolchain matching is
43+
# the same as the order of the toolchains, because:
44+
# * the toolchains are matched by target settings and target_compatible_with
45+
# * the first toolchain satisfying the above wins
46+
#
47+
# this means we need to register the toolchains prefixed with a number of
48+
# format 00xy, where x and y are some digits and the leading zeros to
49+
# ensure lexicographical sorting.
50+
#
51+
# Add 1 so that there is always a leading zero
52+
prefix_len = len(str(len(toolchains))) + 1
53+
prefix = "0" * (prefix_len - 1)
54+
55+
for i, toolchain in enumerate(toolchains):
56+
# prefix with a prefix and then truncate the string.
57+
number_prefix = "{}{}".format(prefix, i)[-prefix_len:]
58+
59+
native.toolchain(
60+
name = "{}_{}_{}".format(number_prefix, name, toolchain),
61+
target_compatible_with = target_compatible_with.get(toolchain, []),
62+
target_settings = target_settings.get(toolchain, []),
63+
toolchain = implementations[toolchain],
64+
toolchain_type = UV_TOOLCHAIN_TYPE,
65+
)

0 commit comments

Comments
 (0)