Skip to content

feat: Support tool.uv.extra-build-dependencies in annotation loading#1297

Open
konsti-openai wants to merge 17 commits into
aspect-build:mainfrom
konsti-openai:konsti/support-uv-extra-build-annotations
Open

feat: Support tool.uv.extra-build-dependencies in annotation loading#1297
konsti-openai wants to merge 17 commits into
aspect-build:mainfrom
konsti-openai:konsti/support-uv-extra-build-annotations

Conversation

@konsti-openai

@konsti-openai konsti-openai commented Jul 14, 2026

Copy link
Copy Markdown

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 uv's tool.uv.extra-build-dependencies to rules_py's uv.project. This moves rules_py closer to uv's interface.

Unlike annotations.toml, tool.uv.extra-build-dependencies supports the full requirements syntax.


Changes are visible to end-users: yes/no

  • Searched for relevant documentation and updated as needed: yes
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

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.

@CLAassistant

CLAassistant commented Jul 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@konsti-openai

Copy link
Copy Markdown
Author

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 unstable_annotate_packages?

@konsti-openai konsti-openai changed the title Support tool.uv.extra-build-dependencies in annotation loading feat: Support tool.uv.extra-build-dependencies in annotation loading Jul 14, 2026
@xangcastle
xangcastle self-requested a review July 16, 2026 19:35
@konsti-openai
konsti-openai force-pushed the konsti/support-uv-extra-build-annotations branch 2 times, most recently from 0e808b5 to 1518240 Compare July 16, 2026 21:06
Comment thread uv/private/extension/defs.bzl Outdated
Comment thread annotations.toml
{ name = "build" },
{ name = "setuptools" },
]
# This format is supported, but

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 tamird left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread uv/private/extension/defs.bzl Outdated
Comment thread uv/private/extension/defs.bzl Outdated
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added that, though it's important to note that build-system.requires extracts only the name (

requires = [
_extract_name(r)
for r in build_system.get("requires", [])
if _extract_name(r)
]
), so we have a divergence between how tool.uv.extra-build-dependencies and build-system.requires are parsed.

Comment thread uv/private/extension/defs.bzl Outdated
Comment thread uv/private/extension/defs.bzl
@konsti-openai

konsti-openai commented Jul 20, 2026

Copy link
Copy Markdown
Author

Codex also flagged two other problems. They are already present with annotations.toml, so I'm cutting scope here:

  • Dependency-group independence: build dependencies resolve through the active runtime group. If a required build package exists only in another group, Bazel analysis can fail.
  • Version preservation: although an annotation can resolve a specific build-dependency version, the generated package alias may select the version associated with the active runtime group instead.

@konsti-openai
konsti-openai force-pushed the konsti/support-uv-extra-build-annotations branch from 6bac123 to 7c53c7d Compare July 20, 2026 12:19
package_versions,
fail_if_missing = False,
)
if not resolved_deps:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@konsti-openai
konsti-openai marked this pull request as ready for review July 20, 2026 22:40
@konsti-openai

Copy link
Copy Markdown
Author

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.

@konsti-openai
konsti-openai force-pushed the konsti/support-uv-extra-build-annotations branch from 4fd294c to a6aaf44 Compare July 20, 2026 22:43
@jbedard
jbedard requested a review from tamird July 21, 2026 19:44

@tamird tamird left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants