Skip to content

Preserve absolute/relative paths in lockfiles#18176

Merged
EliteTK merged 10 commits intomainfrom
tk/absolute-relative-path-fix
Mar 13, 2026
Merged

Preserve absolute/relative paths in lockfiles#18176
EliteTK merged 10 commits intomainfrom
tk/absolute-relative-path-fix

Conversation

@EliteTK
Copy link
Copy Markdown
Contributor

@EliteTK EliteTK commented Feb 24, 2026

Summary

Attempt to track and preserve relative/absolute paths when read from files.

File URLs are treated as absolute. Synthetic VerbatimUrls shouldn't have a given, and are treated as relative.

This means that paths passed as absolute will be output as absolute, although they may get normalized. Paths passed as relative will be output as relative but they may be relative to a different location (so that they continue to work going forwards). Previously in various places we'd either make things absolute unconditionally or relative unconditionally.

Cases which should now be fixed:

  • uv.lock - Path dependencies, indexes, and find-links were always converted to relative paths.
  • pylock.toml export (from_resolution path) - Paths were always relativized. Now preserves the user's original format.
  • pylock.toml export (from_lock path) - Relative paths from the lock file were being converted to absolute paths. Now uses the path exactly as stored in the lock file.

Also noteworthy is the bugfix for a windows misbehaviour. See the commit message for some more information.

Note: For now the uv add side of this has been split off as a breaking change.

Test Plan

Added missing tests, updated existing.

I believe all the changed tests are all now correct and were previously demonstrating buggy behaviour. Well, at least if you are on board with the idea that we should keep relative paths relative and absolute paths and / file URLs absolute.

Related Issues/PRs

@EliteTK EliteTK force-pushed the tk/absolute-relative-path-fix branch 2 times, most recently from ca82125 to c0c3e75 Compare February 26, 2026 00:02
@EliteTK EliteTK marked this pull request as ready for review February 26, 2026 12:57
@EliteTK EliteTK marked this pull request as draft February 26, 2026 12:57
@EliteTK EliteTK force-pushed the tk/absolute-relative-path-fix branch from da28d9b to dd390c1 Compare February 26, 2026 12:59
@EliteTK EliteTK marked this pull request as ready for review February 26, 2026 13:01
@konstin konstin added the bug Something isn't working label Mar 2, 2026
@EliteTK EliteTK added the breaking A breaking change label Mar 2, 2026
@EliteTK EliteTK added this to the v0.11.0 milestone Mar 2, 2026
@konstin
Copy link
Copy Markdown
Member

konstin commented Mar 2, 2026

I consider changing the lockfiles fine - we shuffled those around before in regular releases - though the uv add change is user visible.

