uv/crates/uv-resolver/src/manifest.rs
konsti 53db63f6dd
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
2024-07-09 20:37:24 +02:00

238 lines
9.3 KiB
Rust

use either::Either;
use std::borrow::Cow;
use pep508_rs::MarkerEnvironment;
use pypi_types::Requirement;
use uv_configuration::{Constraints, Overrides};
use uv_normalize::{GroupName, PackageName};
use uv_types::RequestedRequirements;
use crate::preferences::Preferences;
use crate::{DependencyMode, Exclusions};
/// A manifest of requirements, constraints, and preferences.
#[derive(Clone, Debug)]
pub struct Manifest {
/// The direct requirements for the project.
pub(crate) requirements: Vec<Requirement>,
/// The constraints for the project.
pub(crate) constraints: Constraints,
/// The overrides for the project.
pub(crate) overrides: Overrides,
/// The enabled development dependency groups for the project. Dependency groups are global,
/// such that any provided groups will be enabled for all requirements.
pub(crate) dev: Vec<GroupName>,
/// The preferences for the project.
///
/// These represent "preferred" versions of a given package. For example, they may be the
/// versions that are already installed in the environment, or already pinned in an existing
/// lockfile.
pub(crate) preferences: Preferences,
/// The name of the project.
pub(crate) project: Option<PackageName>,
/// The installed packages to exclude from consideration during resolution.
///
/// These typically represent packages that are being upgraded or reinstalled
/// and should be pulled from a remote source like a package index.
pub(crate) exclusions: Exclusions,
/// The lookahead requirements for the project.
///
/// These represent transitive dependencies that should be incorporated when making
/// determinations around "allowed" versions (for example, "allowed" URLs or "allowed"
/// pre-release versions).
pub(crate) lookaheads: Vec<RequestedRequirements>,
}
impl Manifest {
pub fn new(
requirements: Vec<Requirement>,
constraints: Constraints,
overrides: Overrides,
dev: Vec<GroupName>,
preferences: Preferences,
project: Option<PackageName>,
exclusions: Exclusions,
lookaheads: Vec<RequestedRequirements>,
) -> Self {
Self {
requirements,
constraints,
overrides,
dev,
preferences,
project,
exclusions,
lookaheads,
}
}
pub fn simple(requirements: Vec<Requirement>) -> Self {
Self {
requirements,
constraints: Constraints::default(),
overrides: Overrides::default(),
dev: Vec::new(),
preferences: Preferences::default(),
project: None,
exclusions: Exclusions::default(),
lookaheads: Vec::new(),
}
}
/// Return an iterator over all requirements, constraints, and overrides, in priority order,
/// such that requirements come first, followed by constraints, followed by overrides.
///
/// At time of writing, this is used for:
/// - Determining which requirements should allow yanked versions.
/// - Determining which requirements should allow pre-release versions (e.g., `torch>=2.2.0a1`).
/// - Determining which requirements should allow direct URLs (e.g., `torch @ https://...`).
/// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`).
pub fn requirements<'a>(
&'a self,
markers: Option<&'a MarkerEnvironment>,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
self.requirements_no_overrides(markers, mode)
.chain(self.overrides(markers, mode))
}
/// Like [`Self::requirements`], but without the overrides.
pub fn requirements_no_overrides<'a>(
&'a self,
markers: Option<&'a MarkerEnvironment>,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode {
// Include all direct and transitive requirements, with constraints and overrides applied.
DependencyMode::Transitive => Either::Left(
self.lookaheads
.iter()
.flat_map(move |lookahead| {
self.overrides
.apply(lookahead.requirements())
.filter(move |requirement| {
requirement.evaluate_markers(markers, lookahead.extras())
})
})
.chain(
self.overrides
.apply(&self.requirements)
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
)
.chain(
self.constraints
.requirements()
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
.map(Cow::Borrowed),
),
),
// Include direct requirements, with constraints and overrides applied.
DependencyMode::Direct => Either::Right(
self.overrides
.apply(&self.requirements)
.chain(self.constraints.requirements().map(Cow::Borrowed))
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
),
}
}
/// Only the overrides from [`Self::requirements`].
pub fn overrides<'a>(
&'a self,
markers: Option<&'a MarkerEnvironment>,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode {
// Include all direct and transitive requirements, with constraints and overrides applied.
DependencyMode::Transitive => Either::Left(
self.overrides
.requirements()
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
.map(Cow::Borrowed),
),
// Include direct requirements, with constraints and overrides applied.
DependencyMode::Direct => Either::Right(
self.overrides
.requirements()
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
.map(Cow::Borrowed),
),
}
}
/// Return an iterator over the names of all user-provided requirements.
///
/// This includes:
/// - Direct requirements
/// - Dependencies of editable requirements
/// - Transitive dependencies of local package requirements
///
/// At time of writing, this is used for:
/// - Determining which packages should use the "lowest-compatible version" of a package, when
/// the `lowest-direct` strategy is in use.
pub fn user_requirements<'a>(
&'a self,
markers: Option<&'a MarkerEnvironment>,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, PackageName>> + 'a {
match mode {
// Include direct requirements, dependencies of editables, and transitive dependencies
// of local packages.
DependencyMode::Transitive => Either::Left(
self.lookaheads
.iter()
.filter(|lookahead| lookahead.direct())
.flat_map(move |lookahead| {
self.overrides
.apply(lookahead.requirements())
.filter(move |requirement| {
requirement.evaluate_markers(markers, lookahead.extras())
})
})
.chain(
self.overrides
.apply(&self.requirements)
.filter(move |requirement| requirement.evaluate_markers(markers, &[])),
)
.map(|requirement| match requirement {
Cow::Borrowed(requirement) => Cow::Borrowed(&requirement.name),
Cow::Owned(requirement) => Cow::Owned(requirement.name),
}),
),
// Restrict to the direct requirements.
DependencyMode::Direct => Either::Right(
self.overrides
.apply(self.requirements.iter())
.filter(move |requirement| requirement.evaluate_markers(markers, &[]))
.map(|requirement| match requirement {
Cow::Borrowed(requirement) => Cow::Borrowed(&requirement.name),
Cow::Owned(requirement) => Cow::Owned(requirement.name),
}),
),
}
}
/// Apply the overrides and constraints to a set of requirements.
///
/// Constraints are always applied _on top_ of overrides, such that constraints are applied
/// even if a requirement is overridden.
pub fn apply<'a>(
&'a self,
requirements: impl IntoIterator<Item = &'a Requirement>,
) -> impl Iterator<Item = Cow<'a, Requirement>> {
self.constraints.apply(self.overrides.apply(requirements))
}
/// Returns the number of input requirements.
pub fn num_requirements(&self) -> usize {
self.requirements.len()
}
}