Skip to content

Commit 087fb57

Browse files
committed
feat(uv): allow specifying any uv version to use
This is 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 it to the `pip.parse` hub repo if `pyproject.toml` is passed in. - [ ] Add a rule/target for `venv` creation. Work towards #1975.
1 parent 88d52f1 commit 087fb57

File tree

7 files changed

+281
-153
lines changed

7 files changed

+281
-153
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ Unreleased changes template.
5757
* (rules) deprecation warnings for deprecated symbols have been turned off by
5858
default for now and can be enabled with `RULES_PYTHON_DEPRECATION_WARNINGS`
5959
env var.
60+
* (uv) Now the extension can be fully configured via `bzlmod` APIs without the
61+
need to patch `rules_python`. The documentation has been added to `rules_python`
62+
docs but usage of the extension may result in your setup breaking without any
63+
notice. What is more, the URLs and SHA256 values will be retrieved from the
64+
GitHub releases page metadata published by the `uv` project.
6065

6166
{#v0-0-0-fixed}
6267
### Fixed

MODULE.bazel

+76-4
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,86 @@ use_repo(
173173
"build_bazel_bazel_self",
174174
)
175175

176-
# EXPERIMENTAL: This is experimental and may be removed without notice
177-
uv = use_extension(
176+
uv = use_extension("//python/uv:uv.bzl", "uv")
177+
178+
# Here is how we can define platforms for the `uv` binaries - this will affect
179+
# all of the downstream callers because we are using the extension without
180+
# `dev_dependency = True`.
181+
uv.platform(
182+
name = "aarch64-apple-darwin",
183+
compatible_with = [
184+
"@platforms//os:macos",
185+
"@platforms//cpu:aarch64",
186+
],
187+
flag_values = {},
188+
)
189+
uv.platform(
190+
name = "aarch64-unknown-linux-gnu",
191+
compatible_with = [
192+
"@platforms//os:linux",
193+
"@platforms//cpu:aarch64",
194+
],
195+
flag_values = {},
196+
)
197+
uv.platform(
198+
name = "powerpc64-unknown-linux-gnu",
199+
compatible_with = [
200+
"@platforms//os:linux",
201+
"@platforms//cpu:ppc",
202+
],
203+
flag_values = {},
204+
)
205+
uv.platform(
206+
name = "powerpc64le-unknown-linux-gnu",
207+
compatible_with = [
208+
"@platforms//os:linux",
209+
"@platforms//cpu:ppc64le",
210+
],
211+
flag_values = {},
212+
)
213+
uv.platform(
214+
name = "s390x-unknown-linux-gnu",
215+
compatible_with = [
216+
"@platforms//os:linux",
217+
"@platforms//cpu:s390x",
218+
],
219+
flag_values = {},
220+
)
221+
uv.platform(
222+
name = "x86_64-apple-darwin",
223+
compatible_with = [
224+
"@platforms//os:macos",
225+
"@platforms//cpu:x86_64",
226+
],
227+
flag_values = {},
228+
)
229+
uv.platform(
230+
name = "x86_64-pc-windows-msvc",
231+
compatible_with = [
232+
"@platforms//os:windows",
233+
"@platforms//cpu:x86_64",
234+
],
235+
flag_values = {},
236+
)
237+
uv.platform(
238+
name = "x86_64-unknown-linux-gnu",
239+
compatible_with = [
240+
"@platforms//os:linux",
241+
"@platforms//cpu:x86_64",
242+
],
243+
flag_values = {},
244+
)
245+
246+
uv_dev = use_extension(
178247
"//python/uv:uv.bzl",
179248
"uv",
180249
dev_dependency = True,
181250
)
182-
uv.toolchain(uv_version = "0.4.25")
183-
use_repo(uv, "uv_toolchains")
251+
uv_dev.toolchain(
252+
name = "uv_toolchains",
253+
version = "0.5.24",
254+
)
255+
use_repo(uv_dev, "uv_toolchains")
184256

185257
register_toolchains(
186258
"@uv_toolchains//:all",

python/uv/private/BUILD.bazel

-7
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ bzl_library(
5757
deps = [
5858
":toolchain_types_bzl",
5959
":uv_toolchains_repo_bzl",
60-
":versions_bzl",
6160
],
6261
)
6362

@@ -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/uv.bzl

+161-12
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,181 @@ load(":uv_repositories.bzl", "uv_repositories")
2222

2323
_DOC = """\
2424
A module extension for working with uv.
25+
26+
Use it in your own setup by:
27+
```starlark
28+
uv = use_extension(
29+
"@rules_python//python/uv:uv.bzl",
30+
"uv",
31+
dev_dependency = True,
32+
)
33+
uv.toolchain(
34+
name = "uv_toolchains",
35+
version = "0.5.24",
36+
)
37+
use_repo(uv, "uv_toolchains")
38+
39+
register_toolchains(
40+
"@uv_toolchains//:all",
41+
dev_dependency = True,
42+
)
43+
```
44+
45+
Since this is only for locking the requirements files, it should be always
46+
marked as a `dev_dependency`.
2547
"""
2648

49+
_DIST_MANIFEST_JSON = "dist-manifest.json"
50+
_DEFAULT_BASE_URL = "https://github.com/astral-sh/uv/releases/download"
51+
52+
config = tag_class(
53+
doc = "Configure where the binaries are going to be downloaded from.",
54+
attrs = {
55+
"base_url": attr.string(
56+
doc = "Base URL to download metadata about the binaries and the binaries themselves.",
57+
default = _DEFAULT_BASE_URL,
58+
),
59+
},
60+
)
61+
62+
platform = tag_class(
63+
doc = "Configure the available platforms for lock file generation.",
64+
attrs = {
65+
"compatible_with": attr.label_list(
66+
doc = "The compatible with constraint values for toolchain resolution",
67+
),
68+
"flag_values": attr.label_keyed_string_dict(
69+
doc = "The flag values for toolchain resolution",
70+
),
71+
"name": attr.string(
72+
doc = "The platform string used in the UV repository to denote the platform triple.",
73+
mandatory = True,
74+
),
75+
},
76+
)
77+
2778
uv_toolchain = tag_class(
2879
doc = "Configure uv toolchain for lock file generation.",
2980
attrs = {
30-
"uv_version": attr.string(doc = "Explicit version of uv.", mandatory = True),
81+
"name": attr.string(
82+
doc = "The name of the toolchain repo",
83+
default = "uv_toolchains",
84+
),
85+
"version": attr.string(
86+
doc = "Explicit version of uv.",
87+
mandatory = True,
88+
),
3189
},
3290
)
3391

3492
def _uv_toolchain_extension(module_ctx):
93+
config = {
94+
"platforms": {},
95+
}
96+
3597
for mod in module_ctx.modules:
98+
if not mod.is_root and not mod.name == "rules_python":
99+
# Only rules_python and the root module can configure this.
100+
#
101+
# Ignore any attempts to configure the `uv` toolchain elsewhere
102+
#
103+
# Only the root module may configure the uv toolchain.
104+
# This prevents conflicting registrations with any other modules.
105+
#
106+
# NOTE: We may wish to enforce a policy where toolchain configuration is only allowed in the root module, or in rules_python. See https://github.com/bazelbuild/bazel/discussions/22024
107+
continue
108+
109+
# Note, that the first registration will always win, givin priority to
110+
# the root module.
111+
112+
for platform_attr in mod.tags.platform:
113+
config["platforms"].setdefault(platform_attr.name, struct(
114+
name = platform_attr.name.replace("-", "_").lower(),
115+
compatible_with = platform_attr.compatible_with,
116+
flag_values = platform_attr.flag_values,
117+
))
118+
119+
for config_attr in mod.tags.config:
120+
config.setdefault("base_url", config_attr.base_url)
121+
36122
for toolchain in mod.tags.toolchain:
37-
if not mod.is_root:
38-
fail(
39-
"Only the root module may configure the uv toolchain.",
40-
"This prevents conflicting registrations with any other modules.",
41-
"NOTE: We may wish to enforce a policy where toolchain configuration is only allowed in the root module, or in rules_python. See https://github.com/bazelbuild/bazel/discussions/22024",
42-
)
43-
44-
uv_repositories(
45-
uv_version = toolchain.uv_version,
46-
register_toolchains = False,
123+
config.setdefault("version", toolchain.version)
124+
config.setdefault("name", toolchain.name)
125+
126+
if not config["version"]:
127+
return
128+
129+
config.setdefault("base_url", _DEFAULT_BASE_URL)
130+
config["urls"] = _get_tool_urls_from_dist_manifest(
131+
module_ctx,
132+
base_url = "{base_url}/{version}".format(**config),
133+
)
134+
uv_repositories(
135+
name = config["name"],
136+
platforms = config["platforms"],
137+
urls = config["urls"],
138+
version = config["version"],
139+
)
140+
141+
def _get_tool_urls_from_dist_manifest(module_ctx, *, base_url):
142+
"""Download the results about remote tool sources.
143+
144+
This relies on the tools using the cargo packaging to infer the actual
145+
sha256 values for each binary.
146+
"""
147+
dist_manifest = module_ctx.path(_DIST_MANIFEST_JSON)
148+
module_ctx.download(base_url + "/" + _DIST_MANIFEST_JSON, output = dist_manifest)
149+
dist_manifest = json.decode(module_ctx.read(dist_manifest))
150+
151+
artifacts = dist_manifest["artifacts"]
152+
tool_sources = {}
153+
downloads = {}
154+
for fname, artifact in artifacts.items():
155+
if artifact.get("kind") != "executable-zip":
156+
continue
157+
158+
checksum = artifacts[artifact["checksum"]]
159+
checksum_fname = checksum["name"]
160+
checksum_path = module_ctx.path(checksum_fname)
161+
downloads[checksum_path] = struct(
162+
download = module_ctx.download(
163+
"{}/{}".format(base_url, checksum_fname),
164+
output = checksum_path,
165+
block = False,
166+
),
167+
archive_fname = fname,
168+
platforms = checksum["target_triples"],
169+
)
170+
171+
for checksum_path, download in downloads.items():
172+
result = download.download.wait()
173+
if not result.success:
174+
fail(result)
175+
176+
archive_fname = download.archive_fname
177+
178+
sha256, _, checksummed_fname = module_ctx.read(checksum_path).partition(" ")
179+
checksummed_fname = checksummed_fname.strip(" *\n")
180+
if archive_fname != checksummed_fname:
181+
fail("The checksum is for a different file, expected '{}' but got '{}'".format(
182+
archive_fname,
183+
checksummed_fname,
184+
))
185+
186+
for platform in download.platforms:
187+
tool_sources[platform] = struct(
188+
urls = ["{}/{}".format(base_url, archive_fname)],
189+
sha256 = sha256,
47190
)
48191

192+
return tool_sources
193+
49194
uv = module_extension(
50195
doc = _DOC,
51196
implementation = _uv_toolchain_extension,
52-
tag_classes = {"toolchain": uv_toolchain},
197+
tag_classes = {
198+
"config": config,
199+
"platform": platform,
200+
"toolchain": uv_toolchain,
201+
},
53202
)

0 commit comments

Comments
 (0)