From 40f2b370d5a52454de97de65a878ec045021ab87 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 14 Jul 2025 10:46:07 +0200 Subject: [PATCH 1/4] cli: move more self update logic into self_update module --- src/cli/common.rs | 64 ---------------------------------------- src/cli/rustup_mode.rs | 4 +-- src/cli/self_update.rs | 67 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 69 deletions(-) diff --git a/src/cli/common.rs b/src/cli/common.rs index 031d149c95..1d9a771190 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -3,8 +3,6 @@ use std::cell::RefCell; use std::fmt::Display; use std::fs; -#[cfg(not(windows))] -use std::io::ErrorKind; use std::io::{BufRead, Write}; use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock, Mutex}; @@ -15,7 +13,6 @@ use git_testament::{git_testament, render_testament}; use tracing::{debug, error, info, trace, warn}; use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; -use super::self_update; use crate::{ cli::download_tracker::DownloadTracker, config::Cfg, @@ -303,67 +300,6 @@ pub(crate) async fn update_all_channels( Ok(exit_code) } -#[derive(Clone, Copy, Debug)] -pub(crate) enum SelfUpdatePermission { - HardFail, - #[cfg(not(windows))] - Skip, - Permit, -} - -#[cfg(windows)] -pub(crate) fn self_update_permitted(_explicit: bool) -> Result { - Ok(SelfUpdatePermission::Permit) -} - -#[cfg(not(windows))] -pub(crate) fn self_update_permitted(explicit: bool) -> Result { - // Detect if rustup is not meant to self-update - let current_exe = env::current_exe()?; - let current_exe_dir = current_exe.parent().expect("Rustup isn't in a directory‽"); - if let Err(e) = tempfile::Builder::new() - .prefix("updtest") - .tempdir_in(current_exe_dir) - { - match e.kind() { - ErrorKind::PermissionDenied => { - trace!("Skipping self-update because we cannot write to the rustup dir"); - if explicit { - return Ok(SelfUpdatePermission::HardFail); - } else { - return Ok(SelfUpdatePermission::Skip); - } - } - _ => return Err(e.into()), - } - } - Ok(SelfUpdatePermission::Permit) -} - -/// Performs all of a self-update: check policy, download, apply and exit. -pub(crate) async fn self_update(process: &Process) -> Result { - match self_update_permitted(false)? { - SelfUpdatePermission::HardFail => { - error!("Unable to self-update. STOP"); - return Ok(utils::ExitCode(1)); - } - #[cfg(not(windows))] - SelfUpdatePermission::Skip => return Ok(utils::ExitCode(0)), - SelfUpdatePermission::Permit => {} - } - - let setup_path = self_update::prepare_update(process).await?; - - if let Some(setup_path) = &setup_path { - return self_update::run_update(setup_path); - } else { - // Try again in case we emitted "tool `{}` is already installed" last time. - self_update::install_proxies(process)?; - } - - Ok(utils::ExitCode(0)) -} - /// Print a list of items (targets or components) to stdout. /// /// `items` represents the list of items, with the name and a boolean diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index ebaba20cd4..d3a6ffe66f 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -988,7 +988,7 @@ async fn update( } } if self_update { - exit_code &= common::self_update(cfg.process).await?; + exit_code &= self_update::self_update(cfg.process).await?; } } else if ensure_active_toolchain { let (toolchain, reason) = cfg.ensure_active_toolchain(force_non_host, true).await?; @@ -997,7 +997,7 @@ async fn update( } else { exit_code &= common::update_all_channels(cfg, opts.force).await?; if self_update { - exit_code &= common::self_update(cfg.process).await?; + exit_code &= self_update::self_update(cfg.process).await?; } info!("cleaning up downloads & tmp directories"); diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index a9dc44ce20..8e32e8ed68 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -34,7 +34,7 @@ use std::borrow::Cow; use std::env::{self, consts::EXE_SUFFIX}; use std::fmt; use std::fs; -use std::io::Write; +use std::io::{self, Write}; use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf}; use std::process::Command; use std::str::FromStr; @@ -1049,6 +1049,67 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result Result { + Ok(SelfUpdatePermission::Permit) +} + +#[cfg(not(windows))] +pub(crate) fn self_update_permitted(explicit: bool) -> Result { + // Detect if rustup is not meant to self-update + let current_exe = env::current_exe()?; + let current_exe_dir = current_exe.parent().expect("Rustup isn't in a directory‽"); + if let Err(e) = tempfile::Builder::new() + .prefix("updtest") + .tempdir_in(current_exe_dir) + { + match e.kind() { + io::ErrorKind::PermissionDenied => { + trace!("Skipping self-update because we cannot write to the rustup dir"); + if explicit { + return Ok(SelfUpdatePermission::HardFail); + } else { + return Ok(SelfUpdatePermission::Skip); + } + } + _ => return Err(e.into()), + } + } + Ok(SelfUpdatePermission::Permit) +} + +/// Performs all of a self-update: check policy, download, apply and exit. +pub(crate) async fn self_update(process: &Process) -> Result { + match self_update_permitted(false)? { + SelfUpdatePermission::HardFail => { + error!("Unable to self-update. STOP"); + return Ok(utils::ExitCode(1)); + } + #[cfg(not(windows))] + SelfUpdatePermission::Skip => return Ok(utils::ExitCode(0)), + SelfUpdatePermission::Permit => {} + } + + let setup_path = prepare_update(process).await?; + + if let Some(setup_path) = &setup_path { + return run_update(setup_path); + } else { + // Try again in case we emitted "tool `{}` is already installed" last time. + install_proxies(process)?; + } + + Ok(utils::ExitCode(0)) +} + /// Self update downloads rustup-init to `CARGO_HOME`/bin/rustup-init /// and runs it. /// @@ -1067,11 +1128,11 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result) -> Result { common::warn_if_host_is_emulated(cfg.process); - use common::SelfUpdatePermission::*; + use SelfUpdatePermission::*; let update_permitted = if cfg!(feature = "no-self-update") { HardFail } else { - common::self_update_permitted(true)? + self_update_permitted(true)? }; match update_permitted { HardFail => { From b5c5c3196f3e06359606da679cdd2e88a3aa51c9 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 14 Jul 2025 10:53:19 +0200 Subject: [PATCH 2/4] cli: localize self update logic in update() --- src/cli/rustup_mode.rs | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index d3a6ffe66f..2223297d61 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -919,14 +919,6 @@ async fn update( let mut exit_code = utils::ExitCode(0); common::warn_if_host_is_emulated(cfg.process); - let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; - // Priority: no-self-update feature > self_update_mode > no-self-update args. - // Update only if rustup does **not** have the no-self-update feature, - // and auto-self-update is configured to **enable** - // and has **no** no-self-update parameter. - let self_update = !cfg!(feature = "no-self-update") - && self_update_mode == SelfUpdateMode::Enable - && !opts.no_self_update; let force_non_host = opts.force_non_host; if let Some(p) = opts.profile { cfg.set_profile_override(p); @@ -987,31 +979,32 @@ async fn update( cfg.set_default(Some(&desc.into()))?; } } - if self_update { - exit_code &= self_update::self_update(cfg.process).await?; - } } else if ensure_active_toolchain { let (toolchain, reason) = cfg.ensure_active_toolchain(force_non_host, true).await?; info!("the active toolchain `{toolchain}` has been installed"); info!("it's active because: {reason}"); } else { exit_code &= common::update_all_channels(cfg, opts.force).await?; - if self_update { - exit_code &= self_update::self_update(cfg.process).await?; - } - info!("cleaning up downloads & tmp directories"); utils::delete_dir_contents_following_links(&cfg.download_dir); cfg.tmp_cx.clean(); } - if !cfg!(feature = "no-self-update") && self_update_mode == SelfUpdateMode::CheckOnly { - check_rustup_update(cfg.process).await?; - } - + // Priority: no-self-update feature > self_update_mode > no-self-update args. + // Update only if rustup does **not** have the no-self-update feature, + // and auto-self-update is configured to **enable** + // and has **no** no-self-update parameter. + let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; if cfg!(feature = "no-self-update") { info!("self-update is disabled for this build of rustup"); info!("any updates to rustup will need to be fetched with your system package manager") + } else if self_update_mode == SelfUpdateMode::CheckOnly { + check_rustup_update(cfg.process).await?; + } else if self_update_mode == SelfUpdateMode::Enable + && !ensure_active_toolchain + && !opts.no_self_update + { + exit_code &= self_update::self_update(cfg.process).await?; } Ok(exit_code) From 8abc46a91a79d230fb9efa20308e9a9025c9085c Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 14 Jul 2025 11:00:03 +0200 Subject: [PATCH 3/4] cli: move self update logic out of update() --- src/cli/rustup_mode.rs | 17 ++--------------- src/cli/self_update.rs | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 2223297d61..7ef482ba4a 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -990,21 +990,8 @@ async fn update( cfg.tmp_cx.clean(); } - // Priority: no-self-update feature > self_update_mode > no-self-update args. - // Update only if rustup does **not** have the no-self-update feature, - // and auto-self-update is configured to **enable** - // and has **no** no-self-update parameter. - let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; - if cfg!(feature = "no-self-update") { - info!("self-update is disabled for this build of rustup"); - info!("any updates to rustup will need to be fetched with your system package manager") - } else if self_update_mode == SelfUpdateMode::CheckOnly { - check_rustup_update(cfg.process).await?; - } else if self_update_mode == SelfUpdateMode::Enable - && !ensure_active_toolchain - && !opts.no_self_update - { - exit_code &= self_update::self_update(cfg.process).await?; + if !ensure_active_toolchain { + exit_code &= self_update::self_update(opts.no_self_update, cfg).await?; } Ok(exit_code) diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 8e32e8ed68..36531a4163 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -1087,7 +1087,23 @@ pub(crate) fn self_update_permitted(explicit: bool) -> Result Result { +pub(crate) async fn self_update(disabled: bool, cfg: &Cfg<'_>) -> Result { + // Priority: no-self-update feature > self_update_mode > no-self-update args. + // Update only if rustup does **not** have the no-self-update feature, + // and auto-self-update is configured to **enable** + // and has **no** no-self-update parameter. + let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; + if cfg!(feature = "no-self-update") && !disabled { + info!("self-update is disabled for this build of rustup"); + info!("any updates to rustup will need to be fetched with your system package manager") + } else if self_update_mode == SelfUpdateMode::CheckOnly { + check_rustup_update(cfg.process).await?; + return Ok(utils::ExitCode(0)); + } else if disabled { + info!("self-update is disabled by command line argument"); + return Ok(utils::ExitCode(0)); + } + match self_update_permitted(false)? { SelfUpdatePermission::HardFail => { error!("Unable to self-update. STOP"); @@ -1098,13 +1114,13 @@ pub(crate) async fn self_update(process: &Process) -> Result { SelfUpdatePermission::Permit => {} } - let setup_path = prepare_update(process).await?; + let setup_path = prepare_update(&cfg.process).await?; if let Some(setup_path) = &setup_path { return run_update(setup_path); } else { // Try again in case we emitted "tool `{}` is already installed" last time. - install_proxies(process)?; + install_proxies(&cfg.process)?; } Ok(utils::ExitCode(0)) From b5a8ec984fc3d821206f2617c3e66967a60eec98 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 14 Jul 2025 11:04:46 +0200 Subject: [PATCH 4/4] cli: move self update logic out of check_updates() --- src/cli/rustup_mode.rs | 11 +---------- src/cli/self_update.rs | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 7ef482ba4a..cd8b96a335 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -894,16 +894,7 @@ async fn check_updates(cfg: &Cfg<'_>, opts: CheckOpts) -> Result self_update_mode > no-self-update args. - // Check for update only if rustup does **not** have the no-self-update feature, - // and auto-self-update is configured to **enable** - // and has **no** no-self-update parameter. - let self_update = !cfg!(feature = "no-self-update") - && self_update_mode == SelfUpdateMode::Enable - && !opts.no_self_update; - - if self_update && check_rustup_update(cfg.process).await? { + if check_rustup_update(opts.no_self_update, cfg).await? { update_available = true; } diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 36531a4163..d035d2818e 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -1097,7 +1097,7 @@ pub(crate) async fn self_update(disabled: bool, cfg: &Cfg<'_>) -> Result anyhow::Result { - let mut t = process.stdout().terminal(process); +pub(crate) async fn check_rustup_update(disabled: bool, cfg: &Cfg<'_>) -> anyhow::Result { + // Priority: no-self-update feature > self_update_mode > no-self-update args. + // Check for update only if rustup does **not** have the no-self-update feature, + // and auto-self-update is configured to **enable** + // and has **no** no-self-update parameter. + let self_update_mode = SelfUpdateMode::from_cfg(cfg)?; + if cfg!(feature = "no-self-update") || self_update_mode == SelfUpdateMode::Disable || disabled { + return Ok(false); + } + + let mut t = cfg.process.stdout().terminal(&cfg.process); // Get current rustup version let current_version = env!("CARGO_PKG_VERSION"); // Get available rustup version - let available_version = get_available_rustup_version(process).await?; + let available_version = get_available_rustup_version(&cfg.process).await?; let _ = t.attr(terminalsource::Attr::Bold); write!(t.lock(), "rustup - ")?;