Skip to content

Selective uv sync: Install only needed packages on PEX builds#23504

Open
lowvoltage wants to merge 2 commits into
pantsbuild:mainfrom
lowvoltage:dk-uv-selective-sync
Open

Selective uv sync: Install only needed packages on PEX builds#23504
lowvoltage wants to merge 2 commits into
pantsbuild:mainfrom
lowvoltage:dk-uv-selective-sync

Conversation

@lowvoltage

Copy link
Copy Markdown
Contributor

Problem

When building a PEX from a uv lockfile, the uv resolver downloads the entire lockfile contents via uv sync --frozen --all-extras, even when the PEX only needs a small subset of packages. For large monorepo resolves containing ML dependencies like torch with CUDA extensions, this causes:

  • Multi-GB downloads of unnecessary packages on every cold build
  • Disk exhaustion in CI environments with limited scratch space
  • Slow builds for targets that only need 2-3 small packages

This is a regression compared to the Pex resolver, which natively subsets the lockfile via its --lock flag.

Slack ref: https://pantsbuild.slack.com/archives/C0D7TNJHL/p1779314212347949

Solution

Use uv's dependency groups and --only-group flag to selectively install only the packages needed for each PEX build.

How it works

  1. generate_pyproject_toml() now emits a [dependency-groups] section with one group per top-level requirement, named after the canonicalized package name (PEP 503). Since this function is shared by both lockfile generation and sync, the groups are automatically baked into the lockfile at generation time.

  2. VenvFromUvLockfileRequest gains a subset_req_strings field. When populated, create_venv_repository_from_uv_lockfile() uses --only-group <name> + --inexact instead of --all-extras, installing only the requested packages and their transitive dependencies.

  3. build_pex() passes the target's req_strings through to enable selective sync for per-target PEX builds.

Key design decisions

  • Groups must be baked into the lockfile at generation timeuv sync --frozen --only-group requires groups to exist in the lock metadata. An ephemeral-group approach (adding groups only at sync time) would fail with --frozen.
  • --inexact flag prevents removal of previously-installed packages, allowing the shared venv to accumulate packages across different PEX builds.
  • Lockfile content hash is included in the venv path when using --inexact to prevent stale packages from old lockfile versions.
  • Multiple specifiers for the same package (e.g. different extras) are accumulated into a single group rather than overwriting.

Backward Compatibility

  • Old lockfiles (generated before this change) are detected by checking for [package.dev-dependencies] in the lockfile content. When absent, the code falls back to full sync (--all-extras) with a warning to regenerate.
  • Missing groups (target requirement not in the resolve's top-level entries) trigger a graceful fallback to full sync with a descriptive warning.
  • The EntireLockfile path (tools, run_against_entire_lockfile) is unchanged and continues to use --all-extras.

Changes

File Change
uv.py Core implementation: per-requirement dependency groups in generate_pyproject_toml, selective sync logic with --only-group + --inexact, lockfile-has-groups detection, graceful fallback
pex.py 1-line change: pass req_strings to VenvFromUvLockfileRequest.subset_req_strings
pex_test.py Unit tests for group generation (basic, canonicalization, accumulation) + integration test for subset PEX build

Testing

  • test_generate_pyproject_toml_includes_dependency_groups — verifies group generation
  • test_generate_pyproject_toml_canonicalizes_group_names — verifies PEP 503 canonicalization
  • test_generate_pyproject_toml_accumulates_duplicate_groups — verifies collision handling
  • test_build_pex_subset_from_uv_lockfile — end-to-end: generates lockfile with multiple packages, builds PEX requesting only one, verifies it works
  • test_build_pex_from_uv_lockfile — existing test continues to pass (full sync path)

@lowvoltage
lowvoltage marked this pull request as ready for review July 7, 2026 13:57
@cburroughs

Copy link
Copy Markdown
Contributor

Thanks for the contribution. We've just branched for 2.33.x, so merging this pull request now will come out in 2.34.x, please move the release notes updates to docs/notes/2.34.x.md if that's appropriate.

@lowvoltage
lowvoltage force-pushed the dk-uv-selective-sync branch from 50c51d4 to 9d03889 Compare July 8, 2026 18:57
@lowvoltage

Copy link
Copy Markdown
Contributor Author

Thanks for the contribution. We've just branched for 2.33.x, so merging this pull request now will come out in 2.34.x, please move the release notes updates to docs/notes/2.34.x.md if that's appropriate.

Updated

lowvoltage and others added 2 commits July 9, 2026 13:59
When building a PEX from a uv lockfile, the uv resolver previously
downloaded the entire lockfile contents (via `uv sync --frozen --all-extras`),
even when the PEX only needed a small subset of packages. For large resolves
containing ML dependencies like torch/CUDA, this caused multi-GB downloads
and could exhaust disk space.

This change adds selective sync support using uv's dependency groups and
`--only-group` flag:

- `generate_pyproject_toml()` now emits a `[dependency-groups]` section
  with one group per top-level requirement, named after the canonicalized
  package name (PEP 503). Since this function is shared by both lockfile
  generation and sync, the groups are automatically baked into the lockfile.

- `VenvFromUvLockfileRequest` gains a `subset_req_strings` field. When
  populated, `create_venv_repository_from_uv_lockfile()` uses
  `--only-group <name>` + `--inexact` instead of `--all-extras`, installing
  only the requested packages and their transitive dependencies.

- `build_pex()` in pex.py passes the target's `req_strings` through to
  enable selective sync for per-target PEX builds.

Backward compatibility:
- Lockfiles generated before this change (without dependency group metadata)
  are detected by checking for `[package.dev-dependencies]` in the lockfile.
  When absent, the code falls back to full sync with a warning to regenerate.
- The `EntireLockfile` path (tools, `run_against_entire_lockfile`) is
  unchanged and continues to use `--all-extras`.
- A lockfile content hash is included in the venv path when using `--inexact`
  to prevent stale packages from old lockfile versions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lowvoltage
lowvoltage force-pushed the dk-uv-selective-sync branch from 9d03889 to 98c5840 Compare July 9, 2026 10:59
@tdyas
tdyas requested a review from benjyw July 10, 2026 03:09
@lowvoltage

Copy link
Copy Markdown
Contributor Author

bump ^^?

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.

2 participants