Skip to content

Commit 8ba1c31

Browse files
committed
Auto merge of #12798 - epage:msrv-install, r=ehuss
fix(install): Suggest an alternative version on MSRV failure ### What does this PR try to resolve? Moves users from a bad error message, suggesting `--locked` which won't do anything, to suggesting a version of a package to use instead. The side benefit is errors get reported sooner - Before downloading the `.crate` - When installing multiple packages, before building the first This comes at the cost of an extra `rustc` invocation. ### How should we test and review this PR? Per-commit this builds it up, from tests to the final design. ### Additional information This is also written in a way to align fairly well with how we'd likely implement #10903. This improved error message will still be useful after that issue is resolved when the MSRV compatible version is outside of the version req.
2 parents e4fe8f0 + 9b32be7 commit 8ba1c31

File tree

4 files changed

+113
-7
lines changed

4 files changed

+113
-7
lines changed

src/cargo/ops/cargo_install.rs

+39-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ impl<'cfg> InstallablePackage<'cfg> {
6868
force: bool,
6969
no_track: bool,
7070
needs_update_if_source_is_index: bool,
71+
current_rust_version: Option<&semver::Version>,
7172
) -> CargoResult<Option<Self>> {
7273
if let Some(name) = krate {
7374
if name == "." {
@@ -105,6 +106,7 @@ impl<'cfg> InstallablePackage<'cfg> {
105106
dep,
106107
|git: &mut GitSource<'_>| git.read_packages(),
107108
config,
109+
current_rust_version,
108110
)?
109111
} else if source_id.is_path() {
110112
let mut src = path_source(source_id, config)?;
@@ -142,6 +144,7 @@ impl<'cfg> InstallablePackage<'cfg> {
142144
dep,
143145
|path: &mut PathSource<'_>| path.read_packages(),
144146
config,
147+
current_rust_version,
145148
)?
146149
} else if let Some(dep) = dep {
147150
let mut source = map.load(source_id, &HashSet::new())?;
@@ -161,7 +164,13 @@ impl<'cfg> InstallablePackage<'cfg> {
161164
config.shell().status("Ignored", &msg)?;
162165
return Ok(None);
163166
}
164-
select_dep_pkg(&mut source, dep, config, needs_update_if_source_is_index)?
167+
select_dep_pkg(
168+
&mut source,
169+
dep,
170+
config,
171+
needs_update_if_source_is_index,
172+
current_rust_version,
173+
)?
165174
} else {
166175
bail!(
167176
"must specify a crate to install from \
@@ -616,14 +625,40 @@ pub fn install(
616625
let dst = root.join("bin").into_path_unlocked();
617626
let map = SourceConfigMap::new(config)?;
618627

628+
let current_rust_version = if opts.honor_rust_version {
629+
let rustc = config.load_global_rustc(None)?;
630+
631+
// Remove any pre-release identifiers for easier comparison
632+
let current_version = &rustc.version;
633+
let untagged_version = semver::Version::new(
634+
current_version.major,
635+
current_version.minor,
636+
current_version.patch,
637+
);
638+
Some(untagged_version)
639+
} else {
640+
None
641+
};
642+
619643
let (installed_anything, scheduled_error) = if krates.len() <= 1 {
620644
let (krate, vers) = krates
621645
.iter()
622646
.next()
623647
.map(|(k, v)| (Some(k.as_str()), v.as_ref()))
624648
.unwrap_or((None, None));
625649
let installable_pkg = InstallablePackage::new(
626-
config, root, map, krate, source_id, from_cwd, vers, opts, force, no_track, true,
650+
config,
651+
root,
652+
map,
653+
krate,
654+
source_id,
655+
from_cwd,
656+
vers,
657+
opts,
658+
force,
659+
no_track,
660+
true,
661+
current_rust_version.as_ref(),
627662
)?;
628663
let mut installed_anything = true;
629664
if let Some(installable_pkg) = installable_pkg {
@@ -654,6 +689,7 @@ pub fn install(
654689
force,
655690
no_track,
656691
!did_update,
692+
current_rust_version.as_ref(),
657693
) {
658694
Ok(Some(installable_pkg)) => {
659695
did_update = true;
@@ -773,7 +809,7 @@ where
773809
// expensive network call in the case that the package is already installed.
774810
// If this fails, the caller will possibly do an index update and try again, this is just a
775811
// best-effort check to see if we can avoid hitting the network.
776-
if let Ok(pkg) = select_dep_pkg(source, dep, config, false) {
812+
if let Ok(pkg) = select_dep_pkg(source, dep, config, false, None) {
777813
let (_ws, rustc, target) =
778814
make_ws_rustc_target(config, opts, &source.source_id(), pkg.clone())?;
779815
if let Ok(true) = is_installed(&pkg, config, opts, &rustc, &target, root, dst, force) {

src/cargo/ops/cargo_uninstall.rs

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoRe
9090
None,
9191
|path: &mut PathSource<'_>| path.read_packages(),
9292
config,
93+
None,
9394
)?;
9495
let pkgid = pkg.package_id();
9596
uninstall_pkgid(root, tracker, pkgid, bins, config)

src/cargo/ops/common_for_install_and_uninstall.rs

+53-4
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,7 @@ pub fn select_dep_pkg<T>(
532532
dep: Dependency,
533533
config: &Config,
534534
needs_update: bool,
535+
current_rust_version: Option<&semver::Version>,
535536
) -> CargoResult<Package>
536537
where
537538
T: Source,
@@ -551,9 +552,56 @@ where
551552
Poll::Pending => source.block_until_ready()?,
552553
}
553554
};
554-
match deps.iter().map(|p| p.package_id()).max() {
555-
Some(pkgid) => {
556-
let pkg = Box::new(source).download_now(pkgid, config)?;
555+
match deps.iter().max_by_key(|p| p.package_id()) {
556+
Some(summary) => {
557+
if let (Some(current), Some(msrv)) = (current_rust_version, summary.rust_version()) {
558+
let msrv_req = msrv.caret_req();
559+
if !msrv_req.matches(current) {
560+
let name = summary.name();
561+
let ver = summary.version();
562+
let extra = if dep.source_id().is_registry() {
563+
// Match any version, not just the selected
564+
let msrv_dep =
565+
Dependency::parse(dep.package_name(), None, dep.source_id())?;
566+
let msrv_deps = loop {
567+
match source.query_vec(&msrv_dep, QueryKind::Exact)? {
568+
Poll::Ready(deps) => break deps,
569+
Poll::Pending => source.block_until_ready()?,
570+
}
571+
};
572+
if let Some(alt) = msrv_deps
573+
.iter()
574+
.filter(|summary| {
575+
summary
576+
.rust_version()
577+
.map(|msrv| msrv.caret_req().matches(current))
578+
.unwrap_or(true)
579+
})
580+
.max_by_key(|s| s.package_id())
581+
{
582+
if let Some(rust_version) = alt.rust_version() {
583+
format!(
584+
"\n`{name} {}` supports rustc {rust_version}",
585+
alt.version()
586+
)
587+
} else {
588+
format!(
589+
"\n`{name} {}` has an unspecified minimum rustc version",
590+
alt.version()
591+
)
592+
}
593+
} else {
594+
String::new()
595+
}
596+
} else {
597+
String::new()
598+
};
599+
bail!("\
600+
cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}"
601+
)
602+
}
603+
}
604+
let pkg = Box::new(source).download_now(summary.package_id(), config)?;
557605
Ok(pkg)
558606
}
559607
None => {
@@ -599,6 +647,7 @@ pub fn select_pkg<T, F>(
599647
dep: Option<Dependency>,
600648
mut list_all: F,
601649
config: &Config,
650+
current_rust_version: Option<&semver::Version>,
602651
) -> CargoResult<Package>
603652
where
604653
T: Source,
@@ -612,7 +661,7 @@ where
612661
source.invalidate_cache();
613662

614663
return if let Some(dep) = dep {
615-
select_dep_pkg(source, dep, config, false)
664+
select_dep_pkg(source, dep, config, false, current_rust_version)
616665
} else {
617666
let candidates = list_all(source)?;
618667
let binaries = candidates

tests/testsuite/install.rs

+20
Original file line numberDiff line numberDiff line change
@@ -2463,3 +2463,23 @@ For more information, try '--help'.
24632463
.with_status(1)
24642464
.run();
24652465
}
2466+
2467+
#[cargo_test]
2468+
fn install_incompat_msrv() {
2469+
Package::new("foo", "0.1.0")
2470+
.file("src/main.rs", "fn main() {}")
2471+
.rust_version("1.30")
2472+
.publish();
2473+
Package::new("foo", "0.2.0")
2474+
.file("src/main.rs", "fn main() {}")
2475+
.rust_version("1.9876.0")
2476+
.publish();
2477+
2478+
cargo_process("install foo")
2479+
.with_stderr("\
2480+
[UPDATING] `dummy-registry` index
2481+
[ERROR] cannot install package `foo 0.2.0`, it requires rustc 1.9876.0 or newer, while the currently active rustc version is [..]
2482+
`foo 0.1.0` supports rustc 1.30
2483+
")
2484+
.with_status(101).run();
2485+
}

0 commit comments

Comments
 (0)