From ac8de36bf909e4ed2926d90dcbfee33682042dfe Mon Sep 17 00:00:00 2001 From: Bogay Date: Fri, 25 Jul 2025 01:44:27 +0800 Subject: [PATCH 1/2] feat: improve error message for `rustup which` Also adds more tests for `rustup which`. Includes: - ask binary included by uninstalled component - ask binary included by uninstalled component with non-default toolchain - ask binary not known to be included by what toolchain --- src/cli/rustup_mode.rs | 29 ++++++++++++++----- tests/suite/cli_misc.rs | 64 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index dc613b8f8f..c8b2b45ea4 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -19,7 +19,6 @@ use itertools::Itertools; use tracing::{info, trace, warn}; use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; -use crate::dist::AutoInstallMode; use crate::{ cli::{ common::{self, PackageUpdate, update_console_filter}, @@ -28,10 +27,10 @@ use crate::{ self_update::{self, SelfUpdateMode, check_rustup_update}, topical_doc, }, - command, + command, component_for_bin, config::{ActiveReason, Cfg}, dist::{ - PartialToolchainDesc, Profile, TargetTriple, + AutoInstallMode, PartialToolchainDesc, Profile, TargetTriple, manifest::{Component, ComponentStatus}, }, errors::RustupError, @@ -1026,12 +1025,28 @@ async fn which( binary: &str, toolchain: Option, ) -> Result { - let binary_path = cfg.resolve_toolchain(toolchain).await?.binary_file(binary); + let toolchain = cfg.resolve_toolchain(toolchain).await?; + let binary_path = toolchain.binary_file(binary); + if utils::is_file(&binary_path) { + writeln!(cfg.process.stdout().lock(), "{}", binary_path.display())?; + return Ok(utils::ExitCode(0)); + } + + let toolchain_name = toolchain.name(); + let Some(component_name) = component_for_bin(binary) else { + return Err(anyhow!( + "unknown binary '{binary}' in toolchain '{toolchain_name}'", + )); + }; - utils::assert_is_file(&binary_path)?; + let selector = match cfg.active_toolchain() { + Ok(Some((t, _))) if &t == toolchain_name => String::new(), + _ => format!("--toolchain {} ", toolchain.name()), + }; - writeln!(cfg.process.stdout().lock(), "{}", binary_path.display())?; - Ok(utils::ExitCode(0)) + Err(anyhow!( + "'{binary}' is not installed for the toolchain '{toolchain_name}'.\nTo install, run `rustup component add {selector}{component_name}`" + )) } #[tracing::instrument(level = "trace", skip_all)] diff --git a/tests/suite/cli_misc.rs b/tests/suite/cli_misc.rs index ac8f2ceccc..22309e218c 100644 --- a/tests/suite/cli_misc.rs +++ b/tests/suite/cli_misc.rs @@ -1384,6 +1384,70 @@ async fn which_asking_uninstalled_toolchain() { .is_ok(); } +#[tokio::test] +async fn which_asking_uninstalled_components() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + + let path_1 = cx.config.customdir.join("custom-1"); + let path_1 = path_1.to_string_lossy(); + cx.config + .expect(["rustup", "toolchain", "link", "custom-1", &path_1]) + .await + .is_ok(); + cx.config + .expect(["rustup", "default", "custom-1"]) + .await + .is_ok(); + cx.config + .expect(["rustup", "which", "rustfmt"]) + .await + .with_stderr(snapbox::str![[r#" +error: 'rustfmt' is not installed for the toolchain 'custom-1'. +[..]`rustup component add rustfmt` + +"#]]) + .is_err(); + + let path_2 = cx.config.customdir.join("custom-2"); + let path_2 = path_2.to_string_lossy(); + cx.config + .expect(["rustup", "toolchain", "link", "custom-2", &path_2]) + .await + .is_ok(); + cx.config + .expect(["rustup", "which", "--toolchain=custom-2", "rustfmt"]) + .await + .with_stderr(snapbox::str![[r#" +[..]'rustfmt' is not installed for the toolchain 'custom-2'. +[..]`rustup component add --toolchain custom-2 rustfmt` + +"#]]) + .is_err(); +} + +#[tokio::test] +async fn which_unknown_binary() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + let path_1 = cx.config.customdir.join("custom-1"); + let path_1 = path_1.to_string_lossy(); + cx.config + .expect(["rustup", "toolchain", "link", "custom-1", &path_1]) + .await + .is_ok(); + cx.config + .expect(["rustup", "default", "custom-1"]) + .await + .is_ok(); + cx.config + .expect(["rustup", "which", "ls"]) + .await + .with_stderr(snapbox::str![[r#" +[..]unknown binary 'ls' in toolchain 'custom-1' + +"#]]) + .is_err(); +} + #[tokio::test] async fn override_by_toolchain_on_the_command_line() { let cx = CliTestContext::new(Scenario::SimpleV2).await; From f2f25a79b1176433e5c825b4db72170b2cf55a4f Mon Sep 17 00:00:00 2001 From: Bogay Date: Tue, 29 Jul 2025 23:27:41 +0800 Subject: [PATCH 2/2] feat(rustup_mode): revise help message to suggest command to install missing components. make them more consistent. --- src/cli/rustup_mode.rs | 9 +++------ src/toolchain/distributable.rs | 2 +- tests/suite/cli_misc.rs | 8 ++++---- tests/suite/cli_rustup.rs | 2 +- tests/suite/cli_self_upd.rs | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index c8b2b45ea4..80d6074cb6 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1045,7 +1045,7 @@ async fn which( }; Err(anyhow!( - "'{binary}' is not installed for the toolchain '{toolchain_name}'.\nTo install, run `rustup component add {selector}{component_name}`" + "'{binary}' is not installed for the toolchain '{toolchain_name}'.\nhelp: run `rustup component add {selector}{component_name}` to install it" )) } @@ -1714,11 +1714,8 @@ async fn doc( .as_slice() { info!( - "`rust-docs` not installed in toolchain `{}`", - distributable.desc() - ); - info!( - "To install, try `rustup component add --toolchain {} rust-docs`", + "`rust-docs` not installed in toolchain `{}`\nhelp: run `rustup component add --toolchain {} rust-docs` to install it", + distributable.desc(), distributable.desc() ); return Err(anyhow!( diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index 6441b5272f..94a84e0c62 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -453,7 +453,7 @@ impl<'a> DistributableToolchain<'a> { _ => format!("--toolchain {} ", self.toolchain.name()), }; Err(anyhow!( - "'{binary_lossy}' is not installed for the toolchain '{desc}'.\nTo install, run `rustup component add {selector}{component_name}`" + "'{binary_lossy}' is not installed for the toolchain '{desc}'.\nhelp: run `rustup component add {selector}{component_name}` to install it" )) } } else { diff --git a/tests/suite/cli_misc.rs b/tests/suite/cli_misc.rs index 22309e218c..ba4c13e821 100644 --- a/tests/suite/cli_misc.rs +++ b/tests/suite/cli_misc.rs @@ -705,7 +705,7 @@ async fn run_rls_when_not_installed() { .await .with_stderr(snapbox::str![[r#" error: 'rls[EXE]' is not installed for the toolchain 'stable-[HOST_TRIPLE]'. -To install, run `rustup component add rls` +help: run `rustup component add rls` to install it "#]]) .is_err(); @@ -727,7 +727,7 @@ async fn run_rls_when_not_installed_for_nightly() { .await .with_stderr(snapbox::str![[r#" error: 'rls[EXE]' is not installed for the toolchain 'nightly-[HOST_TRIPLE]'. -To install, run `rustup component add --toolchain nightly-[HOST_TRIPLE] rls` +help: run `rustup component add --toolchain nightly-[HOST_TRIPLE] rls` to install it "#]]) .is_err(); @@ -1403,7 +1403,7 @@ async fn which_asking_uninstalled_components() { .await .with_stderr(snapbox::str![[r#" error: 'rustfmt' is not installed for the toolchain 'custom-1'. -[..]`rustup component add rustfmt` +[..]`rustup component add rustfmt`[..] "#]]) .is_err(); @@ -1419,7 +1419,7 @@ error: 'rustfmt' is not installed for the toolchain 'custom-1'. .await .with_stderr(snapbox::str![[r#" [..]'rustfmt' is not installed for the toolchain 'custom-2'. -[..]`rustup component add --toolchain custom-2 rustfmt` +[..]`rustup component add --toolchain custom-2 rustfmt`[..] "#]]) .is_err(); diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 78ae856244..6a23550875 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -3342,7 +3342,7 @@ async fn docs_missing() { .await .with_stderr(snapbox::str![[r#" info: `rust-docs` not installed in toolchain `nightly-[HOST_TRIPLE]` -info: To install, try `rustup component add --toolchain nightly-[HOST_TRIPLE] rust-docs` +help: run `rustup component add --toolchain nightly-[HOST_TRIPLE] rust-docs` to install it error: unable to view documentation which is not installed "#]]) diff --git a/tests/suite/cli_self_upd.rs b/tests/suite/cli_self_upd.rs index 2ab18ba908..6ca1ad1d5f 100644 --- a/tests/suite/cli_self_upd.rs +++ b/tests/suite/cli_self_upd.rs @@ -1020,7 +1020,7 @@ async fn rls_proxy_set_up_after_install() { .await .with_stderr(snapbox::str![[r#" error: 'rls[EXE]' is not installed for the toolchain 'stable-[HOST_TRIPLE]'. -To install, run `rustup component add rls` +help: run `rustup component add rls` to install it "#]]) .is_err();