From 44f01f13822e982e2274660b42bccb4653ae0774 Mon Sep 17 00:00:00 2001 From: rami3l Date: Wed, 16 Sep 2026 15:50:01 +0200 Subject: [PATCH 1/7] refactor(rustup-mode): accept `Vec` from `rustup target rm` --- src/cli/rustup_mode.rs | 5 ++--- src/dist/mod.rs | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 34b3d87061..1b1551aa03 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -551,7 +551,7 @@ enum TargetSubcmd { Remove { /// List of targets to uninstall #[arg(required = true, num_args = 1..)] - target: Vec, + target: Vec, #[arg(long, help = official_toolchain_arg_help())] toolchain: Option, @@ -1514,7 +1514,7 @@ async fn target_add( async fn target_remove( cfg: &Cfg<'_>, - targets: Vec, + targets: Vec, toolchain: Option, ) -> anyhow::Result { let distributable = DistributableToolchain::from_partial( @@ -1524,7 +1524,6 @@ async fn target_remove( .await?; for target in targets { - let target = TargetTuple::new(target); let default_target = cfg.default_host_tuple()?; if target == default_target { warn!( diff --git a/src/dist/mod.rs b/src/dist/mod.rs index 0bad59dd1d..ce18b62ea1 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -678,6 +678,12 @@ impl TargetTuple { } } +impl From for TargetTuple { + fn from(s: String) -> Self { + Self(s) + } +} + impl fmt::Display for TargetTuple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) From d65fc5a74fced175f71afe9db447fda099d7faef Mon Sep 17 00:00:00 2001 From: rami3l Date: Wed, 16 Sep 2026 15:42:30 +0200 Subject: [PATCH 2/7] fix(rustup-mode): make "remove last target" warning more robust The current version only checks whether we are removing target when we have at most one target installed. Unfortunately, the target to be removed don't necessary have to be the last one installed. Instead, we should simulate the remaining targets after the removal and check whether we have nothing left. --- src/cli/rustup_mode.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 1b1551aa03..04ab124bdc 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -25,7 +25,6 @@ use clap_complete::{ }; use futures_util::stream::StreamExt; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use itertools::Itertools; use serde::Serialize; use tokio::sync::Semaphore; use tracing::{info, warn}; @@ -1523,6 +1522,12 @@ async fn target_remove( ) .await?; + let mut remaining_targets = distributable.toolchain.installed_targets()?; + remaining_targets.retain(|it| !targets.contains(it)); + if remaining_targets.is_empty() { + warn!("removing the last target; no build targets will be available"); + } + for target in targets { let default_target = cfg.default_host_tuple()?; if target == default_target { @@ -1530,20 +1535,7 @@ async fn target_remove( "removing the default host target; proc-macros and build scripts might no longer build" ); } - // Whether we have at most 1 component target that is not `None` (wildcard). - let has_at_most_one_target = distributable - .components()? - .into_iter() - .filter_map(|c| match (c.installed, c.component.target) { - (true, Some(t)) => Some(t), - _ => None, - }) - .unique() - .at_most_one() - .is_ok(); - if has_at_most_one_target { - warn!("removing the last target; no build targets will be available"); - } + distributable .remove_component(Component::std(target)) .await?; From 2ab63b487c99eed4db8ba0a5f54da780e92592f5 Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 15 Sep 2026 11:44:33 +0200 Subject: [PATCH 3/7] refactor(toolchain): advance "removing default host target" warning --- src/cli/rustup_mode.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 04ab124bdc..be79ef721b 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1522,6 +1522,12 @@ async fn target_remove( ) .await?; + if targets.contains(&cfg.default_host_tuple()?) { + warn!( + "removing the default host target; proc-macros and build scripts might no longer build" + ); + } + let mut remaining_targets = distributable.toolchain.installed_targets()?; remaining_targets.retain(|it| !targets.contains(it)); if remaining_targets.is_empty() { @@ -1529,13 +1535,6 @@ async fn target_remove( } for target in targets { - let default_target = cfg.default_host_tuple()?; - if target == default_target { - warn!( - "removing the default host target; proc-macros and build scripts might no longer build" - ); - } - distributable .remove_component(Component::std(target)) .await?; From 025d820788ae5d063daec66622c562537e556a79 Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 15 Sep 2026 11:44:33 +0200 Subject: [PATCH 4/7] refactor(toolchain)!: remove multiple components in `rustup target rm` --- src/cli/rustup_mode.rs | 11 ++--- src/toolchain/distributable.rs | 76 ++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index be79ef721b..621cd8fabf 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1534,12 +1534,9 @@ async fn target_remove( warn!("removing the last target; no build targets will be available"); } - for target in targets { - distributable - .remove_component(Component::std(target)) - .await?; - } - + distributable + .remove_components(targets.into_iter().map(|c| Ok(Component::std(c)))) + .await?; Ok(ExitCode::SUCCESS) } @@ -1630,7 +1627,7 @@ async fn component_remove( let mut unknown_components = Vec::new(); for component in parsed_components { - let Err(err) = distributable.remove_component(component).await else { + let Err(err) = distributable.remove_components([Ok(component)]).await else { continue; }; diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index 3b46eb54a7..abb042659e 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -383,52 +383,56 @@ impl<'a> DistributableToolchain<'a> { } } - pub(crate) async fn remove_component(&self, mut component: Component) -> anyhow::Result<()> { - // TODO: take multiple components? + pub(crate) async fn remove_components( + &self, + components: impl IntoIterator>, + ) -> anyhow::Result<()> { let manifestation = self.get_manifestation()?; let config = manifestation.read_config()?.unwrap_or_default(); let manifest = self.get_manifest()?; - // Rename the component if necessary. - if let Some(c) = manifest.rename_component(&component) { - component = c; - } - - if !config.components.contains(&component) { - let wildcard_component = component.wildcard(); - if config.components.contains(&wildcard_component) { - component = wildcard_component; - } else { - let suggestion = - self.get_component_suggestion(&component, &config, &manifest, true); - // Check if the target is installed. - if !config - .components - .iter() - .any(|c| c.target() == component.target()) - { - return Err(RustupError::TargetNotInstalled { - desc: Box::new(self.desc.clone()), - target: component.target.expect("component target should be known"), - suggestion, + let components = components.into_iter(); + let mut renamed_components = Vec::with_capacity(components.size_hint().0); + let mut unknown_components = vec![]; + for component in components { + let mut component = component?; + if let Some(renamed) = manifest.rename_component(&component) { + component = renamed; + } + if !config.components.contains(&component) { + let wildcard_component = component.wildcard(); + if config.components.contains(&wildcard_component) { + component = wildcard_component; + } else { + let suggestion = + self.get_component_suggestion(&component, &config, &manifest, true); + // Check if the target is installed. + if !config + .components + .iter() + .any(|c| c.target() == component.target()) + { + return Err(RustupError::TargetNotInstalled { + desc: Box::new(self.desc.clone()), + target: component.target.expect("component target should be known"), + suggestion, + } + .into()); } - .into()); - } - return Err(RustupError::UnknownComponents { - desc: self.desc.clone(), - components: vec![UnknownComponentInfo { + unknown_components.push(UnknownComponentInfo { name: manifest.short_name(&component).to_string(), description: manifest.description(&component), suggestion, - }], + }); + continue; } - .into()); } + renamed_components.push(component); } let changes = Changes { explicit_add_components: vec![], - remove_components: vec![component], + remove_components: renamed_components, }; let download_cfg = DownloadCfg::new(self.toolchain.cfg); @@ -436,6 +440,14 @@ impl<'a> DistributableToolchain<'a> { .update(manifest, changes, false, &download_cfg, &self.desc, false) .await?; + if !unknown_components.is_empty() { + return Err(RustupError::UnknownComponents { + desc: self.desc().clone(), + components: unknown_components, + } + .into()); + } + Ok(()) } From 60aa923fe9bbd5622fc8b4c5a43a8203ee4b11ec Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 15 Sep 2026 11:44:33 +0200 Subject: [PATCH 5/7] refactor(toolchain): remove multiple components in `rustup component rm` --- src/cli/rustup_mode.rs | 39 ++++++++------------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 621cd8fabf..d80fa6c91d 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1619,37 +1619,14 @@ async fn component_remove( let distributable = DistributableToolchain::from_partial(toolchain, cfg).await?; let target = get_target(target, &distributable); - let parsed_components = components - .iter() - .map(|component| Component::try_new(component, &distributable, target.as_ref())) - .collect::>>()?; - - let mut unknown_components = Vec::new(); - - for component in parsed_components { - let Err(err) = distributable.remove_components([Ok(component)]).await else { - continue; - }; - - if let Some(RustupError::UnknownComponents { components, .. }) = - err.downcast_ref::() - { - unknown_components.extend(components.iter().cloned()); - continue; - } - - return Err(err); - } - - if unknown_components.is_empty() { - Ok(ExitCode::SUCCESS) - } else { - Err(RustupError::UnknownComponents { - desc: distributable.desc().clone(), - components: unknown_components, - } - .into()) - } + distributable + .remove_components( + components + .iter() + .map(|component| Component::try_new(component, &distributable, target.as_ref())), + ) + .await?; + Ok(ExitCode::SUCCESS) } async fn toolchain_link( From 41ac5d42e74fff672f12a6c8f45a2cfe1397b77c Mon Sep 17 00:00:00 2001 From: rami3l Date: Tue, 15 Sep 2026 11:55:27 +0200 Subject: [PATCH 6/7] refactor(toolchain): reduce rightward drift --- src/toolchain/distributable.rs | 55 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index abb042659e..950f310d4a 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -399,35 +399,36 @@ impl<'a> DistributableToolchain<'a> { if let Some(renamed) = manifest.rename_component(&component) { component = renamed; } - if !config.components.contains(&component) { - let wildcard_component = component.wildcard(); - if config.components.contains(&wildcard_component) { - component = wildcard_component; - } else { - let suggestion = - self.get_component_suggestion(&component, &config, &manifest, true); - // Check if the target is installed. - if !config - .components - .iter() - .any(|c| c.target() == component.target()) - { - return Err(RustupError::TargetNotInstalled { - desc: Box::new(self.desc.clone()), - target: component.target.expect("component target should be known"), - suggestion, - } - .into()); - } - unknown_components.push(UnknownComponentInfo { - name: manifest.short_name(&component).to_string(), - description: manifest.description(&component), - suggestion, - }); - continue; + if config.components.contains(&component) { + renamed_components.push(component); + continue; + } + + let wildcard_component = component.wildcard(); + if config.components.contains(&wildcard_component) { + renamed_components.push(wildcard_component); + continue; + } + + let suggestion = self.get_component_suggestion(&component, &config, &manifest, true); + // Check if the target is installed. + if !config + .components + .iter() + .any(|c| c.target() == component.target()) + { + return Err(RustupError::TargetNotInstalled { + desc: Box::new(self.desc.clone()), + target: component.target.expect("component target should be known"), + suggestion, } + .into()); } - renamed_components.push(component); + unknown_components.push(UnknownComponentInfo { + name: manifest.short_name(&component).to_string(), + description: manifest.description(&component), + suggestion, + }); } let changes = Changes { From 367c94afa800d5eb11d6169711a17a5986b88c6c Mon Sep 17 00:00:00 2001 From: rami3l Date: Wed, 16 Sep 2026 17:19:14 +0200 Subject: [PATCH 7/7] refactor(toolchain)!: accept `Result`s in `rustup component add` --- src/cli/rustup_mode.rs | 8 +++----- src/toolchain/distributable.rs | 6 ++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index d80fa6c91d..1fec1ce59d 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -1493,7 +1493,7 @@ async fn target_add( distributable .add_components(distributable.components()?.into_iter().filter_map(|c| { (c.available && !c.installed && c.component.short_name() == "rust-std") - .then_some(c.component) + .then_some(Ok(c.component)) })) .await?; @@ -1504,7 +1504,7 @@ async fn target_add( .add_components( targets .into_iter() - .map(|target| Component::std(TargetTuple::new(target))), + .map(|target| Ok(Component::std(TargetTuple::new(target)))), ) .await?; @@ -1591,9 +1591,7 @@ async fn component_add( .add_components( components .into_iter() - .map(|component| Component::try_new(&component, &distributable, target.as_ref())) - .collect::>>()? - .into_iter(), + .map(|component| Component::try_new(&component, &distributable, target.as_ref())), ) .await?; diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index 950f310d4a..1fdec3cc5d 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -65,7 +65,7 @@ impl<'a> DistributableToolchain<'a> { pub(crate) async fn add_components( &self, - components: impl Iterator, + components: impl IntoIterator>, ) -> anyhow::Result<()> { let manifestation = self.get_manifestation()?; let manifest = self.get_manifest()?; @@ -79,9 +79,11 @@ impl<'a> DistributableToolchain<'a> { .get(&self.desc.target) .expect("installed manifest should have a known target"); + let components = components.into_iter(); let mut validated_components = Vec::with_capacity(components.size_hint().0); - for mut component in components { + for component in components { + let mut component = component?; if let Some(c) = manifest.rename_component(&component) { component = c; }