Skip to content

Commit

Permalink
Don't read metadata from stale .egg-info files (#9760)
Browse files Browse the repository at this point in the history
## Summary

We were reading an `.egg-info` file from the root directory that didn't
apply to the root member -- it was for another workspace member. I think
this is driven from some idiosyncracies in the `setuptools` setup for
that workspace member, but it's still wrong to fail.

This PR adds a few measures to fix this:

1. We validate the `egg-info` filename against the package metadata.
2. We skip, rather than fail, if we see incorrect metadata in an
`egg-info` file or similar. This is an optimization anyway; worst case,
we try to build the package, then fail there.

Closes #9743.
  • Loading branch information
charliermarsh authored Dec 10, 2024
1 parent 25045cb commit 8a2e3a8
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 18 deletions.
2 changes: 0 additions & 2 deletions crates/uv-distribution/src/distribution_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,8 +466,6 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> {
.boxed_local()
.await?;

// Validate that the metadata is consistent with the distribution.

Ok(metadata)
}

Expand Down
83 changes: 67 additions & 16 deletions crates/uv-distribution/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use uv_client::{
CacheControl, CachedClientError, Connectivity, DataWithCachePolicy, RegistryClient,
};
use uv_configuration::{BuildKind, BuildOutput, SourceStrategy};
use uv_distribution_filename::{SourceDistExtension, WheelFilename};
use uv_distribution_filename::{EggInfoFilename, SourceDistExtension, WheelFilename};
use uv_distribution_types::{
BuildableSource, DirectorySourceUrl, FileLocation, GitSourceUrl, HashPolicy, Hashed,
PathSourceUrl, SourceDist, SourceUrl,
Expand Down Expand Up @@ -1973,10 +1973,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Ok(metadata) => {
debug!("Found static `pyproject.toml` for: {source}");

// Validate the metadata.
validate_metadata(source, &metadata)?;

return Ok(Some(metadata));
// Validate the metadata, but ignore it if the metadata doesn't match.
match validate_metadata(source, &metadata) {
Ok(()) => {
return Ok(Some(metadata));
}
Err(Error::WheelMetadataNameMismatch { metadata, given }) => {
debug!(
"Ignoring `pyproject.toml` for: {source} (metadata: {metadata}, given: {given})"
);
}
Err(err) => return Err(err),
}
}
Err(
err @ (Error::MissingPyprojectToml
Expand All @@ -2003,10 +2011,18 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
Ok(metadata) => {
debug!("Found static `PKG-INFO` for: {source}");

// Validate the metadata.
validate_metadata(source, &metadata)?;

return Ok(Some(metadata));
// Validate the metadata, but ignore it if the metadata doesn't match.
match validate_metadata(source, &metadata) {
Ok(()) => {
return Ok(Some(metadata));
}
Err(Error::WheelMetadataNameMismatch { metadata, given }) => {
debug!(
"Ignoring `PKG-INFO` for: {source} (metadata: {metadata}, given: {given})"
);
}
Err(err) => return Err(err),
}
}
Err(
err @ (Error::MissingPkgInfo
Expand All @@ -2023,14 +2039,22 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
}

// Attempt to read static metadata from the `egg-info` directory.
match read_egg_info(source_root, subdirectory).await {
match read_egg_info(source_root, subdirectory, source.name(), source.version()).await {
Ok(metadata) => {
debug!("Found static `egg-info` for: {source}");

// Validate the metadata.
validate_metadata(source, &metadata)?;

return Ok(Some(metadata));
// Validate the metadata, but ignore it if the metadata doesn't match.
match validate_metadata(source, &metadata) {
Ok(()) => {
return Ok(Some(metadata));
}
Err(Error::WheelMetadataNameMismatch { metadata, given }) => {
debug!(
"Ignoring `egg-info` for: {source} (metadata: {metadata}, given: {given})"
);
}
Err(err) => return Err(err),
}
}
Err(
err @ (Error::MissingEggInfo
Expand Down Expand Up @@ -2292,8 +2316,14 @@ impl LocalRevisionPointer {
async fn read_egg_info(
source_tree: &Path,
subdirectory: Option<&Path>,
name: Option<&PackageName>,
version: Option<&Version>,
) -> Result<ResolutionMetadata, Error> {
fn find_egg_info(source_tree: &Path) -> std::io::Result<Option<PathBuf>> {
fn find_egg_info(
source_tree: &Path,
name: Option<&PackageName>,
version: Option<&Version>,
) -> std::io::Result<Option<PathBuf>> {
for entry in fs_err::read_dir(source_tree)? {
let entry = entry?;
let ty = entry.file_type()?;
Expand All @@ -2303,6 +2333,27 @@ async fn read_egg_info(
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("egg-info"))
{
let Some(file_stem) = path.file_stem() else {
continue;
};
let Some(file_stem) = file_stem.to_str() else {
continue;
};
let Ok(file_name) = EggInfoFilename::parse(file_stem) else {
continue;
};
if let Some(name) = name {
debug!("Skipping `{file_stem}.egg-info` due to name mismatch (expected: `{name}`)");
if file_name.name != *name {
continue;
}
}
if let Some(version) = version {
if file_name.version.as_ref().is_some_and(|v| v != version) {
debug!("Skipping `{file_stem}.egg-info` due to version mismatch (expected: `{version}`)");
continue;
}
}
return Ok(Some(path));
}
}
Expand All @@ -2316,7 +2367,7 @@ async fn read_egg_info(
};

// Locate the `egg-info` directory.
let egg_info = match find_egg_info(directory.as_ref()) {
let egg_info = match find_egg_info(directory.as_ref(), name, version) {
Ok(Some(path)) => path,
Ok(None) => return Err(Error::MissingEggInfo),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
Expand Down
104 changes: 104 additions & 0 deletions crates/uv/tests/it/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5110,6 +5110,110 @@ fn sync_derivation_chain_group() -> Result<()> {
Ok(())
}

/// See: <https://github.com/astral-sh/uv/issues/9743>
#[test]
fn sync_stale_egg_info() -> Result<()> {
let context = TestContext::new("3.13");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "foo"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"member @ git+https://github.com/astral-sh/uv-stale-egg-info-test.git#subdirectory=member",
"root @ git+https://github.com/astral-sh/uv-stale-egg-info-test.git",
]
"#,
)?;

uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
"###);

let lock = context.read("uv.lock");

insta::with_settings!(
{
filters => context.filters(),
},
{
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.13"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "foo"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "member" },
{ name = "root" },
]
[package.metadata]
requires-dist = [
{ name = "member", git = "https://github.com/astral-sh/uv-stale-egg-info-test.git?subdirectory=member" },
{ name = "root", git = "https://github.com/astral-sh/uv-stale-egg-info-test.git" },
]
[[package]]
name = "member"
version = "0.1.dev5+gfea1041"
source = { git = "https://github.com/astral-sh/uv-stale-egg-info-test.git?subdirectory=member#fea10416b9c479ac88fb217e14e40249b63bfbee" }
dependencies = [
{ name = "setuptools" },
]
[[package]]
name = "root"
version = "0.1.dev5+gfea1041"
source = { git = "https://github.com/astral-sh/uv-stale-egg-info-test.git#fea10416b9c479ac88fb217e14e40249b63bfbee" }
dependencies = [
{ name = "member" },
]
[[package]]
name = "setuptools"
version = "69.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4d/5b/dc575711b6b8f2f866131a40d053e30e962e633b332acf7cd2c24843d83d/setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", size = 2222950 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/e1/1c8bb3420105e70bdf357d57dd5567202b4ef8d27f810e98bb962d950834/setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c", size = 821485 },
]
"###
);
}
);

uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ member==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee#subdirectory=member)
+ root==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee)
+ setuptools==69.2.0
"###);

Ok(())
}

/// See: <https://github.com/astral-sh/uv/issues/8887>
#[test]
fn sync_git_repeated_member_static_metadata() -> Result<()> {
Expand Down

0 comments on commit 8a2e3a8

Please sign in to comment.