Skip to content

Commit a6dfd39

Browse files
authored
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.
1 parent fe40357 commit a6dfd39

File tree

19 files changed

+220
-133
lines changed

19 files changed

+220
-133
lines changed

crates/bench/benches/uv.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ mod resolver {
9090
use uv_python::PythonEnvironment;
9191
use uv_resolver::{
9292
FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, ResolutionGraph,
93-
Resolver,
93+
Resolver, ResolverMarkers,
9494
};
9595
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
9696

@@ -175,7 +175,7 @@ mod resolver {
175175
manifest,
176176
options,
177177
&python_requirement,
178-
Some(&MARKERS),
178+
ResolverMarkers::SpecificEnvironment(MARKERS.clone()),
179179
Some(&TAGS),
180180
&flat_index,
181181
&index,

crates/uv-dispatch/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use uv_installer::{Installer, Plan, Planner, Preparer, SitePackages};
2626
use uv_python::{Interpreter, PythonEnvironment};
2727
use uv_resolver::{
2828
ExcludeNewer, FlatIndex, InMemoryIndex, Manifest, OptionsBuilder, PythonRequirement, Resolver,
29+
ResolverMarkers,
2930
};
3031
use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
3132

@@ -146,7 +147,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
146147
.index_strategy(self.index_strategy)
147148
.build(),
148149
&python_requirement,
149-
Some(markers),
150+
ResolverMarkers::SpecificEnvironment(markers.clone()),
150151
Some(tags),
151152
self.flat_index,
152153
self.index,

crates/uv-requirements/src/lookahead.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@ use thiserror::Error;
77
use tracing::trace;
88

99
use distribution_types::{BuiltDist, Dist, DistributionMetadata, GitSourceDist, SourceDist};
10-
use pep508_rs::MarkerEnvironment;
1110
use pypi_types::{Requirement, RequirementSource};
1211
use uv_configuration::{Constraints, Overrides};
1312
use uv_distribution::{DistributionDatabase, Reporter};
1413
use uv_git::GitUrl;
1514
use uv_normalize::GroupName;
16-
use uv_resolver::{InMemoryIndex, MetadataResponse};
15+
use uv_resolver::{InMemoryIndex, MetadataResponse, ResolverMarkers};
1716
use uv_types::{BuildContext, HashStrategy, RequestedRequirements};
1817

1918
#[derive(Debug, Error)]
@@ -98,7 +97,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
9897
/// to "only evaluate marker expressions that reference an extra name.")
9998
pub async fn resolve(
10099
self,
101-
markers: Option<&MarkerEnvironment>,
100+
markers: &ResolverMarkers,
102101
) -> Result<Vec<RequestedRequirements>, LookaheadError> {
103102
let mut results = Vec::new();
104103
let mut futures = FuturesUnordered::new();
@@ -108,7 +107,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
108107
let mut queue: VecDeque<_> = self
109108
.constraints
110109
.apply(self.overrides.apply(self.requirements))
111-
.filter(|requirement| requirement.evaluate_markers(markers, &[]))
110+
.filter(|requirement| requirement.evaluate_markers(markers.marker_environment(), &[]))
112111
.map(|requirement| (*requirement).clone())
113112
.collect();
114113

@@ -127,7 +126,9 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> {
127126
.constraints
128127
.apply(self.overrides.apply(lookahead.requirements()))
129128
{
130-
if requirement.evaluate_markers(markers, lookahead.extras()) {
129+
if requirement
130+
.evaluate_markers(markers.marker_environment(), lookahead.extras())
131+
{
131132
queue.push_back((*requirement).clone());
132133
}
133134
}

crates/uv-resolver/src/error.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::pubgrub::{
1818
PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, PubGrubSpecifierError,
1919
};
2020
use crate::python_requirement::PythonRequirement;
21-
use crate::resolver::{IncompletePackage, UnavailablePackage, UnavailableReason};
21+
use crate::resolver::{IncompletePackage, ResolverMarkers, UnavailablePackage, UnavailableReason};
2222

2323
#[derive(Debug, thiserror::Error)]
2424
pub enum ResolveError {
@@ -50,10 +50,10 @@ pub enum ResolveError {
5050
ConflictingOverrideUrls(PackageName, String, String),
5151

5252
#[error("Requirements contain conflicting URLs for package `{0}`:\n- {}", _1.join("\n- "))]
53-
ConflictingUrls(PackageName, Vec<String>),
53+
ConflictingUrlsUniversal(PackageName, Vec<String>),
5454

5555
#[error("Requirements contain conflicting URLs for package `{package_name}` in split `{fork_markers}`:\n- {}", urls.join("\n- "))]
56-
ConflictingUrlsInFork {
56+
ConflictingUrlsFork {
5757
package_name: PackageName,
5858
urls: Vec<String>,
5959
fork_markers: MarkerTree,
@@ -125,9 +125,21 @@ pub struct NoSolutionError {
125125
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
126126
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
127127
fork_urls: ForkUrls,
128+
markers: ResolverMarkers,
128129
}
129130

130131
impl NoSolutionError {
132+
pub fn header(&self) -> String {
133+
match &self.markers {
134+
ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => {
135+
"No solution found when resolving dependencies:".to_string()
136+
}
137+
ResolverMarkers::Fork(markers) => {
138+
format!("No solution found when resolving dependencies for split ({markers}):")
139+
}
140+
}
141+
}
142+
131143
pub(crate) fn new(
132144
error: pubgrub::error::NoSolutionError<UvDependencyProvider>,
133145
available_versions: FxHashMap<PubGrubPackage, BTreeSet<Version>>,
@@ -137,6 +149,7 @@ impl NoSolutionError {
137149
unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
138150
incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, IncompletePackage>>,
139151
fork_urls: ForkUrls,
152+
markers: ResolverMarkers,
140153
) -> Self {
141154
Self {
142155
error,
@@ -147,6 +160,7 @@ impl NoSolutionError {
147160
unavailable_packages,
148161
incomplete_packages,
149162
fork_urls,
163+
markers,
150164
}
151165
}
152166

crates/uv-resolver/src/fork_urls.rs

+16-13
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use std::collections::hash_map::Entry;
33
use rustc_hash::FxHashMap;
44

55
use distribution_types::Verbatim;
6-
use pep508_rs::MarkerTree;
76
use pypi_types::VerbatimParsedUrl;
87
use uv_normalize::PackageName;
98

9+
use crate::resolver::ResolverMarkers;
1010
use crate::ResolveError;
1111

1212
/// See [`crate::resolver::SolveState`].
@@ -29,7 +29,7 @@ impl ForkUrls {
2929
&mut self,
3030
package_name: &PackageName,
3131
url: &VerbatimParsedUrl,
32-
fork_markers: &MarkerTree,
32+
fork_markers: &ResolverMarkers,
3333
) -> Result<(), ResolveError> {
3434
match self.0.entry(package_name.clone()) {
3535
Entry::Occupied(previous) => {
@@ -39,17 +39,20 @@ impl ForkUrls {
3939
url.verbatim.verbatim().to_string(),
4040
];
4141
conflicting_url.sort();
42-
return if fork_markers.is_universal() {
43-
Err(ResolveError::ConflictingUrls(
44-
package_name.clone(),
45-
conflicting_url,
46-
))
47-
} else {
48-
Err(ResolveError::ConflictingUrlsInFork {
49-
package_name: package_name.clone(),
50-
urls: conflicting_url,
51-
fork_markers: fork_markers.clone(),
52-
})
42+
return match fork_markers {
43+
ResolverMarkers::Universal | ResolverMarkers::SpecificEnvironment(_) => {
44+
Err(ResolveError::ConflictingUrlsUniversal(
45+
package_name.clone(),
46+
conflicting_url,
47+
))
48+
}
49+
ResolverMarkers::Fork(fork_markers) => {
50+
Err(ResolveError::ConflictingUrlsFork {
51+
package_name: package_name.clone(),
52+
urls: conflicting_url,
53+
fork_markers: fork_markers.clone(),
54+
})
55+
}
5356
};
5457
}
5558
}

crates/uv-resolver/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use resolution::{AnnotationStyle, DisplayResolutionGraph, ResolutionGraph};
1515
pub use resolution_mode::ResolutionMode;
1616
pub use resolver::{
1717
BuildId, DefaultResolverProvider, InMemoryIndex, MetadataResponse, PackageVersionsResult,
18-
Reporter as ResolverReporter, Resolver, ResolverProvider, VersionsResponse,
18+
Reporter as ResolverReporter, Resolver, ResolverMarkers, ResolverProvider, VersionsResponse,
1919
WheelMetadataResult,
2020
};
2121
pub use version_map::VersionMap;

crates/uv-resolver/src/resolution/display.rs

+15-13
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use petgraph::Direction;
77
use rustc_hash::{FxBuildHasher, FxHashMap};
88

99
use distribution_types::{DistributionMetadata, Name, SourceAnnotation, SourceAnnotations};
10-
use pep508_rs::MarkerEnvironment;
1110
use pep508_rs::MarkerTree;
1211
use uv_normalize::PackageName;
1312

1413
use crate::resolution::{RequirementsTxtDist, ResolutionGraphNode};
15-
use crate::{marker, ResolutionGraph};
14+
use crate::{marker, ResolutionGraph, ResolverMarkers};
15+
16+
static UNIVERSAL_MARKERS: ResolverMarkers = ResolverMarkers::Universal;
1617

1718
/// A [`std::fmt::Display`] implementation for the resolution graph.
1819
#[derive(Debug)]
@@ -21,7 +22,7 @@ pub struct DisplayResolutionGraph<'a> {
2122
/// The underlying graph.
2223
resolution: &'a ResolutionGraph,
2324
/// The marker environment, used to determine the markers that apply to each package.
24-
marker_env: Option<&'a MarkerEnvironment>,
25+
marker_env: &'a ResolverMarkers,
2526
/// The packages to exclude from the output.
2627
no_emit_packages: &'a [PackageName],
2728
/// Whether to include hashes in the output.
@@ -50,7 +51,7 @@ impl<'a> From<&'a ResolutionGraph> for DisplayResolutionGraph<'a> {
5051
fn from(resolution: &'a ResolutionGraph) -> Self {
5152
Self::new(
5253
resolution,
53-
None,
54+
&UNIVERSAL_MARKERS,
5455
&[],
5556
false,
5657
false,
@@ -67,7 +68,7 @@ impl<'a> DisplayResolutionGraph<'a> {
6768
#[allow(clippy::fn_params_excessive_bools)]
6869
pub fn new(
6970
underlying: &'a ResolutionGraph,
70-
marker_env: Option<&'a MarkerEnvironment>,
71+
marker_env: &'a ResolverMarkers,
7172
no_emit_packages: &'a [PackageName],
7273
show_hashes: bool,
7374
include_extras: bool,
@@ -97,12 +98,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
9798
let sources = if self.include_annotations {
9899
let mut sources = SourceAnnotations::default();
99100

100-
for requirement in self
101-
.resolution
102-
.requirements
103-
.iter()
104-
.filter(|requirement| requirement.evaluate_markers(self.marker_env, &[]))
105-
{
101+
for requirement in self.resolution.requirements.iter().filter(|requirement| {
102+
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
103+
}) {
106104
if let Some(origin) = &requirement.origin {
107105
sources.add(
108106
&requirement.name,
@@ -115,7 +113,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
115113
.resolution
116114
.constraints
117115
.requirements()
118-
.filter(|requirement| requirement.evaluate_markers(self.marker_env, &[]))
116+
.filter(|requirement| {
117+
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
118+
})
119119
{
120120
if let Some(origin) = &requirement.origin {
121121
sources.add(
@@ -129,7 +129,9 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> {
129129
.resolution
130130
.overrides
131131
.requirements()
132-
.filter(|requirement| requirement.evaluate_markers(self.marker_env, &[]))
132+
.filter(|requirement| {
133+
requirement.evaluate_markers(self.marker_env.marker_environment(), &[])
134+
})
133135
{
134136
if let Some(origin) = &requirement.origin {
135137
sources.add(

0 commit comments

Comments
 (0)