Skip to content

Commit

Permalink
Show a dedicated error for missing subdirectories
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Dec 10, 2024
1 parent 25045cb commit f8a0325
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 6 deletions.
2 changes: 2 additions & 0 deletions crates/uv-distribution/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ pub enum Error {
MissingEggInfo,
#[error("The source distribution is missing a `requires.txt` file")]
MissingRequiresTxt,
#[error("The source distribution `{}` has no subdirectory `{}`", _0, _1.display())]
MissingSubdirectory(Url, PathBuf),
#[error("Failed to extract static metadata from `PKG-INFO`")]
PkgInfo(#[source] uv_pypi_types::MetadataError),
#[error("Failed to extract metadata from `requires.txt`")]
Expand Down
46 changes: 40 additions & 6 deletions crates/uv-distribution/src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,16 @@ 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() {
Expand Down Expand Up @@ -496,6 +506,16 @@ 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.
if let Some(metadata) =
Self::read_static_metadata(source, source_dist_entry.path(), subdirectory).await?
Expand Down Expand Up @@ -1303,6 +1323,16 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
)
.await?;

// Validate that the subdirectory exists.
if let Some(subdirectory) = resource.subdirectory {
if !fetch.path().join(subdirectory).is_dir() {
return Err(Error::MissingSubdirectory(
resource.url.to_url(),
subdirectory.to_path_buf(),
));
}
}

let git_sha = fetch.git().precise().expect("Exact commit after checkout");
let cache_shard = self.build_context.cache().shard(
CacheBucket::SourceDistributions,
Expand Down Expand Up @@ -1390,6 +1420,16 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
)
.await?;

// Validate that the subdirectory exists.
if let Some(subdirectory) = resource.subdirectory {
if !fetch.path().join(subdirectory).is_dir() {
return Err(Error::MissingSubdirectory(
resource.url.to_url(),
subdirectory.to_path_buf(),
));
}
}

let git_sha = fetch.git().precise().expect("Exact commit after checkout");
let cache_shard = self.build_context.cache().shard(
CacheBucket::SourceDistributions,
Expand Down Expand Up @@ -1438,12 +1478,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.await?
.filter(|metadata| metadata.matches(source.name(), source.version()))
{
let path = if let Some(subdirectory) = resource.subdirectory {
Cow::Owned(fetch.path().join(subdirectory))
} else {
Cow::Borrowed(fetch.path())
};

let git_member = GitWorkspaceMember {
fetch_root: fetch.path(),
git_source: resource,
Expand Down
42 changes: 42 additions & 0 deletions crates/uv/tests/it/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7589,3 +7589,45 @@ fn build_tag() {
----- stderr -----
"###);
}

#[test]
fn missing_subdirectory_git() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;

uv_snapshot!(context.pip_install()
.arg("source-distribution @ git+https://github.com/astral-sh/workspace-in-root-test#subdirectory=missing"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `source-distribution @ git+https://github.com/astral-sh/workspace-in-root-test#subdirectory=missing`
╰─▶ The source distribution `git+https://github.com/astral-sh/workspace-in-root-test#subdirectory=missing` has no subdirectory `missing`
"###
);

Ok(())
}

#[test]
fn missing_subdirectory_url() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;

uv_snapshot!(context.pip_install()
.arg("source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz#subdirectory=missing"), @r###"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× Failed to download and build `source-distribution @ https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz#subdirectory=missing`
╰─▶ The source distribution `https://files.pythonhosted.org/packages/1f/e5/5b016c945d745f8b108e759d428341488a6aee8f51f07c6c4e33498bb91f/source_distribution-0.0.3.tar.gz#subdirectory=missing` has no subdirectory `missing`
"###
);

Ok(())
}
91 changes: 91 additions & 0 deletions crates/uv/tests/it/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5110,6 +5110,97 @@ 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=foo",
"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 = "uv-git-workspace-in-root" },
{ name = "workspace-member-in-subdir" },
]
[package.metadata]
requires-dist = [
{ name = "uv-git-workspace-in-root", git = "https://github.com/astral-sh/workspace-in-root-test.git" },
{ name = "workspace-member-in-subdir", git = "https://github.com/astral-sh/workspace-in-root-test.git?subdirectory=workspace-member-in-subdir" },
]
[[package]]
name = "uv-git-workspace-in-root"
version = "0.1.0"
source = { git = "https://github.com/astral-sh/workspace-in-root-test.git#d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68" }
[[package]]
name = "workspace-member-in-subdir"
version = "0.1.0"
source = { git = "https://github.com/astral-sh/workspace-in-root-test.git?subdirectory=workspace-member-in-subdir#d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68" }
dependencies = [
{ name = "uv-git-workspace-in-root" },
]
"###
);
}
);

uv_snapshot!(context.filters(), context.sync(), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ uv-git-workspace-in-root==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68)
+ workspace-member-in-subdir==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68#subdirectory=workspace-member-in-subdir)
"###);

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 f8a0325

Please sign in to comment.