Skip to content
2 changes: 1 addition & 1 deletion src/cli/proxy_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub async fn main(arg0: &str, current_dir: PathBuf, process: &Process) -> Result
.skip(1 + toolchain.is_some() as usize)
.collect();

let cfg = Cfg::from_env(current_dir, false, process)?;
let cfg = Cfg::from_env(current_dir, false, true, process)?;
let (toolchain, source) = cfg
.local_toolchain(match toolchain {
Some(name) => Some((
Expand Down
200 changes: 118 additions & 82 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, 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 Expand Up @@ -1108,20 +1143,17 @@ async fn which(
async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result<ExitCode> {
common::warn_if_host_is_emulated(cfg.process);

let mut t = cfg.process.stdout();

// Print host tuple
{
let t = cfg.process.stdout();
let mut t = t.lock();
writeln!(
t,
"{HEADER}Default host: {HEADER:#}{}",
cfg.default_host_tuple()?
)?;
}
writeln!(
t.lock(),
"{HEADER}Default host: {HEADER:#}{}",
cfg.default_host_tuple()?
)?;

// Print rustup home directory
{
let t = cfg.process.stdout();
let mut t = t.lock();
writeln!(
t,
Expand All @@ -1146,90 +1178,88 @@ async fn show(cfg: &Cfg<'_>, verbose: bool) -> Result<ExitCode> {
.map(|atar| (&atar.0, &atar.1))
.unzip();

let active_toolchain_targets = match active_toolchain_name {
Some(ToolchainName::Official(desc)) => DistributableToolchain::new(cfg, desc.clone())?
.components()?
.into_iter()
.filter_map(|c| {
(c.installed && c.component.short_name() == "rust-std")
.then(|| c.component.target.expect("rust-std should have a target"))
})
.collect(),
Some(ToolchainName::Custom(name)) => {
Toolchain::new(cfg, LocalToolchainName::Named(name.into()))?.installed_targets()?
}
None => Vec::new(),
};

// show installed toolchains
{
let mut t = cfg.process.stdout();

print_header(&mut t, "installed toolchains")?;

let default_toolchain_name = cfg.get_default()?;
let last_index = installed_toolchains.len().wrapping_sub(1);
for (n, toolchain_name) in installed_toolchains.into_iter().enumerate() {
let is_default_toolchain = default_toolchain_name.as_ref() == Some(&toolchain_name);
let is_active_toolchain = active_toolchain_name == Some(&toolchain_name);
print_header(&mut t, "installed toolchains")?;

let default_toolchain_name = cfg.get_default()?;
let last_index = installed_toolchains.len().wrapping_sub(1);
for (n, toolchain_name) in installed_toolchains.into_iter().enumerate() {
let is_default_toolchain = default_toolchain_name.as_ref() == Some(&toolchain_name);
let is_active_toolchain = active_toolchain_name == Some(&toolchain_name);

let status_str = match (is_active_toolchain, is_default_toolchain) {
(true, true) => " (active, default)",
(true, false) => " (active)",
(false, true) => " (default)",
(false, false) => "",
};

let status_str = match (is_active_toolchain, is_default_toolchain) {
(true, true) => " (active, default)",
(true, false) => " (active)",
(false, true) => " (default)",
(false, false) => "",
};
let mut t = t.lock();

writeln!(t.lock(), "{toolchain_name}{CONTEXT}{status_str}{CONTEXT:#}")?;
writeln!(t, "{toolchain_name}{CONTEXT}{status_str}{CONTEXT:#}")?;

if verbose {
let toolchain = Toolchain::new(cfg, toolchain_name.into())?;
writeln!(
cfg.process.stdout().lock(),
" {}\n path: {}",
toolchain.rustc_version(),
toolchain.path().display()
)?;
if n != last_index {
writeln!(cfg.process.stdout().lock())?;
}
if verbose {
let toolchain = Toolchain::new(cfg, toolchain_name.into())?;
writeln!(
t,
" {}\n path: {}",
toolchain.rustc_version(),
toolchain.path().display()
)?;
if n != last_index {
writeln!(t)?;
}
}
}

// show active toolchain
{
let mut t = cfg.process.stdout();
writeln!(t.lock())?;

writeln!(t.lock())?;
print_header(&mut t, "active toolchain")?;

print_header(&mut t, "active toolchain")?;
let Some((active_toolchain_name, active_source)) = active_toolchain_and_source else {
writeln!(t.lock(), "no active toolchain")?;
return Ok(ExitCode::SUCCESS);
};

match active_toolchain_and_source {
Some((active_toolchain_name, active_source)) => {
let active_toolchain = Toolchain::with_source(
cfg,
active_toolchain_name.clone().into(),
&active_source,
)?;
writeln!(t.lock(), "name: {}", active_toolchain.name())?;
writeln!(t.lock(), "active because: {}", active_source.to_reason())?;
if verbose {
writeln!(t.lock(), "compiler: {}", active_toolchain.rustc_version())?;
writeln!(t.lock(), "path: {}", active_toolchain.path().display())?;
}
writeln!(t.lock(), "name: {active_toolchain_name}")?;
writeln!(t.lock(), "active because: {}", active_source.to_reason())?;

// show installed targets for the active toolchain
writeln!(t.lock(), "installed targets:")?;
match Toolchain::new(cfg, active_toolchain_name.clone().into()) {
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 think it might still be worthwhile here to handle the error cases inside the match and yield the Ok() case out of the match to reduce rightward drift a bit more.

Ok(active_toolchain) => {
if verbose {
writeln!(t.lock(), "compiler: {}", active_toolchain.rustc_version())?;
writeln!(t.lock(), "path: {}", active_toolchain.path().display())?;
}

for target in active_toolchain_targets {
writeln!(t.lock(), " {target}")?;
// show installed targets for the active toolchain
writeln!(t.lock(), "installed targets:")?;

let active_toolchain_targets = match active_toolchain_name {
ToolchainName::Official(desc) => DistributableToolchain::new(cfg, desc.clone())?
.components()?
.into_iter()
.filter_map(|c| {
(c.installed && c.component.short_name() == "rust-std")
.then(|| c.component.target.expect("rust-std should have a target"))
})
.collect(),
ToolchainName::Custom(name) => {
Toolchain::new(cfg, LocalToolchainName::Named(name.into()))?
.installed_targets()?
}
};

for target in active_toolchain_targets {
writeln!(t.lock(), " {target}")?;
}
None => {
writeln!(t.lock(), "no active toolchain")?;
}
}
Err(
RustupError::ToolchainNotInstalled { .. } | RustupError::PathToolchainNotInstalled(..),
) => {
info!("the active toolchain `{active_toolchain_name}` is not installed");
}
Err(e) => return Err(e.into()),
}

fn print_header(t: &mut ColorableTerminal, text: &str) -> Result<(), Error> {
Expand Down Expand Up @@ -1973,8 +2003,8 @@ fn output_completion_script(
}

async fn display_version(current_dir: PathBuf, process: &Process) -> Result<()> {
info!("This is the version for the rustup toolchain manager, not the rustc compiler.");
let mut cfg = Cfg::from_env(current_dir, true, process)?;
info!("this is the version for the rustup toolchain manager, not the rustc compiler");
let mut cfg = Cfg::from_env(current_dir, true, false, process)?;
cfg.toolchain_override = cfg
.process
.args()
Expand All @@ -1987,6 +2017,12 @@ async fn display_version(current_dir: PathBuf, process: &Process) -> Result<()>
"the currently active `rustc` version is `{}`",
tc.rustc_version()
),
Err(RustupError::ToolchainNotInstalled {
name,
is_active: true,
}) => {
info!("the active toolchain `{name}` is not installed");
}
Err(err) => error!("failed to display the current `rustc` version: {err}"),
},
Ok(None) => info!("no `rustc` is currently active"),
Expand Down
2 changes: 1 addition & 1 deletion src/cli/self_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,7 @@ mod tests {
home.apply(&mut vars);
let tp = TestProcess::with_vars(vars);
let mut cfg =
Cfg::from_env(tp.process.current_dir().unwrap(), false, &tp.process).unwrap();
Cfg::from_env(tp.process.current_dir().unwrap(), false, true, &tp.process).unwrap();

let opts = InstallOpts {
default_host_tuple: None,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/setup_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,6 @@ pub async fn main(
targets: &target.iter().map(|s| &**s).collect::<Vec<_>>(),
};

let mut cfg = Cfg::from_env(current_dir, quiet, process)?;
let mut cfg = Cfg::from_env(current_dir, quiet, false, process)?;
self_update::install(no_prompt, opts, &mut cfg).await
}
14 changes: 14 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,19 @@ pub(crate) struct Cfg<'a> {
pub quiet: bool,
pub current_dir: PathBuf,
pub process: &'a Process,

/// If this flag is set to `false`, it can stop `rustup` from automatically installing the
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.

Nit: I think best practice for docstrings is to summarize meaning in a way that fits on one line, then expand on that in subsequent lines (usually after an empty line). That makes it a bit easier to skim/skip over them.

/// 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 allow_auto_install: bool,
}

impl<'a> Cfg<'a> {
pub(crate) fn from_env(
current_dir: PathBuf,
quiet: bool,
allow_auto_install: bool,
process: &'a Process,
) -> Result<Self> {
// Set up the rustup home directory
Expand Down Expand Up @@ -316,6 +323,7 @@ impl<'a> Cfg<'a> {
quiet,
current_dir,
process,
allow_auto_install,
};

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

pub(crate) fn should_auto_install(&self) -> Result<bool> {
if !self.allow_auto_install {
return Ok(false);
}

if let Ok(mode) = self.process.var("RUSTUP_AUTO_INSTALL") {
Ok(mode != "0")
} else {
Expand Down Expand Up @@ -958,6 +970,7 @@ impl Debug for Cfg<'_> {
dist_root_url,
quiet,
current_dir,
allow_auto_install,
process: _,
} = self;

Expand All @@ -975,6 +988,7 @@ impl Debug for Cfg<'_> {
.field("dist_root_url", dist_root_url)
.field("quiet", quiet)
.field("current_dir", current_dir)
.field("allow_auto_install", allow_auto_install)
.finish()
}
}
Expand Down
12 changes: 6 additions & 6 deletions tests/suite/cli_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async fn version_mentions_rustc_version_confusion() {
.await
.with_stderr(snapbox::str![[r#"
...
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: this is the version for the rustup toolchain manager, not the rustc compiler
...
"#]])
.is_ok();
Expand Down Expand Up @@ -1383,11 +1383,11 @@ async fn which_asking_uninstalled_toolchain() {
cx.config
.expect(["rustup", "which", "--toolchain=nightly", "rustc"])
.await
.with_stdout(snapbox::str![[r#"
[..]/toolchains/nightly-[HOST_TUPLE]/bin/rustc[EXE]

.with_stderr(snapbox::str![[r#"
error: toolchain 'nightly-[HOST_TUPLE]' is not installed
...
"#]])
.is_ok();
.is_err();
}

#[tokio::test]
Expand Down Expand Up @@ -1512,7 +1512,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
Loading
Loading