Skip to content

Commit 7d8007c

Browse files
committed
fix(resolve): Improve multi-MSRV workspaces
We do this by resolving for a package version that is compatible with the most number of MSRVs within a workspace. If a version requirement is just right, every package will get the highest MSRV-compatible dependency. If its too high, packages will get MSRV-incompatible dependency versions. This will happen regardless of what we do due to the nature of `"fallback"`. If its too low, packages with higher MSRVs will get older-than-necessary dependency versions. This is similar to the "some with and without MSRV" workspaces. When locking dependencies, we do report to users when newer MSRV/SemVer compatible dependencies are available to help guide them to upgrading if desired. Fixes #14414
1 parent 5ff04c3 commit 7d8007c

File tree

3 files changed

+35
-30
lines changed

3 files changed

+35
-30
lines changed

src/cargo/core/resolver/version_prefs.rs

+23-21
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct VersionPreferences {
2121
try_to_use: HashSet<PackageId>,
2222
prefer_patch_deps: HashMap<InternedString, HashSet<Dependency>>,
2323
version_ordering: VersionOrdering,
24-
max_rust_version: Option<PartialVersion>,
24+
rust_versions: Vec<PartialVersion>,
2525
}
2626

2727
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, Debug)]
@@ -49,8 +49,8 @@ impl VersionPreferences {
4949
self.version_ordering = ordering;
5050
}
5151

