Mark metadata as dynamic when reading from built wheel cache (#11046)

## Summary

The issue here boils down to: when we write metadata that came from
building the wheel itself, we aren't setting the `dynamic` field.

We now _always_ set the dynamic field when reading, even when we read
cached data.

Closes https://github.com/astral-sh/uv/issues/11047.
This commit is contained in:
Charlie Marsh 2025-01-28 20:27:09 -05:00 committed by GitHub
parent 7868d5df95
commit 3878c00dbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 15 deletions

View File

@ -1187,10 +1187,19 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await?
.filter(|metadata| metadata.matches(source.name(), source.version()))
{
debug!("Using cached metadata for: {source}");
// If necessary, mark the metadata as dynamic.
let metadata = if dynamic {
ResolutionMetadata {
dynamic: true,
..metadata.into()
}
} else {
metadata.into()
};
return Ok(ArchiveMetadata::from(
Metadata::from_workspace(
metadata.into(),
metadata,
resource.install_path.as_ref(),
None,
self.build_context.locations(),
@ -1212,6 +1221,14 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.boxed_local()
.await?
{
// Store the metadata.
fs::create_dir_all(metadata_entry.dir())
.await
.map_err(Error::CacheWrite)?;
write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?)
.await
.map_err(Error::CacheWrite)?;
// If necessary, mark the metadata as dynamic.
let metadata = if dynamic {
ResolutionMetadata {
@ -1222,14 +1239,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
metadata
};
// Store the metadata.
fs::create_dir_all(metadata_entry.dir())
.await
.map_err(Error::CacheWrite)?;
write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?)
.await
.map_err(Error::CacheWrite)?;
return Ok(ArchiveMetadata::from(
Metadata::from_workspace(
metadata,
@ -1273,6 +1282,11 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}
}
// Store the metadata.
write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?)
.await
.map_err(Error::CacheWrite)?;
// If necessary, mark the metadata as dynamic.
let metadata = if dynamic {
ResolutionMetadata {
@ -1283,11 +1297,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
metadata
};
// Store the metadata.
write_atomic(metadata_entry.path(), rmp_serde::to_vec(&metadata)?)
.await
.map_err(Error::CacheWrite)?;
Ok(ArchiveMetadata::from(
Metadata::from_workspace(
metadata,

View File

@ -20363,6 +20363,125 @@ fn lock_dynamic_version_self_extra_setuptools() -> Result<()> {
Ok(())
}
/// See: <https://github.com/astral-sh/uv/issues/11047>
#[test]
fn lock_dynamic_built_cache() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
requires-python = ">=3.12"
dynamic = ["version"]
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { file = "src/__about__.py" }]
[tool.hatch.version]
path = "src/__about__.py"
scheme = "standard"
"#,
)?;
context
.temp_dir
.child("src")
.child("__about__.py")
.write_str("__version__ = '0.1.0'")?;
context
.temp_dir
.child("src")
.child("project")
.child("__init__.py")
.touch()?;
// Lock the project, which should omit the dynamic version.
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "project"
source = { editable = "." }
"###
);
});
// Install the project, to force a build.
uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ project==0.1.0 (from file://[TEMP_DIR]/)
"###);
// Remove the lockfile.
fs_err::remove_file(context.temp_dir.join("uv.lock"))?;
// Lock the project, which should omit the dynamic version.
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
"###);
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "project"
source = { editable = "." }
"###
);
});
Ok(())
}
/// Re-lock after converting a package from dynamic to static.
#[test]
fn lock_dynamic_to_static() -> Result<()> {