pub fn try_relative_to_if(
path: impl AsRef<Path>,
base: impl AsRef<Path>,
should_relativize: bool,
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 pass the verbatim URL here instead so it can capture the logic about was_given_absolute?(probably needs to move to verbatim URL then)

I also noticed install_path and url: VerbatimUrl always come as a pair, do we want to bundle them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think if we bundled install_path and url together then it may make sense to have that type handle this.

An initial version of this PR had try_relative_to_if take the VerbatimUrl but I didn't feel like it made sense that way. Until we merge those, I feel like it strictly makes more sense to have them split up.

But I agree that all the code basically having the same combination of install_path and url.was_given_absolute() is an indication that there's an abstraction missing here.

I'll investigate the combination option.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did investigate this, and the issue I am seeing is with RegistrySourceDist and RegistryBuiltWheel where there is no install_path but rather there is an IndexUrl which could be a file.

But the approach could work for PathBuiltDist, PathSourceDist, and DirectorySourceDist.

For the time being I've pushed a commit which reverts the uv add changes for this PR and created another PR which reverts the revert and is stacked. I've marked that other one breaking,

I've requested your re-review for this one just to double check I have actually reverted all the "breaking" changes.

@EliteTK EliteTK temporarily deployed to uv-test-publish March 4, 2026 12:18 — with GitHub Actions Inactive
@EliteTK EliteTK force-pushed the tk/absolute-relative-path-fix branch from 52ea916 to f1b84c2 Compare March 4, 2026 12:38
@EliteTK EliteTK force-pushed the tk/absolute-relative-path-fix branch from f1b84c2 to 31ca0ea Compare March 10, 2026 20:30
@EliteTK EliteTK removed the breaking A breaking change label Mar 11, 2026
@EliteTK EliteTK removed this from the v0.11.0 milestone Mar 11, 2026
@EliteTK EliteTK changed the title Preserve absolute/relative paths Preserve absolute/relative paths in lockfiles Mar 11, 2026
@EliteTK EliteTK marked this pull request as draft March 11, 2026 00:22
@EliteTK EliteTK force-pushed the tk/absolute-relative-path-fix branch from 8c38f1a to 666ae61 Compare March 11, 2026 00:28
@EliteTK EliteTK marked this pull request as ready for review March 11, 2026 00:33
@EliteTK EliteTK requested a review from konstin March 11, 2026 00:35
Copy link
Copy Markdown
Member

@konstin konstin left a comment

Choose a reason for hiding this comment

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

Looks good!

@EliteTK EliteTK enabled auto-merge (squash) March 13, 2026 17:34
@EliteTK EliteTK merged commit eec8048 into main Mar 13, 2026
77 checks passed
@EliteTK EliteTK deleted the tk/absolute-relative-path-fix branch March 13, 2026 17:42
@lheckemann
Copy link
Copy Markdown

I think this broke a use case I have: I used to specify some dependencies that live in the same repo and provide the same python package (and are included in mutually exclusive dependency groups) as

optional-dependencies.a = ["foo-a @ file:///${PROJECT_ROOT}/a"]
optional-dependencies.b = ["foo-b @ file:///${PROJECT_ROOT}/b"]

Up to now, this would generate lock files with relative paths that can be shared between systems where the repo lives in different places. This no longer works.

As a workaround, using

optional-dependencies.a = ["foo-a"]
optional-dependencies.b = ["foo-b"]

[tool.uv.sources]
foo-a = { path = "./a" }
foo-b = { path = "./b" }

seems to do the trick, but overall this does seem to be a bit more breaking than anticipated (producing lockfiles that aren't usable on other systems than the one that produced them, from pyproject configs that worked before)?

@konstin
Copy link
Copy Markdown
Member

konstin commented Mar 23, 2026

Can you describe in more detail in what case the tool.uv.sources solution doesn't work for you? Environment variables in pyproject.toml is kind of a hack, we strongly recommend tool.uv.sources over it.

zaniebot pushed a commit to zaniebot/uv that referenced this pull request Mar 23, 2026
File URL dependencies containing environment variable references (e.g.,
`file:///${PROJECT_ROOT}/a`) should produce relative paths in lockfiles,
since the user is parameterizing the path for portability. Previously,
`was_given_absolute()` treated all file URLs as absolute, causing these
paths to be stored as absolute in the lockfile after PR astral-sh#18176.

https://claude.ai/code/session_018jGTUmqEdwKfaWo1zQ9jWW
@lheckemann
Copy link
Copy Markdown

It works fine in all the cases, but it breaks locking from older pyproject.tomls prior to the fix, i.e. requires changes to downstream consumers of uv. I think it makes a lot of sense to fix them, but is breaking enough that it shouldn't be in a point release.

EliteTK added a commit that referenced this pull request Mar 23, 2026
…les as relative (#18680)

## Summary

Fix a regression caused by and reported in #18176.

PEP 508 doesn't actually permit variables to be specified within these
URLs but we support this probably due to needing to handle it for
requirements files.

To avoid a breaking change in a patch release, any `VerbatimUrl` that
was parsed as a PEP 508 URL that contained variables that were expanded
is always treated as relative.

The determination of if a `VerbatimUrl` qualifies has to be done at
creation time because otherwise we would incorrectly treat a non PEP 508
URL which contained something which looks like a variable reference as a
relative path in cases where this wouldn't be correct.

## Test Plan

Existing test coverage covers the non-regressed case, added a test for
the regressed case.
@zanieb
Copy link
Copy Markdown
Member

zanieb commented Mar 24, 2026

Thanks for the report! This should be fixed in 0.11.0

konstin pushed a commit that referenced this pull request Mar 24, 2026
…les as relative (#18680)

## Summary

Fix a regression caused by and reported in #18176.

PEP 508 doesn't actually permit variables to be specified within these
URLs but we support this probably due to needing to handle it for
requirements files.

To avoid a breaking change in a patch release, any `VerbatimUrl` that
was parsed as a PEP 508 URL that contained variables that were expanded
is always treated as relative.

The determination of if a `VerbatimUrl` qualifies has to be done at
creation time because otherwise we would incorrectly treat a non PEP 508
URL which contained something which looks like a variable reference as a
relative path in cases where this wouldn't be correct.

## Test Plan

Existing test coverage covers the non-regressed case, added a test for
the regressed case.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

4 participants