diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index 68c7e7a0b..2a401cc88 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -415,16 +415,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = cache_shard.shard(revision.id()); let source_dist_entry = cache_shard.entry(SOURCE); - // Validate that the subdirectory exists. - if let Some(subdirectory) = subdirectory { - if !source_dist_entry.path().join(subdirectory).is_dir() { - return Err(Error::MissingSubdirectory( - url.clone(), - subdirectory.to_path_buf(), - )); - } - } - // If there are build settings, we need to scope to a cache shard. let config_settings = self.build_context.config_settings(); let cache_shard = if config_settings.is_empty() { @@ -456,6 +446,16 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await? }; + // Validate that the subdirectory exists. + if let Some(subdirectory) = subdirectory { + if !source_dist_entry.path().join(subdirectory).is_dir() { + return Err(Error::MissingSubdirectory( + url.clone(), + subdirectory.to_path_buf(), + )); + } + } + let task = self .reporter .as_ref() @@ -528,16 +528,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let cache_shard = cache_shard.shard(revision.id()); let source_dist_entry = cache_shard.entry(SOURCE); - // Validate that the subdirectory exists. - if let Some(subdirectory) = subdirectory { - if !source_dist_entry.path().join(subdirectory).is_dir() { - return Err(Error::MissingSubdirectory( - url.clone(), - subdirectory.to_path_buf(), - )); - } - } - // If the metadata is static, return it. let dynamic = match StaticMetadata::read(source, source_dist_entry.path(), subdirectory).await? { @@ -586,6 +576,16 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { .await? }; + // Validate that the subdirectory exists. + if let Some(subdirectory) = subdirectory { + if !source_dist_entry.path().join(subdirectory).is_dir() { + return Err(Error::MissingSubdirectory( + url.clone(), + subdirectory.to_path_buf(), + )); + } + } + // If there are build settings, we need to scope to a cache shard. let config_settings = self.build_context.config_settings(); let cache_shard = if config_settings.is_empty() { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index cc0c3267f..6f2d0e06f 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -7808,3 +7808,54 @@ fn multiple_group_conflicts() -> Result<()> { Ok(()) } + +/// See: +#[test] +fn prune_cache_url_subdirectory() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "root", + ] + + [tool.uv.sources] + root = { url = "https://github.com/user-attachments/files/18216295/subdirectory-test.tar.gz", subdirectory = "packages/root" } + "#})?; + + // Lock the project. + uv_snapshot!(context.filters(), context.lock(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + + // Prune the cache. + context.prune().arg("--ci").assert().success(); + + // Install the project. + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 4 packages in [TIME] + Installed 4 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + root==0.0.1 (from https://github.com/user-attachments/files/18216295/subdirectory-test.tar.gz#subdirectory=packages/root) + + sniffio==1.3.1 + "###); + + Ok(()) +}