Skip to content

Commit 19a0404

Browse files
committed
Auto merge of #12930 - epage:msrv-refactor, r=Eh2406
refactor(resolver): Consolidate logic in `VersionPreferences` ### What does this PR try to resolve? This makes customizing the resolver less intrusive by putting the logic in `VersionPreferences` and making it easy to add new priority cases to it. In particular, this is prep for tweaking the MSRV resolver to prefer compatible versions, rather than require them. See https://rust-lang.zulipchat.com/#narrow/stream/246057-t-cargo/topic/Alternative.20MSRV-aware.20resolver.20approach.3F ### How should we test and review this PR? Each step is broken down into its own commit for easier browsing ### Additional information
2 parents ac9d3ba + 46bb8ee commit 19a0404

File tree

5 files changed

+153
-134
lines changed

5 files changed

+153
-134
lines changed

crates/resolver-tests/src/lib.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::task::Poll;
1212
use std::time::Instant;
1313

1414
use cargo::core::dependency::DepKind;
15-
use cargo::core::resolver::{self, ResolveOpts, VersionPreferences};
15+
use cargo::core::resolver::{self, ResolveOpts, VersionOrdering, VersionPreferences};
1616
use cargo::core::Resolve;
1717
use cargo::core::{Dependency, PackageId, Registry, Summary};
1818
use cargo::core::{GitReference, SourceId};
@@ -190,15 +190,17 @@ pub fn resolve_with_config_raw(
190190
.unwrap();
191191
let opts = ResolveOpts::everything();
192192
let start = Instant::now();
193-
let max_rust_version = None;
193+
let mut version_prefs = VersionPreferences::default();
194+
if config.cli_unstable().minimal_versions {
195+
version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst)
196+
}
194197
let resolve = resolver::resolve(
195198
&[(summary, opts)],
196199
&[],
197200
&mut registry,
198-
&VersionPreferences::default(),
201+
&version_prefs,
199202
Some(config),
200203
true,
201-
max_rust_version,
202204
);
203205

204206
// The largest test in our suite takes less then 30 sec.
@@ -982,14 +984,17 @@ fn meta_test_multiple_versions_strategy() {
982984

983985
/// Assert `xs` contains `elems`
984986
#[track_caller]
985-
pub fn assert_contains<A: PartialEq>(xs: &[A], elems: &[A]) {
987+
pub fn assert_contains<A: PartialEq + std::fmt::Debug>(xs: &[A], elems: &[A]) {
986988
for elem in elems {
987-
assert!(xs.contains(elem));
989+
assert!(
990+
xs.contains(elem),
991+
"missing element\nset: {xs:?}\nmissing: {elem:?}"
992+
);
988993
}
989994
}
990995

991996
#[track_caller]
992-
pub fn assert_same<A: PartialEq>(a: &[A], b: &[A]) {
993-
assert_eq!(a.len(), b.len());
997+
pub fn assert_same<A: PartialEq + std::fmt::Debug>(a: &[A], b: &[A]) {
998+
assert_eq!(a.len(), b.len(), "not equal\n{a:?}\n{b:?}");
994999
assert_contains(b, a);
9951000
}

src/cargo/core/resolver/dep_cache.rs

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ use crate::core::{Dependency, FeatureValue, PackageId, PackageIdSpec, Registry,
2020
use crate::sources::source::QueryKind;
2121
use crate::util::errors::CargoResult;
2222
use crate::util::interning::InternedString;
23-
use crate::util::RustVersion;
2423

2524
use anyhow::Context as _;
2625
use std::collections::{BTreeSet, HashMap, HashSet};
@@ -32,16 +31,11 @@ pub struct RegistryQueryer<'a> {
3231
pub registry: &'a mut (dyn Registry + 'a),
3332
replacements: &'a [(PackageIdSpec, Dependency)],
3433
version_prefs: &'a VersionPreferences,
35-
/// If set the list of dependency candidates will be sorted by minimal
36-
/// versions first. That allows `cargo update -Z minimal-versions` which will
37-
/// specify minimum dependency versions to be used.
38-
minimal_versions: bool,
39-
max_rust_version: Option<RustVersion>,
40-
/// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_minimal_version`)
41-
registry_cache: HashMap<(Dependency, bool), Poll<Rc<Vec<Summary>>>>,
34+
/// a cache of `Candidate`s that fulfil a `Dependency` (and whether `first_version`)
35+
registry_cache: HashMap<(Dependency, Option<VersionOrdering>), Poll<Rc<Vec<Summary>>>>,
4236
/// a cache of `Dependency`s that are required for a `Summary`
4337
///
44-
/// HACK: `first_minimal_version` is not kept in the cache key is it is 1:1 with
38+
/// HACK: `first_version` is not kept in the cache key is it is 1:1 with
4539
/// `parent.is_none()` (the first element of the cache key) as it doesn't change through
4640
/// execution.
4741
summary_cache: HashMap<
@@ -57,15 +51,11 @@ impl<'a> RegistryQueryer<'a> {
5751
registry: &'a mut dyn Registry,
5852
replacements: &'a [(PackageIdSpec, Dependency)],
5953
version_prefs: &'a VersionPreferences,
60-
minimal_versions: bool,
61-
max_rust_version: Option<&RustVersion>,
6254
) -> Self {
6355
RegistryQueryer {
6456
registry,
6557
replacements,
6658
version_prefs,
67-
minimal_versions,
68-
max_rust_version: max_rust_version.cloned(),
6959
registry_cache: HashMap::new(),
7060
summary_cache: HashMap::new(),
7161
used_replacements: HashMap::new(),
@@ -106,23 +96,20 @@ impl<'a> RegistryQueryer<'a> {
10696
pub fn query(
10797
&mut self,
10898
dep: &Dependency,
109-
first_minimal_version: bool,
99+
first_version: Option<VersionOrdering>,
110100
) -> Poll<CargoResult<Rc<Vec<Summary>>>> {
111-
let registry_cache_key = (dep.clone(), first_minimal_version);
101+
let registry_cache_key = (dep.clone(), first_version);
112102
if let Some(out) = self.registry_cache.get(&registry_cache_key).cloned() {
113103
return out.map(Result::Ok);
114104
}
115105

116106
let mut ret = Vec::new();
117107
let ready = self.registry.query(dep, QueryKind::Exact, &mut |s| {
118-
if self.max_rust_version.is_none() || s.rust_version() <= self.max_rust_version.as_ref()
119-
{
120-
ret.push(s);
121-
}
108+
ret.push(s);
122109
})?;
123110
if ready.is_pending() {
124111
self.registry_cache
125-
.insert((dep.clone(), first_minimal_version), Poll::Pending);
112+
.insert((dep.clone(), first_version), Poll::Pending);
126113
return Poll::Pending;
127114
}
128115
for summary in ret.iter() {
@@ -144,7 +131,7 @@ impl<'a> RegistryQueryer<'a> {
144131
Poll::Ready(s) => s.into_iter(),
145132
Poll::Pending => {
146133
self.registry_cache
147-
.insert((dep.clone(), first_minimal_version), Poll::Pending);
134+
.insert((dep.clone(), first_version), Poll::Pending);
148135
return Poll::Pending;
149136
}
150137
};
@@ -215,16 +202,8 @@ impl<'a> RegistryQueryer<'a> {
215202
}
216203
}
217204

218-
// When we attempt versions for a package we'll want to do so in a sorted fashion to pick
219-
// the "best candidates" first. VersionPreferences implements this notion.
220-
let ordering = if first_minimal_version || self.minimal_versions {
221-
VersionOrdering::MinimumVersionsFirst
222-
} else {
223-
VersionOrdering::MaximumVersionsFirst
224-
};
225-
let first_version = first_minimal_version;
226-
self.version_prefs
227-
.sort_summaries(&mut ret, ordering, first_version);
205+
let first_version = first_version;
206+
self.version_prefs.sort_summaries(&mut ret, first_version);
228207

229208
let out = Poll::Ready(Rc::new(ret));
230209

@@ -243,7 +222,7 @@ impl<'a> RegistryQueryer<'a> {
243222
parent: Option<PackageId>,
244223
candidate: &Summary,
245224
opts: &ResolveOpts,
246-
first_minimal_version: bool,
225+
first_version: Option<VersionOrdering>,
247226
) -> ActivateResult<Rc<(HashSet<InternedString>, Rc<Vec<DepInfo>>)>> {
248227
// if we have calculated a result before, then we can just return it,
249228
// as it is a "pure" query of its arguments.
@@ -263,24 +242,22 @@ impl<'a> RegistryQueryer<'a> {
263242
let mut all_ready = true;
264243
let mut deps = deps
265244
.into_iter()
266-
.filter_map(
267-
|(dep, features)| match self.query(&dep, first_minimal_version) {
268-
Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))),
269-
Poll::Pending => {
270-
all_ready = false;
271-
// we can ignore Pending deps, resolve will be repeatedly called
272-
// until there are none to ignore
273-
None
274-
}
275-
Poll::Ready(Err(e)) => Some(Err(e).with_context(|| {
276-
format!(
277-
"failed to get `{}` as a dependency of {}",
278-
dep.package_name(),
279-
describe_path_in_context(cx, &candidate.package_id()),
280-
)
281-
})),
282-
},
283-
)
245+
.filter_map(|(dep, features)| match self.query(&dep, first_version) {
246+
Poll::Ready(Ok(candidates)) => Some(Ok((dep, candidates, features))),
247+
Poll::Pending => {
248+
all_ready = false;
249+
// we can ignore Pending deps, resolve will be repeatedly called
250+
// until there are none to ignore
251+
None
252+
}
253+
Poll::Ready(Err(e)) => Some(Err(e).with_context(|| {
254+
format!(
255+
"failed to get `{}` as a dependency of {}",
256+
dep.package_name(),
257+
describe_path_in_context(cx, &candidate.package_id()),
258+
)
259+
})),
260+
})
284261
.collect::<CargoResult<Vec<DepInfo>>>()?;
285262

286263
// Attempt to resolve dependencies with fewer candidates before trying

src/cargo/core/resolver/mod.rs

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ use crate::util::config::Config;
7171
use crate::util::errors::CargoResult;
7272
use crate::util::network::PollExt;
7373
use crate::util::profile;
74-
use crate::util::RustVersion;
7574

7675
use self::context::Context;
7776
use self::dep_cache::RegistryQueryer;
@@ -139,39 +138,18 @@ pub fn resolve(
139138
version_prefs: &VersionPreferences,
140139
config: Option<&Config>,
141140
check_public_visible_dependencies: bool,
142-
mut max_rust_version: Option<&RustVersion>,
143141
) -> CargoResult<Resolve> {
144142
let _p = profile::start("resolving");
145-
let minimal_versions = match config {
146-
Some(config) => config.cli_unstable().minimal_versions,
147-
None => false,
148-
};
149-
let direct_minimal_versions = match config {
150-
Some(config) => config.cli_unstable().direct_minimal_versions,
151-
None => false,
143+
let first_version = match config {
144+
Some(config) if config.cli_unstable().direct_minimal_versions => {
145+
Some(VersionOrdering::MinimumVersionsFirst)
146+
}
147+
_ => None,
152148
};
153-
if !config
154-
.map(|c| c.cli_unstable().msrv_policy)
155-
.unwrap_or(false)
156-
{
157-
max_rust_version = None;
158-
}
159-
let mut registry = RegistryQueryer::new(
160-
registry,
161-
replacements,
162-
version_prefs,
163-
minimal_versions,
164-
max_rust_version,
165-
);
149+
let mut registry = RegistryQueryer::new(registry, replacements, version_prefs);
166150
let cx = loop {
167151
let cx = Context::new(check_public_visible_dependencies);
168-
let cx = activate_deps_loop(
169-
cx,
170-
&mut registry,
171-
summaries,
172-
direct_minimal_versions,
173-
config,
174-
)?;
152+
let cx = activate_deps_loop(cx, &mut registry, summaries, first_version, config)?;
175153
if registry.reset_pending() {
176154
break cx;
177155
} else {
@@ -223,7 +201,7 @@ fn activate_deps_loop(
223201
mut cx: Context,
224202
registry: &mut RegistryQueryer<'_>,
225203
summaries: &[(Summary, ResolveOpts)],
226-
direct_minimal_versions: bool,
204+
first_version: Option<VersionOrdering>,
227205
config: Option<&Config>,
228206
) -> CargoResult<Context> {
229207
let mut backtrack_stack = Vec::new();
@@ -241,7 +219,7 @@ fn activate_deps_loop(
241219
registry,
242220
None,
243221
summary.clone(),
244-
direct_minimal_versions,
222+
first_version,
245223
opts,
246224
);
247225
match res {
@@ -441,13 +419,13 @@ fn activate_deps_loop(
441419
dep.package_name(),
442420
candidate.version()
443421
);
444-
let direct_minimal_version = false; // this is an indirect dependency
422+
let first_version = None; // this is an indirect dependency
445423
let res = activate(
446424
&mut cx,
447425
registry,
448426
Some((&parent, &dep)),
449427
candidate,
450-
direct_minimal_version,
428+
first_version,
451429
&opts,
452430
);
453431

@@ -659,7 +637,7 @@ fn activate(
659637
registry: &mut RegistryQueryer<'_>,
660638
parent: Option<(&Summary, &Dependency)>,
661639
candidate: Summary,
662-
first_minimal_version: bool,
640+
first_version: Option<VersionOrdering>,
663641
opts: &ResolveOpts,
664642
) -> ActivateResult<Option<(DepsFrame, Duration)>> {
665643
let candidate_pid = candidate.package_id();
@@ -716,7 +694,7 @@ fn activate(
716694
parent.map(|p| p.0.package_id()),
717695
&candidate,
718696
opts,
719-
first_minimal_version,
697+
first_version,
720698
)?;
721699

722700
// Record what list of features is active for this package.
@@ -905,14 +883,14 @@ fn generalize_conflicting(
905883
})
906884
{
907885
for critical_parents_dep in critical_parents_deps.iter() {
908-
// We only want `first_minimal_version=true` for direct dependencies of workspace
886+
// We only want `first_version.is_some()` for direct dependencies of workspace
909887
// members which isn't the case here as this has a `parent`
910-
let first_minimal_version = false;
888+
let first_version = None;
911889
// A dep is equivalent to one of the things it can resolve to.
912890
// Thus, if all the things it can resolve to have already ben determined
913891
// to be conflicting, then we can just say that we conflict with the parent.
914892
if let Some(others) = registry
915-
.query(critical_parents_dep, first_minimal_version)
893+
.query(critical_parents_dep, first_version)
916894
.expect("an already used dep now error!?")
917895
.expect("an already used dep now pending!?")
918896
.iter()

0 commit comments

Comments
 (0)