52-
pub fn max_rust_version(&mut self, ver: Option<PartialVersion>) {
53-
self.max_rust_version = ver;
52+
pub fn rust_versions(&mut self, vers: Vec<PartialVersion>) {
53+
self.rust_versions = vers;
5454
}
5555

5656
/// Sort (and filter) the given vector of summaries in-place
@@ -59,7 +59,7 @@ impl VersionPreferences {
5959
///
6060
/// Sort order:
6161
/// 1. Preferred packages
62-
/// 2. [`VersionPreferences::max_rust_version`]
62+
/// 2. Most compatible [`VersionPreferences::rust_versions`]
6363
/// 3. `first_version`, falling back to [`VersionPreferences::version_ordering`] when `None`
6464
///
6565
/// Filtering:
@@ -85,20 +85,11 @@ impl VersionPreferences {
8585
return previous_cmp;
8686
}
8787

88-
if let Some(max_rust_version) = &self.max_rust_version {
89-
let a_is_compat = a
90-
.rust_version()
91-
.map(|a| a.is_compatible_with(max_rust_version))
92-
.unwrap_or(true);
93-
let b_is_compat = b
94-
.rust_version()
95-
.map(|b| b.is_compatible_with(max_rust_version))
96-
.unwrap_or(true);
97-
match (a_is_compat, b_is_compat) {
98-
(true, true) => {} // fallback
99-
(false, false) => {} // fallback
100-
(true, false) => return Ordering::Less,
101-
(false, true) => return Ordering::Greater,
88+
if !self.rust_versions.is_empty() {
89+
let a_compat_count = self.msrv_compat_count(a);
90+
let b_compat_count = self.msrv_compat_count(b);
91+
if b_compat_count != a_compat_count {
92+
return b_compat_count.cmp(&a_compat_count);
10293
}
10394
}
10495

@@ -112,6 +103,17 @@ impl VersionPreferences {
112103
let _ = summaries.split_off(1);
113104
}
114105
}
106+
107+
fn msrv_compat_count(&self, summary: &Summary) -> usize {
108+
let Some(rust_version) = summary.rust_version() else {
109+
return self.rust_versions.len();
110+
};
111+
112+
self.rust_versions
113+
.iter()
114+
.filter(|max| rust_version.is_compatible_with(max))
115+
.count()
116+
}
115117
}
116118

117119
#[cfg(test)]
@@ -238,7 +240,7 @@ mod test {
238240
#[test]
239241
fn test_single_rust_version() {
240242
let mut vp = VersionPreferences::default();
241-
vp.max_rust_version(Some("1.50".parse().unwrap()));
243+
vp.rust_versions(vec!["1.50".parse().unwrap()]);
242244

243245
let mut summaries = vec![
244246
summ("foo", "1.2.4", None),
@@ -270,7 +272,7 @@ mod test {
270272
#[test]
271273
fn test_multiple_rust_versions() {
272274
let mut vp = VersionPreferences::default();
273-
vp.max_rust_version(Some("1.45".parse().unwrap()));
275+
vp.rust_versions(vec!["1.45".parse().unwrap(), "1.55".parse().unwrap()]);
274276

275277
let mut summaries = vec![
276278
summ("foo", "1.2.4", None),
@@ -286,7 +288,7 @@ mod test {
286288
vp.sort_summaries(&mut summaries, None);
287289
assert_eq!(
288290
describe(&summaries),
289-
"foo/1.2.4, foo/1.2.2, foo/1.2.0, foo/1.1.0, foo/1.0.9, foo/1.2.3, foo/1.2.1"
291+
"foo/1.2.4, foo/1.2.2, foo/1.2.0, foo/1.1.0, foo/1.0.9, foo/1.2.1, foo/1.2.3"
290292
.to_string()
291293
);
292294

src/cargo/ops/resolve.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ use crate::util::errors::CargoResult;
7979
use crate::util::CanonicalUrl;
8080
use anyhow::Context as _;
8181
use cargo_util::paths;
82+
use cargo_util_schemas::core::PartialVersion;
8283
use std::collections::{HashMap, HashSet};
8384
use tracing::{debug, trace};
8485

@@ -357,14 +358,16 @@ pub fn resolve_with_previous<'gctx>(
357358
version_prefs.version_ordering(VersionOrdering::MinimumVersionsFirst)
358359
}
359360
if ws.resolve_honors_rust_version() {
360-
let rust_version = if let Some(ver) = ws.lowest_rust_version() {
361-
ver.clone().into_partial()
362-
} else {
361+
let mut rust_versions: Vec<_> = ws
362+
.members()
363+
.filter_map(|p| p.rust_version().map(|rv| rv.as_partial().clone()))
364+
.collect();
365+
if rust_versions.is_empty() {
363366
let rustc = ws.gctx().load_global_rustc(Some(ws))?;
364-
let rustc_version = rustc.version.clone().into();
365-
rustc_version
366-
};
367-
version_prefs.max_rust_version(Some(rust_version));
367+
let rust_version: PartialVersion = rustc.version.clone().into();
368+
rust_versions.push(rust_version);
369+
}
370+
version_prefs.rust_versions(rust_versions);
368371
}
369372

370373
let avoid_patch_ids = if register_patches {

tests/testsuite/rust_version.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ higher v0.0.1 ([ROOT]/foo)
510510
.with_stderr_data(str![[r#"
511511
[UPDATING] `dummy-registry` index
512512
[LOCKING] 6 packages to latest Rust 1.50.0 compatible versions
513-
[ADDING] higher-newer-and-older v1.65.0 (requires Rust 1.65.0)
513+
[ADDING] higher-newer-and-older v1.55.0 (available: v1.65.0, requires Rust 1.65.0)
514514
[ADDING] higher-only-newer v1.65.0 (requires Rust 1.65.0)
515515
[ADDING] lower-newer-and-older v1.45.0 (available: v1.55.0, requires Rust 1.55.0)
516516
[ADDING] lower-only-newer v1.65.0 (requires Rust 1.65.0)
@@ -522,7 +522,7 @@ higher v0.0.1 ([ROOT]/foo)
522522
p.cargo("tree")
523523
.with_stdout_data(str![[r#"
524524
higher v0.0.1 ([ROOT]/foo)
525-
├── higher-newer-and-older v1.65.0
525+
├── higher-newer-and-older v1.55.0
526526
├── higher-only-newer v1.65.0
527527
├── shared-newer-and-older v1.45.0
528528
└── shared-only-newer v1.65.0

0 commit comments

Comments
 (0)