Skip to content

Commit 79ade56

Browse files
committed
wip
1 parent 4ad131c commit 79ade56

8 files changed

+183
-91
lines changed

crates/uv-resolver/src/graph_ops.rs

Lines changed: 100 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
55
use std::collections::hash_map::Entry;
66

77
use uv_normalize::{ExtraName, PackageName};
8+
use uv_pypi_types::Conflicts;
89

910
use crate::resolution::ResolutionGraphNode;
1011
use crate::universal_marker::UniversalMarker;
@@ -88,11 +89,21 @@ pub(crate) fn marker_reachability<T>(
8889
/// For example, given an edge like `foo[x1] -> bar`, then it is known that
8990
/// `x1` is activated. This in turn can be used to simplify any downstream
9091
/// conflict markers with `extra == "x1"` in them.
91-
pub(crate) fn simplify_conflict_markers(graph: &mut Graph<ResolutionGraphNode, UniversalMarker>) {
92+
pub(crate) fn simplify_conflict_markers(
93+
conflicts: &Conflicts,
94+
graph: &mut Graph<ResolutionGraphNode, UniversalMarker>,
95+
) {
96+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
97+
struct Inference {
98+
package: PackageName,
99+
extra: ExtraName,
100+
included: bool,
101+
}
102+
92103
// The set of activated extras (and TODO, in the future, groups)
93104
// for each node. The ROOT nodes don't have any extras activated.
94-
let mut activated: FxHashMap<NodeIndex, FxHashSet<(PackageName, ExtraName)>> =
95-
FxHashMap::with_capacity_and_hasher(graph.node_count(), FxBuildHasher);
105+
let mut activated: FxHashMap<NodeIndex, Vec<FxHashSet<(PackageName, ExtraName)>>> =
106+
FxHashMap::default();
96107

97108
// Collect the root nodes.
98109
//
@@ -108,35 +119,101 @@ pub(crate) fn simplify_conflict_markers(graph: &mut Graph<ResolutionGraphNode, U
108119
})
109120
.collect();
110121

111-
let mut assume_by_edge: FxHashMap<EdgeIndex, FxHashSet<(PackageName, ExtraName)>> =
112-
FxHashMap::default();
122+
// let mut assume_by_edge: FxHashMap<EdgeIndex, FxHashSet<(PackageName, ExtraName)>> =
123+
// FxHashMap::default();
113124
let mut seen: FxHashSet<NodeIndex> = FxHashSet::default();
114125
while let Some(parent_index) = queue.pop() {
115-
for child_edge in graph.edges_directed(parent_index, Direction::Outgoing) {
116-
// TODO: The below seems excessively clone-y.
117-
// Consider tightening this up a bit.
118-
let target = child_edge.target();
119-
let mut extras: FxHashSet<(PackageName, ExtraName)> =
120-
activated.get(&parent_index).cloned().unwrap_or_default();
121-
if let Some((package, extra)) = graph[parent_index].package_extra_names() {
122-
extras.insert((package.clone(), extra.clone()));
126+
if let Some((package, extra)) = graph[parent_index].package_extra_names() {
127+
for set in activated
128+
.entry(parent_index)
129+
.or_insert_with(|| vec![FxHashSet::default()])
130+
{
131+
set.insert((package.clone(), extra.clone()));
123132
}
124-
if let Some((package, extra)) = graph[target].package_extra_names() {
125-
extras.insert((package.clone(), extra.clone()));
133+
}
134+
let sets = activated.get(&parent_index).cloned().unwrap_or_default();
135+
for child_edge in graph.edges_directed(parent_index, Direction::Outgoing) {
136+
for set in sets.clone() {
137+
activated.entry(child_edge.target()).or_default().push(set);
126138
}
127-
activated.entry(target).or_default().extend(extras.clone());
128-
assume_by_edge
129-
.entry(child_edge.id())
130-
.or_default()
131-
.extend(extras);
132139
if seen.insert(child_edge.target()) {
133140
queue.push(child_edge.target());
134141
}
135142
}
136143
}
137-
for (edge_id, extras) in assume_by_edge {
138-
for &(ref package, ref extra) in &extras {
139-
graph[edge_id].assume_extra(package, extra);
144+
145+
let mut inferences: FxHashMap<NodeIndex, Vec<FxHashSet<Inference>>> = FxHashMap::default();
146+
for (node_id, sets) in activated {
147+
let mut new_sets = vec![];
148+
for set in sets {
149+
let definitive_conflict = conflicts.iter().any(|conflict_set| {
150+
set.iter()
151+
.filter(|(package, extra)| conflict_set.contains(package, extra))
152+
.count()
153+
>= 2
154+
});
155+
if definitive_conflict {
156+
continue;
157+
}
158+
159+
let mut new_set = FxHashSet::default();
160+
for (package, extra) in set {
161+
for conflict_set in conflicts.iter() {
162+
if !conflict_set.contains(&package, &extra) {
163+
continue;
164+
}
165+
for conflict_item in conflict_set.iter() {
166+
let Some(conflict_item_extra) = conflict_item.extra() else {
167+
continue;
168+
};
169+
if conflict_item.package() == &package && conflict_item_extra == &extra {
170+
continue;
171+
}
172+
new_set.insert(Inference {
173+
package: conflict_item.package().clone(),
174+
extra: conflict_item_extra.clone(),
175+
included: false,
176+
});
177+
}
178+
}
179+
new_set.insert(Inference {
180+
package,
181+
extra,
182+
included: true,
183+
});
184+
}
185+
new_sets.push(new_set);
186+
}
187+
inferences.insert(node_id, new_sets);
188+
}
189+
190+
for edge_index in (0..graph.edge_count()).map(EdgeIndex::new) {
191+
let (from_index, _) = graph.edge_endpoints(edge_index).unwrap();
192+
let Some(inference) = inferences.get(&from_index) else {
193+
continue;
194+
};
195+
let all_paths_satisfied = inference.iter().all(|set| {
196+
let extras = set
197+
.iter()
198+
.filter_map(|inf| {
199+
if !inf.included {
200+
return None;
201+
}
202+
Some((inf.package.clone(), inf.extra.clone()))
203+
})
204+
.collect::<Vec<(PackageName, ExtraName)>>();
205+
graph[edge_index].conflict().evaluate(&extras)
206+
});
207+
if all_paths_satisfied {
208+
for set in inference {
209+
for inf in set {
210+
if inf.included {
211+
graph[edge_index].assume_extra(&inf.package, &inf.extra);
212+
} else {
213+
graph[edge_index].assume_not_extra(&inf.package, &inf.extra);
214+
}
215+
}
216+
}
140217
}
141218
}
142219
}

crates/uv-resolver/src/lock/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl Lock {
147147
resolution
148148
.fork_markers
149149
.iter()
150-
.filter(|fork_markers| !fork_markers.is_disjoint(&dist.marker))
150+
.filter(|fork_markers| !fork_markers.is_disjoint(dist.marker))
151151
.copied()
152152
.collect()
153153
} else {
@@ -294,16 +294,16 @@ impl Lock {
294294
tag.starts_with(linux_tag) || tag == "linux_armv6l" || tag == "linux_armv7l"
295295
})
296296
}) {
297-
!graph.graph[node_index].marker().is_disjoint(&LINUX_MARKERS)
297+
!graph.graph[node_index].marker().is_disjoint(*LINUX_MARKERS)
298298
} else if platform_tags
299299
.iter()
300300
.all(|tag| windows_tags.contains(&&**tag))
301301
{
302302
!graph.graph[node_index]
303303
.marker()
304-
.is_disjoint(&WINDOWS_MARKERS)
304+
.is_disjoint(*WINDOWS_MARKERS)
305305
} else if platform_tags.iter().all(|tag| tag.starts_with("macosx_")) {
306-
!graph.graph[node_index].marker().is_disjoint(&MAC_MARKERS)
306+
!graph.graph[node_index].marker().is_disjoint(*MAC_MARKERS)
307307
} else {
308308
true
309309
}
@@ -3537,7 +3537,7 @@ impl Dependency {
35373537
complexified_marker: UniversalMarker,
35383538
) -> Dependency {
35393539
let simplified_marker =
3540-
SimplifiedMarkerTree::new(requires_python, complexified_marker.pep508());
3540+
SimplifiedMarkerTree::new(requires_python, complexified_marker.combined());
35413541
Dependency {
35423542
package_id,
35433543
extra,
@@ -3582,12 +3582,14 @@ impl Dependency {
35823582
if let Some(marker) = self.simplified_marker.try_to_string() {
35833583
table.insert("marker", value(marker));
35843584
}
3585+
/*
35853586
if !self.complexified_marker.conflict().is_true() {
35863587
table.insert(
35873588
"conflict-marker",
3588-
value(self.complexified_marker.conflict().to_string()),
3589+
value(self.complexified_marker.combined().to_string()),
35893590
);
35903591
}
3592+
*/
35913593

35923594
table
35933595
}

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_unambiguous.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ Ok(
136136
true,
137137
),
138138
complexified_marker: UniversalMarker {
139-
pep508_marker: python_full_version >= '3.12',
140-
conflict_marker: ConflictMarker(true),
139+
marker: python_full_version >= '3.12',
141140
},
142141
},
143142
],

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_source_version_unambiguous.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ Ok(
136136
true,
137137
),
138138
complexified_marker: UniversalMarker {
139-
pep508_marker: python_full_version >= '3.12',
140-
conflict_marker: ConflictMarker(true),
139+
marker: python_full_version >= '3.12',
141140
},
142141
},
143142
],

crates/uv-resolver/src/lock/snapshots/uv_resolver__lock__tests__missing_dependency_version_unambiguous.snap

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ Ok(
136136
true,
137137
),
138138
complexified_marker: UniversalMarker {
139-
pep508_marker: python_full_version >= '3.12',
140-
conflict_marker: ConflictMarker(true),
139+
marker: python_full_version >= '3.12',
141140
},
142141
},
143142
],

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ impl ResolverOutput {
200200
for weight in graph.edge_weights_mut() {
201201
weight.imbibe(conflicts);
202202
}
203-
simplify_conflict_markers(&mut graph);
203+
204+
simplify_conflict_markers(conflicts, &mut graph);
204205

205206
// Discard any unreachable nodes.
206207
graph.retain_nodes(|graph, node| !graph[node].marker().is_false());
@@ -746,8 +747,8 @@ impl ResolverOutput {
746747
}
747748
let mut dupes = vec![];
748749
for (name, marker_trees) in name_to_markers {
749-
for (i, (version1, marker1)) in marker_trees.iter().enumerate() {
750-
for (version2, marker2) in &marker_trees[i + 1..] {
750+
for (i, (version1, &marker1)) in marker_trees.iter().enumerate() {
751+
for (version2, &marker2) in &marker_trees[i + 1..] {
751752
if version1 == version2 {
752753
continue;
753754
}
@@ -756,8 +757,8 @@ impl ResolverOutput {
756757
name: name.clone(),
757758
version1: (*version1).clone(),
758759
version2: (*version2).clone(),
759-
marker1: *(*marker1),
760-
marker2: *(*marker2),
760+
marker1,
761+
marker2,
761762
});
762763
}
763764
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ impl<'dist> RequirementsTxtDist<'dist> {
181181
// OK because we've asserted above that this dist
182182
// does not have a non-trivial conflicting marker
183183
// that we would otherwise need to care about.
184-
markers: annotated.marker.pep508(),
184+
markers: annotated.marker.combined(),
185185
extras: if let Some(extra) = annotated.extra.clone() {
186186
vec![extra]
187187
} else {

0 commit comments

Comments
 (0)