cli: move more self update logic into self_update module

This commit is contained in:
Dirkjan Ochtman
2025-07-14 10:46:07 +02:00
committed by rami3l
parent 9fc3af392a
commit e9246d08f0
3 changed files with 68 additions and 70 deletions

View File

@@ -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<SelfUpdatePermission> {
Ok(SelfUpdatePermission::Permit)
}
#[cfg(not(windows))]
pub(crate) fn self_update_permitted(explicit: bool) -> Result<SelfUpdatePermission> {
// 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<utils::ExitCode> {
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

View File

@@ -996,7 +996,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?;
@@ -1005,7 +1005,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");

View File

@@ -32,12 +32,13 @@
use std::borrow::Cow;
use std::env::{self, consts::EXE_SUFFIX};
use std::fmt;
use std::fs;
#[cfg(not(windows))]
use std::io;
use std::io::Write;
use std::path::{Component, MAIN_SEPARATOR, Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use std::{fmt, fs};
use anyhow::{Context, Result, anyhow};
use cfg_if::cfg_if;
@@ -1071,6 +1072,67 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result<utils::Exi
Ok(utils::ExitCode(0))
}
#[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<SelfUpdatePermission> {
Ok(SelfUpdatePermission::Permit)
}
#[cfg(not(windows))]
pub(crate) fn self_update_permitted(explicit: bool) -> Result<SelfUpdatePermission> {
// 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<utils::ExitCode> {
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.
///
@@ -1089,11 +1151,11 @@ pub(crate) fn uninstall(no_prompt: bool, process: &Process) -> Result<utils::Exi
pub(crate) async fn update(cfg: &Cfg<'_>) -> Result<utils::ExitCode> {
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 => {