diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 45e793cfd7..7e22d627dd 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -689,34 +689,40 @@ pub async fn main( toolchain, force_non_host, } => default_(cfg, toolchain, force_non_host).await, - RustupSubcmd::Target { subcmd } => match subcmd { - TargetSubcmd::List { - toolchain, - installed, - quiet, - } => handle_epipe(target_list(cfg, toolchain, installed, quiet).await), - TargetSubcmd::Add { target, toolchain } => target_add(cfg, target, toolchain).await, - TargetSubcmd::Remove { target, toolchain } => { - target_remove(cfg, target, toolchain).await + RustupSubcmd::Target { subcmd } => { + cfg.skip_auto_install = true; + match subcmd { + TargetSubcmd::List { + toolchain, + installed, + quiet, + } => handle_epipe(target_list(cfg, toolchain, installed, quiet).await), + TargetSubcmd::Add { target, toolchain } => target_add(cfg, target, toolchain).await, + TargetSubcmd::Remove { target, toolchain } => { + target_remove(cfg, target, toolchain).await + } } - }, - RustupSubcmd::Component { subcmd } => match subcmd { - ComponentSubcmd::List { - toolchain, - installed, - quiet, - } => handle_epipe(component_list(cfg, toolchain, installed, quiet).await), - ComponentSubcmd::Add { - component, - toolchain, - target, - } => component_add(cfg, component, toolchain, target).await, - ComponentSubcmd::Remove { - component, - toolchain, - target, - } => component_remove(cfg, component, toolchain, target).await, - }, + } + RustupSubcmd::Component { subcmd } => { + cfg.skip_auto_install = true; + match subcmd { + ComponentSubcmd::List { + toolchain, + installed, + quiet, + } => handle_epipe(component_list(cfg, toolchain, installed, quiet).await), + ComponentSubcmd::Add { + component, + toolchain, + target, + } => component_add(cfg, component, toolchain, target).await, + ComponentSubcmd::Remove { + component, + toolchain, + target, + } => component_remove(cfg, component, toolchain, target).await, + } + } RustupSubcmd::Override { subcmd } => match subcmd { OverrideSubcmd::List => handle_epipe(common::list_overrides(cfg)), OverrideSubcmd::Set { toolchain, path } => { @@ -1061,7 +1067,7 @@ async fn run( install: bool, ) -> Result { let toolchain = toolchain.resolve(&cfg.get_default_host_triple()?)?; - let toolchain = Toolchain::from_local(toolchain, install, cfg).await?; + let toolchain = Toolchain::from_local(toolchain, || Ok(install), cfg).await?; let cmd = toolchain.command(&command[0])?; command::run_command_for_dir(cmd, &command[0], &command[1..]) } diff --git a/src/config.rs b/src/config.rs index a4682ff6fd..023867e239 100644 --- a/src/config.rs +++ b/src/config.rs @@ -246,6 +246,12 @@ pub(crate) struct Cfg<'a> { pub quiet: bool, pub current_dir: PathBuf, pub process: &'a Process, + + /// If this flag is set to `true`, it can stop `rustup` from automatically installing the active + /// toolchain in certain undesired cases, such as under `rustup component` and `rustup target` + /// subcommands. This effect has higher precedence than the `RUSTUP_AUTO_INSTALL` environment + /// variable and the `rustup set auto-install` setting. + pub skip_auto_install: bool, } impl<'a> Cfg<'a> { @@ -317,6 +323,7 @@ impl<'a> Cfg<'a> { quiet, current_dir, process, + skip_auto_install: false, }; // Run some basic checks against the constructed configuration @@ -373,12 +380,30 @@ impl<'a> Cfg<'a> { } pub(crate) fn should_auto_install(&self) -> Result { - if let Ok(mode) = self.process.var("RUSTUP_AUTO_INSTALL") { - Ok(mode != "0") - } else { - self.settings_file - .with(|s| Ok(s.auto_install != Some(AutoInstallMode::Disable))) + if self.skip_auto_install { + return Ok(false); + } + + let should_auto = match self.process.var("RUSTUP_AUTO_INSTALL") { + Ok(mode) => mode != "0", + Err(_) => self + .settings_file + .with(|s| Ok(s.auto_install != Some(AutoInstallMode::Disable)))?, + }; + if !should_auto { + return Ok(false); } + + // We also need to suppress this warning if we're deep inside a recursive call. + let recursions = self.process.var("RUST_RECURSION_COUNT"); + if recursions.is_ok_and(|it| it != "0") { + return Ok(true); + } + + warn!("auto-install is enabled, active toolchain will be installed if absent"); + warn!("this might cause rustup commands to take longer time to finish than expected"); + info!("you may opt out with `RUSTUP_AUTO_INSTALL=0` or `rustup set auto-install disable`"); + Ok(true) } // Returns a profile, if one exists in the settings file. @@ -706,13 +731,10 @@ impl<'a> Cfg<'a> { name: Option<(LocalToolchainName, ActiveSource)>, ) -> Result<(Toolchain<'_>, ActiveSource)> { match name { - Some((tc, source)) => { - let install_if_missing = self.should_auto_install()?; - Ok(( - Toolchain::from_local(tc, install_if_missing, self).await?, - source, - )) - } + Some((tc, src)) => Ok(( + Toolchain::from_local(tc, || self.should_auto_install(), self).await?, + src, + )), None => { let (tc, source) = self .maybe_ensure_active_toolchain(None) @@ -959,6 +981,7 @@ impl Debug for Cfg<'_> { dist_root_url, quiet, current_dir, + skip_auto_install, process: _, } = self; @@ -976,6 +999,7 @@ impl Debug for Cfg<'_> { .field("dist_root_url", dist_root_url) .field("quiet", quiet) .field("current_dir", current_dir) + .field("skip_auto_install", skip_auto_install) .finish() } } diff --git a/src/test/clitools.rs b/src/test/clitools.rs index 0b5032d3cd..2102bf64c4 100644 --- a/src/test/clitools.rs +++ b/src/test/clitools.rs @@ -314,6 +314,12 @@ impl Config { "/bogus-config-file.toml", ); + // Clear current recursion count to avoid messing up related logic + cmd.env("RUST_RECURSION_COUNT", ""); + + // Clear override for auto installation of active toolchain unless explicitly requested + cmd.env("RUSTUP_AUTO_INSTALL", ""); + // Pass `RUSTUP_CI` over to the test process in case it is required downstream if let Some(ci) = env::var_os("RUSTUP_CI") { cmd.env("RUSTUP_CI", ci); diff --git a/src/toolchain.rs b/src/toolchain.rs index 2dc0b23506..5cd54ac2c8 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -52,7 +52,7 @@ pub(crate) struct Toolchain<'a> { impl<'a> Toolchain<'a> { pub(crate) async fn from_local( name: LocalToolchainName, - install_if_missing: bool, + install_if_missing: impl Fn() -> anyhow::Result, cfg: &'a Cfg<'a>, ) -> anyhow::Result> { match Self::new(cfg, name) { @@ -60,7 +60,7 @@ impl<'a> Toolchain<'a> { Err(RustupError::ToolchainNotInstalled { name: ToolchainName::Official(desc), .. - }) if install_if_missing => { + }) if install_if_missing()? => { let options = DistOptions::new(&[], &[], &desc, cfg.get_profile()?, true, cfg)?; Ok(DistributableToolchain::install(options).await?.1.toolchain) } diff --git a/tests/suite/cli_misc.rs b/tests/suite/cli_misc.rs index 5eb1b11f04..0641385e37 100644 --- a/tests/suite/cli_misc.rs +++ b/tests/suite/cli_misc.rs @@ -64,7 +64,7 @@ async fn rustc_with_bad_rustup_toolchain_env_var() { .expect_with_env(["rustc"], [("RUSTUP_TOOLCHAIN", "bogus")]) .await .with_stderr(snapbox::str![[r#" -error: override toolchain 'bogus' is not installed[..] +error:[..] toolchain 'bogus' is not installed[..] "#]]) .is_err(); @@ -1381,7 +1381,10 @@ async fn which_asking_uninstalled_toolchain() { "#]]) .is_ok(); cx.config - .expect(["rustup", "which", "--toolchain=nightly", "rustc"]) + .expect_with_env( + ["rustup", "which", "--toolchain=nightly", "rustc"], + [("RUSTUP_AUTO_INSTALL", "1")], + ) .await .with_stdout(snapbox::str![[r#" [..]/toolchains/nightly-[HOST_TUPLE]/bin/rustc[EXE] @@ -1512,7 +1515,7 @@ active because: overridden by +toolchain on the command line .expect(["rustup", "+foo", "which", "rustc"]) .await .with_stderr(snapbox::str![[r#" -error: override toolchain 'foo' is not installed: the +toolchain on the command line specifies an uninstalled toolchain +error:[..] toolchain 'foo' is not installed[..] "#]]) .is_err(); @@ -1746,3 +1749,26 @@ info: falling back to "[EXTERN_PATH]" "#]]) .is_ok(); } + +#[tokio::test] +async fn warn_auto_install() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect_with_env( + ["rustc", "--version"], + [("RUSTUP_TOOLCHAIN", "stable"), ("RUSTUP_AUTO_INSTALL", "1")], + ) + .await + .with_stdout(snapbox::str![[r#" +1.1.0 (hash-stable-1.1.0) + +"#]]) + .with_stderr(snapbox::str![[r#" +... +warn: auto-install is enabled, active toolchain will be installed if absent +warn: this might cause rustup commands to take longer time to finish than expected +info: you may opt out with `RUSTUP_AUTO_INSTALL=0` or `rustup set auto-install disable` +... +"#]]) + .is_ok(); +} diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 9581f8e23f..d10757d33c 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -1240,7 +1240,7 @@ async fn show_toolchain_override_not_installed() { .await .is_ok(); cx.config - .expect(["rustup", "show"]) + .expect_with_env(["rustup", "show"], [("RUSTUP_AUTO_INSTALL", "1")]) .await .extend_redactions([("[RUSTUP_DIR]", &cx.config.rustupdir.to_string())]) .with_stdout(snapbox::str![[r#" @@ -1355,7 +1355,13 @@ installed targets: async fn show_toolchain_env_not_installed() { let cx = CliTestContext::new(Scenario::SimpleV2).await; cx.config - .expect_with_env(["rustup", "show"], [("RUSTUP_TOOLCHAIN", "nightly")]) + .expect_with_env( + ["rustup", "show"], + [ + ("RUSTUP_TOOLCHAIN", "nightly"), + ("RUSTUP_AUTO_INSTALL", "1"), + ], + ) .await .extend_redactions([("[RUSTUP_DIR]", &cx.config.rustupdir.to_string())]) .is_ok() diff --git a/tests/suite/cli_v1.rs b/tests/suite/cli_v1.rs index 465b26b59e..2a98f95b65 100644 --- a/tests/suite/cli_v1.rs +++ b/tests/suite/cli_v1.rs @@ -271,7 +271,7 @@ async fn remove_override_toolchain_err_handling() { .await .is_ok(); cx.config - .expect(["rustc", "--version"]) + .expect_with_env(["rustc", "--version"], [("RUSTUP_AUTO_INSTALL", "1")]) .await .with_stdout(snapbox::str![[r#" 1.2.0 (hash-beta-1.2.0) diff --git a/tests/suite/cli_v2.rs b/tests/suite/cli_v2.rs index cfc84af3ed..aff293239d 100644 --- a/tests/suite/cli_v2.rs +++ b/tests/suite/cli_v2.rs @@ -478,7 +478,7 @@ async fn remove_override_toolchain_err_handling() { .await .is_ok(); cx.config - .expect(["rustc", "--version"]) + .expect_with_env(["rustc", "--version"], [("RUSTUP_AUTO_INSTALL", "1")]) .await .with_stdout(snapbox::str![[r#" 1.2.0 (hash-beta-1.2.0) @@ -511,7 +511,7 @@ async fn file_override_toolchain_err_handling() { let toolchain_file = cwd.join("rust-toolchain"); rustup::utils::raw::write_file(&toolchain_file, "beta").unwrap(); cx.config - .expect(["rustc", "--version"]) + .expect_with_env(["rustc", "--version"], [("RUSTUP_AUTO_INSTALL", "1")]) .await .with_stdout(snapbox::str![[r#" 1.2.0 (hash-beta-1.2.0) @@ -553,7 +553,10 @@ error: toolchain 'beta-[HOST_TUPLE]' is not installed "#]]) .is_err(); cx.config - .expect(["rustc", "+beta", "--version"]) + .expect_with_env( + ["rustc", "+beta", "--version"], + [("RUSTUP_AUTO_INSTALL", "1")], + ) .await .with_stdout(snapbox::str![[r#" 1.2.0 (hash-beta-1.2.0)