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

View file

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

View file

@ -147,7 +147,7 @@ impl Interpreter {
} }
/// Return the [`ResolverMarkerEnvironment`] for this Python executable. /// 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()) ResolverMarkerEnvironment::from(self.markers().clone())
} }

View file

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

View file

@ -15,7 +15,7 @@ use crate::preferences::Preferences;
use crate::prerelease::{AllowPrerelease, PrereleaseStrategy}; use crate::prerelease::{AllowPrerelease, PrereleaseStrategy};
use crate::resolution_mode::ResolutionStrategy; use crate::resolution_mode::ResolutionStrategy;
use crate::version_map::{VersionMap, VersionMapDistHandle}; use crate::version_map::{VersionMap, VersionMapDistHandle};
use crate::{Exclusions, Manifest, Options, ResolverMarkers}; use crate::{Exclusions, Manifest, Options, ResolverEnvironment};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(clippy::struct_field_names)] #[allow(clippy::struct_field_names)]
@ -30,19 +30,19 @@ impl CandidateSelector {
pub(crate) fn for_resolution( pub(crate) fn for_resolution(
options: Options, options: Options,
manifest: &Manifest, manifest: &Manifest,
markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Self { ) -> Self {
Self { Self {
resolution_strategy: ResolutionStrategy::from_mode( resolution_strategy: ResolutionStrategy::from_mode(
options.resolution_mode, options.resolution_mode,
manifest, manifest,
markers, env,
options.dependency_mode, options.dependency_mode,
), ),
prerelease_strategy: PrereleaseStrategy::from_mode( prerelease_strategy: PrereleaseStrategy::from_mode(
options.prerelease_mode, options.prerelease_mode,
manifest, manifest,
markers, env,
options.dependency_mode, options.dependency_mode,
), ),
index_strategy: options.index_strategy, index_strategy: options.index_strategy,
@ -80,7 +80,7 @@ impl CandidateSelector {
preferences: &'a Preferences, preferences: &'a Preferences,
installed_packages: &'a InstalledPackages, installed_packages: &'a InstalledPackages,
exclusions: &'a Exclusions, exclusions: &'a Exclusions,
markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Option<Candidate<'a>> { ) -> Option<Candidate<'a>> {
let is_excluded = exclusions.contains(package_name); let is_excluded = exclusions.contains(package_name);
@ -93,7 +93,7 @@ impl CandidateSelector {
preferences, preferences,
installed_packages, installed_packages,
is_excluded, is_excluded,
markers, env,
) { ) {
trace!("Using preference {} {}", preferred.name, preferred.version); trace!("Using preference {} {}", preferred.name, preferred.version);
return Some(preferred); 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 /// If the package has a preference, an existing version from an existing lockfile or a version
@ -131,26 +131,16 @@ impl CandidateSelector {
preferences: &'a Preferences, preferences: &'a Preferences,
installed_packages: &'a InstalledPackages, installed_packages: &'a InstalledPackages,
is_excluded: bool, is_excluded: bool,
resolver_markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Option<Candidate> { ) -> Option<Candidate> {
// In the branches, we "sort" the preferences by marker-matching through an iterator that // In the branches, we "sort" the preferences by marker-matching through an iterator that
// first has the matching half and then the mismatching half. // first has the matching half and then the mismatching half.
match resolver_markers { let preferences_match = preferences.get(package_name).filter(|(marker, _version)| {
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. // `.unwrap_or(true)` because the universal marker is considered matching.
marker marker.map(|marker| env.included(marker)).unwrap_or(true)
.map(|marker| marker.evaluate(env, &[]))
.unwrap_or(true)
}); });
let preferences_mismatch = let preferences_mismatch = preferences.get(package_name).filter(|(marker, _version)| {
preferences.get(package_name).filter(|(marker, _version)| { marker.map(|marker| !env.included(marker)).unwrap_or(false)
marker
.map(|marker| !marker.evaluate(env, &[]))
.unwrap_or(false)
}); });
self.get_preferred_from_iter( self.get_preferred_from_iter(
preferences_match.chain(preferences_mismatch), preferences_match.chain(preferences_mismatch),
@ -159,47 +149,9 @@ impl CandidateSelector {
version_maps, version_maps,
installed_packages, installed_packages,
is_excluded, is_excluded,
resolver_markers, env,
) )
} }
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,
)
}
}
}
/// Return the first preference that satisfies the current range and is allowed. /// Return the first preference that satisfies the current range and is allowed.
fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>( fn get_preferred_from_iter<'a, InstalledPackages: InstalledPackagesProvider>(
@ -210,7 +162,7 @@ impl CandidateSelector {
version_maps: &'a [VersionMap], version_maps: &'a [VersionMap],
installed_packages: &'a InstalledPackages, installed_packages: &'a InstalledPackages,
is_excluded: bool, is_excluded: bool,
resolver_markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Option<Candidate<'a>> { ) -> Option<Candidate<'a>> {
for (marker, version) in preferences { for (marker, version) in preferences {
// Respect the version range for this requirement. // Respect the version range for this requirement.
@ -247,10 +199,7 @@ impl CandidateSelector {
// Respect the pre-release strategy for this fork. // Respect the pre-release strategy for this fork.
if version.any_prerelease() { if version.any_prerelease() {
let allow = match self let allow = match self.prerelease_strategy.allows(package_name, env) {
.prerelease_strategy
.allows(package_name, resolver_markers)
{
AllowPrerelease::Yes => true, AllowPrerelease::Yes => true,
AllowPrerelease::No => false, AllowPrerelease::No => false,
// If the pre-release is "global" (i.e., provided via a lockfile, rather than // If the pre-release is "global" (i.e., provided via a lockfile, rather than
@ -321,7 +270,7 @@ impl CandidateSelector {
package_name: &'a PackageName, package_name: &'a PackageName,
range: &Range<Version>, range: &Range<Version>,
version_maps: &'a [VersionMap], version_maps: &'a [VersionMap],
markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Option<Candidate> { ) -> Option<Candidate> {
trace!( trace!(
"Selecting candidate for {package_name} with range {range} with {} remote versions", "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 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::Yes => true,
AllowPrerelease::No => false, AllowPrerelease::No => false,
// Allow pre-releases if there are no stable versions available. // 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::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
use crate::python_requirement::PythonRequirement; use crate::python_requirement::PythonRequirement;
use crate::resolution::ConflictingDistributionError; use crate::resolution::ConflictingDistributionError;
use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason}; use crate::resolver::{
IncompletePackage, ResolverEnvironment, UnavailablePackage, UnavailableReason,
};
use crate::Options; use crate::Options;
use tracing::trace; use tracing::trace;
use uv_distribution_types::{ use uv_distribution_types::{
@ -20,7 +22,6 @@ use uv_distribution_types::{
}; };
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::MarkerTree;
use uv_static::EnvVars; use uv_static::EnvVars;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
@ -40,24 +41,34 @@ pub enum ResolveError {
#[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")] #[error("Overrides contain conflicting URLs for package `{0}`:\n- {1}\n- {2}")]
ConflictingOverrideUrls(PackageName, String, String), ConflictingOverrideUrls(PackageName, String, String),
#[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))] #[error(
ConflictingUrlsUniversal(PackageName, Vec<String>), "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
if env.marker_environment().is_some() {
#[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers:?}`:\n- {}", urls.join("\n- "))] String::new()
ConflictingUrlsFork { } else {
format!(" in {env}")
},
urls.join("\n- "),
)]
ConflictingUrls {
package_name: PackageName, package_name: PackageName,
urls: Vec<String>, urls: Vec<String>,
fork_markers: MarkerTree, env: ResolverEnvironment,
}, },
#[error("Requirements contain conflicting indexes for package `{0}`:\n- {}", _1.join("\n- "))] #[error(
ConflictingIndexesUniversal(PackageName, Vec<String>), "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
if env.marker_environment().is_some() {
#[error("Requirements contain conflicting indexes for package `{package_name}` in split `{fork_markers:?}`:\n- {}", indexes.join("\n- "))] String::new()
ConflictingIndexesFork { } else {
format!(" in {env}")
},
indexes.join("\n- "),
)]
ConflictingIndexesForEnvironment {
package_name: PackageName, package_name: PackageName,
indexes: Vec<String>, indexes: Vec<String>,
fork_markers: MarkerTree, env: ResolverEnvironment,
}, },
#[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")] #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
@ -127,7 +138,7 @@ pub struct NoSolutionError {
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
fork_urls: ForkUrls, fork_urls: ForkUrls,
markers: ResolverMarkers, env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>, workspace_members: BTreeSet<PackageName>,
options: Options, options: Options,
} }
@ -145,7 +156,7 @@ impl NoSolutionError {
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>, unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>, incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
fork_urls: ForkUrls, fork_urls: ForkUrls,
markers: ResolverMarkers, env: ResolverEnvironment,
workspace_members: BTreeSet<PackageName>, workspace_members: BTreeSet<PackageName>,
options: Options, options: Options,
) -> Self { ) -> Self {
@ -160,7 +171,7 @@ impl NoSolutionError {
unavailable_packages, unavailable_packages,
incomplete_packages, incomplete_packages,
fork_urls, fork_urls,
markers, env,
workspace_members, workspace_members,
options, options,
} }
@ -212,7 +223,7 @@ impl NoSolutionError {
/// Initialize a [`NoSolutionHeader`] for this error. /// Initialize a [`NoSolutionHeader`] for this error.
pub fn header(&self) -> NoSolutionHeader { 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.unavailable_packages,
&self.incomplete_packages, &self.incomplete_packages,
&self.fork_urls, &self.fork_urls,
&self.markers, &self.env,
&self.workspace_members, &self.workspace_members,
self.options, self.options,
&mut additional_hints, &mut additional_hints,
@ -689,19 +700,16 @@ fn drop_root_dependency_on_project(
#[derive(Debug)] #[derive(Debug)]
pub struct NoSolutionHeader { pub struct NoSolutionHeader {
/// The [`ResolverMarkers`] that caused the failure. /// The [`ResolverEnvironment`] that caused the failure.
markers: ResolverMarkers, env: ResolverEnvironment,
/// The additional context for the resolution failure. /// The additional context for the resolution failure.
context: Option<&'static str>, context: Option<&'static str>,
} }
impl NoSolutionHeader { impl NoSolutionHeader {
/// Create a new [`NoSolutionHeader`] with the given [`ResolverMarkers`]. /// Create a new [`NoSolutionHeader`] with the given [`ResolverEnvironment`].
pub fn new(markers: ResolverMarkers) -> Self { pub fn new(env: ResolverEnvironment) -> Self {
Self { Self { env, context: None }
markers,
context: None,
}
} }
/// Set the context for the resolution failure. /// Set the context for the resolution failure.
@ -714,30 +722,20 @@ impl NoSolutionHeader {
impl std::fmt::Display for NoSolutionHeader { impl std::fmt::Display for NoSolutionHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.markers { match (self.context, self.env.end_user_fork_display()) {
ResolverMarkers::SpecificEnvironment(_) | ResolverMarkers::Universal { .. } => { (None, None) => write!(f, "No solution found when resolving dependencies:"),
if let Some(context) = self.context { (Some(context), None) => write!(
write!(
f, f,
"No solution found when resolving {context} dependencies:" "No solution found when resolving {context} dependencies:"
) ),
} else { (None, Some(split)) => write!(
write!(f, "No solution found when resolving dependencies:")
}
}
ResolverMarkers::Fork(markers) => {
if let Some(context) = self.context {
write!(
f, f,
"No solution found when resolving {context} dependencies for split ({markers:?}):", "No solution found when resolving dependencies for {split}:"
) ),
} else { (Some(context), Some(split)) => write!(
write!(
f, f,
"No solution found when resolving dependencies for split ({markers:?}):", "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_distribution_types::IndexUrl;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use crate::resolver::ResolverMarkers; use crate::resolver::ResolverEnvironment;
use crate::ResolveError; use crate::ResolveError;
/// See [`crate::resolver::ForkState`]. /// See [`crate::resolver::ForkState`].
@ -20,27 +20,17 @@ impl ForkIndexes {
&mut self, &mut self,
package_name: &PackageName, package_name: &PackageName,
index: &IndexUrl, index: &IndexUrl,
fork_markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Result<(), ResolveError> { ) -> Result<(), ResolveError> {
if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) { if let Some(previous) = self.0.insert(package_name.clone(), index.clone()) {
if &previous != index { if &previous != index {
let mut conflicts = vec![previous.to_string(), index.to_string()]; let mut conflicts = vec![previous.to_string(), index.to_string()];
conflicts.sort(); conflicts.sort();
return match fork_markers { return Err(ResolveError::ConflictingIndexesForEnvironment {
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => {
Err(ResolveError::ConflictingIndexesUniversal(
package_name.clone(),
conflicts,
))
}
ResolverMarkers::Fork(fork_markers) => {
Err(ResolveError::ConflictingIndexesFork {
package_name: package_name.clone(), package_name: package_name.clone(),
indexes: conflicts, indexes: conflicts,
fork_markers: fork_markers.clone(), env: env.clone(),
}) });
}
};
} }
} }
Ok(()) Ok(())

View file

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

View file

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

View file

@ -9,7 +9,7 @@ use uv_pypi_types::Requirement;
use uv_types::RequestedRequirements; use uv_types::RequestedRequirements;
use crate::preferences::Preferences; use crate::preferences::Preferences;
use crate::{DependencyMode, Exclusions, ResolverMarkers}; use crate::{DependencyMode, Exclusions, ResolverEnvironment};
/// A manifest of requirements, constraints, and preferences. /// A manifest of requirements, constraints, and preferences.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -109,57 +109,55 @@ impl Manifest {
/// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`). /// - Determining which requirements should allow local version specifiers (e.g., `torch==2.2.0+cpu`).
pub fn requirements<'a>( pub fn requirements<'a>(
&'a self, &'a self,
markers: &'a ResolverMarkers, env: &'a ResolverEnvironment,
mode: DependencyMode, mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
self.requirements_no_overrides(markers, mode) self.requirements_no_overrides(env, mode)
.chain(self.overrides(markers, mode)) .chain(self.overrides(env, mode))
} }
/// Like [`Self::requirements`], but without the overrides. /// Like [`Self::requirements`], but without the overrides.
pub fn requirements_no_overrides<'a>( pub fn requirements_no_overrides<'a>(
&'a self, &'a self,
markers: &'a ResolverMarkers, env: &'a ResolverEnvironment,
mode: DependencyMode, mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode { match mode {
// Include all direct and transitive requirements, with constraints and overrides applied. // Include all direct and transitive requirements, with constraints and overrides applied.
DependencyMode::Transitive => { DependencyMode::Transitive => Either::Left(
Either::Left(
self.lookaheads self.lookaheads
.iter() .iter()
.flat_map(move |lookahead| { .flat_map(move |lookahead| {
self.overrides.apply(lookahead.requirements()).filter( self.overrides
move |requirement| { .apply(lookahead.requirements())
requirement.evaluate_markers( .filter(move |requirement| {
markers.marker_environment(), requirement
lookahead.extras(), .evaluate_markers(env.marker_environment(), lookahead.extras())
)
},
)
}) })
.chain(self.overrides.apply(&self.requirements).filter( })
move |requirement| { .chain(
requirement.evaluate_markers(markers.marker_environment(), &[]) self.overrides
}, .apply(&self.requirements)
)) .filter(move |requirement| {
requirement.evaluate_markers(env.marker_environment(), &[])
}),
)
.chain( .chain(
self.constraints self.constraints
.requirements() .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[]) requirement.evaluate_markers(env.marker_environment(), &[])
}) })
.map(Cow::Borrowed), .map(Cow::Borrowed),
), ),
) ),
}
// Include direct requirements, with constraints and overrides applied. // Include direct requirements, with constraints and overrides applied.
DependencyMode::Direct => Either::Right( DependencyMode::Direct => Either::Right(
self.overrides self.overrides
.apply(&self.requirements) .apply(&self.requirements)
.chain(self.constraints.requirements().map(Cow::Borrowed)) .chain(self.constraints.requirements().map(Cow::Borrowed))
.filter(move |requirement| { .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`]. /// Only the overrides from [`Self::requirements`].
pub fn overrides<'a>( pub fn overrides<'a>(
&'a self, &'a self,
markers: &'a ResolverMarkers, env: &'a ResolverEnvironment,
mode: DependencyMode, mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode { match mode {
@ -177,7 +175,7 @@ impl Manifest {
self.overrides self.overrides
.requirements() .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[]) requirement.evaluate_markers(env.marker_environment(), &[])
}) })
.map(Cow::Borrowed), .map(Cow::Borrowed),
), ),
@ -186,7 +184,7 @@ impl Manifest {
self.overrides self.overrides
.requirements() .requirements()
.filter(move |requirement| { .filter(move |requirement| {
requirement.evaluate_markers(markers.marker_environment(), &[]) requirement.evaluate_markers(env.marker_environment(), &[])
}) })
.map(Cow::Borrowed), .map(Cow::Borrowed),
), ),
@ -205,41 +203,37 @@ impl Manifest {
/// the `lowest-direct` strategy is in use. /// the `lowest-direct` strategy is in use.
pub fn user_requirements<'a>( pub fn user_requirements<'a>(
&'a self, &'a self,
markers: &'a ResolverMarkers, env: &'a ResolverEnvironment,
mode: DependencyMode, mode: DependencyMode,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
match mode { match mode {
// Include direct requirements, dependencies of editables, and transitive dependencies // Include direct requirements, dependencies of editables, and transitive dependencies
// of local packages. // of local packages.
DependencyMode::Transitive => { DependencyMode::Transitive => Either::Left(
Either::Left(
self.lookaheads self.lookaheads
.iter() .iter()
.filter(|lookahead| lookahead.direct()) .filter(|lookahead| lookahead.direct())
.flat_map(move |lookahead| { .flat_map(move |lookahead| {
self.overrides.apply(lookahead.requirements()).filter( self.overrides
move |requirement| { .apply(lookahead.requirements())
requirement.evaluate_markers( .filter(move |requirement| {
markers.marker_environment(), requirement
lookahead.extras(), .evaluate_markers(env.marker_environment(), lookahead.extras())
)
},
)
}) })
.chain(self.overrides.apply(&self.requirements).filter( })
move |requirement| { .chain(
requirement.evaluate_markers(markers.marker_environment(), &[]) self.overrides
}, .apply(&self.requirements)
)), .filter(move |requirement| {
) requirement.evaluate_markers(env.marker_environment(), &[])
} }),
),
),
// Restrict to the direct requirements. // Restrict to the direct requirements.
DependencyMode::Direct => { DependencyMode::Direct => {
Either::Right(self.overrides.apply(self.requirements.iter()).filter( Either::Right(self.overrides.apply(self.requirements.iter()).filter(
move |requirement| { move |requirement| requirement.evaluate_markers(env.marker_environment(), &[]),
requirement.evaluate_markers(markers.marker_environment(), &[])
},
)) ))
} }
} }
@ -252,13 +246,11 @@ impl Manifest {
/// resolution (assuming the user enabled development dependencies). /// resolution (assuming the user enabled development dependencies).
pub fn direct_requirements<'a>( pub fn direct_requirements<'a>(
&'a self, &'a self,
markers: &'a ResolverMarkers, env: &'a ResolverEnvironment,
) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a { ) -> impl Iterator<Item = Cow<'a, Requirement>> + 'a {
self.overrides self.overrides
.apply(self.requirements.iter()) .apply(self.requirements.iter())
.filter(move |requirement| { .filter(move |requirement| requirement.evaluate_markers(env.marker_environment(), &[]))
requirement.evaluate_markers(markers.marker_environment(), &[])
})
} }
/// Apply the overrides and constraints to a set of requirements. /// 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_pypi_types::{HashDigest, HashError};
use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement}; use uv_requirements_txt::{RequirementEntry, RequirementsTxtRequirement};
use crate::ResolverMarkers; use crate::ResolverEnvironment;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum PreferenceError { pub enum PreferenceError {
@ -119,16 +119,16 @@ pub struct Preferences(FxHashMap<PackageName, Vec<(Option<MarkerTree>, Pin)>>);
impl Preferences { impl Preferences {
/// Create a map of pinned packages from an iterator of [`Preference`] entries. /// 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. /// to an applicable subset.
pub fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>( pub fn from_iter<PreferenceIterator: IntoIterator<Item = Preference>>(
preferences: PreferenceIterator, preferences: PreferenceIterator,
markers: &ResolverMarkers, env: &ResolverEnvironment,
) -> Self { ) -> Self {
let mut slf = Self::default(); let mut slf = Self::default();
for preference in preferences { for preference in preferences {
// Filter non-matching preferences when resolving for an environment. // 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, &[]) { if !preference.marker.evaluate(markers, &[]) {
trace!("Excluding {preference} from preferences due to unmatched markers"); trace!("Excluding {preference} from preferences due to unmatched markers");
continue; continue;

View file

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

View file

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

View file

@ -1,5 +1,5 @@
use uv_pep440::Version; use uv_pep440::Version;
use uv_pep508::MarkerTree; use uv_pep508::{MarkerEnvironment, MarkerTree};
use uv_python::{Interpreter, PythonVersion}; use uv_python::{Interpreter, PythonVersion};
use crate::{RequiresPython, RequiresPythonRange}; use crate::{RequiresPython, RequiresPythonRange};
@ -38,14 +38,7 @@ impl PythonRequirement {
interpreter: &Interpreter, interpreter: &Interpreter,
requires_python: RequiresPython, requires_python: RequiresPython,
) -> Self { ) -> Self {
let exact = interpreter.python_full_version().version.clone(); Self::from_marker_environment(interpreter.markers(), requires_python)
let installed = interpreter.python_full_version().version.only_release();
Self {
exact,
installed: RequiresPython::greater_than_equal_version(&installed),
target: requires_python,
source: PythonRequirementSource::RequiresPython,
}
} }
/// Create a [`PythonRequirement`] to resolve against an [`Interpreter`]. /// 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) /// Narrow the [`PythonRequirement`] to the given version, if it's stricter (i.e., greater)
/// than the current `Requires-Python` minimum. /// than the current `Requires-Python` minimum.
pub fn narrow(&self, target: &RequiresPythonRange) -> Option<Self> { pub fn narrow(&self, target: &RequiresPythonRange) -> Option<Self> {

View file

@ -10,7 +10,7 @@ use uv_normalize::PackageName;
use uv_pep508::MarkerTree; use uv_pep508::MarkerTree;
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode}; use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode};
use crate::{ResolutionGraph, ResolverMarkers}; use crate::{ResolutionGraph, ResolverEnvironment};
/// A [`std::fmt::Display`] implementation for the resolution graph. /// A [`std::fmt::Display`] implementation for the resolution graph.
#[derive(Debug)] #[derive(Debug)]
@ -18,8 +18,8 @@ use crate::{ResolutionGraph, ResolverMarkers};
pub struct DisplayResolutionGraph<'a> { pub struct DisplayResolutionGraph<'a> {
/// The underlying graph. /// The underlying graph.
resolution: &'a ResolutionGraph, resolution: &'a ResolutionGraph,
/// The marker environment, used to determine the markers that apply to each package. /// The resolver marker environment, used to determine the markers that apply to each package.
marker_env: &'a ResolverMarkers, env: &'a ResolverEnvironment,
/// The packages to exclude from the output. /// The packages to exclude from the output.
no_emit_packages: &'a [PackageName], no_emit_packages: &'a [PackageName],
/// Whether to include hashes in the output. /// Whether to include hashes in the output.
@ -58,7 +58,7 @@ impl<'a> DisplayResolutionGraph<'a> {
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub fn new( pub fn new(
underlying: &'a ResolutionGraph, underlying: &'a ResolutionGraph,
marker_env: &'a ResolverMarkers, env: &'a ResolverEnvironment,
no_emit_packages: &'a [PackageName], no_emit_packages: &'a [PackageName],
show_hashes: bool, show_hashes: bool,
include_extras: bool, include_extras: bool,
@ -69,7 +69,7 @@ impl<'a> DisplayResolutionGraph<'a> {
) -> DisplayResolutionGraph<'a> { ) -> DisplayResolutionGraph<'a> {
Self { Self {
resolution: underlying, resolution: underlying,
marker_env, env,
no_emit_packages, no_emit_packages,
show_hashes, show_hashes,
include_extras, include_extras,
@ -89,7 +89,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
let mut sources = SourceAnnotations::default(); let mut sources = SourceAnnotations::default();
for requirement in self.resolution.requirements.iter().filter(|requirement| { 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 { if let Some(origin) = &requirement.origin {
sources.add( sources.add(
@ -104,7 +104,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.constraints .constraints
.requirements() .requirements()
.filter(|requirement| { .filter(|requirement| {
requirement.evaluate_markers(self.marker_env.marker_environment(), &[]) requirement.evaluate_markers(self.env.marker_environment(), &[])
}) })
{ {
if let Some(origin) = &requirement.origin { if let Some(origin) = &requirement.origin {
@ -120,7 +120,7 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.overrides .overrides
.requirements() .requirements()
.filter(|requirement| { .filter(|requirement| {
requirement.evaluate_markers(self.marker_env.marker_environment(), &[]) requirement.evaluate_markers(self.env.marker_environment(), &[])
}) })
{ {
if let Some(origin) = &requirement.origin { 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::resolver::{Resolution, ResolutionDependencyEdge, ResolutionPackage};
use crate::{ use crate::{
InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError, InMemoryIndex, MetadataResponse, Options, PythonRequirement, RequiresPython, ResolveError,
ResolverMarkers, VersionsResponse, VersionsResponse,
}; };
pub(crate) type MarkersForDistribution = Vec<MarkerTree>; pub(crate) type MarkersForDistribution = Vec<MarkerTree>;
@ -124,7 +124,7 @@ impl ResolutionGraph {
if package.is_base() { if package.is_base() {
// For packages with diverging versions, store which version comes from which // For packages with diverging versions, store which version comes from which
// fork. // fork.
if let Some(markers) = resolution.markers.fork_markers() { if let Some(markers) = resolution.env.try_markers() {
package_markers package_markers
.entry(package.name.clone()) .entry(package.name.clone())
.or_default() .or_default()
@ -152,11 +152,7 @@ impl ResolutionGraph {
let mut seen = FxHashSet::default(); let mut seen = FxHashSet::default();
for resolution in resolutions { for resolution in resolutions {
let marker = resolution let marker = resolution.env.try_markers().cloned().unwrap_or_default();
.markers
.fork_markers()
.cloned()
.unwrap_or_default();
// Add every edge to the graph, propagating the marker for the current fork, if // Add every edge to the graph, propagating the marker for the current fork, if
// necessary. // necessary.
@ -180,32 +176,31 @@ impl ResolutionGraph {
let requires_python = python.target().clone(); let requires_python = python.target().clone();
let fork_markers = if let [resolution] = resolutions { let fork_markers = if let [resolution] = resolutions {
match resolution.markers { resolution
ResolverMarkers::Universal { .. } | ResolverMarkers::SpecificEnvironment(_) => { .env
vec![] .try_markers()
} .map(|_| {
ResolverMarkers::Fork(_) => {
resolutions resolutions
.iter() .iter()
.map(|resolution| { .map(|resolution| {
resolution resolution
.markers .env
.fork_markers() .try_markers()
.expect("A non-forking resolution exists in forking mode") .expect("A non-forking resolution exists in forking mode")
.clone() .clone()
}) })
// Any unsatisfiable forks were skipped. // Any unsatisfiable forks were skipped.
.filter(|fork| !fork.is_false()) .filter(|fork| !fork.is_false())
.collect() .collect()
} })
} .unwrap_or_else(Vec::new)
} else { } else {
resolutions resolutions
.iter() .iter()
.map(|resolution| { .map(|resolution| {
resolution resolution
.markers .env
.fork_markers() .try_markers()
.expect("A non-forking resolution exists in forking mode") .expect("A non-forking resolution exists in forking mode")
.clone() .clone()
}) })

View file

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

View file

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

View file

@ -2,7 +2,7 @@ use rustc_hash::FxHashMap;
use uv_pep508::{MarkerTree, PackageName}; use uv_pep508::{MarkerTree, PackageName};
use uv_pypi_types::Requirement; use uv_pypi_types::Requirement;
use crate::ResolverMarkers; use crate::ResolverEnvironment;
/// A set of package names associated with a given fork. /// A set of package names associated with a given fork.
pub(crate) type ForkSet = ForkMap<()>; 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 /// Returns `true` if the map contains any values for a package that are compatible with the
/// given fork. /// given fork.
pub(crate) fn contains(&self, package_name: &PackageName, markers: &ResolverMarkers) -> bool { pub(crate) fn contains(&self, package_name: &PackageName, env: &ResolverEnvironment) -> bool {
!self.get(package_name, markers).is_empty() !self.get(package_name, env).is_empty()
} }
/// Returns `true` if the map contains any values for a package. /// 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 /// 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 /// 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. /// 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 { let Some(values) = self.0.get(package_name) else {
return Vec::new(); return Vec::new();
}; };
values
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() .iter()
.filter(|entry| !fork.is_disjoint(&entry.marker)) .filter(|entry| env.included(&entry.marker))
.map(|entry| &entry.value) .map(|entry| &entry.value)
.collect(), .collect()
}
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,7 +64,7 @@ pub(crate) fn pip_freeze(
// Validate that the environment is consistent. // Validate that the environment is consistent.
if strict { if strict {
// Determine the markers to use for resolution. // 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)? { for diagnostic in site_packages.diagnostics(&markers)? {
writeln!( writeln!(

View file

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

View file

@ -112,7 +112,7 @@ pub(crate) fn pip_list(
// Validate that the environment is consistent. // Validate that the environment is consistent.
if strict { if strict {
// Determine the markers to use for resolution. // 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)? { for diagnostic in site_packages.diagnostics(&markers)? {
writeln!( writeln!(

View file

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

View file

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

View file

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

View file

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

View file

@ -59,7 +59,7 @@ pub(crate) fn pip_tree(
}; };
// Determine the markers to use for the resolution. // Determine the markers to use for the resolution.
let markers = environment.interpreter().resolver_markers(); let markers = environment.interpreter().resolver_marker_environment();
// Render the tree. // Render the tree.
let rendered_tree = DisplayDependencyGraph::new( 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_requirements::ExtrasResolver;
use uv_resolver::{ use uv_resolver::{
FlatIndex, InMemoryIndex, Lock, LockVersion, Options, OptionsBuilder, PythonRequirement, 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_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once}; 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 // `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 // 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. // 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 forks_lock
.map(|lock| lock.fork_markers().to_vec()) .map(|lock| lock.fork_markers().to_vec())
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -633,7 +633,7 @@ async fn do_lock(
&Reinstall::default(), &Reinstall::default(),
upgrade, upgrade,
None, None,
resolver_markers, resolver_env,
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,

View file

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

View file

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

View file

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

View file

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