Skip to content
Open
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
41 changes: 38 additions & 3 deletions src/cli/rustup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,36 @@ fn update_toolchain_value_parser(s: &str) -> Result<PartialToolchainDesc> {
})
}

impl RustupSubcmd {
fn allow_auto_install(&self) -> bool {
match self {
// These subcommands execute or rely on the active toolchain, so auto-installing it when
// missing may be reasonable depending on the user's decision.
#[cfg(not(windows))]
RustupSubcmd::Man { .. } => true,
RustupSubcmd::Doc { .. } | RustupSubcmd::Run { .. } => true,

// These subcommands don't require the active toolchain, so auto-installing it should be
// disabled to avoid surprises.
RustupSubcmd::Check { .. }
| RustupSubcmd::Completions { .. }
| RustupSubcmd::Component { .. }
| RustupSubcmd::Default { .. }
| RustupSubcmd::DumpTestament
| RustupSubcmd::Install { .. }
| RustupSubcmd::Override { .. }
| RustupSubcmd::Self_ { .. }
| RustupSubcmd::Set { .. }
| RustupSubcmd::Show { .. }
| RustupSubcmd::Target { .. }
| RustupSubcmd::Toolchain { .. }
| RustupSubcmd::Uninstall { .. }
| RustupSubcmd::Update { .. }
| RustupSubcmd::Which { .. } => false,
}
}
}

#[derive(Debug, Subcommand)]
enum ShowSubcmd {
/// Show the active toolchain
Expand Down Expand Up @@ -631,15 +661,20 @@ pub async fn main(

update_console_filter(process, &console_filter, matches.quiet, matches.verbose);

let cfg = &mut Cfg::from_env(current_dir, matches.quiet, true, process)?;
cfg.toolchain_override = matches.plus_toolchain;

let Some(subcmd) = matches.subcmd else {
let help = Rustup::command().render_long_help();
writeln!(process.stderr().lock(), "{}", help.ansi())?;
return Ok(ExitCode::FAILURE);
};

let cfg = &mut Cfg::from_env(
current_dir,
matches.quiet,
subcmd.allow_auto_install(),
process,
)?;
cfg.toolchain_override = matches.plus_toolchain;

match subcmd {
RustupSubcmd::DumpTestament => common::dump_testament(process),
RustupSubcmd::Install { opts } => update(cfg, opts, true).await,
Expand Down
22 changes: 14 additions & 8 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ impl<T> EnsureInstalled<T> {
}

impl<T: Display> EnsureInstalled<T> {
fn warn_auto_install(&self, process: &Process) {
fn warn_auto_install(&self, cfg: &Cfg<'_>) {
// If we're already in a recursion, or we haven't just installed the active toolchain, then
// don't print the warning.
let recursions = process.var("RUST_RECURSION_COUNT");
let recursions = cfg.process.var("RUST_RECURSION_COUNT");
if recursions.is_ok_and(|it| it != "0") || !matches!(self.status, UpdateStatus::Installed) {
return;
}
Expand All @@ -154,6 +154,16 @@ impl<T: Display> EnsureInstalled<T> {
"the missing active toolchain `{}` has been auto-installed",
self.inner,
);

if !cfg.allow_auto_install {
// NOTE: Special behavior for rustup v1.29
warn!(
"this is deprecated for most `rustup` commands and may not work in a future release"
);
warn!("see <https://github.com/rust-lang/rustup/issues/4836> for more info");
return;
Comment on lines +159 to +164

@djc djc Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay, now I am confused about this (sorry). The way I read this, this will issue a warning for subcommands for which allow_auto_install() yields false exactly because they don't use the active toolchain anyway.

So am I misunderstanding this or is this pure noise?

View changes since the review

@rami3l rami3l Jun 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@djc Your understanding looks correct to me.

Looking back I'd argue that allow_auto_install might no longer be the best name here anymore because it now stands for "allows auto install in v1.30" or "should not emit a deprecation warning in v1.29" which is making the code harder for you to review. Is that you are trying to say?

I can offer to do a cleanup commit upfront in which I'll rename this variable, if that makes it easier for you 🙏

One downside for that is that it might make future backports more difficult if they happen to touch this part 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm suggesting that it does not make sense to warn about this because automatic installation is not actually affecting the running command anyway.

@rami3l rami3l Jun 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@djc It doesn't mean anything in the context of the current command indeed, but I've seen people using this (e.g. rustup show) to install the active toolchain, because there was a time when rustup toolchain install wasn't available (v1.27 and earlier) and that was the only way to install the active toolchain. Maybe it makes more sense to you now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hmmm maybe for the sake of clarity I should have added "to install the active toolchain, use rustup toolchain install instead" or something like that.

}

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`");
}
Expand Down Expand Up @@ -423,10 +433,6 @@ impl<'a> Cfg<'a> {
}

pub(crate) fn should_auto_install(&self) -> Result<bool> {
if !self.allow_auto_install {
Comment thread
djc marked this conversation as resolved.
return Ok(false);
}

if let Ok(mode) = self.process.var("RUSTUP_AUTO_INSTALL") {
Ok(mode != "0")
} else {
Expand Down Expand Up @@ -562,7 +568,7 @@ impl<'a> Cfg<'a> {
match self.ensure_active_toolchain(true, false).await {
Ok(r) => {
let (tc, source) = r;
tc.warn_auto_install(self.process);
tc.warn_auto_install(self);
Ok(Some((tc.inner, source)))
}
Err(e) => match e.downcast_ref::<RustupError>() {
Expand Down Expand Up @@ -768,7 +774,7 @@ impl<'a> Cfg<'a> {
let install_if_missing = self.should_auto_install()?;
let EnsureInstalled { inner: tc, status } =
Toolchain::from_local(tc, install_if_missing, self).await?;
EnsureInstalled::new(tc.name(), status).warn_auto_install(self.process);
EnsureInstalled::new(tc.name(), status).warn_auto_install(self);
Ok((tc, source))
}
None => {
Expand Down
24 changes: 24 additions & 0 deletions tests/suite/cli_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1795,3 +1795,27 @@ info: you may opt out with `RUSTUP_AUTO_INSTALL=0` or `rustup set auto-install d
"#]])
.is_ok();
}

#[tokio::test]
async fn warn_auto_install_on_rustup_deprecated() {
let cx = CliTestContext::new(Scenario::SimpleV2).await;
cx.config
.expect_with_env(
["rustup", "component", "list"],
[("RUSTUP_TOOLCHAIN", "stable"), ("RUSTUP_AUTO_INSTALL", "1")],
)
.await
.with_stdout(snapbox::str![[r#"
...
rustc-[HOST_TUPLE] (installed)
...
"#]])
.with_stderr(snapbox::str![[r#"
...
warn: the missing active toolchain `stable-[HOST_TUPLE]` has been auto-installed
warn: this is deprecated for most `rustup` commands and may not work in a future release
warn: see <https://github.com/rust-lang/rustup/issues/4836> for more info

"#]])
.is_ok();
}
Loading