Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't read metadata from stale .egg-info files #9760

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading