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.
This commit is contained in:
konsti 2024-07-17 18:59:33 +02:00 committed by GitHub
parent fe403576c5
commit a6dfd3953a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 220 additions and 133 deletions

View file

@ -90,7 +90,7 @@ mod resolver {
use uv_python::PythonEnvironment; use uv_python::PythonEnvironment;
use uv_resolver::{ use uv_resolver::{
FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, ResolutionGraph, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, ResolutionGraph,
Resolver, Resolver, ResolverMarkers,
}; };
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
@ -175,7 +175,7 @@ mod resolver {
manifest, manifest,
options, options,
&python_requirement, &python_requirement,
Some(&MARKERS), ResolverMarkers::SpecificEnvironment(MARKERS.clone()),
Some(&TAGS), Some(&TAGS),
&flat_index, &flat_index,
&index, &index,

View file

@ -26,6 +26,7 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
use uv_python::{Interpreter, PythonEnvironment}; use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::{ use uv_resolver::{
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver, ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver,
ResolverMarkers,
}; };
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
@ -146,7 +147,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
.index_strategy(self.index_strategy) .index_strategy(self.index_strategy)
.build(), .build(),
&python_requirement, &python_requirement,
Some(markers), ResolverMarkers::SpecificEnvironment(markers.clone()),
Some(tags), Some(tags),
self.flat_index, self.flat_index,
self.index, self.index,

View file

@ -7,13 +7,12 @@ use thiserror::Error;
use tracing::trace; use tracing::trace;
use distribution_types::{BuiltDist, Dist, DistributionMetadata, GitSourceDist, SourceDist}; use distribution_types::{BuiltDist, Dist, DistributionMetadata, GitSourceDist, SourceDist};
use pep508_rs::MarkerEnvironment;
use pypi_types::{Requirement, RequirementSource}; use pypi_types::{Requirement, RequirementSource};
use uv_configuration::{Constraints, Overrides}; use uv_configuration::{Constraints, Overrides};
use uv_distribution::{DistributionDatabase, Reporter}; use uv_distribution::{DistributionDatabase, Reporter};
use uv_git::GitUrl; use uv_git::GitUrl;
use uv_normalize::GroupName; use uv_normalize::GroupName;
use uv_resolver::{InMemoryIndex, MetadataResponse}; use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverMarkers};
use uv_types::{BuildContext, HashStrategy, RequestedRequirements}; use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -98,7 +97,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: Option<&MarkerEnvironment>, markers: &ResolverMarkers,
) -> Result<Vec<RequestedRequirements>, LookaheadError> { ) -> Result<Vec<RequestedRequirements>, LookaheadError> {
let mut results = Vec::new(); let mut results = Vec::new();
let mut futures = FuturesUnordered::new(); let mut futures = FuturesUnordered::new();
@ -108,7 +107,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, &[])) .filter(|requirement| requirement.evaluate_markers(markers.marker_environment(), &[]))
.map(|requirement| (*requirement).clone()) .map(|requirement| (*requirement).clone())
.collect(); .collect();
@ -127,7 +126,9 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
.constraints .constraints
.apply(self.overrides.apply(lookahead.requirements())) .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()); queue.push_back((*requirement).clone());
} }
} }

View file

@ -18,7 +18,7 @@ use crate::pubgrub::{
PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError, PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError,
}; };
use crate::python_requirement::PythonRequirement; use crate::python_requirement::PythonRequirement;
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason}; use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ResolveError { pub enum ResolveError {
@ -50,10 +50,10 @@ pub enum ResolveError {
ConflictingOverrideUrls(PackageName, String, String), ConflictingOverrideUrls(PackageName, String, String),
#[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))] #[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))]
ConflictingUrls(PackageName, Vec<String>), ConflictingUrlsUniversal(PackageName, Vec<String>),
#[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers}`:\n- {}", urls.join("\n- "))] #[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers}`:\n- {}", urls.join("\n- "))]
ConflictingUrlsInFork { ConflictingUrlsFork {
package_name: PackageName, package_name: PackageName,
urls: Vec<String>, urls: Vec<String>,
fork_markers: MarkerTree, fork_markers: MarkerTree,
@ -125,9 +125,21 @@ 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,
} }
impl NoSolutionError { 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( pub(crate) fn new(
error: pubgrub::error::NoSolutionError<UvDependencyProvider>, error: pubgrub::error::NoSolutionError<UvDependencyProvider>,
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>, available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
@ -137,6 +149,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,
) -> Self { ) -> Self {
Self { Self {
error, error,
@ -147,6 +160,7 @@ impl NoSolutionError {
unavailable_packages, unavailable_packages,
incomplete_packages, incomplete_packages,
fork_urls, fork_urls,
markers,
} }
} }

View file

@ -3,10 +3,10 @@ use std::collections::hash_map::Entry;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use distribution_types::Verbatim; use distribution_types::Verbatim;
use pep508_rs::MarkerTree;
use pypi_types::VerbatimParsedUrl; use pypi_types::VerbatimParsedUrl;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use crate::resolver::ResolverMarkers;
use crate::ResolveError; use crate::ResolveError;
/// See [`crate::resolver::SolveState`]. /// See [`crate::resolver::SolveState`].
@ -29,7 +29,7 @@ impl ForkUrls {
&mut self, &mut self,
package_name: &PackageName, package_name: &PackageName,
url: &VerbatimParsedUrl, url: &VerbatimParsedUrl,
fork_markers: &MarkerTree, fork_markers: &ResolverMarkers,
) -> 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,17 +39,20 @@ impl ForkUrls {
url.verbatim.verbatim().to_string(), url.verbatim.verbatim().to_string(),
]; ];
conflicting_url.sort(); conflicting_url.sort();
return if fork_markers.is_universal() { return match fork_markers {
Err(ResolveError::ConflictingUrls( ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => {
package_name.clone(), Err(ResolveError::ConflictingUrlsUniversal(
conflicting_url, package_name.clone(),
)) conflicting_url,
} else { ))
Err(ResolveError::ConflictingUrlsInFork { }
package_name: package_name.clone(), ResolverMarkers::Fork(fork_markers) => {
urls: conflicting_url, Err(ResolveError::ConflictingUrlsFork {
fork_markers: fork_markers.clone(), package_name: package_name.clone(),
}) urls: conflicting_url,
fork_markers: fork_markers.clone(),
})
}
}; };
} }
} }

View file

@ -15,7 +15,7 @@ pub use resolution::{AnnotationStyle, DisplayResolutionGraph, ResolutionGraph};
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, ResolverProvider, VersionsResponse, Reporter as ResolverReporter, Resolver, ResolverMarkers, ResolverProvider, VersionsResponse,
WheelMetadataResult, WheelMetadataResult,
}; };
pub use version_map::VersionMap; pub use version_map::VersionMap;

View file

@ -7,12 +7,13 @@ use petgraph::Direction;
use rustc_hash::{FxBuildHasher, FxHashMap}; use rustc_hash::{FxBuildHasher, FxHashMap};
use distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations}; use distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations};
use pep508_rs::MarkerEnvironment;
use pep508_rs::MarkerTree; use pep508_rs::MarkerTree;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode}; 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. /// A [`std::fmt::Display`] implementation for the resolution graph.
#[derive(Debug)] #[derive(Debug)]
@ -21,7 +22,7 @@ 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 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. /// 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.
@ -50,7 +51,7 @@ impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> {
fn from(resolution: &'a ResolutionGraph) -> Self { fn from(resolution: &'a ResolutionGraph) -> Self {
Self::new( Self::new(
resolution, resolution,
None, &UNIVERSAL_MARKERS,
&[], &[],
false, false,
false, false,
@ -67,7 +68,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: Option<&'a MarkerEnvironment>, marker_env: &'a ResolverMarkers,
no_emit_packages: &'a [PackageName], no_emit_packages: &'a [PackageName],
show_hashes: bool, show_hashes: bool,
include_extras: bool, include_extras: bool,
@ -97,12 +98,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
let sources = if self.include_annotations { let sources = if self.include_annotations {
let mut sources = SourceAnnotations::default(); let mut sources = SourceAnnotations::default();
for requirement in self for requirement in self.resolution.requirements.iter().filter(|requirement| {
.resolution requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
.requirements }) {
.iter()
.filter(|requirement| requirement.evaluate_markers(self.marker_env, &[]))
{
if let Some(origin) = &requirement.origin { if let Some(origin) = &requirement.origin {
sources.add( sources.add(
&requirement.name, &requirement.name,
@ -115,7 +113,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.resolution .resolution
.constraints .constraints
.requirements() .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 { if let Some(origin) = &requirement.origin {
sources.add( sources.add(
@ -129,7 +129,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
.resolution .resolution
.overrides .overrides
.requirements() .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 { if let Some(origin) = &requirement.origin {
sources.add( sources.add(

View file

@ -28,9 +28,10 @@ use distribution_types::{
}; };
pub(crate) use locals::ForkLocals; pub(crate) use locals::ForkLocals;
use pep440_rs::{Version, MIN_VERSION}; use pep440_rs::{Version, MIN_VERSION};
use pep508_rs::{MarkerEnvironment, MarkerTree}; use pep508_rs::MarkerTree;
use platform_tags::Tags; use platform_tags::Tags;
use pypi_types::{Metadata23, Requirement, VerbatimParsedUrl}; use pypi_types::{Metadata23, Requirement, VerbatimParsedUrl};
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};
@ -73,6 +74,7 @@ mod index;
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> {
@ -94,8 +96,7 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
urls: Urls, urls: Urls,
dependency_mode: DependencyMode, dependency_mode: DependencyMode,
hasher: HashStrategy, hasher: HashStrategy,
/// When not set, the resolver is in "universal" mode. markers: ResolverMarkers,
markers: Option<MarkerEnvironment>,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
/// This is derived from `PythonRequirement` once at initialization /// This is derived from `PythonRequirement` once at initialization
/// time. It's used in universal mode to filter our dependencies with /// time. It's used in universal mode to filter our dependencies with
@ -140,7 +141,7 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
manifest: Manifest, manifest: Manifest,
options: Options, options: Options,
python_requirement: &'a PythonRequirement, python_requirement: &'a PythonRequirement,
markers: Option<&'a MarkerEnvironment>, markers: ResolverMarkers,
tags: Option<&'a Tags>, tags: Option<&'a Tags>,
flat_index: &'a FlatIndex, flat_index: &'a FlatIndex,
index: &'a InMemoryIndex, index: &'a InMemoryIndex,
@ -156,7 +157,11 @@ impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
python_requirement python_requirement
.target() .target()
.and_then(|target| target.as_requires_python()), .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, hasher,
options.exclude_newer, options.exclude_newer,
build_context.build_options(), build_context.build_options(),
@ -184,7 +189,7 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
manifest: Manifest, manifest: Manifest,
options: Options, options: Options,
hasher: &HashStrategy, hasher: &HashStrategy,
markers: Option<&MarkerEnvironment>, markers: ResolverMarkers,
python_requirement: &PythonRequirement, python_requirement: &PythonRequirement,
index: &InMemoryIndex, index: &InMemoryIndex,
git: &GitResolver, git: &GitResolver,
@ -196,9 +201,18 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
git: git.clone(), git: git.clone(),
unavailable_packages: DashMap::default(), unavailable_packages: DashMap::default(),
incomplete_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, 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, project: manifest.project,
requirements: manifest.requirements, requirements: manifest.requirements,
constraints: manifest.constraints, constraints: manifest.constraints,
@ -207,12 +221,12 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
preferences: manifest.preferences, preferences: manifest.preferences,
exclusions: manifest.exclusions, exclusions: manifest.exclusions,
hasher: hasher.clone(), hasher: hasher.clone(),
markers: markers.cloned(), requires_python: if markers.marker_environment().is_some() {
requires_python: if markers.is_some() {
None None
} else { } else {
python_requirement.to_marker_tree() python_requirement.to_marker_tree()
}, },
markers,
python_requirement: python_requirement.clone(), python_requirement: python_requirement.clone(),
reporter: None, reporter: None,
installed_packages, installed_packages,
@ -296,7 +310,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
fork_locals: ForkLocals::default(), fork_locals: ForkLocals::default(),
priorities: PubGrubPriorities::default(), priorities: PubGrubPriorities::default(),
added_dependencies: FxHashMap::default(), added_dependencies: FxHashMap::default(),
markers: MarkerTree::And(vec![]), markers: self.markers.clone(),
python_requirement: self.python_requirement.clone(), python_requirement: self.python_requirement.clone(),
requires_python: self.requires_python.clone(), requires_python: self.requires_python.clone(),
}; };
@ -305,14 +319,14 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
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 !state.markers.is_universal() { if let ResolverMarkers::Fork(markers) = &state.markers {
if let Some(requires_python) = state.requires_python.as_ref() { if let Some(requires_python) = state.requires_python.as_ref() {
debug!( debug!(
"Solving split {} (requires-python: {})", "Solving split {} (requires-python: {})",
state.markers, requires_python markers, requires_python
); );
} else { } else {
debug!("Solving split {}", state.markers); debug!("Solving split {}", markers);
} }
} }
let start = Instant::now(); let start = Instant::now();
@ -321,7 +335,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
if let Err(err) = state.pubgrub.unit_propagation(state.next.clone()) { if let Err(err) = state.pubgrub.unit_propagation(state.next.clone()) {
return Err(self.convert_no_solution_err( return Err(self.convert_no_solution_err(
err, err,
state.fork_urls.clone(), state.fork_urls,
state.markers,
&visited, &visited,
&index_locations, &index_locations,
)); ));
@ -348,7 +363,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
prefetcher.log_tried_versions(); prefetcher.log_tried_versions();
} }
debug!( debug!(
"Split {} took {:.3}s", "Split {} resolution took {:.3}s",
state.markers, state.markers,
start.elapsed().as_secs_f32() start.elapsed().as_secs_f32()
); );
@ -540,15 +555,15 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
cur_state = Some(forked_state.clone()); cur_state = Some(forked_state.clone());
} }
forked_state.markers.and(fork.markers); let combined_markers = forked_state.markers.and(fork.markers);
forked_state.markers = normalize(forked_state.markers, None) let combined_markers = normalize(combined_markers, None)
.unwrap_or(MarkerTree::And(Vec::new())); .expect("Fork markers are universal");
// If the fork contains a narrowed Python requirement, apply it. // If the fork contains a narrowed Python requirement, apply it.
let python_requirement = requires_python_marker( let python_requirement = requires_python_marker(&combined_markers)
&forked_state.markers, .and_then(|marker| {
) forked_state.python_requirement.narrow(&marker)
.and_then(|marker| forked_state.python_requirement.narrow(&marker)); });
if let Some(python_requirement) = python_requirement { if let Some(python_requirement) = python_requirement {
if let Some(target) = python_requirement.target() { if let Some(target) = python_requirement.target() {
debug!("Narrowed `requires-python` bound to: {target}"); debug!("Narrowed `requires-python` bound to: {target}");
@ -562,6 +577,8 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
forked_state.python_requirement = python_requirement; forked_state.python_requirement = python_requirement;
} }
forked_state.markers = ResolverMarkers::Fork(combined_markers);
forked_state.add_package_version_dependencies( forked_state.add_package_version_dependencies(
for_package.as_deref(), for_package.as_deref(),
&version, &version,
@ -584,18 +601,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
} }
forked_states.push(forked_state); forked_states.push(forked_state);
} }
if markers.is_universal() { debug!(
debug!( "Pre-fork split {} took {:.3}s",
"Pre-fork split universal took {:.3}s", markers,
start.elapsed().as_secs_f32() start.elapsed().as_secs_f32()
); );
} else {
debug!(
"Pre-fork split {} took {:.3}s",
markers,
start.elapsed().as_secs_f32()
);
}
continue 'FORK; continue 'FORK;
} }
} }
@ -1083,7 +1093,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
version: &Version, version: &Version,
fork_urls: &ForkUrls, fork_urls: &ForkUrls,
fork_locals: &ForkLocals, fork_locals: &ForkLocals,
markers: &MarkerTree, markers: &ResolverMarkers,
requires_python: Option<&MarkerTree>, requires_python: Option<&MarkerTree>,
) -> Result<ForkedDependencies, ResolveError> { ) -> Result<ForkedDependencies, ResolveError> {
let result = self.get_dependencies( let result = self.get_dependencies(
@ -1094,13 +1104,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
markers, markers,
requires_python, requires_python,
); );
if self.markers.is_some() { match markers {
return result.map(|deps| match deps { ResolverMarkers::SpecificEnvironment(_) => result.map(|deps| match deps {
Dependencies::Available(deps) => ForkedDependencies::Unforked(deps), Dependencies::Available(deps) => ForkedDependencies::Unforked(deps),
Dependencies::Unavailable(err) => ForkedDependencies::Unavailable(err), 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. /// Given a candidate package and version, return its dependencies.
@ -1111,7 +1121,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
version: &Version, version: &Version,
fork_urls: &ForkUrls, fork_urls: &ForkUrls,
fork_locals: &ForkLocals, fork_locals: &ForkLocals,
markers: &MarkerTree, markers: &ResolverMarkers,
requires_python: Option<&MarkerTree>, requires_python: Option<&MarkerTree>,
) -> 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));
@ -1383,7 +1393,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 MarkerTree, markers: &'a ResolverMarkers,
requires_python: Option<&'a MarkerTree>, requires_python: Option<&'a MarkerTree>,
) -> 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
@ -1448,7 +1458,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 MarkerTree, markers: &'parameters ResolverMarkers,
requires_python: Option<&'parameters MarkerTree>, requires_python: Option<&'parameters MarkerTree>,
) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters ) -> impl Iterator<Item = Cow<'data, Requirement>> + 'parameters
where where
@ -1469,31 +1479,31 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
return false; return false;
} }
// If we're in universal mode, `fork_markers` might correspond to a // If we're in a fork in universal mode, ignore any dependency that isn't part of
// non-trivial marker expression that provoked the resolver to fork. // this fork (but will be part of another fork).
// In that case, we should ignore any dependency that cannot possibly if let ResolverMarkers::Fork(markers) = markers {
// satisfy the markers that provoked the fork. if !possible_to_satisfy_markers(markers, requirement) {
if !possible_to_satisfy_markers(markers, requirement) { trace!("skipping {requirement} because of context resolver markers {markers}");
trace!("skipping {requirement} because of context resolver markers {markers}"); return false;
return false; }
} }
// 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(self.markers.as_ref(), &[]) { if requirement.evaluate_markers(markers.marker_environment(), &[]) {
return false; return false;
} }
if !requirement.evaluate_markers( if !requirement.evaluate_markers(
self.markers.as_ref(), markers.marker_environment(),
std::slice::from_ref(source_extra), std::slice::from_ref(source_extra),
) { ) {
return false; return false;
} }
} }
None => { None => {
if !requirement.evaluate_markers(self.markers.as_ref(), &[]) { if !requirement.evaluate_markers(markers.marker_environment(), &[]) {
return false; return false;
} }
} }
@ -1516,23 +1526,27 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
return false; return false;
} }
if !possible_to_satisfy_markers(markers, constraint) { // If we're in a fork in universal mode, ignore any dependency that isn't part of
trace!("skipping {constraint} because of context resolver markers {markers}"); // this fork (but will be part of another fork).
return false; if let ResolverMarkers::Fork(markers) = markers {
if !possible_to_satisfy_markers(markers, constraint) {
trace!("skipping {constraint} because of context resolver markers {markers}");
return false;
}
} }
// 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(
self.markers.as_ref(), markers.marker_environment(),
std::slice::from_ref(source_extra), std::slice::from_ref(source_extra),
) { ) {
return false; return false;
} }
} }
None => { None => {
if !constraint.evaluate_markers(self.markers.as_ref(), &[]) { if !constraint.evaluate_markers(markers.marker_environment(), &[]) {
return false; return false;
} }
} }
@ -1835,6 +1849,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&self, &self,
mut err: pubgrub::error::NoSolutionError<UvDependencyProvider>, mut err: pubgrub::error::NoSolutionError<UvDependencyProvider>,
fork_urls: ForkUrls, fork_urls: ForkUrls,
markers: ResolverMarkers,
visited: &FxHashSet<PackageName>, visited: &FxHashSet<PackageName>,
index_locations: &IndexLocations, index_locations: &IndexLocations,
) -> ResolveError { ) -> ResolveError {
@ -1897,6 +1912,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
unavailable_packages, unavailable_packages,
incomplete_packages, incomplete_packages,
fork_urls, fork_urls,
markers,
)) ))
} }
@ -1976,7 +1992,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: MarkerTree, markers: ResolverMarkers,
/// 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.

