mirror of https://github.com/astral-sh/uv
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:
parent
a5372d4e4d
commit
4b5b9835fd
|
|
@ -190,24 +190,24 @@ impl<'a> SitePackages<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the dependencies are installed.
|
// Verify that the dependencies are installed.
|
||||||
for requirement in &metadata.requires_dist {
|
for dependency in &metadata.requires_dist {
|
||||||
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
|
if !dependency.evaluate_markers(self.venv.interpreter().markers(), &[]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(installed) = self
|
let Some(installed) = self
|
||||||
.by_name
|
.by_name
|
||||||
.get(&requirement.name)
|
.get(&dependency.name)
|
||||||
.map(|idx| &self.distributions[*idx])
|
.map(|idx| &self.distributions[*idx])
|
||||||
else {
|
else {
|
||||||
diagnostics.push(Diagnostic::MissingDependency {
|
diagnostics.push(Diagnostic::MissingDependency {
|
||||||
package: package.clone(),
|
package: package.clone(),
|
||||||
requirement: requirement.clone(),
|
requirement: dependency.clone(),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
match &requirement.version_or_url {
|
match &dependency.version_or_url {
|
||||||
None | Some(pep508_rs::VersionOrUrl::Url(_)) => {
|
None | Some(pep508_rs::VersionOrUrl::Url(_)) => {
|
||||||
// Nothing to do (accept any installed version).
|
// Nothing to do (accept any installed version).
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +216,7 @@ impl<'a> SitePackages<'a> {
|
||||||
diagnostics.push(Diagnostic::IncompatibleDependency {
|
diagnostics.push(Diagnostic::IncompatibleDependency {
|
||||||
package: package.clone(),
|
package: package.clone(),
|
||||||
version: installed.version().clone(),
|
version: installed.version().clone(),
|
||||||
requirement: requirement.clone(),
|
requirement: dependency.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,10 +234,19 @@ impl<'a> SitePackages<'a> {
|
||||||
editables: &[EditableRequirement],
|
editables: &[EditableRequirement],
|
||||||
constraints: &[Requirement],
|
constraints: &[Requirement],
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let mut requirements = requirements.to_vec();
|
let mut stack = Vec::<Requirement>::with_capacity(requirements.len());
|
||||||
let mut seen =
|
let mut seen =
|
||||||
FxHashSet::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
|
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.
|
// Verify that all editable requirements are met.
|
||||||
for requirement in editables {
|
for requirement in editables {
|
||||||
let Some(distribution) = self
|
let Some(distribution) = self
|
||||||
|
|
@ -253,15 +262,21 @@ impl<'a> SitePackages<'a> {
|
||||||
let metadata = distribution
|
let metadata = distribution
|
||||||
.metadata()
|
.metadata()
|
||||||
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
|
.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.
|
// Verify that all non-editable requirements are met.
|
||||||
while let Some(requirement) = requirements.pop() {
|
while let Some(requirement) = stack.pop() {
|
||||||
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(distribution) = self
|
let Some(distribution) = self
|
||||||
.by_name
|
.by_name
|
||||||
.get(&requirement.name)
|
.get(&requirement.name)
|
||||||
|
|
@ -300,11 +315,19 @@ impl<'a> SitePackages<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into the dependencies.
|
// Recurse into the dependencies.
|
||||||
if seen.insert(requirement) {
|
|
||||||
let metadata = distribution
|
let metadata = distribution
|
||||||
.metadata()
|
.metadata()
|
||||||
.with_context(|| format!("Failed to read metadata for: {distribution}"))?;
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,65 @@ fn respect_installed_and_reinstall() -> Result<()> {
|
||||||
Ok(())
|
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.
|
/// Like `pip`, we (unfortunately) allow incompatible environments.
|
||||||
#[test]
|
#[test]
|
||||||
fn allow_incompatibilities() -> Result<()> {
|
fn allow_incompatibilities() -> Result<()> {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue