uv: use ResolverEnvironment instead of ResolverMarkers

This updates the surrounding code to use the new ResolverEnvironment
type. In some cases, this simplifies caller code by removing case
analysis. There *shouldn't* be any behavior changes here. Some test
snapshots were updated to account for some minor tweaks to error
messages.

I didn't split this up into separate commits because it would have been
too difficult/costly.
This commit is contained in:
Andrew Gallant 2024-10-11 15:23:38 -04:00 committed by Andrew Gallant
parent 44c9ef6aea
commit acaed763b7
42 changed files with 416 additions and 600 deletions

View file

@ -101,7 +101,7 @@ mod resolver {
use uv_python::Interpreter;
use uv_resolver::{
FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, RequiresPython,
ResolutionGraph, Resolver, ResolverMarkers,
ResolutionGraph, Resolver, ResolverEnvironment,
};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
@ -198,9 +198,9 @@ mod resolver {
);
let markers = if universal {
ResolverMarkers::universal(vec![])
ResolverEnvironment::universal(vec![])
} else {
ResolverMarkers::specific_environment(ResolverMarkerEnvironment::from(MARKERS.clone()))
ResolverEnvironment::specific(ResolverMarkerEnvironment::from(MARKERS.clone()))
};
let resolver = Resolver::new(

View file

@ -30,7 +30,7 @@ use uv_pypi_types::Requirement;
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::{
ExcludeNewer, FlatIndex, Flexibility, InMemoryIndex, Manifest, OptionsBuilder,
PythonRequirement, Resolver, ResolverMarkers,
PythonRequirement, Resolver, ResolverEnvironment,
};
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
@ -174,7 +174,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
async fn resolve<'data>(&'data self, requirements: &'data [Requirement]) -> Result<Resolution> {
let python_requirement = PythonRequirement::from_interpreter(self.interpreter);
let markers = self.interpreter.resolver_markers();
let marker_env = self.interpreter.resolver_marker_environment();
let tags = self.interpreter.tags()?;
let resolver = Resolver::new(
@ -185,7 +185,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
.flexibility(Flexibility::Fixed)
.build(),
&python_requirement,
ResolverMarkers::specific_environment(markers),
ResolverEnvironment::specific(marker_env),
Some(tags),
self.flat_index,
self.index,

View file

@ -147,7 +147,7 @@ impl Interpreter {
}
/// Return the [`ResolverMarkerEnvironment`] for this Python executable.
pub fn resolver_markers(&self) -> ResolverMarkerEnvironment {
pub fn resolver_marker_environment(&self) -> ResolverMarkerEnvironment {
ResolverMarkerEnvironment::from(self.markers().clone())
}

View file

@ -10,7 +10,7 @@ use uv_distribution::{DistributionDatabase, Reporter};
use uv_distribution_types::{Dist, DistributionMetadata};
use uv_normalize::GroupName;
use uv_pypi_types::{Requirement, RequirementSource};
use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverMarkers};
use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverEnvironment};
use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
use crate::{required_dist, Error};
@ -87,7 +87,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
/// to "only evaluate marker expressions that reference an extra name.")
pub async fn resolve(
self,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Result<Vec<RequestedRequirements>, Error> {
let mut results = Vec::new();
let mut futures = FuturesUnordered::new();
@ -97,7 +97,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
let mut queue: VecDeque<_> = self
.constraints
.apply(self.overrides.apply(self.requirements))
.filter(|requirement| requirement.evaluate_markers(markers.marker_environment(), &[]))
.filter(|requirement| requirement.evaluate_markers(env.marker_environment(), &[]))
.map(|requirement| (*requirement).clone())
.collect();
@ -117,7 +117,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
.apply(self.overrides.apply(lookahead.requirements()))
{
if requirement
.evaluate_markers(markers.marker_environment(), lookahead.extras())
.evaluate_markers(env.marker_environment(), lookahead.extras())
{
queue.push_back((*requirement).clone());
}

View file

@ -15,7 +15,7 @@ use crate::preferences::Preferences;
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
use crate::resolution_mode::ResolutionStrategy;
use crate::version_map::{VersionMap, VersionMapDistHandle};
use crate::{Exclusions, Manifest, Options, ResolverMarkers};
use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
#[derive(Debug, Clone)]
#[allow(clippy::struct_field_names)]
@ -30,19 +30,19 @@ impl CandidateSelector {
pub(crate) fn for_resolution(
options: Options,
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Self {
Self {
resolution_strategy: ResolutionStrategy::from_mode(
options.resolution_mode,
manifest,
markers,
env,
options.dependency_mode,
),
prerelease_strategy: PrereleaseStrategy::from_mode(
options.prerelease_mode,
manifest,
markers,
env,
options.dependency_mode,
),
index_strategy: options.index_strategy,
@ -80,7 +80,7 @@ impl CandidateSelector {
preferences: &'a Preferences,
installed_packages: &'a InstalledPackages,
exclusions: &'a Exclusions,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Option<Candidate<'a>> {
let is_excluded = exclusions.contains(package_name);
@ -93,7 +93,7 @@ impl CandidateSelector {
preferences,
installed_packages,
is_excluded,
markers,
env,
) {
trace!("Using preference {} {}", preferred.name, preferred.version);
return Some(preferred);
@ -111,7 +111,7 @@ impl CandidateSelector {
}
}
self.select_no_preference(package_name, range, version_maps, markers)
self.select_no_preference(package_name, range, version_maps, env)
}
/// If the package has a preference, an existing version from an existing lockfile or a version
@ -131,74 +131,26 @@ impl CandidateSelector {
preferences: &'a Preferences,
installed_packages: &'a InstalledPackages,
is_excluded: bool,
resolver_markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Option<Candidate> {
// In the branches, we "sort" the preferences by marker-matching through an iterator that
// first has the matching half and then the mismatching half.
match resolver_markers {
ResolverMarkers::SpecificEnvironment(env) => {
// We may hit a combination of fork markers preferences with specific environment
// output in the future when adding support for the PEP 665 successor.
let preferences_match =
preferences.get(package_name).filter(|(marker, _version)| {
// `.unwrap_or(true)` because the universal marker is considered matching.
marker
.map(|marker| marker.evaluate(env, &[]))
.unwrap_or(true)
});
let preferences_mismatch =
preferences.get(package_name).filter(|(marker, _version)| {
marker
.map(|marker| !marker.evaluate(env, &[]))
.unwrap_or(false)
});
self.get_preferred_from_iter(
preferences_match.chain(preferences_mismatch),
package_name,
range,
version_maps,
installed_packages,
is_excluded,
resolver_markers,
)
}
ResolverMarkers::Universal { .. } => {
// In universal mode, all preferences are matching.
self.get_preferred_from_iter(
preferences.get(package_name),
package_name,
range,
version_maps,
installed_packages,
is_excluded,
resolver_markers,
)
}
ResolverMarkers::Fork(fork_markers) => {
let preferences_match =
preferences.get(package_name).filter(|(marker, _version)| {
// `.unwrap_or(true)` because the universal marker is considered matching.
marker
.map(|marker| !marker.is_disjoint(fork_markers))
.unwrap_or(true)
});
let preferences_mismatch =
preferences.get(package_name).filter(|(marker, _version)| {
marker
.map(|marker| marker.is_disjoint(fork_markers))
.unwrap_or(false)
});
self.get_preferred_from_iter(
preferences_match.chain(preferences_mismatch),
package_name,
range,
version_maps,
installed_packages,
is_excluded,
resolver_markers,
)
}
}
let preferences_match = preferences.get(package_name).filter(|(marker, _version)| {
// `.unwrap_or(true)` because the universal marker is considered matching.
marker.map(|marker| env.included(marker)).unwrap_or(true)
});
let preferences_mismatch = preferences.get(package_name).filter(|(marker, _version)| {
marker.map(|marker| !env.included(marker)).unwrap_or(false)
});
self.get_preferred_from_iter(
preferences_match.chain(preferences_mismatch),
package_name,
range,
version_maps,
installed_packages,
is_excluded,
env,
)
}
/// Return the first preference that satisfies the current range and is allowed.
@ -210,7 +162,7 @@ impl CandidateSelector {
version_maps: &'a [VersionMap],
installed_packages: &'a InstalledPackages,
is_excluded: bool,
resolver_markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Option<Candidate<'a>> {
for (marker, version) in preferences {
// Respect the version range for this requirement.
@ -247,10 +199,7 @@ impl CandidateSelector {
// Respect the pre-release strategy for this fork.
if version.any_prerelease() {
let allow = match self
.prerelease_strategy
.allows(package_name, resolver_markers)
{
let allow = match self.prerelease_strategy.allows(package_name, env) {
AllowPrerelease::Yes => true,
AllowPrerelease::No => false,
// If the pre-release is "global" (i.e., provided via a lockfile, rather than
@ -321,7 +270,7 @@ impl CandidateSelector {
package_name: &'a PackageName,
range: &Range<Version>,
version_maps: &'a [VersionMap],
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Option<Candidate> {
trace!(
"Selecting candidate for {package_name} with range {range} with {} remote versions",
@ -329,7 +278,7 @@ impl CandidateSelector {
);
let highest = self.use_highest_version(package_name);
let allow_prerelease = match self.prerelease_strategy.allows(package_name, markers) {
let allow_prerelease = match self.prerelease_strategy.allows(package_name, env) {
AllowPrerelease::Yes => true,
AllowPrerelease::No => false,
// Allow pre-releases if there are no stable versions available.

View file

@ -12,7 +12,9 @@ use crate::fork_urls::ForkUrls;
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
use crate::python_requirement::PythonRequirement;
use crate::resolution::ConflictingDistributionError;
use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason};
use crate::resolver::{
IncompletePackage, ResolverEnvironment, UnavailablePackage, UnavailableReason,
};
use crate::Options;
use tracing::trace;
use uv_distribution_types::{
@ -20,7 +22,6 @@ use uv_distribution_types::{
};
use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pep508::MarkerTree;
use uv_static::EnvVars;
#[derive(Debug, thiserror::Error)]
@ -40,24 +41,34 @@ pub enum ResolveError {
#[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
ConflictingOverrideUrls(PackageName, String, String),
#[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))]
ConflictingUrlsUniversal(PackageName, Vec<String>),
#[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers:?}`:\n- {}", urls.join("\n- "))]
ConflictingUrlsFork {
#[error(
"Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
if env.marker_environment().is_some() {
String::new()
} else {
format!(" in {env}")
},
urls.join("\n- "),
)]
ConflictingUrls {
package_name: PackageName,
urls: Vec<String>,
fork_markers: MarkerTree,
env: ResolverEnvironment,
},
#[error("Requirements contain conflicting indexes for package `{0}`:\n- {}", _1.join("\n- "))]
ConflictingIndexesUniversal(PackageName, Vec<String>),
#[error("Requirements contain conflicting indexes for package `{package_name}` in split `{fork_markers:?}`:\n- {}", indexes.join("\n- "))]
ConflictingIndexesFork {
#[error(
"Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
if env.marker_environment().is_some() {
String::new()
} else {
format!(" in {env}")
},
indexes.join("\n- "),
)]
ConflictingIndexesForEnvironment {
package_name: PackageName,
indexes: Vec<String>,
fork_markers: MarkerTree,
env: ResolverEnvironment,
},
#[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
@ -127,7 +138,7 @@ pub struct NoSolutionError {
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
fork_urls: ForkUrls,
markers: ResolverMarkers,
env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>,
options: Options,
}
@ -145,7 +156,7 @@ impl NoSolutionError {
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
fork_urls: ForkUrls,
markers: ResolverMarkers,
env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>,
options: Options,
) -> Self {
@ -160,7 +171,7 @@ impl NoSolutionError {
unavailable_packages,
incomplete_packages,
fork_urls,
markers,
env,
workspace_members,
options,
}
@ -212,7 +223,7 @@ impl NoSolutionError {
/// Initialize a [`NoSolutionHeader`] for this error.
pub fn header(&self) -> NoSolutionHeader {
NoSolutionHeader::new(self.markers.clone())
NoSolutionHeader::new(self.env.clone())
}
}
@ -266,7 +277,7 @@ impl std::fmt::Display for NoSolutionError {
&self.unavailable_packages,
&self.incomplete_packages,
&self.fork_urls,
&self.markers,
&self.env,
&self.workspace_members,
self.options,
&mut additional_hints,
@ -689,19 +700,16 @@ fn drop_root_dependency_on_project(
#[derive(Debug)]
pub struct NoSolutionHeader {
/// The [`ResolverMarkers`] that caused the failure.
markers: ResolverMarkers,
/// The [`ResolverEnvironment`] that caused the failure.
env: ResolverEnvironment,
/// The additional context for the resolution failure.
context: Option<&'static str>,
}
impl NoSolutionHeader {
/// Create a new [`NoSolutionHeader`] with the given [`ResolverMarkers`].
pub fn new(markers: ResolverMarkers) -> Self {
Self {
markers,
context: None,
}
/// Create a new [`NoSolutionHeader`] with the given [`ResolverEnvironment`].
pub fn new(env: ResolverEnvironment) -> Self {
Self { env, context: None }
}
/// Set the context for the resolution failure.
@ -714,30 +722,20 @@ impl NoSolutionHeader {
impl std::fmt::Display for NoSolutionHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.markers {
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => {
if let Some(context) = self.context {
write!(
f,
"No solution found when resolving {context} dependencies:"
)
} else {
write!(f, "No solution found when resolving dependencies:")
}
}
ResolverMarkers::Fork(markers) => {
if let Some(context) = self.context {
write!(
f,
"No solution found when resolving {context} dependencies for split ({markers:?}):",
)
} else {
write!(
f,
"No solution found when resolving dependencies for split ({markers:?}):",
)
}
}
match (self.context, self.env.end_user_fork_display()) {
(None, None) => write!(f, "No solution found when resolving dependencies:"),
(Some(context), None) => write!(
f,
"No solution found when resolving {context} dependencies:"
),
(None, Some(split)) => write!(
f,
"No solution found when resolving dependencies for {split}:"
),
(Some(context), Some(split)) => write!(
f,
"No solution found when resolving {context} dependencies for {split}:"
),
}
}
}

View file

@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
use uv_distribution_types::IndexUrl;
use uv_normalize::PackageName;
use crate::resolver::ResolverMarkers;
use crate::resolver::ResolverEnvironment;
use crate::ResolveError;
/// See [`crate::resolver::ForkState`].
@ -20,27 +20,17 @@ impl ForkIndexes {
&mut self,
package_name: &PackageName,
index: &IndexUrl,
fork_markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Result<(), ResolveError> {
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
if &previous != index {
let mut conflicts = vec![previous.to_string(), index.to_string()];
conflicts.sort();
return match fork_markers {
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
Err(ResolveError::ConflictingIndexesUniversal(
package_name.clone(),
conflicts,
))
}
ResolverMarkers::Fork(fork_markers) => {
Err(ResolveError::ConflictingIndexesFork {
package_name: package_name.clone(),
indexes: conflicts,
fork_markers: fork_markers.clone(),
})
}
};
return Err(ResolveError::ConflictingIndexesForEnvironment {
package_name: package_name.clone(),
indexes: conflicts,
env: env.clone(),
});
}
}
Ok(())

View file

@ -6,7 +6,7 @@ use uv_distribution_types::Verbatim;
use uv_normalize::PackageName;
use uv_pypi_types::VerbatimParsedUrl;
use crate::resolver::ResolverMarkers;
use crate::resolver::ResolverEnvironment;
use crate::ResolveError;
/// See [`crate::resolver::ForkState`].
@ -29,7 +29,7 @@ impl ForkUrls {
&mut self,
package_name: &PackageName,
url: &VerbatimParsedUrl,
fork_markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Result<(), ResolveError> {
match self.0.entry(package_name.clone()) {
Entry::Occupied(previous) => {
@ -39,22 +39,11 @@ impl ForkUrls {
url.verbatim.verbatim().to_string(),
];
conflicting_url.sort();
return match fork_markers {
ResolverMarkers::Universal { .. }
| ResolverMarkers::SpecificEnvironment(_) => {
Err(ResolveError::ConflictingUrlsUniversal(
package_name.clone(),
conflicting_url,
))
}
ResolverMarkers::Fork(fork_markers) => {
Err(ResolveError::ConflictingUrlsFork {
package_name: package_name.clone(),
urls: conflicting_url,
fork_markers: fork_markers.clone(),
})
}
};
return Err(ResolveError::ConflictingUrls {
package_name: package_name.clone(),
urls: conflicting_url,
env: env.clone(),
});
}
}
Entry::Vacant(vacant) => {

View file

@ -19,8 +19,8 @@ pub use resolution::{
pub use resolution_mode::ResolutionMode;
pub use resolver::{
BuildId, DefaultResolverProvider, InMemoryIndex, MetadataResponse, PackageVersionsResult,
Reporter as ResolverReporter, Resolver, ResolverMarkers, ResolverProvider, VersionsResponse,
WheelMetadataResult,
Reporter as ResolverReporter, Resolver, ResolverEnvironment, ResolverProvider,
VersionsResponse, WheelMetadataResult,
};
pub use version_map::VersionMap;
pub use yanks::AllowedYanks;

View file

@ -9,7 +9,7 @@ use uv_pypi_types::Requirement;
use uv_types::RequestedRequirements;
use crate::preferences::Preferences;
use crate::{DependencyMode, Exclusions, ResolverMarkers};
use crate::{DependencyMode, Exclusions, ResolverEnvironment};
/// A manifest of requirements, constraints, and preferences.
#[derive(Clone, Debug)]
@ -109,57 +109,55 @@ impl Manifest {
/// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`).
pub fn requirements<'a>(
&'a self,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
self.requirements_no_overrides(markers, mode)
.chain(self.overrides(markers, mode))
self.requirements_no_overrides(env, mode)
.chain(self.overrides(env, mode))
}
/// Like [`Self::requirements`], but without the overrides.
pub fn requirements_no_overrides<'a>(
&'a self,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
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.marker_environment(),
lookahead.extras(),
)
},
)
})
.chain(self.overrides.apply(&self.requirements).filter(
move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
},
))
.chain(
self.constraints
.requirements()
.filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
})
.map(Cow::Borrowed),
),
)
}
DependencyMode::Transitive => Either::Left(
self.lookaheads
.iter()
.flat_map(move |lookahead| {
self.overrides
.apply(lookahead.requirements())
.filter(move |requirement| {
requirement
.evaluate_markers(env.marker_environment(), lookahead.extras())
})
})
.chain(
self.overrides
.apply(&self.requirements)
.filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), &[])
}),
)
.chain(
self.constraints
.requirements()
.filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), &[])
})
.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.marker_environment(), &[])
requirement.evaluate_markers(env.marker_environment(), &[])
}),
),
}
@ -168,7 +166,7 @@ impl Manifest {
/// Only the overrides from [`Self::requirements`].
pub fn overrides<'a>(
&'a self,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode {
@ -177,7 +175,7 @@ impl Manifest {
self.overrides
.requirements()
.filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
requirement.evaluate_markers(env.marker_environment(), &[])
})
.map(Cow::Borrowed),
),
@ -186,7 +184,7 @@ impl Manifest {
self.overrides
.requirements()
.filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
requirement.evaluate_markers(env.marker_environment(), &[])
})
.map(Cow::Borrowed),
),
@ -205,41 +203,37 @@ impl Manifest {
/// the `lowest-direct` strategy is in use.
pub fn user_requirements<'a>(
&'a self,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + '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.marker_environment(),
lookahead.extras(),
)
},
)
})
.chain(self.overrides.apply(&self.requirements).filter(
move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
},
)),
)
}
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(env.marker_environment(), lookahead.extras())
})
})
.chain(
self.overrides
.apply(&self.requirements)
.filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), &[])
}),
),
),
// Restrict to the direct requirements.
DependencyMode::Direct => {
Either::Right(self.overrides.apply(self.requirements.iter()).filter(
move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
},
move |requirement| requirement.evaluate_markers(env.marker_environment(), &[]),
))
}
}
@ -252,13 +246,11 @@ impl Manifest {
/// resolution (assuming the user enabled development dependencies).
pub fn direct_requirements<'a>(
&'a self,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
self.overrides
.apply(self.requirements.iter())
.filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[])
})
.filter(move |requirement| requirement.evaluate_markers(env.marker_environment(), &[]))
}
/// Apply the overrides and constraints to a set of requirements.

View file

@ -10,7 +10,7 @@ use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pypi_types::{HashDigest, HashError};
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
use crate::ResolverMarkers;
use crate::ResolverEnvironment;
#[derive(thiserror::Error, Debug)]
pub enum PreferenceError {
@ -119,16 +119,16 @@ pub struct Preferences(FxHashMap<PackageName, Vec<(Option<MarkerTree>, Pin)>>);
impl Preferences {
/// Create a map of pinned packages from an iterator of [`Preference`] entries.
///
/// The provided [`MarkerEnvironment`] will be used to filter the preferences
/// The provided [`ResolverEnvironment`] will be used to filter the preferences
/// to an applicable subset.
pub fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>(
preferences: PreferenceIterator,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Self {
let mut slf = Self::default();
for preference in preferences {
// Filter non-matching preferences when resolving for an environment.
if let Some(markers) = markers.marker_environment() {
if let Some(markers) = env.marker_environment() {
if !preference.marker.evaluate(markers, &[]) {
trace!("Excluding {preference} from preferences due to unmatched markers");
continue;

View file

@ -3,7 +3,7 @@ use uv_pypi_types::RequirementSource;
use uv_normalize::PackageName;
use crate::resolver::ForkSet;
use crate::{DependencyMode, Manifest, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolverEnvironment};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
@ -67,7 +67,7 @@ impl PrereleaseStrategy {
pub(crate) fn from_mode(
mode: PrereleaseMode,
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
dependencies: DependencyMode,
) -> Self {
let mut packages = ForkSet::default();
@ -77,7 +77,7 @@ impl PrereleaseStrategy {
PrereleaseMode::Allow => Self::Allow,
PrereleaseMode::IfNecessary => Self::IfNecessary,
_ => {
for requirement in manifest.requirements(markers, dependencies) {
for requirement in manifest.requirements(env, dependencies) {
let RequirementSource::Registry { specifier, .. } = &requirement.source else {
continue;
};
@ -103,21 +103,21 @@ impl PrereleaseStrategy {
pub(crate) fn allows(
&self,
package_name: &PackageName,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> AllowPrerelease {
match self {
PrereleaseStrategy::Disallow => AllowPrerelease::No,
PrereleaseStrategy::Allow => AllowPrerelease::Yes,
PrereleaseStrategy::IfNecessary => AllowPrerelease::IfNecessary,
PrereleaseStrategy::Explicit(packages) => {
if packages.contains(package_name, markers) {
if packages.contains(package_name, env) {
AllowPrerelease::Yes
} else {
AllowPrerelease::No
}
}
PrereleaseStrategy::IfNecessaryOrExplicit(packages) => {
if packages.contains(package_name, markers) {
if packages.contains(package_name, env) {
AllowPrerelease::Yes
} else {
AllowPrerelease::IfNecessary

View file

@ -19,7 +19,7 @@ use crate::fork_urls::ForkUrls;
use crate::prerelease::AllowPrerelease;
use crate::python_requirement::{PythonRequirement, PythonRequirementSource};
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
use crate::{Flexibility, Options, RequiresPython, ResolverMarkers};
use crate::{Flexibility, Options, RequiresPython, ResolverEnvironment};
use super::{PubGrubPackage, PubGrubPackageInner, PubGrubPython};
@ -510,7 +510,7 @@ impl PubGrubReportFormatter<'_> {
unavailable_packages: &FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: &FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
fork_urls: &ForkUrls,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
workspace_members: &BTreeSet<PackageName>,
options: Options,
output_hints: &mut IndexSet<PubGrubHint>,
@ -528,7 +528,7 @@ impl PubGrubReportFormatter<'_> {
name,
set,
selector,
markers,
env,
output_hints,
);
}
@ -596,7 +596,7 @@ impl PubGrubReportFormatter<'_> {
unavailable_packages,
incomplete_packages,
fork_urls,
markers,
env,
workspace_members,
options,
output_hints,
@ -610,7 +610,7 @@ impl PubGrubReportFormatter<'_> {
unavailable_packages,
incomplete_packages,
fork_urls,
markers,
env,
workspace_members,
options,
output_hints,
@ -756,7 +756,7 @@ impl PubGrubReportFormatter<'_> {
name: &PackageName,
set: &Range<Version>,
selector: &CandidateSelector,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
hints: &mut IndexSet<PubGrubHint>,
) {
let any_prerelease = set.iter().any(|(start, end)| {
@ -775,7 +775,7 @@ impl PubGrubReportFormatter<'_> {
if any_prerelease {
// A pre-release marker appeared in the version requirements.
if selector.prerelease_strategy().allows(name, markers) != AllowPrerelease::Yes {
if selector.prerelease_strategy().allows(name, env) != AllowPrerelease::Yes {
hints.insert(PubGrubHint::PrereleaseRequested {
package: package.clone(),
range: self.simplify_set(set, package).into_owned(),
@ -793,7 +793,7 @@ impl PubGrubReportFormatter<'_> {
})
{
// There are pre-release versions available for the package.
if selector.prerelease_strategy().allows(name, markers) != AllowPrerelease::Yes {
if selector.prerelease_strategy().allows(name, env) != AllowPrerelease::Yes {
hints.insert(PubGrubHint::PrereleaseAvailable {
package: package.clone(),
version: version.clone(),

View file

@ -1,5 +1,5 @@
use uv_pep440::Version;
use uv_pep508::MarkerTree;
use uv_pep508::{MarkerEnvironment, MarkerTree};
use uv_python::{Interpreter, PythonVersion};
use crate::{RequiresPython, RequiresPythonRange};
@ -38,14 +38,7 @@ impl PythonRequirement {
interpreter: &Interpreter,
requires_python: RequiresPython,
) -> Self {
let exact = interpreter.python_full_version().version.clone();
let installed = interpreter.python_full_version().version.only_release();
Self {
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: requires_python,
source: PythonRequirementSource::RequiresPython,
}
Self::from_marker_environment(interpreter.markers(), requires_python)
}
/// Create a [`PythonRequirement`] to resolve against an [`Interpreter`].
@ -60,6 +53,26 @@ impl PythonRequirement {
}
}
/// Create a [`PythonRequirement`] from a [`MarkerEnvironment`] and a
/// specific `Requires-Python` directive.
///
/// This has the same "source" as
/// [`PythonRequirement::from_requires_python`], but is useful for
/// constructing a `PythonRequirement` without an [`Interpreter`].
pub fn from_marker_environment(
marker_env: &MarkerEnvironment,
requires_python: RequiresPython,
) -> Self {
let exact = marker_env.python_full_version().version.clone();
let installed = marker_env.python_full_version().version.only_release();
Self {
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: requires_python,
source: PythonRequirementSource::RequiresPython,
}
}
/// Narrow the [`PythonRequirement`] to the given version, if it's stricter (i.e., greater)
/// than the current `Requires-Python` minimum.
pub fn narrow(&self, target: &RequiresPythonRange) -> Option<Self> {

View file

@ -10,7 +10,7 @@ use uv_normalize::PackageName;
use uv_pep508::MarkerTree;
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode};
use crate::{ResolutionGraph, ResolverMarkers};
use crate::{ResolutionGraph, ResolverEnvironment};
/// A [`std::fmt::Display`] implementation for the resolution graph.
#[derive(Debug)]
@ -18,8 +18,8 @@ use crate::{ResolutionGraph, ResolverMarkers};
pub struct DisplayResolutionGraph<'a> {
/// The underlying graph.
resolution: &'a ResolutionGraph,
/// The marker environment, used to determine the markers that apply to each package.
marker_env: &'a ResolverMarkers,
/// The resolver marker environment, used to determine the markers that apply to each package.
env: &'a ResolverEnvironment,
/// The packages to exclude from the output.
no_emit_packages: &'a [PackageName],
/// Whether to include hashes in the output.
@ -58,7 +58,7 @@ impl<'a> DisplayResolutionGraph<'a> {
#[allow(clippy::fn_params_excessive_bools)]
pub fn new(
underlying: &'a ResolutionGraph,
marker_env: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
no_emit_packages: &'a [PackageName],
show_hashes: bool,
include_extras: bool,
@ -69,7 +69,7 @@ impl<'a> DisplayResolutionGraph<'a> {
) -> DisplayResolutionGraph<'a> {
Self {
resolution: underlying,
marker_env,
env,
no_emit_packages,
show_hashes,
include_extras,
@ -89,7 +89,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
let mut sources = SourceAnnotations::default();
for requirement in self.resolution.requirements.iter().filter(|requirement| {
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
requirement.evaluate_markers(self.env.marker_environment(), &[])
}) {
if let Some(origin) = &requirement.origin {
sources.add(
@ -104,7 +104,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.constraints
.requirements()
.filter(|requirement| {
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
requirement.evaluate_markers(self.env.marker_environment(), &[])
})
{
if let Some(origin) = &requirement.origin {
@ -120,7 +120,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.overrides
.requirements()
.filter(|requirement| {
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
requirement.evaluate_markers(self.env.marker_environment(), &[])
})
{
if let Some(origin) = &requirement.origin {

View file

@ -28,7 +28,7 @@ use crate::resolution_mode::ResolutionStrategy;
use crate::resolver::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
use crate::{
InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError,
ResolverMarkers, VersionsResponse,
VersionsResponse,
};
pub(crate) type MarkersForDistribution = Vec<MarkerTree>;
@ -124,7 +124,7 @@ impl ResolutionGraph {
if package.is_base() {
// For packages with diverging versions, store which version comes from which
// fork.
if let Some(markers) = resolution.markers.fork_markers() {
if let Some(markers) = resolution.env.try_markers() {
package_markers
.entry(package.name.clone())
.or_default()
@ -152,11 +152,7 @@ impl ResolutionGraph {
let mut seen = FxHashSet::default();
for resolution in resolutions {
let marker = resolution
.markers
.fork_markers()
.cloned()
.unwrap_or_default();
let marker = resolution.env.try_markers().cloned().unwrap_or_default();
// Add every edge to the graph, propagating the marker for the current fork, if
// necessary.
@ -180,32 +176,31 @@ impl ResolutionGraph {
let requires_python = python.target().clone();
let fork_markers = if let [resolution] = resolutions {
match resolution.markers {
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
vec![]
}
ResolverMarkers::Fork(_) => {
resolution
.env
.try_markers()
.map(|_| {
resolutions
.iter()
.map(|resolution| {
resolution
.markers
.fork_markers()
.env
.try_markers()
.expect("A non-forking resolution exists in forking mode")
.clone()
})
// Any unsatisfiable forks were skipped.
.filter(|fork| !fork.is_false())
.collect()
}
}
})
.unwrap_or_else(Vec::new)
} else {
resolutions
.iter()
.map(|resolution| {
resolution
.markers
.fork_markers()
.env
.try_markers()
.expect("A non-forking resolution exists in forking mode")
.clone()
})

View file

@ -2,7 +2,7 @@ use rustc_hash::FxHashSet;
use uv_normalize::PackageName;
use crate::{DependencyMode, Manifest, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolverEnvironment};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
@ -46,7 +46,7 @@ impl ResolutionStrategy {
pub(crate) fn from_mode(
mode: ResolutionMode,
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
dependencies: DependencyMode,
) -> Self {
match mode {
@ -54,7 +54,7 @@ impl ResolutionStrategy {
ResolutionMode::Lowest => Self::Lowest,
ResolutionMode::LowestDirect => Self::LowestDirect(
manifest
.user_requirements(markers, dependencies)
.user_requirements(env, dependencies)
.map(|requirement| requirement.name.clone())
.collect(),
),

View file

@ -9,7 +9,9 @@ use tracing::{debug, trace};
use crate::candidate_selector::CandidateSelector;
use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner};
use crate::resolver::Request;
use crate::{InMemoryIndex, PythonRequirement, ResolveError, ResolverMarkers, VersionsResponse};
use crate::{
InMemoryIndex, PythonRequirement, ResolveError, ResolverEnvironment, VersionsResponse,
};
use uv_distribution_types::{CompatibleDist, DistributionMetadata, IndexCapabilities, IndexUrl};
use uv_pep440::Version;
@ -55,7 +57,7 @@ impl BatchPrefetcher {
in_memory: &InMemoryIndex,
capabilities: &IndexCapabilities,
selector: &CandidateSelector,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> anyhow::Result<(), ResolveError> {
let PubGrubPackageInner::Package {
name,
@ -102,7 +104,7 @@ impl BatchPrefetcher {
previous,
} => {
if let Some(candidate) =
selector.select_no_preference(name, &compatible, version_map, markers)
selector.select_no_preference(name, &compatible, version_map, env)
{
let compatible = compatible.intersection(
&Range::singleton(candidate.version().clone()).complement(),
@ -137,7 +139,7 @@ impl BatchPrefetcher {
};
}
if let Some(candidate) =
selector.select_no_preference(name, &range, version_map, markers)
selector.select_no_preference(name, &range, version_map, env)
{
phase = BatchPrefetchStrategy::InOrder {
previous: candidate.version().clone(),

View file

@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
use uv_pep508::{MarkerTree, PackageName};
use uv_pypi_types::Requirement;
use crate::ResolverMarkers;
use crate::ResolverEnvironment;
/// A set of package names associated with a given fork.
pub(crate) type ForkSet = ForkMap<()>;
@ -40,8 +40,8 @@ impl<T> ForkMap<T> {
/// Returns `true` if the map contains any values for a package that are compatible with the
/// given fork.
pub(crate) fn contains(&self, package_name: &PackageName, markers: &ResolverMarkers) -> bool {
!self.get(package_name, markers).is_empty()
pub(crate) fn contains(&self, package_name: &PackageName, env: &ResolverEnvironment) -> bool {
!self.get(package_name, env).is_empty()
}
/// Returns `true` if the map contains any values for a package.
@ -54,27 +54,14 @@ impl<T> ForkMap<T> {
/// Compatibility implies that the markers on the requirement that contained this value
/// are not disjoint with the given fork. Note that this does not imply that the requirement
/// diverged in the given fork - values from overlapping forks may be combined.
pub(crate) fn get(&self, package_name: &PackageName, markers: &ResolverMarkers) -> Vec<&T> {
pub(crate) fn get(&self, package_name: &PackageName, env: &ResolverEnvironment) -> Vec<&T> {
let Some(values) = self.0.get(package_name) else {
return Vec::new();
};
match markers {
// If we are solving for a specific environment we already filtered
// compatible requirements `from_manifest`.
//
// Or, if we haven't forked yet, all values are potentially compatible.
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => {
values.iter().map(|entry| &entry.value).collect()
}
// Return all values that were requested with markers that are compatible
// with the current fork, i.e. the markers are not disjoint.
ResolverMarkers::Fork(fork) => values
.iter()
.filter(|entry| !fork.is_disjoint(&entry.marker))
.map(|entry| &entry.value)
.collect(),
}
values
.iter()
.filter(|entry| env.included(&entry.marker))
.map(|entry| &entry.value)
.collect()
}
}

View file

@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
use uv_normalize::{GroupName, PackageName};
use crate::{Manifest, ResolverMarkers};
use crate::{Manifest, ResolverEnvironment};
/// A map of package names to their activated dependency groups.
#[derive(Debug, Default, Clone)]
@ -10,13 +10,13 @@ pub(crate) struct Groups(FxHashMap<PackageName, Vec<GroupName>>);
impl Groups {
/// Determine the set of enabled dependency groups in the [`Manifest`].
pub(crate) fn from_manifest(manifest: &Manifest, markers: &ResolverMarkers) -> Self {
pub(crate) fn from_manifest(manifest: &Manifest, env: &ResolverEnvironment) -> Self {
let mut groups = FxHashMap::default();
// Enable the groups for all direct dependencies. In practice, this tends to mean: when
// development dependencies are enabled, enable them for all direct dependencies.
for group in &manifest.dev {
for requirement in manifest.direct_requirements(markers) {
for requirement in manifest.direct_requirements(env) {
groups
.entry(requirement.name.clone())
.or_insert_with(Vec::new)

View file

@ -4,7 +4,7 @@ use uv_pep508::VerbatimUrl;
use uv_pypi_types::RequirementSource;
use crate::resolver::ForkMap;
use crate::{DependencyMode, Manifest, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolverEnvironment};
/// A map of package names to their explicit index.
///
@ -26,12 +26,12 @@ impl Indexes {
/// Determine the set of explicit, pinned indexes in the [`Manifest`].
pub(crate) fn from_manifest(
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
dependencies: DependencyMode,
) -> Self {
let mut indexes = ForkMap::default();
for requirement in manifest.requirements(markers, dependencies) {
for requirement in manifest.requirements(env, dependencies) {
let RequirementSource::Registry {
index: Some(index), ..
} = &requirement.source
@ -54,8 +54,8 @@ impl Indexes {
pub(crate) fn get(
&self,
package_name: &PackageName,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Vec<&IndexUrl> {
self.0.get(package_name, markers)
self.0.get(package_name, env)
}
}

View file

@ -7,7 +7,7 @@ use uv_pep508::PackageName;
use uv_pypi_types::RequirementSource;
use crate::resolver::ForkMap;
use crate::{DependencyMode, Manifest, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolverEnvironment};
/// A map of package names to their associated, required local versions across all forks.
#[derive(Debug, Default, Clone)]
@ -17,14 +17,14 @@ impl Locals {
/// Determine the set of permitted local versions in the [`Manifest`].
pub(crate) fn from_manifest(
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
dependencies: DependencyMode,
) -> Self {
let mut locals = ForkMap::default();
// Add all direct requirements and constraints. There's no need to look for conflicts,
// since conflicts will be enforced by the solver.
for requirement in manifest.requirements(markers, dependencies) {
for requirement in manifest.requirements(env, dependencies) {
if let Some(local) = from_source(&requirement.source) {
locals.add(&requirement, local);
}
@ -37,9 +37,9 @@ impl Locals {
pub(crate) fn get(
&self,
package_name: &PackageName,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
) -> Vec<&Version> {
self.0.get(package_name, markers)
self.0.get(package_name, env)
}
/// Given a specifier that may include the version _without_ a local segment, return a specifier

View file

@ -1,5 +1,7 @@
//! Given a set of requirements, find a set of compatible packages.
#![allow(warnings)]
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
@ -20,9 +22,9 @@ use tokio::sync::oneshot;
use tokio_stream::wrappers::ReceiverStream;
use tracing::{debug, info, instrument, trace, warn, Level};
pub use environment::ResolverEnvironment;
pub(crate) use fork_map::{ForkMap, ForkSet};
use locals::Locals;
pub use resolver_markers::ResolverMarkers;
pub(crate) use urls::Urls;
use uv_configuration::{Constraints, Overrides};
use uv_distribution::{ArchiveMetadata, DistributionDatabase};
@ -74,6 +76,7 @@ use crate::{marker, DependencyMode, Exclusions, FlatIndex, Options};
mod availability;
mod batch_prefetch;
mod environment;
mod fork_map;
mod groups;
mod index;
@ -81,7 +84,6 @@ mod indexes;
mod locals;
mod provider;
mod reporter;
mod resolver_markers;
mod urls;
pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
@ -107,7 +109,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
indexes: Indexes,
dependency_mode: DependencyMode,
hasher: HashStrategy,
markers: ResolverMarkers,
env: ResolverEnvironment,
python_requirement: PythonRequirement,
workspace_members: BTreeSet<PackageName>,
selector: CandidateSelector,
@ -148,7 +150,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
manifest: Manifest,
options: Options,
python_requirement: &'a PythonRequirement,
markers: ResolverMarkers,
env: ResolverEnvironment,
tags: Option<&'a Tags>,
flat_index: &'a FlatIndex,
index: &'a InMemoryIndex,
@ -162,7 +164,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
flat_index,
tags,
python_requirement.target(),
AllowedYanks::from_manifest(&manifest, &markers, options.dependency_mode),
AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
hasher,
options.exclude_newer,
build_context.build_options(),
@ -173,7 +175,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
manifest,
options,
hasher,
markers,
env,
python_requirement,
index,
build_context.git(),
@ -193,7 +195,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
manifest: Manifest,
options: Options,
hasher: &HashStrategy,
markers: ResolverMarkers,
env: ResolverEnvironment,
python_requirement: &PythonRequirement,
index: &InMemoryIndex,
git: &GitResolver,
@ -206,12 +208,12 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
index: index.clone(),
git: git.clone(),
capabilities: capabilities.clone(),
selector: CandidateSelector::for_resolution(options, &manifest, &markers),
selector: CandidateSelector::for_resolution(options, &manifest, &env),
dependency_mode: options.dependency_mode,
urls: Urls::from_manifest(&manifest, &markers, git, options.dependency_mode)?,
locals: Locals::from_manifest(&manifest, &markers, options.dependency_mode),
indexes: Indexes::from_manifest(&manifest, &markers, options.dependency_mode),
groups: Groups::from_manifest(&manifest, &markers),
urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode)?,
locals: Locals::from_manifest(&manifest, &env, options.dependency_mode),
indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
groups: Groups::from_manifest(&manifest, &env),
project: manifest.project,
workspace_members: manifest.workspace_members,
requirements: manifest.requirements,
@ -221,7 +223,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
exclusions: manifest.exclusions,
hasher: hasher.clone(),
locations: locations.clone(),
markers,
env,
python_requirement: python_requirement.clone(),
installed_packages,
unavailable_packages: DashMap::default(),
@ -304,32 +306,17 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
let state = ForkState::new(
State::init(root.clone(), MIN_VERSION.clone()),
root,
self.markers.clone(),
self.env.clone(),
self.python_requirement.clone(),
);
let mut preferences = self.preferences.clone();
let mut forked_states =
if let ResolverMarkers::Universal { fork_preferences } = &self.markers {
if fork_preferences.is_empty() {
vec![state]
} else {
fork_preferences
.iter()
.rev()
.filter_map(|fork_preference| {
state.clone().with_markers(fork_preference.clone())
})
.collect()
}
} else {
vec![state]
};
let mut forked_states = self.env.initial_forked_states(state);
let mut resolutions = vec![];
'FORK: while let Some(mut state) = forked_states.pop() {
if let ResolverMarkers::Fork(markers) = &state.markers {
if let Some(split) = state.env.end_user_fork_display() {
let requires_python = state.python_requirement.target();
debug!("Solving split {markers:?} (requires-python: {requires_python:?})");
debug!("Solving {split} (requires-python: {requires_python:?})");
}
let start = Instant::now();
loop {
@ -339,7 +326,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
err,
state.fork_urls,
&state.fork_indexes,
state.markers,
state.env,
&visited,
&self.locations,
&self.capabilities,
@ -367,8 +354,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
prefetcher.log_tried_versions();
}
debug!(
"Split {} resolution took {:.3}s",
state.markers,
"{} resolution took {:.3}s",
state.env,
start.elapsed().as_secs_f32()
);
@ -380,7 +367,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
for (package, version) in &resolution.nodes {
preferences.insert(
package.name.clone(),
resolution.markers.fork_markers().cloned(),
resolution.env.try_markers().cloned(),
version.clone(),
);
}
@ -423,7 +410,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&mut state.pins,
&preferences,
&state.fork_urls,
&state.markers,
&state.env,
&state.python_requirement,
&mut visited,
&request_sink,
@ -488,7 +475,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self.index,
&self.capabilities,
&self.selector,
&state.markers,
&state.env,
)?;
}
@ -519,7 +506,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&state.next,
&version,
&state.fork_urls,
&state.markers,
&state.env,
&state.python_requirement,
)?;
match forked_deps {
@ -563,8 +550,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
diverging_packages,
} => {
debug!(
"Pre-fork split {} took {:.3}s",
state.markers,
"Pre-fork {} took {:.3}s",
state.env,
start.elapsed().as_secs_f32()
);
@ -590,9 +577,9 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
);
}
for resolution in &resolutions {
if let Some(markers) = resolution.markers.fork_markers() {
if let Some(env) = resolution.env.end_user_fork_display() {
debug!(
"Distinct solution for ({markers:?}) with {} packages",
"Distinct solution for {env} with {} packages",
resolution.nodes.len()
);
}
@ -623,11 +610,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
if !tracing::enabled!(Level::TRACE) {
return;
}
if let Some(markers) = combined.markers.fork_markers() {
trace!("Resolution: {:?}", markers);
} else {
trace!("Resolution: <matches all marker environments>");
}
trace!("Resolution: {:?}", combined.env);
for edge in &combined.edges {
trace!(
"Resolution edge: {} -> {}",
@ -703,7 +686,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
let markers = fork.markers.clone();
Some((fork, forked_state.with_markers(markers)?))
Some((fork, forked_state.with_env(&markers)?))
})
.map(move |(fork, mut forked_state)| {
forked_state.add_package_version_dependencies(
@ -738,7 +721,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// match any marker environments.
.filter(|result| {
if let Ok(ref forked_state) = result {
let markers = forked_state.markers.fork_markers().expect("is a fork");
let markers = forked_state.env.try_markers().expect("is a fork");
if markers.is_false() {
return false;
}
@ -862,7 +845,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
pins: &mut FilePins,
preferences: &Preferences,
fork_urls: &ForkUrls,
fork_markers: &ResolverMarkers,
env: &ResolverEnvironment,
python_requirement: &PythonRequirement,
visited: &mut FxHashSet<PackageName>,
request_sink: &Sender<Request>,
@ -892,7 +875,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
range,
package,
preferences,
fork_markers,
env,
python_requirement,
pins,
visited,
@ -1011,7 +994,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
range: &Range<Version>,
package: &PubGrubPackage,
preferences: &Preferences,
fork_markers: &ResolverMarkers,
env: &ResolverEnvironment,
python_requirement: &PythonRequirement,
pins: &mut FilePins,
visited: &mut FxHashSet<PackageName>,
@ -1060,7 +1043,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
preferences,
&self.installed_packages,
&self.exclusions,
fork_markers,
env,
) else {
// Short circuit: we couldn't find _any_ versions for a package.
return Ok(None);
@ -1186,21 +1169,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
package: &PubGrubPackage,
version: &Version,
fork_urls: &ForkUrls,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
python_requirement: &PythonRequirement,
) -> Result<ForkedDependencies, ResolveError> {
let result =
self.get_dependencies(package, version, fork_urls, markers, python_requirement);
match markers {
ResolverMarkers::SpecificEnvironment(_) => result.map(|deps| match deps {
let result = self.get_dependencies(package, version, fork_urls, env, python_requirement);
if env.marker_environment().is_some() {
result.map(|deps| match deps {
Dependencies::Available(deps) | Dependencies::Unforkable(deps) => {
ForkedDependencies::Unforked(deps)
}
Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err),
}),
ResolverMarkers::Universal { .. } | ResolverMarkers::Fork(_) => {
Ok(result?.fork(python_requirement))
}
})
} else {
Ok(result?.fork(python_requirement))
}
}
@ -1211,7 +1192,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
package: &PubGrubPackage,
version: &Version,
fork_urls: &ForkUrls,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
python_requirement: &PythonRequirement,
) -> Result<Dependencies, ResolveError> {
let url = package.name().and_then(|name| fork_urls.get(name));
@ -1224,7 +1205,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
None,
None,
None,
markers,
env,
python_requirement,
);
@ -1357,7 +1338,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra.as_ref(),
dev.as_ref(),
Some(name),
markers,
env,
python_requirement,
);
@ -1481,7 +1462,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
extra: Option<&'a ExtraName>,
dev: Option<&'a GroupName>,
name: Option<&PackageName>,
markers: &'a ResolverMarkers,
env: &'a ResolverEnvironment,
python_requirement: &'a PythonRequirement,
) -> Vec<Cow<'a, Requirement>> {
// Start with the requirements for the current extra of the package (for an extra
@ -1493,12 +1474,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
Either::Right(dependencies.iter())
};
let mut requirements = self
.requirements_for_extra(
regular_and_dev_dependencies,
extra,
markers,
python_requirement,
)
.requirements_for_extra(regular_and_dev_dependencies, extra, env, python_requirement)
.collect::<Vec<_>>();
// Check if there are recursive self inclusions and we need to go into the expensive branch.
@ -1522,7 +1498,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
continue;
}
for requirement in
self.requirements_for_extra(dependencies, Some(&extra), markers, python_requirement)
self.requirements_for_extra(dependencies, Some(&extra), env, python_requirement)
{
if name == Some(&requirement.name) {
// Add each transitively included extra.
@ -1546,7 +1522,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&'data self,
dependencies: impl IntoIterator<Item = &'data Requirement> + 'parameters,
extra: Option<&'parameters ExtraName>,
markers: &'parameters ResolverMarkers,
env: &'parameters ResolverEnvironment,
python_requirement: &'parameters PythonRequirement,
) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
where
@ -1568,29 +1544,27 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// If we're in a fork in universal mode, ignore any dependency that isn't part of
// this fork (but will be part of another fork).
if let ResolverMarkers::Fork(markers) = markers {
if markers.is_disjoint(&requirement.marker) {
trace!("skipping {requirement} because of context resolver markers {markers:?}");
return None;
}
if !env.included(&requirement.marker) {
trace!("skipping {requirement} because of {env}");
return None;
}
// If the requirement isn't relevant for the current platform, skip it.
match extra {
Some(source_extra) => {
// Only include requirements that are relevant for the current extra.
if requirement.evaluate_markers(markers.marker_environment(), &[]) {
if requirement.evaluate_markers(env.marker_environment(), &[]) {
return None;
}
if !requirement.evaluate_markers(
markers.marker_environment(),
env.marker_environment(),
std::slice::from_ref(source_extra),
) {
return None;
}
}
None => {
if !requirement.evaluate_markers(markers.marker_environment(), &[]) {
if !requirement.evaluate_markers(env.marker_environment(), &[]) {
return None;
}
}
@ -1662,25 +1636,23 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// If we're in a fork in universal mode, ignore any dependency that isn't part of
// this fork (but will be part of another fork).
if let ResolverMarkers::Fork(markers) = markers {
if markers.is_disjoint(&constraint.marker) {
trace!("skipping {constraint} because of context resolver markers {markers:?}");
return None;
}
if !env.included(&constraint.marker) {
trace!("skipping {constraint} because of {env}");
return None;
}
// If the constraint isn't relevant for the current platform, skip it.
match extra {
Some(source_extra) => {
if !constraint.evaluate_markers(
markers.marker_environment(),
env.marker_environment(),
std::slice::from_ref(source_extra),
) {
return None;
}
}
None => {
if !constraint.evaluate_markers(markers.marker_environment(), &[]) {
if !constraint.evaluate_markers(env.marker_environment(), &[]) {
return None;
}
}
@ -1871,7 +1843,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self.exclusions,
// We don't have access to the fork state when prefetching, so assume that
// pre-release versions are allowed.
&ResolverMarkers::universal(vec![]),
&ResolverEnvironment::universal(vec![]),
) else {
return Ok(None);
};
@ -1975,7 +1947,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
fork_urls: ForkUrls,
fork_indexes: &ForkIndexes,
markers: ResolverMarkers,
env: ResolverEnvironment,
visited: &FxHashSet<PackageName>,
index_locations: &IndexLocations,
index_capabilities: &IndexCapabilities,
@ -2056,7 +2028,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
unavailable_packages,
incomplete_packages,
fork_urls,
markers,
env,
self.workspace_members.clone(),
self.options,
))
@ -2141,7 +2113,7 @@ struct ForkState {
/// completely disjoint marker expression (i.e., it can never be true given
/// that the marker expression that provoked the fork is true), then that
/// dependency is completely ignored.
markers: ResolverMarkers,
env: ResolverEnvironment,
/// The Python requirement for this fork. Defaults to the Python requirement for
/// the resolution, but may be narrowed if a `python_version` marker is present
/// in a given fork.
@ -2161,7 +2133,7 @@ impl ForkState {
fn new(
pubgrub: State<UvDependencyProvider>,
root: PubGrubPackage,
markers: ResolverMarkers,
env: ResolverEnvironment,
python_requirement: PythonRequirement,
) -> Self {
Self {
@ -2172,7 +2144,7 @@ impl ForkState {
fork_indexes: ForkIndexes::default(),
priorities: PubGrubPriorities::default(),
added_dependencies: FxHashMap::default(),
markers,
env,
python_requirement,
}
}
@ -2204,15 +2176,15 @@ impl ForkState {
// requirement was a URL requirement. `Urls` applies canonicalization to this and
// override URLs to both URL and registry requirements, which we then check for
// conflicts using [`ForkUrl`].
if let Some(url) = urls.get_url(name, url.as_ref(), git)? {
self.fork_urls.insert(name, url, &self.markers)?;
if let Some(url) = urls.get_url(&self.env, name, url.as_ref(), git)? {
self.fork_urls.insert(name, url, &self.env)?;
has_url = true;
};
// If the specifier is an exact version and the user requested a local version for this
// fork that's more precise than the specifier, use the local version instead.
if let Some(specifier) = specifier {
let locals = locals.get(name, &self.markers);
let locals = locals.get(name, &self.env);
// It's possible that there are multiple matching local versions requested with
// different marker expressions. All of these are potentially compatible until we
@ -2235,8 +2207,8 @@ impl ForkState {
}
// If the package is pinned to an exact index, add it to the fork.
for index in indexes.get(name, &self.markers) {
self.fork_indexes.insert(name, index, &self.markers)?;
for index in indexes.get(name, &self.env) {
self.fork_indexes.insert(name, index, &self.env)?;
}
}
@ -2321,29 +2293,18 @@ impl ForkState {
/// Subset the current markers with the new markers and update the python requirements fields
/// accordingly.
fn with_markers(mut self, markers: MarkerTree) -> Option<Self> {
let combined_markers = self.markers.and(markers);
let python_marker = self.python_requirement.to_marker_tree();
if combined_markers.is_disjoint(&python_marker) {
debug!(
"Skipping split {combined_markers:?} \
because of Python requirement {python_marker:?}",
);
return None;
}
///
/// If the fork should be dropped (e.g., because its markers can never be true for its
/// Python requirement), then this returns `None`.
fn with_env(mut self, markers: &MarkerTree) -> Option<Self> {
self.env = self
.env
.narrow_environment(&self.python_requirement, markers)?;
// If the fork contains a narrowed Python requirement, apply it.
let python_requirement = marker::requires_python(&combined_markers)
.and_then(|marker| self.python_requirement.narrow(&marker));
if let Some(python_requirement) = python_requirement {
debug!(
"Narrowed `requires-python` bound to: {}",
python_requirement.target()
);
self.python_requirement = python_requirement;
if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
debug!("Narrowed `requires-python` bound to: {}", req.target());
self.python_requirement = req;
}
self.markers = ResolverMarkers::Fork(combined_markers);
Some(self)
}
@ -2542,7 +2503,7 @@ impl ForkState {
nodes,
edges,
pins: self.pins,
markers: self.markers,
env: self.env,
}
}
}
@ -2556,8 +2517,8 @@ pub(crate) struct Resolution {
pub(crate) edges: FxHashSet<ResolutionDependencyEdge>,
/// Map each package name, version tuple from `packages` to a distribution.
pub(crate) pins: FilePins,
/// The marker setting this resolution was found under.
pub(crate) markers: ResolverMarkers,
/// The environment setting this resolution was found under.
pub(crate) env: ResolverEnvironment,
}
/// Package representation we used during resolution where each extra and also the dev-dependencies

View file

@ -1,73 +0,0 @@
use std::fmt::{Display, Formatter};
use uv_pep508::{MarkerEnvironment, MarkerTree};
use uv_pypi_types::ResolverMarkerEnvironment;
#[derive(Debug, Clone)]
/// Whether we're solving for a specific environment, universally or for a specific fork.
pub enum ResolverMarkers {
/// We're solving for this specific environment only.
SpecificEnvironment(ResolverMarkerEnvironment),
/// We're doing a universal resolution for all environments (a python version
/// constraint is expressed separately).
Universal {
/// Start the resolution with these forks.
fork_preferences: Vec<MarkerTree>,
},
/// We're in a fork of the universal resolution solving only for specific markers.
Fork(MarkerTree),
}
impl ResolverMarkers {
/// Set the resolver to perform a resolution for a specific environment.
pub fn specific_environment(markers: ResolverMarkerEnvironment) -> Self {
Self::SpecificEnvironment(markers)
}
/// Set the resolver to perform a universal resolution.
pub fn universal(fork_preferences: Vec<MarkerTree>) -> Self {
Self::Universal { fork_preferences }
}
/// Add the markers of an initial or subsequent fork to the current markers.
pub(crate) fn and(self, other: MarkerTree) -> MarkerTree {
match self {
ResolverMarkers::Universal { .. } => other,
ResolverMarkers::Fork(mut current) => {
current.and(other);
current
}
ResolverMarkers::SpecificEnvironment(_) => {
unreachable!("Specific environment mode must not fork")
}
}
}
/// If solving for a specific environment, return this environment.
pub fn marker_environment(&self) -> Option<&MarkerEnvironment> {
match self {
ResolverMarkers::Universal { .. } | ResolverMarkers::Fork(_) => None,
ResolverMarkers::SpecificEnvironment(env) => Some(env),
}
}
/// If solving a fork, return that fork's markers.
pub fn fork_markers(&self) -> Option<&MarkerTree> {
match self {
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => None,
ResolverMarkers::Fork(markers) => Some(markers),
}
}
}
impl Display for ResolverMarkers {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ResolverMarkers::Universal { .. } => f.write_str("universal"),
ResolverMarkers::SpecificEnvironment(_) => f.write_str("specific environment"),
ResolverMarkers::Fork(markers) => {
write!(f, "({markers:?})")
}
}
}
}

View file

@ -11,7 +11,7 @@ use uv_normalize::PackageName;
use uv_pep508::VerbatimUrl;
use uv_pypi_types::{ParsedDirectoryUrl, ParsedUrl, VerbatimParsedUrl};
use crate::{DependencyMode, Manifest, ResolveError, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolveError, ResolverEnvironment};
/// The URLs that are allowed for packages.
///
@ -36,7 +36,7 @@ pub(crate) struct Urls {
impl Urls {
pub(crate) fn from_manifest(
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
git: &GitResolver,
dependencies: DependencyMode,
) -> Result<Self, ResolveError> {
@ -44,7 +44,7 @@ impl Urls {
let mut overrides: FxHashMap<PackageName, VerbatimParsedUrl> = FxHashMap::default();
// Add all direct regular requirements and constraints URL.
for requirement in manifest.requirements_no_overrides(markers, dependencies) {
for requirement in manifest.requirements_no_overrides(env, dependencies) {
let Some(url) = requirement.source.to_verbatim_parsed_url() else {
// Registry requirement
continue;
@ -77,7 +77,7 @@ impl Urls {
// Add all URLs from overrides. If there is an override URL, all other URLs from
// requirements and constraints are moot and will be removed.
for requirement in manifest.overrides(markers, dependencies) {
for requirement in manifest.overrides(env, dependencies) {
let Some(url) = requirement.source.to_verbatim_parsed_url() else {
// Registry requirement
continue;
@ -112,6 +112,7 @@ impl Urls {
/// if there is no override.
pub(crate) fn get_url<'a>(
&'a self,
env: &ResolverEnvironment,
name: &'a PackageName,
url: Option<&'a VerbatimParsedUrl>,
git: &'a GitResolver,
@ -120,6 +121,7 @@ impl Urls {
Ok(Some(override_url))
} else if let Some(url) = url {
Ok(Some(self.canonicalize_allowed_url(
env,
name,
git,
&url.verbatim,
@ -150,6 +152,7 @@ impl Urls {
/// Check if a URL is allowed (known), and if so, return its canonical form.
fn canonicalize_allowed_url<'a>(
&'a self,
env: &ResolverEnvironment,
package_name: &'a PackageName,
git: &'a GitResolver,
verbatim_url: &'a VerbatimUrl,
@ -174,10 +177,11 @@ impl Urls {
.chain(iter::once(verbatim_url.verbatim().to_string()))
.collect();
conflicting_urls.sort();
return Err(ResolveError::ConflictingUrlsUniversal(
package_name.clone(),
conflicting_urls,
));
return Err(ResolveError::ConflictingUrls {
package_name: package_name.clone(),
urls: conflicting_urls,
env: env.clone(),
});
};
Ok(*allowed_url)
}

View file

@ -6,7 +6,7 @@ use uv_normalize::PackageName;
use uv_pep440::Version;
use uv_pypi_types::RequirementSource;
use crate::{DependencyMode, Manifest, ResolverMarkers};
use crate::{DependencyMode, Manifest, ResolverEnvironment};
/// A set of package versions that are permitted, even if they're marked as yanked by the
/// relevant index.
@ -16,13 +16,13 @@ pub struct AllowedYanks(Arc<FxHashMap<PackageName, FxHashSet<Version>>>);
impl AllowedYanks {
pub fn from_manifest(
manifest: &Manifest,
markers: &ResolverMarkers,
env: &ResolverEnvironment,
dependencies: DependencyMode,
) -> Self {
let mut allowed_yanks = FxHashMap::<PackageName, FxHashSet<Version>>::default();
// Allow yanks for any pinned input requirements.
for requirement in manifest.requirements(markers, dependencies) {
for requirement in manifest.requirements(env, dependencies) {
let RequirementSource::Registry { specifier, .. } = &requirement.source else {
continue;
};

View file

@ -438,7 +438,7 @@ async fn build_package(
build_constraints
.iter()
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
Some(&interpreter.resolver_markers()),
Some(&interpreter.resolver_marker_environment()),
hash_checking,
)?
} else {

View file

@ -48,7 +48,7 @@ pub(crate) fn pip_check(
)?;
// Determine the markers to use for resolution.
let markers = environment.interpreter().resolver_markers();
let markers = environment.interpreter().resolver_marker_environment();
// Run the diagnostics.
let diagnostics: Vec<SitePackagesDiagnostic> =

View file

@ -34,7 +34,7 @@ use uv_requirements::{
use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex,
InMemoryIndex, OptionsBuilder, PrereleaseMode, PythonRequirement, RequiresPython,
ResolutionMode, ResolverMarkers,
ResolutionMode, ResolverEnvironment,
};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_warnings::warn_user;
@ -251,15 +251,15 @@ pub(crate) async fn pip_compile(
};
// Determine the environment for the resolution.
let (tags, markers) = if universal {
let (tags, resolver_env) = if universal {
(
None,
ResolverMarkers::universal(environments.into_markers()),
ResolverEnvironment::universal(environments.into_markers()),
)
} else {
let (tags, markers) =
let (tags, marker_env) =
resolution_environment(python_version, python_platform, &interpreter)?;
(Some(tags), ResolverMarkers::specific_environment(markers))
(Some(tags), ResolverEnvironment::specific(marker_env))
};
// Generate, but don't enforce hashes for the requirements.
@ -392,7 +392,7 @@ pub(crate) async fn pip_compile(
&Reinstall::None,
&upgrade,
tags.as_deref(),
markers.clone(),
resolver_env.clone(),
python_requirement,
&client,
&flat_index,
@ -446,8 +446,8 @@ pub(crate) async fn pip_compile(
}
if include_marker_expression {
if let ResolverMarkers::SpecificEnvironment(markers) = &markers {
let relevant_markers = resolution.marker_tree(&top_level_index, markers)?;
if let Some(marker_env) = resolver_env.marker_environment() {
let relevant_markers = resolution.marker_tree(&top_level_index, marker_env)?;
if let Some(relevant_markers) = relevant_markers.contents() {
writeln!(
writer,
@ -524,7 +524,7 @@ pub(crate) async fn pip_compile(
"{}",
DisplayResolutionGraph::new(
&resolution,
&markers,
&resolver_env,
&no_emit_packages,
generate_hashes,
include_extras,

View file

@ -64,7 +64,7 @@ pub(crate) fn pip_freeze(
// Validate that the environment is consistent.
if strict {
// Determine the markers to use for resolution.
let markers = environment.interpreter().resolver_markers();
let markers = environment.interpreter().resolver_marker_environment();
for diagnostic in site_packages.diagnostics(&markers)? {
writeln!(

View file

@ -27,7 +27,7 @@ use uv_python::{
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PythonRequirement,
ResolutionMode, ResolverMarkers,
ResolutionMode, ResolverEnvironment,
};
use uv_types::{BuildIsolation, HashStrategy};
@ -191,7 +191,7 @@ pub(crate) async fn pip_install(
// Determine the markers to use for the resolution.
let interpreter = environment.interpreter();
let markers = resolution_markers(
let marker_env = resolution_markers(
python_version.as_ref(),
python_platform.as_ref(),
interpreter,
@ -209,7 +209,7 @@ pub(crate) async fn pip_install(
&& overrides.is_empty()
&& matches!(modifications, Modifications::Sufficient)
{
match site_packages.satisfies(&requirements, &constraints, &markers)? {
match site_packages.satisfies(&requirements, &constraints, &marker_env)? {
// If the requirements are already satisfied, we're done.
SatisfiesResult::Fresh {
recursive_requirements,
@ -260,7 +260,7 @@ pub(crate) async fn pip_install(
constraints
.iter()
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
Some(&markers),
Some(&marker_env),
hash_checking,
)?
} else {
@ -334,7 +334,7 @@ pub(crate) async fn pip_install(
build_constraints
.iter()
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
Some(&markers),
Some(&marker_env),
HashCheckingMode::Verify,
)?
} else {
@ -398,7 +398,7 @@ pub(crate) async fn pip_install(
&reinstall,
&upgrade,
Some(&tags),
ResolverMarkers::specific_environment(markers.clone()),
ResolverEnvironment::specific(marker_env.clone()),
python_requirement,
&client,
&flat_index,
@ -457,7 +457,7 @@ pub(crate) async fn pip_install(
// Notify the user of any environment diagnostics.
if strict && !dry_run {
operations::diagnose_environment(&resolution, &environment, &markers, printer)?;
operations::diagnose_environment(&resolution, &environment, &marker_env, printer)?;
}
Ok(ExitStatus::Success)

View file

@ -112,7 +112,7 @@ pub(crate) fn pip_list(
// Validate that the environment is consistent.
if strict {
// Determine the markers to use for resolution.
let markers = environment.interpreter().resolver_markers();
let markers = environment.interpreter().resolver_marker_environment();
for diagnostic in site_packages.diagnostics(&markers)? {
writeln!(

View file

@ -32,7 +32,7 @@ pub(crate) fn resolution_markers(
(None, Some(python_version)) => {
ResolverMarkerEnvironment::from(python_version.markers(interpreter.markers()))
}
(None, None) => interpreter.resolver_markers(),
(None, None) => interpreter.resolver_marker_environment(),
}
}
@ -115,7 +115,7 @@ pub(crate) fn resolution_environment(
(None, Some(python_version)) => {
ResolverMarkerEnvironment::from(python_version.markers(interpreter.markers()))
}
(None, None) => interpreter.resolver_markers(),
(None, None) => interpreter.resolver_marker_environment(),
};
Ok((tags, markers))

View file

@ -37,7 +37,7 @@ use uv_requirements::{
};
use uv_resolver::{
DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference,
Preferences, PythonRequirement, ResolutionGraph, Resolver, ResolverMarkers,
Preferences, PythonRequirement, ResolutionGraph, Resolver, ResolverEnvironment,
};
use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider};
use uv_warnings::warn_user;
@ -102,7 +102,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
reinstall: &Reinstall,
upgrade: &Upgrade,
tags: Option<&Tags>,
markers: ResolverMarkers,
resolver_env: ResolverEnvironment,
python_requirement: PythonRequirement,
client: &RegistryClient,
flat_index: &FlatIndex,
@ -238,7 +238,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
.chain(upgrade.constraints().cloned()),
);
let overrides = Overrides::from_requirements(overrides);
let preferences = Preferences::from_iter(preferences, &markers);
let preferences = Preferences::from_iter(preferences, &resolver_env);
// Determine any lookahead requirements.
let lookaheads = match options.dependency_mode {
@ -253,7 +253,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
DistributionDatabase::new(client, build_dispatch, concurrency.downloads),
)
.with_reporter(ResolverReporter::from(printer))
.resolve(&markers)
.resolve(&resolver_env)
.await?
}
DependencyMode::Direct => Vec::new(),
@ -289,7 +289,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
manifest,
options,
&python_requirement,
markers,
resolver_env,
tags,
flat_index,
index,

View file

@ -54,7 +54,7 @@ pub(crate) fn pip_show(
let site_packages = SitePackages::from_environment(&environment)?;
// Determine the markers to use for resolution.
let markers = environment.interpreter().resolver_markers();
let markers = environment.interpreter().resolver_marker_environment();
// Sort and deduplicate the packages, which are keyed by name.
packages.sort_unstable();

View file

@ -23,7 +23,7 @@ use uv_python::{
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_resolver::{
DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PrereleaseMode, PythonRequirement,
ResolutionMode, ResolverMarkers,
ResolutionMode, ResolverEnvironment,
};
use uv_types::{BuildIsolation, HashStrategy};
@ -182,7 +182,7 @@ pub(crate) async fn pip_sync(
};
// Determine the markers and tags to use for resolution.
let markers = resolution_markers(
let marker_env = resolution_markers(
python_version.as_ref(),
python_platform.as_ref(),
interpreter,
@ -202,7 +202,7 @@ pub(crate) async fn pip_sync(
constraints
.iter()
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
Some(&markers),
Some(&marker_env),
hash_checking,
)?
} else {
@ -270,7 +270,7 @@ pub(crate) async fn pip_sync(
build_constraints
.iter()
.map(|entry| (&entry.requirement, entry.hashes.as_slice())),
Some(&markers),
Some(&marker_env),
HashCheckingMode::Verify,
)?
} else {
@ -342,7 +342,7 @@ pub(crate) async fn pip_sync(
&reinstall,
&upgrade,
Some(&tags),
ResolverMarkers::specific_environment(markers.clone()),
ResolverEnvironment::specific(marker_env.clone()),
python_requirement,
&client,
&flat_index,
@ -401,7 +401,7 @@ pub(crate) async fn pip_sync(
// Notify the user of any environment diagnostics.
if strict && !dry_run {
operations::diagnose_environment(&resolution, &environment, &markers, printer)?;
operations::diagnose_environment(&resolution, &environment, &marker_env, printer)?;
}
Ok(ExitStatus::Success)

View file

@ -59,7 +59,7 @@ pub(crate) fn pip_tree(
};
// Determine the markers to use for the resolution.
let markers = environment.interpreter().resolver_markers();
let markers = environment.interpreter().resolver_marker_environment();
// Render the tree.
let rendered_tree = DisplayDependencyGraph::new(

View file

@ -28,7 +28,7 @@ use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements};
use uv_requirements::ExtrasResolver;
use uv_resolver::{
FlatIndex, InMemoryIndex, Lock, LockVersion, Options, OptionsBuilder, PythonRequirement,
RequiresPython, ResolverManifest, ResolverMarkers, SatisfiesResult, VERSION,
RequiresPython, ResolverEnvironment, ResolverManifest, SatisfiesResult, VERSION,
};
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
@ -590,7 +590,7 @@ async fn do_lock(
// `preferences-dependent-forking` packse scenario). To avoid this, we store the forks in the
// lockfile. We read those after all the lockfile filters, to allow the forks to change when
// the environment changed, e.g. the python bound check above can lead to different forking.
let resolver_markers = ResolverMarkers::universal(
let resolver_env = ResolverEnvironment::universal(
forks_lock
.map(|lock| lock.fork_markers().to_vec())
.unwrap_or_else(|| {
@ -633,7 +633,7 @@ async fn do_lock(
&Reinstall::default(),
upgrade,
None,
resolver_markers,
resolver_env,
python_requirement,
&client,
&flat_index,

View file

@ -32,7 +32,7 @@ use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements};
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
use uv_resolver::{
FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph,
ResolverMarkers,
ResolverEnvironment,
};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
@ -813,7 +813,7 @@ pub(crate) async fn resolve_environment<'a>(
// Determine the tags, markers, and interpreter to use for resolution.
let tags = interpreter.tags()?;
let markers = interpreter.resolver_markers();
let marker_env = interpreter.resolver_marker_environment();
let python_requirement = PythonRequirement::from_interpreter(interpreter);
// Add all authenticated sources to the cache.
@ -929,7 +929,7 @@ pub(crate) async fn resolve_environment<'a>(
&reinstall,
&upgrade,
Some(tags),
ResolverMarkers::specific_environment(markers),
ResolverEnvironment::specific(marker_env),
python_requirement,
&client,
&flat_index,
@ -1143,12 +1143,12 @@ pub(crate) async fn update_environment(
// Determine markers to use for resolution.
let interpreter = venv.interpreter();
let markers = venv.interpreter().resolver_markers();
let marker_env = venv.interpreter().resolver_marker_environment();
// Check if the current environment satisfies the requirements
let site_packages = SitePackages::from_environment(&venv)?;
if source_trees.is_empty() && reinstall.is_none() && upgrade.is_none() && overrides.is_empty() {
match site_packages.satisfies(&requirements, &constraints, &markers)? {
match site_packages.satisfies(&requirements, &constraints, &marker_env)? {
// If the requirements are already satisfied, we're done.
SatisfiesResult::Fresh {
recursive_requirements,
@ -1271,7 +1271,7 @@ pub(crate) async fn update_environment(
reinstall,
upgrade,
Some(tags),
ResolverMarkers::specific_environment(markers.clone()),
ResolverEnvironment::specific(marker_env.clone()),
python_requirement,
&client,
&flat_index,

View file

@ -1009,7 +1009,7 @@ fn can_skip_ephemeral(
match site_packages.satisfies(
&spec.requirements,
&spec.constraints,
&base_interpreter.resolver_markers(),
&base_interpreter.resolver_marker_environment(),
) {
// If the requirements are already satisfied, we're done.
Ok(SatisfiesResult::Fresh {

View file

@ -277,12 +277,15 @@ pub(super) async fn do_sync(
}
// Determine the markers to use for resolution.
let markers = venv.interpreter().resolver_markers();
let marker_env = venv.interpreter().resolver_marker_environment();
// Validate that the platform is supported by the lockfile.
let environments = target.lock().supported_environments();
if !environments.is_empty() {
if !environments.iter().any(|env| env.evaluate(&markers, &[])) {
if !environments
.iter()
.any(|env| env.evaluate(&marker_env, &[]))
{
return Err(ProjectError::LockedPlatformIncompatibility(
// For error reporting, we use the "simplified"
// supported environments, because these correspond to
@ -304,8 +307,14 @@ pub(super) async fn do_sync(
let tags = venv.interpreter().tags()?;
// Read the lockfile.
let resolution =
target.to_resolution(&markers, tags, extras, dev, build_options, &install_options)?;
let resolution = target.to_resolution(
&marker_env,
tags,
extras,
dev,
build_options,
&install_options,
)?;
// Always skip virtual projects, which shouldn't be built or installed.
let resolution = apply_no_virtual_project(resolution);

View file

@ -540,7 +540,7 @@ async fn get_or_create_environment(
site_packages.satisfies(
&requirements,
&constraints,
&interpreter.resolver_markers()
&interpreter.resolver_marker_environment()
),
Ok(SatisfiesResult::Fresh { .. })
) {