View file

@ -0,0 +1,50 @@
use pep508_rs::{MarkerEnvironment, MarkerTree};
use std::fmt::{Display, Formatter};
#[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(MarkerEnvironment),
/// We're doing a universal resolution for all environments (a python version
/// constraint is expressed separately).
Universal,
/// We're in a fork of the universal resolution solving only for specific markers.
Fork(MarkerTree),
}
impl ResolverMarkers {
/// 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),
}
}
}
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

@ -174,7 +174,7 @@ 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::ConflictingUrls( return Err(ResolveError::ConflictingUrlsUniversal(
package_name.clone(), package_name.clone(),
conflicting_urls, conflicting_urls,
)); ));

View file

@ -33,7 +33,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, ResolutionMode, ResolverMarkers,
}; };
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -230,11 +230,14 @@ 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, markers) = if universal {
(None, None) (None, ResolverMarkers::Universal)
} else { } else {
let (tags, markers) = let (tags, markers) =
resolution_environment(python_version, python_platform, &interpreter)?; 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. // Generate, but don't enforce hashes for the requirements.
@ -335,7 +338,7 @@ pub(crate) async fn pip_compile(
&Reinstall::None, &Reinstall::None,
&upgrade, &upgrade,
tags.as_deref(), tags.as_deref(),
markers.as_deref(), markers.clone(),
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,
@ -351,8 +354,7 @@ pub(crate) async fn pip_compile(
{ {
Ok(resolution) => resolution, Ok(resolution) => resolution,
Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
eprint!("{report:?}"); eprint!("{report:?}");
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }
@ -384,7 +386,7 @@ pub(crate) async fn pip_compile(
} }
if include_marker_expression { 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)?; let relevant_markers = resolution.marker_tree(&top_level_index, markers)?;
writeln!( writeln!(
writer, writer,
@ -457,7 +459,7 @@ pub(crate) async fn pip_compile(
"{}", "{}",
DisplayResolutionGraph::new( DisplayResolutionGraph::new(
&resolution, &resolution,
markers.as_deref(), &markers,
&no_emit_packages, &no_emit_packages,
generate_hashes, generate_hashes,
include_extras, include_extras,

View file

@ -25,7 +25,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, ResolutionMode, ResolverMarkers,
}; };
use uv_types::{BuildIsolation, HashStrategy}; use uv_types::{BuildIsolation, HashStrategy};
@ -326,7 +326,7 @@ pub(crate) async fn pip_install(
&reinstall, &reinstall,
&upgrade, &upgrade,
Some(&tags), Some(&tags),
Some(&markers), ResolverMarkers::SpecificEnvironment((*markers).clone()),
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,
@ -342,8 +342,7 @@ pub(crate) async fn pip_install(
{ {
Ok(resolution) => Resolution::from(resolution), Ok(resolution) => Resolution::from(resolution),
Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
eprint!("{report:?}"); eprint!("{report:?}");
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }

View file

@ -16,7 +16,6 @@ use distribution_types::{
DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, Name, Resolution, DistributionMetadata, IndexLocations, InstalledMetadata, LocalDist, Name, Resolution,
}; };
use install_wheel_rs::linker::LinkMode; use install_wheel_rs::linker::LinkMode;
use pep508_rs::MarkerEnvironment;
use platform_tags::Tags; use platform_tags::Tags;
use pypi_types::Requirement; use pypi_types::Requirement;
use uv_cache::Cache; use uv_cache::Cache;
@ -37,7 +36,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, Preferences, PythonRequirement, ResolutionGraph, Resolver, ResolverMarkers,
}; };
use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider}; use uv_types::{HashStrategy, InFlight, InstalledPackagesProvider};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -88,7 +87,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
reinstall: &Reinstall, reinstall: &Reinstall,
upgrade: &Upgrade, upgrade: &Upgrade,
tags: Option<&Tags>, tags: Option<&Tags>,
markers: Option<&MarkerEnvironment>, markers: ResolverMarkers,
python_requirement: PythonRequirement, python_requirement: PythonRequirement,
client: &RegistryClient, client: &RegistryClient,
flat_index: &FlatIndex, flat_index: &FlatIndex,
@ -188,7 +187,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, markers.marker_environment());
// Determine any lookahead requirements. // Determine any lookahead requirements.
let lookaheads = match options.dependency_mode { let lookaheads = match options.dependency_mode {
@ -203,7 +202,7 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
DistributionDatabase::new(client, build_dispatch, concurrency.downloads, preview), DistributionDatabase::new(client, build_dispatch, concurrency.downloads, preview),
) )
.with_reporter(ResolverReporter::from(printer)) .with_reporter(ResolverReporter::from(printer))
.resolve(markers) .resolve(&markers)
.await? .await?
} }
DependencyMode::Direct => Vec::new(), DependencyMode::Direct => Vec::new(),

View file

@ -24,7 +24,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, ResolutionMode, ResolverMarkers,
}; };
use uv_types::{BuildIsolation, HashStrategy}; use uv_types::{BuildIsolation, HashStrategy};
@ -281,7 +281,7 @@ pub(crate) async fn pip_sync(
&reinstall, &reinstall,
&upgrade, &upgrade,
Some(&tags), Some(&tags),
Some(&markers), ResolverMarkers::SpecificEnvironment((*markers).clone()),
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,
@ -297,8 +297,7 @@ pub(crate) async fn pip_sync(
{ {
Ok(resolution) => Resolution::from(resolution), Ok(resolution) => Resolution::from(resolution),
Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => { Err(operations::Error::Resolve(uv_resolver::ResolveError::NoSolution(err))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
eprint!("{report:?}"); eprint!("{report:?}");
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }

View file

@ -17,7 +17,9 @@ use uv_git::ResolvedRepositoryReference;
use uv_normalize::PackageName; use uv_normalize::PackageName;
use uv_python::{Interpreter, PythonFetch, PythonPreference, PythonRequest}; use uv_python::{Interpreter, PythonFetch, PythonPreference, PythonRequest};
use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements}; 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_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once}; use uv_warnings::{warn_user, warn_user_once};
@ -88,8 +90,7 @@ pub(crate) async fn lock(
Err(ProjectError::Operation(pip::operations::Error::Resolve( Err(ProjectError::Operation(pip::operations::Error::Resolve(
uv_resolver::ResolveError::NoSolution(err), uv_resolver::ResolveError::NoSolution(err),
))) => { ))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
eprint!("{report:?}"); eprint!("{report:?}");
Ok(ExitStatus::Failure) Ok(ExitStatus::Failure)
} }
@ -274,7 +275,7 @@ pub(super) async fn do_lock(
&Reinstall::default(), &Reinstall::default(),
upgrade, upgrade,
None, None,
None, ResolverMarkers::Universal,
python_requirement.clone(), python_requirement.clone(),
&client, &client,
&flat_index, &flat_index,
@ -350,7 +351,7 @@ pub(super) async fn do_lock(
&Reinstall::default(), &Reinstall::default(),
upgrade, upgrade,
None, None,
None, ResolverMarkers::Universal,
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,

View file

@ -21,7 +21,9 @@ use uv_python::{
PythonInstallation, PythonPreference, PythonRequest, VersionRequest, PythonInstallation, PythonPreference, PythonRequest, VersionRequest,
}; };
use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification}; 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_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -485,7 +487,7 @@ pub(crate) async fn resolve_environment<'a>(
&reinstall, &reinstall,
&upgrade, &upgrade,
Some(tags), Some(tags),
Some(markers), ResolverMarkers::SpecificEnvironment(markers.clone()),
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,
@ -737,7 +739,7 @@ pub(crate) async fn update_environment(
reinstall, reinstall,
upgrade, upgrade,
Some(tags), Some(tags),
Some(markers), ResolverMarkers::SpecificEnvironment(markers.clone()),
python_requirement, python_requirement,
&client, &client,
&flat_index, &flat_index,

View file

@ -92,8 +92,7 @@ pub(crate) async fn sync(
Err(ProjectError::Operation(pip::operations::Error::Resolve( Err(ProjectError::Operation(pip::operations::Error::Resolve(
uv_resolver::ResolveError::NoSolution(err), uv_resolver::ResolveError::NoSolution(err),
))) => { ))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
anstream::eprint!("{report:?}"); anstream::eprint!("{report:?}");
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }
@ -133,8 +132,7 @@ pub(crate) async fn sync(
Err(ProjectError::Operation(pip::operations::Error::Resolve( Err(ProjectError::Operation(pip::operations::Error::Resolve(
uv_resolver::ResolveError::NoSolution(err), uv_resolver::ResolveError::NoSolution(err),
))) => { ))) => {
let report = miette::Report::msg(format!("{err}")) let report = miette::Report::msg(format!("{err}")).context(err.header());
.context("No solution found when resolving dependencies:");
anstream::eprint!("{report:?}"); anstream::eprint!("{report:?}");
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }

View file

@ -362,7 +362,7 @@ fn conflict_in_fork() -> Result<()> {
----- stderr ----- ----- stderr -----
warning: `uv lock` is experimental and may change without warning. 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. 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-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. 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.

View file

@ -6872,7 +6872,7 @@ fn universal_requires_python() -> Result<()> {
----- stderr ----- ----- stderr -----
warning: The requested Python version 3.8 is not available; 3.12.[X] will be used to build dependencies instead. 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: 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.0
numpy{python_version >= '3.9'}==1.26.1 numpy{python_version >= '3.9'}==1.26.1