diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index 5339d3aa82fe0..3a55bd9adc559 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -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) } diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 7c36bb2c814a7..78ef2f2cf0f49 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -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, @@ -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 @@ -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 @@ -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 @@ -2292,8 +2316,14 @@ impl LocalRevisionPointer { async fn read_egg_info( source_tree: &Path, subdirectory: Option<&Path>, + name: Option<&PackageName>, + version: Option<&Version>, ) -> Result { - fn find_egg_info(source_tree: &Path) -> std::io::Result> { + fn find_egg_info( + source_tree: &Path, + name: Option<&PackageName>, + version: Option<&Version>, + ) -> std::io::Result> { for entry in fs_err::read_dir(source_tree)? { let entry = entry?; let ty = entry.file_type()?; @@ -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)); } } @@ -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 => { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 1f9fd3abb94d8..00ee615802836 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -5110,6 +5110,110 @@ fn sync_derivation_chain_group() -> Result<()> { Ok(()) } +/// See: +#[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: #[test] fn sync_git_repeated_member_static_metadata() -> Result<()> {