Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 34 additions & 28 deletions src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 } => {
Expand Down Expand Up @@ -1061,7 +1067,7 @@ async fn run(
install: bool,
) -> Result<ExitStatus> {
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..])
}
Expand Down
48 changes: 36 additions & 12 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -317,6 +323,7 @@ impl<'a> Cfg<'a> {
quiet,
current_dir,
process,
skip_auto_install: false,
};

// Run some basic checks against the constructed configuration
Expand Down Expand Up @@ -373,12 +380,30 @@ impl<'a> Cfg<'a> {
}

pub(crate) fn should_auto_install(&self) -> Result<bool> {
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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -959,6 +981,7 @@ impl Debug for Cfg<'_> {
dist_root_url,
quiet,
current_dir,
skip_auto_install,
process: _,
} = self;

Expand All @@ -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()
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/test/clitools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/toolchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ 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<bool>,
Copy link
Copy Markdown
Member Author

@rami3l rami3l Aug 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is made so that the self.should_auto_install() call is postponed to L64, and that the false positive in L60 can be suppressed.

cfg: &'a Cfg<'a>,
) -> anyhow::Result<Toolchain<'a>> {
match Self::new(cfg, name) {
Ok(tc) => Ok(tc),
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)
}
Expand Down
32 changes: 29 additions & 3 deletions tests/suite/cli_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}
10 changes: 8 additions & 2 deletions tests/suite/cli_rustup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion tests/suite/cli_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions tests/suite/cli_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading