Skip to content

Commit 63448f2

Browse files
committed
Change backtracking when packages conflict a lot
1 parent f80ddf1 commit 63448f2

File tree

7 files changed

+313
-40
lines changed

7 files changed

+313
-40
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ petgraph = { version = "0.6.5" }
130130
platform-info = { version = "2.0.3" }
131131
proc-macro2 = { version = "1.0.86" }
132132
procfs = { version = "0.17.0", default-features = false, features = ["flate2"] }
133-
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "57832d0588fbb7aab824813481104761dc1c7740" }
133+
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "036aab424f917b0b8e9b878e402c05e733f312a3" }
134134
quote = { version = "1.0.37" }
135135
rayon = { version = "1.10.0" }
136136
reflink-copy = { version = "0.1.19" }
@@ -175,7 +175,7 @@ unicode-width = { version = "0.1.13" }
175175
unscanny = { version = "0.1.0" }
176176
url = { version = "2.5.2", features = ["serde"] }
177177
urlencoding = { version = "2.1.3" }
178-
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "57832d0588fbb7aab824813481104761dc1c7740" }
178+
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "036aab424f917b0b8e9b878e402c05e733f312a3" }
179179
walkdir = { version = "2.5.0" }
180180
which = { version = "7.0.0", features = ["regex"] }
181181
windows-registry = { version = "0.3.0" }

crates/uv-resolver/src/pubgrub/priority.rs

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::cmp::Reverse;
2-
31
use pubgrub::Range;
42
use rustc_hash::FxHashMap;
3+
use std::cmp::Reverse;
4+
use std::collections::hash_map::OccupiedEntry;
55

66
use crate::fork_urls::ForkUrls;
77
use uv_normalize::PackageName;
@@ -40,19 +40,22 @@ impl PubGrubPriorities {
4040
match self.0.entry(name.clone()) {
4141
std::collections::hash_map::Entry::Occupied(mut entry) => {
4242
// Preserve the original index.
43-
let index = match entry.get() {
44-
PubGrubPriority::Unspecified(Reverse(index)) => *index,
45-
PubGrubPriority::Singleton(Reverse(index)) => *index,
46-
PubGrubPriority::DirectUrl(Reverse(index)) => *index,
47-
PubGrubPriority::Root => next,
48-
};
43+
let index = Self::get_index(next, &mut entry);
4944

5045
// Compute the priority.
5146
let priority = if urls.get(name).is_some() {
5247
PubGrubPriority::DirectUrl(Reverse(index))
5348
} else if version.as_singleton().is_some() {
5449
PubGrubPriority::Singleton(Reverse(index))
5550
} else {
51+
// Keep the conflict-causing packages to avoid loops where we seesaw between
52+
// `Unspecified` and `Conflict*`.
53+
if matches!(
54+
entry.get(),
55+
PubGrubPriority::ConflictEarly(_) | PubGrubPriority::ConflictLate(_)
56+
) {
57+
return;
58+
}
5659
PubGrubPriority::Unspecified(Reverse(index))
5760
};
5861

@@ -77,6 +80,17 @@ impl PubGrubPriorities {
7780
}
7881
}
7982

83+
fn get_index(next: usize, entry: &mut OccupiedEntry<PackageName, PubGrubPriority>) -> usize {
84+
match entry.get() {
85+
PubGrubPriority::ConflictLate(Reverse(index)) => *index,
86+
PubGrubPriority::Unspecified(Reverse(index)) => *index,
87+
PubGrubPriority::ConflictEarly(Reverse(index)) => *index,
88+
PubGrubPriority::Singleton(Reverse(index)) => *index,
89+
PubGrubPriority::DirectUrl(Reverse(index)) => *index,
90+
PubGrubPriority::Root => next,
91+
}
92+
}
93+
8094
/// Return the [`PubGrubPriority`] of the given package, if it exists.
8195
pub(crate) fn get(&self, package: &PubGrubPackage) -> Option<PubGrubPriority> {
8296
match &**package {
@@ -88,6 +102,66 @@ impl PubGrubPriorities {
88102
PubGrubPackageInner::Package { name, .. } => self.0.get(name).copied(),
89103
}
90104
}
105+
106+
/// Returns whether the priority was changed, i.e., it's the first time we hit this condition
107+
/// for the package.
108+
pub(crate) fn make_conflict_early(&mut self, package: &PubGrubPackage) -> bool {
109+
let next = self.0.len();
110+
let Some(name) = package.name_no_root() else {
111+
// Not a correctness bug
112+
assert!(
113+
!cfg!(debug_assertions),
114+
"URL packages must not be involved in conflict handling"
115+
);
116+
return false;
117+
};
118+
match self.0.entry(name.clone()) {
119+
std::collections::hash_map::Entry::Occupied(mut entry) => {
120+
if matches!(entry.get(), PubGrubPriority::ConflictEarly(_)) {
121+
// Already in the right category
122+
return false;
123+
};
124+
let index = Self::get_index(next, &mut entry);
125+
entry.insert(PubGrubPriority::ConflictEarly(Reverse(index)));
126+
true
127+
}
128+
std::collections::hash_map::Entry::Vacant(entry) => {
129+
entry.insert(PubGrubPriority::ConflictEarly(Reverse(next)));
130+
true
131+
}
132+
}
133+
}
134+
135+
pub(crate) fn make_conflict_late(&mut self, package: &PubGrubPackage) -> bool {
136+
let next = self.0.len();
137+
let Some(name) = package.name_no_root() else {
138+
// Not a correctness bug
139+
assert!(
140+
!cfg!(debug_assertions),
141+
"URL packages must not be involved in conflict handling"
142+
);
143+
return false;
144+
};
145+
match self.0.entry(name.clone()) {
146+
std::collections::hash_map::Entry::Occupied(mut entry) => {
147+
// The ConflictEarly` match avoids infinite loops.
148+
if matches!(
149+
entry.get(),
150+
PubGrubPriority::ConflictLate(_) | PubGrubPriority::ConflictEarly(_)
151+
) {
152+
// Already in the right category
153+
return false;
154+
};
155+
let index = Self::get_index(next, &mut entry);
156+
entry.insert(PubGrubPriority::ConflictLate(Reverse(index)));
157+
true
158+
}
159+
std::collections::hash_map::Entry::Vacant(entry) => {
160+
entry.insert(PubGrubPriority::ConflictLate(Reverse(next)));
161+
true
162+
}
163+
}
164+
}
91165
}
92166

93167
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -101,6 +175,15 @@ pub(crate) enum PubGrubPriority {
101175
/// in the dependency graph.
102176
Unspecified(Reverse<usize>),
103177

178+
/// Selected version of this package were often the culprit of rejecting another package, so
179+
/// it's deprioritized behind `ConflictEarly`. It's still the higher than `Unspecified` to
180+
/// conflict before selecting unrelated packages.
181+
ConflictLate(Reverse<usize>),
182+
183+
/// Selected version of this package were often rejected, so it's prioritized over
184+
/// `ConflictLate`.
185+
ConflictEarly(Reverse<usize>),
186+
104187
/// The version range is constrained to a single version (e.g., with the `==` operator).
105188
Singleton(Reverse<usize>),
106189

0 commit comments

Comments
 (0)