Avoid enforcing extra-only constraints (#4570)

## Summary

In the dependency refactor (https://github.com/astral-sh/uv/pull/4430),
the logic for requirements and constraints was combined. Specifically,
we were applying constraints _before_ filtering on markers and extras,
and then applying that same filtering to the constraints. As a result,
constraints that should only be activated when an extra is enabled were
being enabled unconditionally.

Closes https://github.com/astral-sh/uv/issues/4569.
This commit is contained in:
Charlie Marsh 2024-06-26 18:52:46 -04:00 committed by GitHub
parent a8c28c4612
commit bbbe1f3968
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 77 additions and 12 deletions

View file

@ -1302,15 +1302,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
) -> impl Iterator<Item = &'a Requirement> {
self.overrides
.apply(dependencies)
.flat_map(|requirement| {
iter::once(requirement).chain(
// If the requirement was constrained, add those constraints.
self.constraints
.get(&requirement.name)
.into_iter()
.flatten(),
)
})
.filter(move |requirement| {
// If the requirement would not be selected with any Python version
// supported by the root, skip it.
@ -1353,8 +1344,50 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
}
}
true
})
.flat_map(move |requirement| {
iter::once(requirement).chain(
self.constraints
.get(&requirement.name)
.into_iter()
.flatten()
.filter(move |constraint| {
if !satisfies_requires_python(self.requires_python.as_ref(), constraint) {
trace!(
"skipping {constraint} because of Requires-Python {requires_python}",
requires_python = self.requires_python.as_ref().unwrap()
);
return false;
}
if !possible_to_satisfy_markers(markers, constraint) {
trace!("skipping {constraint} because of context resolver markers {markers}");
return false;
}
// If the constraint isn't relevant for the current platform, skip it.
match extra {
Some(source_extra) => {
if !constraint.evaluate_markers(
self.markers.as_ref(),
std::slice::from_ref(source_extra),
) {
return false;
}
}
None => {
if !constraint.evaluate_markers(self.markers.as_ref(), &[]) {
return false;
}
}
}
true
}),
)
})
}
/// Fetch the metadata for a stream of packages and versions.

View file

@ -9731,3 +9731,38 @@ fn no_binary_only_binary() -> Result<()> {
Ok(())
}
/// `gunicorn` only depends on `eventlet` via an extra, so the resolution should succeed despite
/// the nonsensical extra.
#[test]
fn ignore_invalid_constraint() -> Result<()> {
let context = TestContext::new("3.12");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("gunicorn>=20")?;
let constraints_txt = context.temp_dir.child("constraints.txt");
constraints_txt.write_str("eventlet==9999.0.1.2.3.4.5")?;
uv_snapshot!(context
.pip_compile()
.arg("requirements.in")
.arg("-c")
.arg("constraints.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] requirements.in -c constraints.txt
gunicorn==21.2.0
# via -r requirements.in
packaging==24.0
# via gunicorn
----- stderr -----
Resolved 2 packages in [TIME]
"###
);
Ok(())
}

View file

@ -1,3 +0,0 @@
--index-url file:///Users/crmarsh/workspace/packse/index/simple-html/
example-a-961b4c22