Skip to content

Commit 94db932

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 64abeb2 commit 94db932

File tree

4 files changed

+38
-31
lines changed

4 files changed

+38
-31
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 {

src/doc/src/reference/unstable.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,9 @@ This was stabilized in 1.79 in [#13608](https://github.com/rust-lang/cargo/pull/
348348
- `package.edition = "2024"` (only in workspace root)
349349

350350
The resolver will prefer dependencies with a `package.rust-version` that is the same or older than your project's MSRV.
351-
Your project's MSRV is determined by taking the lowest `package.rust-version` set among your workspace members.
351+
As the resolver is unable to determine which workspace members will eventually
352+
depend on a package when it is being selected, we prioritize versions based on
353+
how many workspace member MSRVs they are compatible with.
352354
If there is no MSRV set then your toolchain version will be used, allowing it to pick up the toolchain version from pinned in rustup (e.g. `rust-toolchain.toml`).
353355

354356
#### `resolver.incompatible-rust-versions`

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)