mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-28 10:50:29 +00:00
Apply extra to overrides and constraints (#4829)
This is an attempt to solve https://github.com/astral-sh/uv/issues/ by applying the extra marker of the requirement to overrides and constraints. Say in `a` we have a requirements ``` b==1; python_version < "3.10" c==1; extra == "feature" ``` and overrides ``` b==2; python_version < "3.10" b==3; python_version >= "3.10" c==2; python_version < "3.10" c==3; python_version >= "3.10" ``` Our current strategy is to discard the markers in the original requirements. This means that on 3.12 for `a` we install `b==3`, but it also means that we add `c` to `a` without `a[feature]`, causing #4826. With this PR, the new requirement become, ``` b==2; python_version < "3.10" b==3; python_version >= "3.10" c==2; python_version < "3.10" and extra == "feature" c==3; python_version >= "3.10" and extra == "feature" ``` allowing to override markers while preserving optional dependencies as such. Fixes #4826
This commit is contained in:
parent
0a04108a15
commit
53db63f6dd
8 changed files with 159 additions and 30 deletions
|
|
@ -1,6 +1,8 @@
|
|||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
|
||||
use either::Either;
|
||||
use pep508_rs::MarkerTree;
|
||||
use pypi_types::Requirement;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
use std::borrow::Cow;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
/// A set of constraints for a set of requirements.
|
||||
|
|
@ -36,16 +38,49 @@ impl Constraints {
|
|||
}
|
||||
|
||||
/// Apply the constraints to a set of requirements.
|
||||
///
|
||||
/// NB: Change this method together with [`Overrides::apply`].
|
||||
pub fn apply<'a>(
|
||||
&'a self,
|
||||
requirements: impl IntoIterator<Item = &'a Requirement>,
|
||||
) -> impl Iterator<Item = &Requirement> {
|
||||
requirements: impl IntoIterator<Item = Cow<'a, Requirement>>,
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> {
|
||||
requirements.into_iter().flat_map(|requirement| {
|
||||
std::iter::once(requirement).chain(
|
||||
self.get(&requirement.name)
|
||||
.into_iter()
|
||||
.flat_map(|constraints| constraints.iter()),
|
||||
)
|
||||
let Some(constraints) = self.get(&requirement.name) else {
|
||||
// Case 1: No constraint(s).
|
||||
return Either::Left(std::iter::once(requirement));
|
||||
};
|
||||
|
||||
// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part
|
||||
// of the main conjunction.
|
||||
let Some(extra_expression) = requirement
|
||||
.marker
|
||||
.as_ref()
|
||||
.and_then(|marker| marker.top_level_extra())
|
||||
.cloned()
|
||||
else {
|
||||
// Case 2: A non-optional dependency with constraint(s).
|
||||
return Either::Right(Either::Right(
|
||||
std::iter::once(requirement).chain(constraints.iter().map(Cow::Borrowed)),
|
||||
));
|
||||
};
|
||||
|
||||
// Case 3: An optional dependency with constraint(s).
|
||||
//
|
||||
// When the original requirement is an optional dependency, the constraint(s) need to
|
||||
// be optional for the same extra, otherwise we activate extras that should be inactive.
|
||||
Either::Right(Either::Left(std::iter::once(requirement).chain(
|
||||
constraints.iter().cloned().map(move |constraint| {
|
||||
// Add the extra to the override marker.
|
||||
let mut joint_marker = MarkerTree::Expression(extra_expression.clone());
|
||||
if let Some(marker) = &constraint.marker {
|
||||
joint_marker.and(marker.clone());
|
||||
}
|
||||
Cow::Owned(Requirement {
|
||||
marker: Some(joint_marker.clone()),
|
||||
..constraint
|
||||
})
|
||||
}),
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use either::Either;
|
||||
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
|
||||
use pep508_rs::MarkerTree;
|
||||
use pypi_types::Requirement;
|
||||
use uv_normalize::PackageName;
|
||||
|
||||
|
|
@ -33,16 +36,44 @@ impl Overrides {
|
|||
}
|
||||
|
||||
/// Apply the overrides to a set of requirements.
|
||||
///
|
||||
/// NB: Change this method together with [`Constraints::apply`].
|
||||
pub fn apply<'a>(
|
||||
&'a self,
|
||||
requirements: impl IntoIterator<Item = &'a Requirement>,
|
||||
) -> impl Iterator<Item = &Requirement> {
|
||||
) -> impl Iterator<Item = Cow<'a, Requirement>> {
|
||||
requirements.into_iter().flat_map(|requirement| {
|
||||
if let Some(overrides) = self.get(&requirement.name) {
|
||||
Either::Left(overrides.iter())
|
||||
} else {
|
||||
Either::Right(std::iter::once(requirement))
|
||||
}
|
||||
let Some(overrides) = self.get(&requirement.name) else {
|
||||
// Case 1: No override(s).
|
||||
return Either::Left(std::iter::once(Cow::Borrowed(requirement)));
|
||||
};
|
||||
|
||||
// ASSUMPTION: There is one `extra = "..."`, and it's either the only marker or part
|
||||
// of the main conjunction.
|
||||
let Some(extra_expression) = requirement
|
||||
.marker
|
||||
.as_ref()
|
||||
.and_then(|marker| marker.top_level_extra())
|
||||
else {
|
||||
// Case 2: A non-optional dependency with override(s).
|
||||
return Either::Right(Either::Right(overrides.iter().map(Cow::Borrowed)));
|
||||
};
|
||||
|
||||
// Case 3: An optional dependency with override(s).
|
||||
//
|
||||
// When the original requirement is an optional dependency, the override(s) need to
|
||||
// be optional for the same extra, otherwise we activate extras that should be inactive.
|
||||
Either::Right(Either::Left(overrides.iter().map(|override_requirement| {
|
||||
// Add the extra to the override marker.
|
||||
let mut joint_marker = MarkerTree::Expression(extra_expression.clone());
|
||||
if let Some(marker) = &override_requirement.marker {
|
||||
joint_marker.and(marker.clone());
|
||||
}
|
||||
Cow::Owned(Requirement {
|
||||
marker: Some(joint_marker.clone()),
|
||||
..override_requirement.clone()
|
||||
})
|
||||
})))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue