fix: anchor [tool.uv.sources] paths to workspace root in pixi.lock#6187
fix: anchor [tool.uv.sources] paths to workspace root in pixi.lock#6187jevandezande wants to merge 10 commits into
Conversation
When a path-dep's transitive deps come from another package's `[tool.uv.sources]`, uv emits a `VerbatimUrl` whose `given` is relative to that nested package (e.g. `../pkg-b` inside `workspace/pkg-a`). The pixi lockfile resolves relative paths against itself (the workspace root), so writing that `given` directly mislocates the dep after a round-trip, causing `pixi install --locked` to reject a lockfile that `pixi install` just wrote. Co-Authored-By: Claude <noreply@anthropic.com>
9cd3778 to
e0d6b33
Compare
|
Hey thanks for the PR! Also thanks for the additional notes regarding AI usage! The fix looks correct: uv’s given can be relative to a nested package, while pixi.lock interprets relative paths from the workspace root, so re-anchoring before serialization makes sense. I’d prefer not to add another standalone path helper in Although, I'm kinda unsure we can fit that abstraction in that struct. Could we maybe extract the “make this resolved path lockfile-relative” logic into a small shared abstraction, and use it from both:
Something like: struct WorkspaceAnchor<'a> {
root: &'a Path,
}
impl WorkspaceAnchor<'_> {
fn relative_path(&self, abs_path: &Path) -> Result<Utf8TypedPathBuf, AnchorError>;
fn relative_given_for_file_url(&self, url: &VerbatimUrl) -> Option<String>;
}Then Also worth testing/documenting whether explicitly absolute user paths should stay absolute in requires_dist, since process_uv_path_url currently preserves that behavior for locations. |
|
Other than the conflicts this looks good to me :) |
|
Great! What is the standard in this repo for fixing them (e.g. should I rebase on top of |
|
Feel free to take any route it's squashed merged in the end :) |
…elative-paths # Conflicts: # Cargo.lock # crates/pixi_core/src/lock_file/resolve/pypi.rs # crates/pixi_core/src/lock_file/satisfiability/pypi.rs # crates/pixi_uv_conversions/src/conversions.rs
…elative-paths # Conflicts: # Cargo.lock # crates/pixi_core/src/lock_file/resolve/pypi.rs # crates/pixi_core/src/lock_file/satisfiability/pypi.rs
|
All tests are passing. This code should be ready for merge. |
Description
When a path-dep's transitive deps come from another package's
[tool.uv.sources], uv emits aVerbatimUrlwhosegivenis relative to that nested package (e.g.../pkg-binsideworkspace/pkg-a). The pixi lockfile resolves relative paths against itself (the workspace root), so writing thatgivendirectly mislocates the dep after a round-trip, causingpixi install --lockedto reject a lockfile thatpixi installjust wrote.Fixes #4573
Root cause
When uv lowers pkg-a's
requires_distthrough[tool.uv.sources], it produces aRequirementwhoseRequirementSource::Directorycarries aVerbatimUrlwith two pieces:url: the resolved absolute path (file:///workspace/pkg-b),given: the original spelling, relative to pkg-a (../pkg-b).Pixi was writing that
giveninto the lockfile in two places:location(pypi: ../pkg-b),requires_dist(pkg-b @ ../pkg-b).The lockfile resolves relative paths against itself (== workspace root), so on load
../pkg-bbecame<workspace_parent>/pkg-b, the wrong directory. The freshly-computed satisfiability side resolved the samegivenagainst pkg-a's dir (correctly) and gotfile:///workspace/pkg-b. The two absolute URLs differed, so the comparison failed.pixi install --frozenis also affected in a fresh.pixi/: it tries to install from<workspace>/../pkg-b, which doesn't exist.Fix
Re-anchor path
givens against the workspace root before serialization, via a newWorkspaceAnchorabstraction inpixi_uv_conversions:WorkspaceAnchor { root: &Path }owns all lockfile-relative path logic (pathdiff,./prefix convention, Windows backslash normalization, absolute-path preservation). It provides two call-site-appropriate methods:given_for_location(for the package's top-levellocation, honours "keep absolute if user wrote it absolute") andrelative_given_for_file_url(forrequires_distentries, re-anchors relative andfile://givens, preserves explicit absolute paths). This mirrors the existingSourceAnchordesign inpixi_spec.process_uv_path_urlhelper inlock_file/resolve/pypi.rsand the privateworkspace_relative_givenhelper inpixi_uv_conversionsare both deleted; their logic now lives inWorkspaceAnchor.to_requirements_relative_totakesOption<&WorkspaceAnchor>instead ofOption<&Path>, so the anchor travels with the context rather than being reconstructed at each call site.pkg = { path = "/abs/pkg" }) are now preserved uniformly in bothlocationandrequires_dist, matching the previous behavior ofprocess_uv_path_url.After the fix the lockfile contains
pypi: ./pkg-bandpkg-b @ ./pkg-b, and round-trips cleanly.How Has This Been Tested?
workspace_anchormodule: 8 unit tests coveringrelative_path(descending/ascending),relative_given_for_file_url(non-file URL → None, absolute preserved,file://re-anchored), andgiven_for_location(file://relativized, absolute preserved, relative re-anchored).conversionsmodule:to_requirements_re_anchors_nested_tool_uv_sources_given(the bug case),to_requirements_leaves_workspace_relative_given_alone(idempotence),to_requirements_preserves_absolute_given(absolute user path survives re-anchoring).lock_file/resolve/pypimodule: 3 existing behavioral tests (process_uv_path_relative_path,process_uv_path_project_root_subdir,process_uv_path_absolute_path) now exercise the same logic throughWorkspaceAnchorand serve as a behavioral pin for the refactor.AI Disclosure
Tools: Claude Opus 4.7 (on auto mode)
Further disclaimer, I write Python, not Rust and have never committed to Pixi previously. I tried my best to read and understand all of my code, but could very well lack the necessary context or expertise.
AI Prompt
See attached ISSUE.md file
Checklist: