From a6dfd3953a6f8b15140777f34a708478131408c6 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 17 Jul 2024 18:59:33 +0200 Subject: [PATCH] Handle universal vs. fork markers with `ResolverMarkers` (#5099) * Use a dedicated `ResolverMarkers` check in the fork state. This is better than the `MarkerTree::And(Vec::new())` check. * Report the timing correct naming universal resolution instead of two spaces around an empty string when there are no markers. * On resolution error, show the split that we're in. I'm not sure how to word this, since we're doing a universal resolution until we fork, so the trace may contain information from requirements that are not part of this fork. --- crates/bench/benches/uv.rs | 4 +- crates/uv-dispatch/src/lib.rs | 3 +- crates/uv-requirements/src/lookahead.rs | 11 +- crates/uv-resolver/src/error.rs | 20 ++- crates/uv-resolver/src/fork_urls.rs | 29 ++-- crates/uv-resolver/src/lib.rs | 2 +- crates/uv-resolver/src/resolution/display.rs | 28 ++-- crates/uv-resolver/src/resolver/mod.rs | 134 ++++++++++-------- .../src/resolver/resolver_markers.rs | 50 +++++++ crates/uv-resolver/src/resolver/urls.rs | 2 +- crates/uv/src/commands/pip/compile.rs | 18 +-- crates/uv/src/commands/pip/install.rs | 7 +- crates/uv/src/commands/pip/operations.rs | 9 +- crates/uv/src/commands/pip/sync.rs | 7 +- crates/uv/src/commands/project/lock.rs | 11 +- crates/uv/src/commands/project/mod.rs | 8 +- crates/uv/src/commands/project/sync.rs | 6 +- crates/uv/tests/lock_scenarios.rs | 2 +- crates/uv/tests/pip_compile.rs | 2 +- 19 files changed, 220 insertions(+), 133 deletions(-) create mode 100644 crates/uv-resolver/src/resolver/resolver_markers.rs diff --git a/crates/bench/benches/uv.rs b/crates/bench/benches/uv.rs index a7f9aaf359b4..8f39edddc681 100644 --- a/crates/bench/benches/uv.rs +++ b/crates/bench/benches/uv.rs @@ -90,7 +90,7 @@ mod resolver { use uv_python::PythonEnvironment; use uv_resolver::{ FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, ResolutionGraph, - Resolver, + Resolver, ResolverMarkers, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -175,7 +175,7 @@ mod resolver { manifest, options, &python_requirement, - Some(&MARKERS), + ResolverMarkers::SpecificEnvironment(MARKERS.clone()), Some(&TAGS), &flat_index, &index, diff --git a/crates/uv-dispatch/src/lib.rs b/crates/uv-dispatch/src/lib.rs index c910c52abe4b..3928e63b9666 100644 --- a/crates/uv-dispatch/src/lib.rs +++ b/crates/uv-dispatch/src/lib.rs @@ -26,6 +26,7 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages}; use uv_python::{Interpreter, PythonEnvironment}; use uv_resolver::{ ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, + ResolverMarkers, }; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; @@ -146,7 +147,7 @@ impl<'a> BuildContext for BuildDispatch<'a> { .index_strategy(self.index_strategy) .build(), &python_requirement, - Some(markers), + ResolverMarkers::SpecificEnvironment(markers.clone()), Some(tags), self.flat_index, self.index, diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index d10c9df87405..fa7b48eba9be 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -7,13 +7,12 @@ use thiserror::Error; use tracing::trace; use distribution_types::{BuiltDist, Dist, DistributionMetadata, GitSourceDist, SourceDist}; -use pep508_rs::MarkerEnvironment; use pypi_types::{Requirement, RequirementSource}; use uv_configuration::{Constraints, Overrides}; use uv_distribution::{DistributionDatabase, Reporter}; use uv_git::GitUrl; use uv_normalize::GroupName; -use uv_resolver::{InMemoryIndex, MetadataResponse}; +use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverMarkers}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; #[derive(Debug, Error)] @@ -98,7 +97,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { /// to "only evaluate marker expressions that reference an extra name.") pub async fn resolve( self, - markers: Option<&MarkerEnvironment>, + markers: &ResolverMarkers, ) -> Result, LookaheadError> { let mut results = Vec::new(); let mut futures = FuturesUnordered::new(); @@ -108,7 +107,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { let mut queue: VecDeque<_> = self .constraints .apply(self.overrides.apply(self.requirements)) - .filter(|requirement| requirement.evaluate_markers(markers, &[])) + .filter(|requirement| requirement.evaluate_markers(markers.marker_environment(), &[])) .map(|requirement| (*requirement).clone()) .collect(); @@ -127,7 +126,9 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { .constraints .apply(self.overrides.apply(lookahead.requirements())) { - if requirement.evaluate_markers(markers, lookahead.extras()) { + if requirement + .evaluate_markers(markers.marker_environment(), lookahead.extras()) + { queue.push_back((*requirement).clone()); } } diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index be3fe6530a18..d869a7fd2996 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -18,7 +18,7 @@ use crate::pubgrub::{ PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError, }; use crate::python_requirement::PythonRequirement; -use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; +use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason}; #[derive(Debug, thiserror::Error)] pub enum ResolveError { @@ -50,10 +50,10 @@ pub enum ResolveError { ConflictingOverrideUrls(PackageName, String, String), #[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))] - ConflictingUrls(PackageName, Vec), + ConflictingUrlsUniversal(PackageName, Vec), #[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers}`:\n- {}", urls.join("\n- "))] - ConflictingUrlsInFork { + ConflictingUrlsFork { package_name: PackageName, urls: Vec, fork_markers: MarkerTree, @@ -125,9 +125,21 @@ pub struct NoSolutionError { unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, fork_urls: ForkUrls, + markers: ResolverMarkers, } impl NoSolutionError { + pub fn header(&self) -> String { + match &self.markers { + ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => { + "No solution found when resolving dependencies:".to_string() + } + ResolverMarkers::Fork(markers) => { + format!("No solution found when resolving dependencies for split ({markers}):") + } + } + } + pub(crate) fn new( error: pubgrub::error::NoSolutionError, available_versions: FxHashMap>, @@ -137,6 +149,7 @@ impl NoSolutionError { unavailable_packages: FxHashMap, incomplete_packages: FxHashMap>, fork_urls: ForkUrls, + markers: ResolverMarkers, ) -> Self { Self { error, @@ -147,6 +160,7 @@ impl NoSolutionError { unavailable_packages, incomplete_packages, fork_urls, + markers, } } diff --git a/crates/uv-resolver/src/fork_urls.rs b/crates/uv-resolver/src/fork_urls.rs index 675e421075be..f34c26de2cf8 100644 --- a/crates/uv-resolver/src/fork_urls.rs +++ b/crates/uv-resolver/src/fork_urls.rs @@ -3,10 +3,10 @@ use std::collections::hash_map::Entry; use rustc_hash::FxHashMap; use distribution_types::Verbatim; -use pep508_rs::MarkerTree; use pypi_types::VerbatimParsedUrl; use uv_normalize::PackageName; +use crate::resolver::ResolverMarkers; use crate::ResolveError; /// See [`crate::resolver::SolveState`]. @@ -29,7 +29,7 @@ impl ForkUrls { &mut self, package_name: &PackageName, url: &VerbatimParsedUrl, - fork_markers: &MarkerTree, + fork_markers: &ResolverMarkers, ) -> Result<(), ResolveError> { match self.0.entry(package_name.clone()) { Entry::Occupied(previous) => { @@ -39,17 +39,20 @@ impl ForkUrls { url.verbatim.verbatim().to_string(), ]; conflicting_url.sort(); - return if fork_markers.is_universal() { - Err(ResolveError::ConflictingUrls( - package_name.clone(), - conflicting_url, - )) - } else { - Err(ResolveError::ConflictingUrlsInFork { - package_name: package_name.clone(), - urls: conflicting_url, - fork_markers: fork_markers.clone(), - }) + return match fork_markers { + ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => { + Err(ResolveError::ConflictingUrlsUniversal( + package_name.clone(), + conflicting_url, + )) + } + ResolverMarkers::Fork(fork_markers) => { + Err(ResolveError::ConflictingUrlsFork { + package_name: package_name.clone(), + urls: conflicting_url, + fork_markers: fork_markers.clone(), + }) + } }; } } diff --git a/crates/uv-resolver/src/lib.rs b/crates/uv-resolver/src/lib.rs index 353305bd1472..db790c35a527 100644 --- a/crates/uv-resolver/src/lib.rs +++ b/crates/uv-resolver/src/lib.rs @@ -15,7 +15,7 @@ pub use resolution::{AnnotationStyle, DisplayResolutionGraph, ResolutionGraph}; pub use resolution_mode::ResolutionMode; pub use resolver::{ BuildId, DefaultResolverProvider, InMemoryIndex, MetadataResponse, PackageVersionsResult, - Reporter as ResolverReporter, Resolver, ResolverProvider, VersionsResponse, + Reporter as ResolverReporter, Resolver, ResolverMarkers, ResolverProvider, VersionsResponse, WheelMetadataResult, }; pub use version_map::VersionMap; diff --git a/crates/uv-resolver/src/resolution/display.rs b/crates/uv-resolver/src/resolution/display.rs index 6af1b0f09f54..cf579d5dc6f2 100644 --- a/crates/uv-resolver/src/resolution/display.rs +++ b/crates/uv-resolver/src/resolution/display.rs @@ -7,12 +7,13 @@ use petgraph::Direction; use rustc_hash::{FxBuildHasher, FxHashMap}; use distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations}; -use pep508_rs::MarkerEnvironment; use pep508_rs::MarkerTree; use uv_normalize::PackageName; use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode}; -use crate::{marker, ResolutionGraph}; +use crate::{marker, ResolutionGraph, ResolverMarkers}; + +static UNIVERSAL_MARKERS: ResolverMarkers = ResolverMarkers::Universal; /// A [`std::fmt::Display`] implementation for the resolution graph. #[derive(Debug)] @@ -21,7 +22,7 @@ pub struct DisplayResolutionGraph<'a> { /// The underlying graph. resolution: &'a ResolutionGraph, /// The marker environment, used to determine the markers that apply to each package. - marker_env: Option<&'a MarkerEnvironment>, + marker_env: &'a ResolverMarkers, /// The packages to exclude from the output. no_emit_packages: &'a [PackageName], /// Whether to include hashes in the output. @@ -50,7 +51,7 @@ impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> { fn from(resolution: &'a ResolutionGraph) -> Self { Self::new( resolution, - None, + &UNIVERSAL_MARKERS, &[], false, false, @@ -67,7 +68,7 @@ impl<'a> DisplayResolutionGraph<'a> { #[allow(clippy::fn_params_excessive_bools)] pub fn new( underlying: &'a ResolutionGraph, - marker_env: Option<&'a MarkerEnvironment>, + marker_env: &'a ResolverMarkers, no_emit_packages: &'a [PackageName], show_hashes: bool, include_extras: bool, @@ -97,12 +98,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { let sources = if self.include_annotations { let mut sources = SourceAnnotations::default(); - for requirement in self - .resolution - .requirements - .iter() - .filter(|requirement| requirement.evaluate_markers(self.marker_env, &[])) - { + for requirement in self.resolution.requirements.iter().filter(|requirement| { + requirement.evaluate_markers(self.marker_env.marker_environment(), &[]) + }) { if let Some(origin) = &requirement.origin { sources.add( &requirement.name, @@ -115,7 +113,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { .resolution .constraints .requirements() - .filter(|requirement| requirement.evaluate_markers(self.marker_env, &[])) + .filter(|requirement| { + requirement.evaluate_markers(self.marker_env.marker_environment(), &[]) + }) { if let Some(origin) = &requirement.origin { sources.add( @@ -129,7 +129,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { .resolution .overrides .requirements() - .filter(|requirement| requirement.evaluate_markers(self.marker_env, &[])) + .filter(|requirement| { + requirement.evaluate_markers(self.marker_env.marker_environment(), &[]) + }) { if let Some(origin) = &requirement.origin { sources.add( diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index dd8e724c8012..3d7a12c6cbd4 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -28,9 +28,10 @@ use distribution_types::{ }; pub(crate) use locals::ForkLocals; use pep440_rs::{Version, MIN_VERSION}; -use pep508_rs::{MarkerEnvironment, MarkerTree}; +use pep508_rs::MarkerTree; use platform_tags::Tags; use pypi_types::{Metadata23, Requirement, VerbatimParsedUrl}; +pub use resolver_markers::ResolverMarkers; pub(crate) use urls::Urls; use uv_configuration::{Constraints, Overrides}; use uv_distribution::{ArchiveMetadata, DistributionDatabase}; @@ -73,6 +74,7 @@ mod index; mod locals; mod provider; mod reporter; +mod resolver_markers; mod urls; pub struct Resolver { @@ -94,8 +96,7 @@ struct ResolverState { urls: Urls, dependency_mode: DependencyMode, hasher: HashStrategy, - /// When not set, the resolver is in "universal" mode. - markers: Option, + markers: ResolverMarkers, python_requirement: PythonRequirement, /// This is derived from `PythonRequirement` once at initialization /// time. It's used in universal mode to filter our dependencies with @@ -140,7 +141,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider> manifest: Manifest, options: Options, python_requirement: &'a PythonRequirement, - markers: Option<&'a MarkerEnvironment>, + markers: ResolverMarkers, tags: Option<&'a Tags>, flat_index: &'a FlatIndex, index: &'a InMemoryIndex, @@ -156,7 +157,11 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider> python_requirement .target() .and_then(|target| target.as_requires_python()), - AllowedYanks::from_manifest(&manifest, markers, options.dependency_mode), + AllowedYanks::from_manifest( + &manifest, + markers.marker_environment(), + options.dependency_mode, + ), hasher, options.exclude_newer, build_context.build_options(), @@ -184,7 +189,7 @@ impl manifest: Manifest, options: Options, hasher: &HashStrategy, - markers: Option<&MarkerEnvironment>, + markers: ResolverMarkers, python_requirement: &PythonRequirement, index: &InMemoryIndex, git: &GitResolver, @@ -196,9 +201,18 @@ impl git: git.clone(), unavailable_packages: DashMap::default(), incomplete_packages: DashMap::default(), - selector: CandidateSelector::for_resolution(options, &manifest, markers), + selector: CandidateSelector::for_resolution( + options, + &manifest, + markers.marker_environment(), + ), dependency_mode: options.dependency_mode, - urls: Urls::from_manifest(&manifest, markers, git, options.dependency_mode)?, + urls: Urls::from_manifest( + &manifest, + markers.marker_environment(), + git, + options.dependency_mode, + )?, project: manifest.project, requirements: manifest.requirements, constraints: manifest.constraints, @@ -207,12 +221,12 @@ impl preferences: manifest.preferences, exclusions: manifest.exclusions, hasher: hasher.clone(), - markers: markers.cloned(), - requires_python: if markers.is_some() { + requires_python: if markers.marker_environment().is_some() { None } else { python_requirement.to_marker_tree() }, + markers, python_requirement: python_requirement.clone(), reporter: None, installed_packages, @@ -296,7 +310,7 @@ impl ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState ResolverState, ) -> Result { let result = self.get_dependencies( @@ -1094,13 +1104,13 @@ impl ResolverState result.map(|deps| match deps { Dependencies::Available(deps) => ForkedDependencies::Unforked(deps), Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err), - }); + }), + ResolverMarkers::Universal | ResolverMarkers::Fork(_) => Ok(result?.fork()), } - Ok(result?.fork()) } /// Given a candidate package and version, return its dependencies. @@ -1111,7 +1121,7 @@ impl ResolverState, ) -> Result { let url = package.name().and_then(|name| fork_urls.get(name)); @@ -1383,7 +1393,7 @@ impl ResolverState, dev: Option<&'a GroupName>, name: Option<&PackageName>, - markers: &'a MarkerTree, + markers: &'a ResolverMarkers, requires_python: Option<&'a MarkerTree>, ) -> Vec> { // Start with the requirements for the current extra of the package (for an extra @@ -1448,7 +1458,7 @@ impl ResolverState + 'parameters, extra: Option<&'parameters ExtraName>, - markers: &'parameters MarkerTree, + markers: &'parameters ResolverMarkers, requires_python: Option<&'parameters MarkerTree>, ) -> impl Iterator> + 'parameters where @@ -1469,31 +1479,31 @@ impl ResolverState { // Only include requirements that are relevant for the current extra. - if requirement.evaluate_markers(self.markers.as_ref(), &[]) { + if requirement.evaluate_markers(markers.marker_environment(), &[]) { return false; } if !requirement.evaluate_markers( - self.markers.as_ref(), + markers.marker_environment(), std::slice::from_ref(source_extra), ) { return false; } } None => { - if !requirement.evaluate_markers(self.markers.as_ref(), &[]) { + if !requirement.evaluate_markers(markers.marker_environment(), &[]) { return false; } } @@ -1516,23 +1526,27 @@ impl ResolverState { if !constraint.evaluate_markers( - self.markers.as_ref(), + markers.marker_environment(), std::slice::from_ref(source_extra), ) { return false; } } None => { - if !constraint.evaluate_markers(self.markers.as_ref(), &[]) { + if !constraint.evaluate_markers(markers.marker_environment(), &[]) { return false; } } @@ -1835,6 +1849,7 @@ impl ResolverState, fork_urls: ForkUrls, + markers: ResolverMarkers, visited: &FxHashSet, index_locations: &IndexLocations, ) -> ResolveError { @@ -1897,6 +1912,7 @@ impl ResolverState 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), + } + } +} + +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})") + } + } + } +} diff --git a/crates/uv-resolver/src/resolver/urls.rs b/crates/uv-resolver/src/resolver/urls.rs index 7a0e64d749f3..2ddf482c6f81 100644 --- a/crates/uv-resolver/src/resolver/urls.rs +++ b/crates/uv-resolver/src/resolver/urls.rs @@ -174,7 +174,7 @@ impl Urls { .chain(iter::once(verbatim_url.verbatim().to_string())) .collect(); conflicting_urls.sort(); - return Err(ResolveError::ConflictingUrls( + return Err(ResolveError::ConflictingUrlsUniversal( package_name.clone(), conflicting_urls, )); diff --git a/crates/uv/src/commands/pip/compile.rs b/crates/uv/src/commands/pip/compile.rs index 251f483b1a81..d820f7ec166e 100644 --- a/crates/uv/src/commands/pip/compile.rs +++ b/crates/uv/src/commands/pip/compile.rs @@ -33,7 +33,7 @@ use uv_requirements::{ use uv_resolver::{ AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, FlatIndex, InMemoryIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, RequiresPython, - ResolutionMode, + ResolutionMode, ResolverMarkers, }; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_warnings::warn_user; @@ -230,11 +230,14 @@ pub(crate) async fn pip_compile( // Determine the environment for the resolution. let (tags, markers) = if universal { - (None, None) + (None, ResolverMarkers::Universal) } else { let (tags, markers) = resolution_environment(python_version, python_platform, &interpreter)?; - (Some(tags), Some(markers)) + ( + Some(tags), + ResolverMarkers::SpecificEnvironment((*markers).clone()), + ) }; // Generate, but don't enforce hashes for the requirements. @@ -335,7 +338,7 @@ pub(crate) async fn pip_compile( &Reinstall::None, &upgrade, tags.as_deref(), - markers.as_deref(), + markers.clone(), python_requirement, &client, &flat_index, @@ -351,8 +354,7 @@ pub(crate) async fn pip_compile( { Ok(resolution) => resolution, Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); eprint!("{report:?}"); return Ok(ExitStatus::Failure); } @@ -384,7 +386,7 @@ pub(crate) async fn pip_compile( } if include_marker_expression { - if let Some(markers) = markers.as_deref() { + if let ResolverMarkers::SpecificEnvironment(markers) = &markers { let relevant_markers = resolution.marker_tree(&top_level_index, markers)?; writeln!( writer, @@ -457,7 +459,7 @@ pub(crate) async fn pip_compile( "{}", DisplayResolutionGraph::new( &resolution, - markers.as_deref(), + &markers, &no_emit_packages, generate_hashes, include_extras, diff --git a/crates/uv/src/commands/pip/install.rs b/crates/uv/src/commands/pip/install.rs index e4a104d3b25c..48260a6cb310 100644 --- a/crates/uv/src/commands/pip/install.rs +++ b/crates/uv/src/commands/pip/install.rs @@ -25,7 +25,7 @@ use uv_python::{ use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, - ResolutionMode, + ResolutionMode, ResolverMarkers, }; use uv_types::{BuildIsolation, HashStrategy}; @@ -326,7 +326,7 @@ pub(crate) async fn pip_install( &reinstall, &upgrade, Some(&tags), - Some(&markers), + ResolverMarkers::SpecificEnvironment((*markers).clone()), python_requirement, &client, &flat_index, @@ -342,8 +342,7 @@ pub(crate) async fn pip_install( { Ok(resolution) => Resolution::from(resolution), Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); eprint!("{report:?}"); return Ok(ExitStatus::Failure); } diff --git a/crates/uv/src/commands/pip/operations.rs b/crates/uv/src/commands/pip/operations.rs index 2e9c2f4a21b8..483aa5020661 100644 --- a/crates/uv/src/commands/pip/operations.rs +++ b/crates/uv/src/commands/pip/operations.rs @@ -16,7 +16,6 @@ use distribution_types::{ DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, Name, Resolution, }; use install_wheel_rs::linker::LinkMode; -use pep508_rs::MarkerEnvironment; use platform_tags::Tags; use pypi_types::Requirement; use uv_cache::Cache; @@ -37,7 +36,7 @@ use uv_requirements::{ }; use uv_resolver::{ DependencyMode, Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, Preference, - Preferences, PythonRequirement, ResolutionGraph, Resolver, + Preferences, PythonRequirement, ResolutionGraph, Resolver, ResolverMarkers, }; use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider}; use uv_warnings::warn_user; @@ -88,7 +87,7 @@ pub(crate) async fn resolve( reinstall: &Reinstall, upgrade: &Upgrade, tags: Option<&Tags>, - markers: Option<&MarkerEnvironment>, + markers: ResolverMarkers, python_requirement: PythonRequirement, client: &RegistryClient, flat_index: &FlatIndex, @@ -188,7 +187,7 @@ pub(crate) async fn resolve( .chain(upgrade.constraints().cloned()), ); let overrides = Overrides::from_requirements(overrides); - let preferences = Preferences::from_iter(preferences, markers); + let preferences = Preferences::from_iter(preferences, markers.marker_environment()); // Determine any lookahead requirements. let lookaheads = match options.dependency_mode { @@ -203,7 +202,7 @@ pub(crate) async fn resolve( DistributionDatabase::new(client, build_dispatch, concurrency.downloads, preview), ) .with_reporter(ResolverReporter::from(printer)) - .resolve(markers) + .resolve(&markers) .await? } DependencyMode::Direct => Vec::new(), diff --git a/crates/uv/src/commands/pip/sync.rs b/crates/uv/src/commands/pip/sync.rs index 98de1554e960..e64c1b123e22 100644 --- a/crates/uv/src/commands/pip/sync.rs +++ b/crates/uv/src/commands/pip/sync.rs @@ -24,7 +24,7 @@ use uv_python::{ use uv_requirements::{RequirementsSource, RequirementsSpecification}; use uv_resolver::{ DependencyMode, ExcludeNewer, FlatIndex, OptionsBuilder, PreReleaseMode, PythonRequirement, - ResolutionMode, + ResolutionMode, ResolverMarkers, }; use uv_types::{BuildIsolation, HashStrategy}; @@ -281,7 +281,7 @@ pub(crate) async fn pip_sync( &reinstall, &upgrade, Some(&tags), - Some(&markers), + ResolverMarkers::SpecificEnvironment((*markers).clone()), python_requirement, &client, &flat_index, @@ -297,8 +297,7 @@ pub(crate) async fn pip_sync( { Ok(resolution) => Resolution::from(resolution), Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); eprint!("{report:?}"); return Ok(ExitStatus::Failure); } diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 12a3864ab542..168bfab08a15 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -17,7 +17,9 @@ use uv_git::ResolvedRepositoryReference; use uv_normalize::PackageName; use uv_python::{Interpreter, PythonFetch, PythonPreference, PythonRequest}; use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements}; -use uv_resolver::{FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython}; +use uv_resolver::{ + FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython, ResolverMarkers, +}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::{warn_user, warn_user_once}; @@ -88,8 +90,7 @@ pub(crate) async fn lock( Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); eprint!("{report:?}"); Ok(ExitStatus::Failure) } @@ -274,7 +275,7 @@ pub(super) async fn do_lock( &Reinstall::default(), upgrade, None, - None, + ResolverMarkers::Universal, python_requirement.clone(), &client, &flat_index, @@ -350,7 +351,7 @@ pub(super) async fn do_lock( &Reinstall::default(), upgrade, None, - None, + ResolverMarkers::Universal, python_requirement, &client, &flat_index, diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 8441fde31adf..92d12c02be9d 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -21,7 +21,9 @@ use uv_python::{ PythonInstallation, PythonPreference, PythonRequest, VersionRequest, }; use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification}; -use uv_resolver::{FlatIndex, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph}; +use uv_resolver::{ + FlatIndex, OptionsBuilder, PythonRequirement, RequiresPython, ResolutionGraph, ResolverMarkers, +}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::warn_user; @@ -485,7 +487,7 @@ pub(crate) async fn resolve_environment<'a>( &reinstall, &upgrade, Some(tags), - Some(markers), + ResolverMarkers::SpecificEnvironment(markers.clone()), python_requirement, &client, &flat_index, @@ -737,7 +739,7 @@ pub(crate) async fn update_environment( reinstall, upgrade, Some(tags), - Some(markers), + ResolverMarkers::SpecificEnvironment(markers.clone()), python_requirement, &client, &flat_index, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 8b2288a07647..8882ca5569b7 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -92,8 +92,7 @@ pub(crate) async fn sync( Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); anstream::eprint!("{report:?}"); return Ok(ExitStatus::Failure); } @@ -133,8 +132,7 @@ pub(crate) async fn sync( Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { - let report = miette::Report::msg(format!("{err}")) - .context("No solution found when resolving dependencies:"); + let report = miette::Report::msg(format!("{err}")).context(err.header()); anstream::eprint!("{report:?}"); return Ok(ExitStatus::Failure); } diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 5840b1a0d576..454430c671ff 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -362,7 +362,7 @@ fn conflict_in_fork() -> Result<()> { ----- stderr ----- warning: `uv lock` is experimental and may change without warning. - × No solution found when resolving dependencies: + × No solution found when resolving dependencies for split (sys_platform == 'darwin'): ╰─▶ Because only package-b==1.0.0 is available and package-b==1.0.0 depends on package-d==1, we can conclude that all versions of package-b depend on package-d==1. And because package-c==1.0.0 depends on package-d==2 and only package-c==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-c are incompatible. And because package-a{sys_platform == 'darwin'}==1.0.0 depends on package-b and package-c, we can conclude that package-a{sys_platform == 'darwin'}==1.0.0 cannot be used. diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 7637914f620d..f0478c2ba34d 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -6872,7 +6872,7 @@ fn universal_requires_python() -> Result<()> { ----- stderr ----- warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead. - × No solution found when resolving dependencies: + × No solution found when resolving dependencies for split (python_version >= '3.9'): ╰─▶ Because only the following versions of numpy{python_version >= '3.9'} are available: numpy{python_version >= '3.9'}<=1.26.0 numpy{python_version >= '3.9'}==1.26.1