feat: Support tool.uv.extra-build-dependencies in annotation loading#1297
feat: Support tool.uv.extra-build-dependencies in annotation loading#1297konsti-openai wants to merge 17 commits into
tool.uv.extra-build-dependencies in annotation loading#1297Conversation
|
I'm new to rules_py, so I'm interested in feedback in how to expose this best: Should we just load extra build dependencies by default? Should this be a separate command similar to |
tool.uv.extra-build-dependencies in annotation loadingtool.uv.extra-build-dependencies in annotation loading
0e808b5 to
1518240
Compare
| { name = "build" }, | ||
| { name = "setuptools" }, | ||
| ] | ||
| # This format is supported, but |
There was a problem hiding this comment.
Should we still be testing both though? Or is this old/deprecated enough we can just delete it (instead of commenting it out like this)?
There was a problem hiding this comment.
I kept it there to not confuse user who come across the old format. imho it's fine to keep both format around and avoid breaking users, the comment should help users to understand what they should use and why they may see something different in existing code.
tamird
left a comment
There was a problem hiding this comment.
This currently accepts only a narrow subset of uv's extra-build-dependencies syntax and silently drops several valid cases. Please either preserve uv's semantics for requirement objects, markers, split versions, and build-only dependencies or explicitly reject unsupported forms, and add an e2e case whose build really depends on the annotation.
| if project.load_annotations: | ||
| extra_build_dependencies = tool_uv.get("extra-build-dependencies", {}) | ||
| for package, extra_deps in extra_build_dependencies.items(): | ||
| target = _resolve(package, None) |
There was a problem hiding this comment.
Resolving the annotated package only through default_versions silently skips every valid split-version lock: that map is populated only when a name has one version, while uv applies the annotation by package name across selected versions. The same issue affects build dependencies below. Please expand the target/dependency resolution over package_versions and add a conflicting-version or platform-fork regression.
There was a problem hiding this comment.
I've added that, though it's important to note that build-system.requires extracts only the name (
rules_py/uv/private/sdist_configure/detect_native.py
Lines 124 to 128 in 5204fc2
tool.uv.extra-build-dependencies and build-system.requires are parsed.
|
Codex also flagged two other problems. They are already present with
|
6bac123 to
7c53c7d
Compare
| package_versions, | ||
| fail_if_missing = False, | ||
| ) | ||
| if not resolved_deps: |
There was a problem hiding this comment.
It's arguable whether this should or shouldn't fail. Codex complains that this fails for wheel-only packages while annotations.toml skips, but OTOH annotating a wheel-only package with requirements missing in the lock sounds bogus.
|
I'm having a hard time scoping this against preexisting gaps and making this too large for review, but I think the current state is ready for a review. |
Currently, `annotations.toml` supports declaring extra build dependencies in a format that runs parallel to `tool.uv.extra-build-dependencies`. This PR adds support for loading `tool.uv.extra-build-dependencies` to `uv.project` by declaring `load_annotations = True`.
… through conflicts
4fd294c to
a6aaf44
Compare
tamird
left a comment
There was a problem hiding this comment.
Re-reviewed because Jason requested another pass in #ext-aspect-build. The updated implementation now covers markers, extras, split target versions, URL/git references, and composition with legacy annotations, but two correctness gaps remain: selected build-dependency versions are erased (and match-runtime is accepted but ignored), and a marker on a requested extra is not propagated to that extra's transitive dependencies.
Pre-existing path amplified by this change: uv/private/extension/projectfile.bzl:261-270 traverses children using only each edge marker and drops the parent's reachability marker. Thus urllib3[socks]; sys_platform != 'win32' can activate its transitive PySocks on Windows. The newly added collect_activated_extras_build_extra_test codifies this loss by supplying a sys_platform root marker and asserting the child has only its CPython edge marker. Carry/conjoin parent and edge markers through the worklist and add a false-platform regression.
The primary uv use case is also still unsupported: the new negative fixture is the documented cchardet/cython example, so users must add a build-only dependency to runtime to use this API. If that limitation is intentional for this release, it needs to be explicit in the user-facing documentation/release note, and wheel-only targets should not fail an otherwise unnecessary annotation. The current fixtures do not falsify these cases.
— tamirdex
| # A base requirement and its extras resolve through the same | ||
| # project package label, so deduplicate after rendering labels. | ||
| sbuild_deps = sets.to_list(sets.make([ | ||
| "@{0}//:{1}".format(*dep) |
There was a problem hiding this comment.
This discards the resolved build-dependency version and always emits the versionless project alias. With conflicting groups, a plain requirement such as torch==2.5 can therefore build against the active runtime torch==2.6; conversely the object form's match-runtime flag is explicitly ignored above, so the documented guarantee is not enforced. The current fixtures keep six/pyparsing single-version and cannot catch either case. Please preserve version-bearing install labels for pinned requirements, implement (or explicitly reject) match-runtime, and add a two-group/two-version source-build regression that asserts the configured build tool receives the intended version. uv documents the distinction here: https://docs.astral.sh/uv/concepts/projects/config/#augmenting-build-dependencies
— tamirdex
| locked_urls = locked_urls, | ||
| fail_if_missing = False, | ||
| ) | ||
| if not resolved_deps: |
There was a problem hiding this comment.
This resolves and fails every annotated package before checking whether a source build is needed. A wheel-only package with an otherwise harmless missing build annotation therefore breaks module evaluation, even though uv would never build it. More fundamentally, the new missing-extra-build-dep fixture is uv's canonical cchardet = ["cython"] example, so this API cannot support its primary build-only use case without polluting runtime dependencies. Please skip irrelevant wheel-only annotations and either resolve build-only requirements separately or clearly document this limitation in the public release/API documentation.
— tamirdex
| for artifact in artifacts: | ||
| url = artifact.get("url") | ||
| if url: | ||
| locked_urls[(locked_package["name"], url)] = dependency |
There was a problem hiding this comment.
The direct-reference index includes wheel/sdist URLs and git remotes, but not locked source.path/directory/editable entries. A valid locked requirement such as name @ file:///... therefore reaches the missing-dependency error even though the source is in uv.lock. Since this change advertises full requirement syntax, please support the locked local-source forms or explicitly reject/document them, with a local-source regression.
— tamirdex
Currently,
annotations.tomlsupports declaring extra build dependencies in a format that runs parallel totool.uv.extra-build-dependencies. This PR adds support for loading uv'stool.uv.extra-build-dependenciestorules_py'suv.project. This moves rules_py closer to uv's interface.Unlike
annotations.toml,tool.uv.extra-build-dependenciessupports the full requirements syntax.Changes are visible to end-users: yes/no
Test plan
Extended the existing e2e snapshot test on top of uv-sdist-fallback to cover both the old and the new syntax for two different packages.