fix: normalize self-ref extras on both sides of compare (#6049)#6052
Merged
ruben-arts merged 1 commit intoMay 11, 2026
Merged
Conversation
…-dev#6049) `pixi install --locked` rejected freshly-written lockfiles when pyproject.toml contained a self-referential extra such as `dev = ["foo[test]"]`: the lockfile-write path stored `foo[test]; extra == 'dev'` verbatim while the satisfiability path expanded it to `pytest; extra == 'dev'`, producing a phantom "added: [pytest], removed: [foo]" diff. `compare_metadata` now normalizes both sides via `expand_self_extras` before diffing. Each side scans only its own `requires_dist` (uv's static parse already flattens `[project.optional-dependencies]` into it with `; extra == "X"` markers), so no parallel optional-deps map is needed and edits to a group still surface as a real diff. Self-extras expansion runs on an explicit work stack rather than recursion, so deep optional-deps graphs can't blow the call stack. Tests: - Issue reproducer at the unit level + the build-backend-expanded companion case. - Stale `[project.optional-dependencies]` still surfaces a diff. - Direct self-loop and `a -> b -> a` cycle terminate. - Non-extra marker constraints (e.g. `python_version`) preserved through expansion. - A test that calls `uv_pypi_types::RequiresDist::from_pyproject_toml` directly to pin the assumption that uv flattens optional-deps with `; extra == "X"` markers. - Integration test `self_referential_extras_lockfile_roundtrip`: builds the issue's exact pyproject.toml, runs `update_lock_file()` then re-runs with `LockFileUsage::Locked` to exercise the `--locked` satisfiability path without spinning up a conda prefix or building a wheel.
d1b7a4e to
a4f8d3c
Compare
Contributor
Author
|
@ruben-arts THis is ready too |
ruben-arts
approved these changes
May 11, 2026
Contributor
ruben-arts
left a comment
There was a problem hiding this comment.
Thanks, I didn't test it manually but the test cases look solid!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
pixi install --lockedimmediately rejected a freshly-written lockfile whenpyproject.tomlcontained a self-referential extra such asdev = ["foo[test]"]. The lockfile-write path storedfoo[test]; extra == 'dev'verbatim, while the satisfiability path expanded it topytest; extra == 'dev', producing a phantom "added: [pytest], removed: [foo]" diff.This PR makes
compare_metadatanormalize both sides throughexpand_self_extrasbefore diffing. Each side scans only its ownrequires_dist(uv's static parse already flattens[project.optional-dependencies]into it with; extra == "X"markers), so no parallel optional-deps map is needed and edits to a group still surface as a real diff. Self-extras expansion runs on an explicit work stack, not recursion, so deep optional-deps graphs can't stack-overflow.Fixes #6049
How Has This Been Tested?
pypi_metadata.rs:foo[test]; extra == 'dev', current static parse, asserts no mismatch).[project.optional-dependencies]still surfaces as a diff.a -> b -> acycle terminate.python_version+extramarkers preserved through expansion.uv_pypi_types::RequiresDist::from_pyproject_tomldirectly to pin the assumption that uv flattens optional-deps with; extra == "X"markers — if uv ever changes that, the snapshot catches it.self_referential_extras_lockfile_roundtripinpypi_tests.rs: builds the issue's exactpyproject.toml, runsupdate_lock_file()then re-runs withLockFileUsage::Lockedto exercise the--lockedsatisfiability path without spinning up a conda prefix or building a wheel.AI Disclosure
Tools: Claude Code
Checklist: