mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-02 15:01:16 +00:00
Allow transitive URLs via recursive extras (#4155)
## Summary Closes https://github.com/astral-sh/uv/issues/4152.
This commit is contained in:
parent
c46fa74e65
commit
ac1ddf5e4e
2 changed files with 96 additions and 30 deletions
|
@ -141,38 +141,11 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
requirement: Requirement,
|
||||
) -> Result<Option<RequestedRequirements>, LookaheadError> {
|
||||
trace!("Performing lookahead for {requirement}");
|
||||
|
||||
// Determine whether the requirement represents a local distribution and convert to a
|
||||
// buildable distribution.
|
||||
let dist = match requirement.source {
|
||||
RequirementSource::Registry { .. } => return Ok(None),
|
||||
RequirementSource::Url {
|
||||
subdirectory,
|
||||
location,
|
||||
url,
|
||||
} => Dist::from_http_url(requirement.name, url, location, subdirectory)?,
|
||||
RequirementSource::Git {
|
||||
repository,
|
||||
reference,
|
||||
precise,
|
||||
subdirectory,
|
||||
url,
|
||||
} => {
|
||||
let mut git_url = GitUrl::new(repository, reference);
|
||||
if let Some(precise) = precise {
|
||||
git_url = git_url.with_precise(precise);
|
||||
}
|
||||
Dist::Source(SourceDist::Git(GitSourceDist {
|
||||
name: requirement.name,
|
||||
git: Box::new(git_url),
|
||||
subdirectory,
|
||||
url,
|
||||
}))
|
||||
}
|
||||
RequirementSource::Path {
|
||||
path,
|
||||
url,
|
||||
editable,
|
||||
} => Dist::from_file_url(requirement.name, url, &path, editable)?,
|
||||
let Some(dist) = required_dist(&requirement)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Fetch the metadata for the distribution.
|
||||
|
@ -217,6 +190,21 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
}
|
||||
};
|
||||
|
||||
// Respect recursive extras by propagating the source extras to the dependencies.
|
||||
let requires_dist = requires_dist
|
||||
.into_iter()
|
||||
.map(|dependency| {
|
||||
if dependency.name == requirement.name {
|
||||
Requirement {
|
||||
source: requirement.source.clone(),
|
||||
..dependency
|
||||
}
|
||||
} else {
|
||||
dependency
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Consider the dependencies to be "direct" if the requirement is a local source tree.
|
||||
let direct = if let Dist::Source(source_dist) = &dist {
|
||||
source_dist.as_path().is_some_and(std::path::Path::is_dir)
|
||||
|
@ -232,3 +220,43 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
|
|||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [`Requirement`] into a [`Dist`], if it is a direct URL.
|
||||
fn required_dist(requirement: &Requirement) -> Result<Option<Dist>, distribution_types::Error> {
|
||||
Ok(Some(match &requirement.source {
|
||||
RequirementSource::Registry { .. } => return Ok(None),
|
||||
RequirementSource::Url {
|
||||
subdirectory,
|
||||
location,
|
||||
url,
|
||||
} => Dist::from_http_url(
|
||||
requirement.name.clone(),
|
||||
url.clone(),
|
||||
location.clone(),
|
||||
subdirectory.clone(),
|
||||
)?,
|
||||
RequirementSource::Git {
|
||||
repository,
|
||||
reference,
|
||||
precise,
|
||||
subdirectory,
|
||||
url,
|
||||
} => {
|
||||
let mut git_url = GitUrl::new(repository.clone(), reference.clone());
|
||||
if let Some(precise) = precise {
|
||||
git_url = git_url.with_precise(*precise);
|
||||
}
|
||||
Dist::Source(SourceDist::Git(GitSourceDist {
|
||||
name: requirement.name.clone(),
|
||||
git: Box::new(git_url),
|
||||
subdirectory: subdirectory.clone(),
|
||||
url: url.clone(),
|
||||
}))
|
||||
}
|
||||
RequirementSource::Path {
|
||||
path,
|
||||
url,
|
||||
editable,
|
||||
} => Dist::from_file_url(requirement.name.clone(), url.clone(), path, *editable)?,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -5211,3 +5211,41 @@ fn tool_uv_sources_is_in_preview() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allow transitive URLs via recursive extras.
|
||||
#[test]
|
||||
fn recursive_extra_transitive_url() -> 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.0.0"
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
all = [
|
||||
"project[docs]",
|
||||
]
|
||||
docs = [
|
||||
"iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl",
|
||||
]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.install()
|
||||
.arg(".[all]"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Downloaded 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl)
|
||||
+ project==0.0.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue