Ensure extras trigger an install (#1727)

## Summary

We weren't respecting extras when auditing the existing environment.

Closes https://github.com/astral-sh/uv/issues/1726.
This commit is contained in:
Charlie Marsh 2024-02-19 22:37:35 -05:00 committed by GitHub
parent a5372d4e4d
commit 4b5b9835fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 100 additions and 18 deletions

View File

@ -190,24 +190,24 @@ impl<'a> SitePackages<'a> {
}
// Verify that the dependencies are installed.
for requirement in &metadata.requires_dist {
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
for dependency in &metadata.requires_dist {
if !dependency.evaluate_markers(self.venv.interpreter().markers(), &[]) {
continue;
}
let Some(installed) = self
.by_name
.get(&requirement.name)
.get(&dependency.name)
.map(|idx| &self.distributions[*idx])
else {
diagnostics.push(Diagnostic::MissingDependency {
package: package.clone(),
requirement: requirement.clone(),
requirement: dependency.clone(),
});
continue;
};
match &requirement.version_or_url {
match &dependency.version_or_url {
None | Some(pep508_rs::VersionOrUrl::Url(_)) => {
// Nothing to do (accept any installed version).
}
@ -216,7 +216,7 @@ impl<'a> SitePackages<'a> {
diagnostics.push(Diagnostic::IncompatibleDependency {
package: package.clone(),
version: installed.version().clone(),
requirement: requirement.clone(),
requirement: dependency.clone(),
});
}
}
@ -234,10 +234,19 @@ impl<'a> SitePackages<'a> {
editables: &[EditableRequirement],
constraints: &[Requirement],
) -> Result<bool> {
let mut requirements = requirements.to_vec();
let mut stack = Vec::<Requirement>::with_capacity(requirements.len());
let mut seen =
FxHashSet::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
// Add the direct requirements to the queue.
for dependency in requirements {
if dependency.evaluate_markers(self.venv.interpreter().markers(), &[]) {
if seen.insert(dependency.clone()) {
stack.push(dependency.clone());
}
}
}
// Verify that all editable requirements are met.
for requirement in editables {
let Some(distribution) = self
@ -253,15 +262,21 @@ impl<'a> SitePackages<'a> {
let metadata = distribution
.metadata()
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
requirements.extend(metadata.requires_dist);
// Add the dependencies to the queue.
for dependency in metadata.requires_dist {
if dependency
.evaluate_markers(self.venv.interpreter().markers(), &requirement.extras)
{
if seen.insert(dependency.clone()) {
stack.push(dependency);
}
}
}
}
// Verify that all non-editable requirements are met.
while let Some(requirement) = requirements.pop() {
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
continue;
}
while let Some(requirement) = stack.pop() {
let Some(distribution) = self
.by_name
.get(&requirement.name)
@ -300,11 +315,19 @@ impl<'a> SitePackages<'a> {
}
// Recurse into the dependencies.
if seen.insert(requirement) {
let metadata = distribution
.metadata()
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
requirements.extend(metadata.requires_dist);
let metadata = distribution
.metadata()
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
// Add the dependencies to the queue.
for dependency in metadata.requires_dist {
if dependency
.evaluate_markers(self.venv.interpreter().markers(), &requirement.extras)
{
if seen.insert(dependency.clone()) {
stack.push(dependency);
}
}
}
}

View File

@ -317,6 +317,65 @@ fn respect_installed_and_reinstall() -> Result<()> {
Ok(())
}
/// Respect installed versions when resolving.
#[test]
#[cfg(not(windows))]
fn reinstall_extras() -> Result<()> {
let context = TestContext::new("3.12");
// Install anyio.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio")?;
uv_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Downloaded 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.0.0
+ idna==3.4
+ sniffio==1.3.0
"###
);
context.assert_command("import anyio").success();
// Re-install anyio, with an extra.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.touch()?;
requirements_txt.write_str("anyio[trio]")?;
uv_snapshot!(command(&context)
.arg("-r")
.arg("requirements.txt")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 7 packages in [TIME]
Downloaded 4 packages in [TIME]
Installed 4 packages in [TIME]
+ attrs==23.1.0
+ outcome==1.3.0.post0
+ sortedcontainers==2.4.0
+ trio==0.23.1
"###
);
context.assert_command("import anyio").success();
Ok(())
}
/// Like `pip`, we (unfortunately) allow incompatible environments.
#[test]
fn allow_incompatibilities() -> Result<()> {