Skip to content

Commit 517392f

Browse files
committed
Finish an initial annotations cut
1 parent 0af74ee commit 517392f

File tree

4 files changed

+101
-10
lines changed

4 files changed

+101
-10
lines changed

annotations.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Annotations.toml
2+
#
3+
# This is an aspect_rules_py//uv specific configuration file which allows
4+
# packages to be annoted with their build time dependencies. This fills a gap in
5+
# both the pylock and uv lockfile formats, neither of which allow for build
6+
# requirements to be specified.
7+
#
8+
# For instance if a package requires cython be available, this is how you can
9+
# configure it to be delivered.
10+
#
11+
# Takes the place of giant MODULE.bazel annotation tables.
12+
#
13+
# In future this machinery may allow for annotating requirements with
14+
# Bazel-managed dependencies (C libraries, etc.) by label. The exact semantics
15+
# there are TBD.
16+
17+
# We version lockfiles and support semver semantics here
18+
version = "0.0.0"
19+
20+
# Bravado doesn't have bdists, need to build it
21+
# Mark explicitly that we need wheel setuptools and build for it
22+
[[package]]
23+
name = "bravado-core"
24+
build-dependencies = [
25+
{ name = "build" },
26+
{ name = "setuptools" },
27+
]

bazel/include/python.MODULE.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ uv.declare_entrypoint(
4545
name = "py.test",
4646
entrypoint = "pytest:console_main",
4747
)
48+
uv.annotate_requirements(
49+
hub_name = "pypi",
50+
venv_name = "default",
51+
src = "//:annotations.toml"
52+
)
4853
use_repo(uv, "pypi")
4954

5055
http_file = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")

uv/private/extension.bzl

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ def _parse_locks(module_ctx, venv_specs):
137137
lock_specs.setdefault(lock.hub_name, {})
138138

139139
lockfile = toml.decode_file(module_ctx, lock.src)
140+
if lockfile.get("version") != 1:
141+
fail("Lockfile %s is an unsupported format version!" % lock.src)
142+
140143
if not lockfile:
141144
problems.append("Failed to extract {} in {}".format(lock.src, mod.name))
142145
continue
@@ -198,6 +201,55 @@ Problems:
198201

199202
return lock_specs
200203

204+
_default_annotations = struct(
205+
per_package={},
206+
default_build_deps=[
207+
{"name": "setuptools"},
208+
{"name": "build"},
209+
],
210+
)
211+
212+
def _parse_annotations(module_ctx, hub_specs, venv_specs):
213+
# Map of hub to venv to lock contents
214+
annotations = {}
215+
216+
for mod in module_ctx.modules:
217+
for ann in mod.tags.annotate_requirements:
218+
if ann.hub_name not in venv_specs:
219+
fail("Annotations file %s attaches to undefined hub %s" % (ann.src, ann.hub_name))
220+
221+
annotations.setdefault(ann.hub_name, {})
222+
223+
if ann.venv_name not in venv_specs.get(ann.hub_name, {}):
224+
fail("Annotations file %s attaches to undefined venv %s" % (ann.src, ann.venv_name))
225+
226+
# FIXME: Allow the default build deps to be changed
227+
annotations[ann.hub_name].setdefault(ann.venv_name, struct(
228+
per_package = {},
229+
default_build_deps = [] + _default_annotations.default_build_deps,
230+
))
231+
232+
annotations = toml.decode_file(module_ctx, ann.src)
233+
if annotations.get("version") != "0.0.0":
234+
fail("Annotations file %s doesn't specify a valid version= key" % ann.src)
235+
236+
for package in annotations.get("package", []):
237+
if not "name" in package:
238+
fail("Annotations file %s is invalid; all [[package]] entries must have a name" % ann.src)
239+
240+
# Apply name normalization so we don't forget about it
241+
package["name"] = normalize_name(package["name"])
242+
243+
if not "build-dependencies" in package:
244+
fail("Annotations file %s is invalid; all [[package]] entries must specify build-dependencies" % ann.src)
245+
246+
if package["name"] in annotations[ann.hub_name][ann.venv_name]:
247+
fail("Annotation conflict! Package %s is annotated in venv %s multiple times!" % (package["name"], ann.venv_name))
248+
249+
annotations[ann.hub_name][ann.venv_name].per_package[package["name"]] = package
250+
251+
return annotations
252+
201253
def _parse_overrides(module_ctx, venv_specs):
202254
# Map of hub -> venv -> target -> override label
203255
overrides = {}
@@ -379,7 +431,7 @@ def _venv_target(hub_name, venv, package_name):
379431
package_name,
380432
)
381433

382-
def _sbuild_repos(module_ctx, lock_specs, override_specs):
434+
def _sbuild_repos(module_ctx, lock_specs, annotation_specs, override_specs):
383435
for hub_name, venvs in lock_specs.items():
384436
for venv_name, lock in venvs.items():
385437
for package in lock.get("package", []):
@@ -392,17 +444,22 @@ def _sbuild_repos(module_ctx, lock_specs, override_specs):
392444

393445
name = _sbuild_repo_name(hub_name, venv_name, package)
394446

447+
venv_anns = annotation_specs.get(hub_name, {}).get(venv_name, _default_annotations)
448+
build_deps = venv_anns.per_package.get(package["name"], {}).get("build-dependencies", [])
449+
450+
# Per-package build deps, plus global defaults
451+
build_deps = {
452+
it["name"]: it for it in build_deps + venv_anns.default_build_deps
453+
}
454+
395455
# print("Creating sdist repo", name)
396456
sdist_build(
397457
name = name,
398458
src = "@" + _sdist_repo_name(package) + "//file",
399459
# FIXME: Add support for build deps and annotative build deps
400460
deps = [
401461
"@" + _venv_target(hub_name, venv_name, package["name"])
402-
for package in package.get("build-dependencies", [
403-
{"name": "setuptools"},
404-
{"name": "build"}
405-
])
462+
for package in build_deps.values()
406463
],
407464
)
408465

@@ -732,6 +789,8 @@ def _uv_impl(module_ctx):
732789
venv_specs = _parse_venvs(module_ctx, hub_specs)
733790

734791
lock_specs = _parse_locks(module_ctx, venv_specs)
792+
793+
annotation_specs = _parse_annotations(module_ctx, hub_specs, venv_specs)
735794

736795
override_specs = _parse_overrides(module_ctx, venv_specs)
737796
# Roll through all the configured wheels, collect & validate the unique
@@ -741,15 +800,15 @@ def _uv_impl(module_ctx):
741800

742801
# Collect declared entrypoints for packages
743802
entrypoints = _collect_entrypoints(module_ctx, lock_specs)
744-
print(entrypoints)
803+
# print(entrypoints)
745804

746805
# Roll through and create sdist and whl repos for all configured sources
747806
# Note that these have no deps to this point
748807
_raw_sdist_repos(module_ctx, lock_specs, override_specs)
749808
_raw_whl_repos(module_ctx, lock_specs, override_specs)
750809

751810
# Roll through and create per-venv sdist build repos
752-
_sbuild_repos(module_ctx, lock_specs, override_specs)
811+
_sbuild_repos(module_ctx, lock_specs, annotation_specs, override_specs)
753812

754813
# Roll through and create per-venv whl installs
755814
#
@@ -792,7 +851,7 @@ _lockfile_tag = tag_class(
792851
},
793852
)
794853

795-
_install_requires_tag = tag_class(
854+
_annotations_tag = tag_class(
796855
attrs = {
797856
"hub_name": attr.string(mandatory = True),
798857
"venv_name": attr.string(mandatory = True),
@@ -829,7 +888,7 @@ uv = module_extension(
829888
"declare_hub": _hub_tag,
830889
"declare_venv": _venv_tag,
831890
"lockfile": _lockfile_tag,
832-
"annotate_requirements": _install_requires_tag,
891+
"annotate_requirements": _annotations_tag,
833892
"declare_entrypoint": _declare_entrypoint,
834893
"discover_entrypoints": _create_entrypoints,
835894
"override_requirement": _override_requirement,

uv/private/tomltool/toml.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def _decode_file(ctx, content_path):
6868
return json.decode(out.stdout)
6969

7070
else:
71-
return None
71+
fail("Unable to decode TOML file %s" % content_path)
7272

7373
toml = struct(
7474
decode_file = _decode_file,

0 commit comments

Comments
 (0)