diff --git a/src/cli/common.rs b/src/cli/common.rs index 716f2181f9..28d5bd857f 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -16,9 +16,9 @@ use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; use crate::{ config::Cfg, - dist::{TargetTriple, ToolchainDesc}, + dist::{DistOptions, TargetTriple, ToolchainDesc}, errors::RustupError, - install::UpdateStatus, + install::{InstallMethod, UpdateStatus}, process::Process, toolchain::{LocalToolchainName, Toolchain, ToolchainName}, utils, @@ -217,10 +217,10 @@ pub(crate) async fn update_all_channels( ) -> Result { let profile = cfg.get_profile()?; let mut toolchains = Vec::new(); - for (desc, mut distributable) in cfg.list_channels()? { - let result = distributable - .update(&[], &[], profile, force_update, false) - .await; + for (desc, distributable) in cfg.list_channels()? { + let options = DistOptions::new(&[], &[], &desc, profile, force_update, cfg)? + .for_update(&distributable, false); + let result = InstallMethod::Dist(options).install().await; if let Err(e) = &result { error!("{e}"); diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 94cead6bd9..6979b8f4a7 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -41,7 +41,7 @@ use crate::{ command, component_for_bin, config::{ActiveSource, Cfg}, dist::{ - AutoInstallMode, PartialToolchainDesc, Profile, TargetTriple, + AutoInstallMode, DistOptions, PartialToolchainDesc, Profile, TargetTriple, download::DownloadCfg, manifest::{Component, ComponentStatus}, }, @@ -386,6 +386,10 @@ struct UpdateOpts { #[arg(long)] no_self_update: bool, + /// Don't try to update the installed toolchain + #[arg(long)] + no_update: bool, + /// Force an update, even if some components are missing #[arg(long)] force: bool, @@ -960,27 +964,27 @@ async fn update( let components = opts.component.iter().map(|s| &**s).collect::>(); let targets = opts.target.iter().map(|s| &**s).collect::>(); + let dist_opts = DistOptions::new( + &components, + &targets, + &desc, + cfg.get_profile()?, + opts.force, + cfg, + )?; - let force = opts.force; - let allow_downgrade = opts.allow_downgrade; - let profile = cfg.get_profile()?; let status = match DistributableToolchain::new(cfg, desc.clone()) { - Ok(mut d) => { - let profile = cfg.get_profile()?; - d.update(&components, &targets, profile, force, allow_downgrade) - .await? + Ok(d) => { + if !opts.no_update { + InstallMethod::Dist(dist_opts.for_update(&d, opts.allow_downgrade)) + .install() + .await? + } else { + UpdateStatus::Unchanged + } } Err(RustupError::ToolchainNotInstalled { .. }) => { - DistributableToolchain::install( - cfg, - &desc, - &components, - &targets, - profile, - force, - ) - .await? - .0 + DistributableToolchain::install(dist_opts).await?.0 } Err(e) => Err(e)?, }; @@ -1520,10 +1524,8 @@ async fn override_add( Err(e @ RustupError::ToolchainNotInstalled { .. }) => match &toolchain_name { ToolchainName::Custom(_) => Err(e)?, ToolchainName::Official(desc) => { - let status = - DistributableToolchain::install(cfg, desc, &[], &[], cfg.get_profile()?, false) - .await? - .0; + let options = DistOptions::new(&[], &[], desc, cfg.get_profile()?, false, cfg)?; + let status = DistributableToolchain::install(options).await?.0; writeln!(cfg.process.stdout().lock())?; common::show_channel_update( cfg, diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index a5d8cd6d2e..2e70b187d9 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -59,10 +59,13 @@ use crate::{ markdown::md, }, config::Cfg, - dist::{PartialToolchainDesc, Profile, TargetTriple, ToolchainDesc, download::DownloadCfg}, + dist::{ + DistOptions, PartialToolchainDesc, Profile, TargetTriple, ToolchainDesc, + download::DownloadCfg, + }, download::download_file, errors::RustupError, - install::UpdateStatus, + install::{InstallMethod, UpdateStatus}, process::Process, toolchain::{ DistributableToolchain, MaybeOfficialToolchainName, ResolvableToolchainName, Toolchain, @@ -996,6 +999,7 @@ async fn maybe_install_rust(opts: InstallOpts<'_>, cfg: &mut Cfg<'_>) -> Result< let (components, targets) = (opts.components, opts.targets); let toolchain = opts.install(cfg)?; if let Some(desc) = &toolchain { + let options = DistOptions::new(components, targets, desc, cfg.get_profile()?, true, cfg)?; let status = if Toolchain::exists(cfg, &desc.into())? { warn!("Updating existing toolchain, profile choice will be ignored"); // If we have a partial install we might not be able to read content here. We could: @@ -1003,21 +1007,12 @@ async fn maybe_install_rust(opts: InstallOpts<'_>, cfg: &mut Cfg<'_>) -> Result< // - silently ignore it (and provide inconsistent metadata for reporting the install/update change) // - delete the partial install and start over // For now, we error. - let mut toolchain = DistributableToolchain::new(cfg, desc.clone())?; - toolchain - .update(components, targets, cfg.get_profile()?, true, false) + let toolchain = DistributableToolchain::new(cfg, desc.clone())?; + InstallMethod::Dist(options.for_update(&toolchain, false)) + .install() .await? } else { - DistributableToolchain::install( - cfg, - desc, - components, - targets, - cfg.get_profile()?, - true, - ) - .await? - .0 + DistributableToolchain::install(options).await?.0 }; check_proxy_sanity(cfg.process, components, desc)?; diff --git a/src/config.rs b/src/config.rs index 5fff6c5ec7..bbba13a075 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,13 +8,15 @@ use serde::Deserialize; use thiserror::Error as ThisError; use tracing::{debug, error, info, trace, warn}; -use crate::dist::AutoInstallMode; use crate::{ cli::{common, self_update::SelfUpdateMode}, - dist::{self, PartialToolchainDesc, Profile, TargetTriple, ToolchainDesc, temp}, + dist::{ + self, AutoInstallMode, DistOptions, PartialToolchainDesc, Profile, TargetTriple, + ToolchainDesc, temp, + }, errors::RustupError, fallback_settings::FallbackSettings, - install::UpdateStatus, + install::{InstallMethod, UpdateStatus}, process::Process, settings::{MetadataVersion, Settings, SettingsFile}, toolchain::{ @@ -787,29 +789,30 @@ impl<'a> Cfg<'a> { } let components: Vec<_> = components.iter().map(AsRef::as_ref).collect(); let targets: Vec<_> = targets.iter().map(AsRef::as_ref).collect(); - let profile = match profile { - Some(profile) => profile, - None => self.get_profile()?, - }; + let mut options = DistOptions::new( + &components, + &targets, + toolchain, + match profile { + Some(p) => p, + None => self.get_profile()?, + }, + false, + self, + )?; + let (status, toolchain) = match DistributableToolchain::new(self, toolchain.clone()) { Err(RustupError::ToolchainNotInstalled { .. }) => { - DistributableToolchain::install( - self, - toolchain, - &components, - &targets, - profile, - false, - ) - .await? + DistributableToolchain::install(options).await? } - Ok(mut distributable) => { + Ok(distributable) => { if verbose { info!("using existing install for {toolchain}"); } let status = if !distributable.components_exist(&components, &targets)? { - distributable - .update(&components, &targets, profile, true, false) + options.force = true; + InstallMethod::Dist(options.for_update(&distributable, false)) + .install() .await? } else { UpdateStatus::Unchanged diff --git a/src/dist/mod.rs b/src/dist/mod.rs index 5198237aa0..7691447f62 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -19,7 +19,13 @@ use serde::{Deserialize, Serialize}; use thiserror::Error as ThisError; use tracing::{debug, info, warn}; -use crate::{config::Cfg, errors::RustupError, process::Process, toolchain::ToolchainName, utils}; +use crate::{ + config::Cfg, + errors::RustupError, + process::Process, + toolchain::{DistributableToolchain, ToolchainName}, + utils, +}; pub mod component; pub(crate) mod config; @@ -876,14 +882,14 @@ impl fmt::Display for Profile { } } -pub(crate) struct DistOptions<'a> { - pub(super) cfg: &'a Cfg<'a>, +pub(crate) struct DistOptions<'cfg, 'a> { + pub(super) cfg: &'cfg Cfg<'cfg>, pub(super) toolchain: &'a ToolchainDesc, profile: Profile, pub(super) update_hash: PathBuf, - dl_cfg: DownloadCfg<'a>, + dl_cfg: DownloadCfg<'cfg>, /// --force bool is whether to force an update/install - force: bool, + pub(super) force: bool, /// --allow-downgrade pub(super) allow_downgrade: bool, /// toolchain already exists @@ -896,14 +902,14 @@ pub(crate) struct DistOptions<'a> { targets: &'a [&'a str], } -impl<'a> DistOptions<'a> { +impl<'cfg, 'a> DistOptions<'cfg, 'a> { pub(super) fn new( components: &'a [&'a str], targets: &'a [&'a str], toolchain: &'a ToolchainDesc, profile: Profile, force: bool, - cfg: &'a Cfg<'_>, + cfg: &'cfg Cfg<'cfg>, ) -> Result { Ok(Self { cfg, @@ -919,6 +925,31 @@ impl<'a> DistOptions<'a> { targets, }) } + + pub(super) fn for_update( + mut self, + toolchain: &'a DistributableToolchain<'cfg>, + allow_downgrade: bool, + ) -> Self { + self.allow_downgrade = allow_downgrade; + self.exists = true; + self.old_date_version = + // Ignore a missing manifest: we can't report the old version + // correctly, and it probably indicates an incomplete install, so do + // not report an old rustc version either. + toolchain.get_manifest() + .map(|m| { + ( + m.date, + // should rustc_version be a free function on a trait? + // note that prev_version can be junk if the rustc component is missing ... + toolchain.toolchain.rustc_version(), + ) + }) + .ok(); + + self + } } // Installs or updates a toolchain from a dist server. If an initial @@ -929,7 +960,7 @@ impl<'a> DistOptions<'a> { #[tracing::instrument(level = "trace", err(level = "trace"), skip_all, fields(profile = ?opts.profile, prefix = %prefix.path().display()))] pub(crate) async fn update_from_dist( prefix: &InstallPrefix, - opts: &DistOptions<'_>, + opts: &DistOptions<'_, '_>, ) -> Result> { let fresh_install = !prefix.path().exists(); // fresh_install means the toolchain isn't present, but hash_exists means there is a stray hash file diff --git a/src/install.rs b/src/install.rs index 43637deebc..a7147e76f1 100644 --- a/src/install.rs +++ b/src/install.rs @@ -20,21 +20,21 @@ pub(crate) enum UpdateStatus { Unchanged, } -pub(crate) enum InstallMethod<'a> { +pub(crate) enum InstallMethod<'cfg, 'a> { Copy { src: &'a Path, dest: &'a CustomToolchainName, - cfg: &'a Cfg<'a>, + cfg: &'cfg Cfg<'cfg>, }, Link { src: &'a Path, dest: &'a CustomToolchainName, - cfg: &'a Cfg<'a>, + cfg: &'cfg Cfg<'cfg>, }, - Dist(DistOptions<'a>), + Dist(DistOptions<'cfg, 'a>), } -impl InstallMethod<'_> { +impl InstallMethod<'_, '_> { // Install a toolchain #[tracing::instrument(level = "trace", err(level = "trace"), skip_all)] pub(crate) async fn install(self) -> Result { diff --git a/src/toolchain.rs b/src/toolchain.rs index 827d6152b2..f13ef0aaf3 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -23,7 +23,7 @@ use crate::{ RustupError, config::{ActiveSource, Cfg, InstalledPath}, dist::{ - PartialToolchainDesc, TargetTriple, + DistOptions, PartialToolchainDesc, TargetTriple, component::{Component, Components}, prefix::InstallPrefix, }, @@ -61,12 +61,8 @@ impl<'a> Toolchain<'a> { name: ToolchainName::Official(desc), .. }) if install_if_missing => { - Ok( - DistributableToolchain::install(cfg, &desc, &[], &[], cfg.get_profile()?, true) - .await? - .1 - .toolchain, - ) + let options = DistOptions::new(&[], &[], &desc, cfg.get_profile()?, true, cfg)?; + Ok(DistributableToolchain::install(options).await?.1.toolchain) } Err(e) => Err(e.into()), } diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index f7573691ae..93c6d3bed0 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -11,7 +11,7 @@ use crate::{ RustupError, component_for_bin, config::{ActiveSource, Cfg}, dist::{ - DistOptions, PartialToolchainDesc, Profile, ToolchainDesc, + DistOptions, PartialToolchainDesc, ToolchainDesc, config::Config, download::DownloadCfg, manifest::{Component, ComponentStatus, Manifest}, @@ -29,21 +29,16 @@ use super::{ /// An official toolchain installed on the local disk #[derive(Debug)] pub(crate) struct DistributableToolchain<'a> { - pub(super) toolchain: Toolchain<'a>, + pub(crate) toolchain: Toolchain<'a>, desc: ToolchainDesc, } impl<'a> DistributableToolchain<'a> { #[tracing::instrument(level = "trace", err(level = "trace"), skip_all)] pub(crate) async fn install( - cfg: &'a Cfg<'a>, - toolchain: &ToolchainDesc, - components: &[&str], - targets: &[&str], - profile: Profile, - force: bool, + options: DistOptions<'a, '_>, ) -> anyhow::Result<(UpdateStatus, Self)> { - let options = DistOptions::new(components, targets, toolchain, profile, force, cfg)?; + let (cfg, toolchain) = (options.cfg, options.toolchain); let status = InstallMethod::Dist(options).install().await?; Ok((status, Self::new(cfg, toolchain.clone())?)) } @@ -350,45 +345,6 @@ impl<'a> DistributableToolchain<'a> { InstallPrefix::from(self.toolchain.path().to_owned()).guess_v1_manifest() } - /// Update a toolchain with control over the channel behaviour - #[tracing::instrument(level = "trace", err(level = "trace"), skip_all)] - pub(crate) async fn update( - &mut self, - components: &[&str], - targets: &[&str], - profile: Profile, - force: bool, - allow_downgrade: bool, - ) -> anyhow::Result { - let mut options = DistOptions::new( - components, - targets, - &self.desc, - profile, - force, - self.toolchain.cfg, - )?; - - options.allow_downgrade = allow_downgrade; - options.exists = true; - options.old_date_version = - // Ignore a missing manifest: we can't report the old version - // correctly, and it probably indicates an incomplete install, so do - // not report an old rustc version either. - self.get_manifest() - .map(|m| { - ( - m.date, - // should rustc_version be a free function on a trait? - // note that prev_version can be junk if the rustc component is missing ... - self.toolchain.rustc_version(), - ) - }) - .ok(); - - InstallMethod::Dist(options).install().await - } - pub fn recursion_error(&self, binary_lossy: String) -> Result { let prefix = InstallPrefix::from(self.toolchain.path()); let manifestation = Manifestation::open(prefix, self.desc.target.clone())?; diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 708e3757ec..c65cdc119a 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -1737,6 +1737,32 @@ info: it's active because: overridden by '[TOOLCHAIN_FILE]' .is_ok(); } +#[tokio::test] +async fn toolchain_install_no_change_with_no_update() { + let mut cx = CliTestContext::new(Scenario::None).await; + + { + let cx = cx.with_dist_dir(Scenario::ArchivesV2_2015_01_01); + cx.config + .expect(["rustup", "toolchain", "add", "stable"]) + .await + .is_ok(); + } + + let cx = cx.with_dist_dir(Scenario::SimpleV2); + cx.config + .expect(["rustup", "install", "--no-update", "stable"]) + .await + .with_stdout(snapbox::str![[r#" + + stable-[HOST_TRIPLE] unchanged - 1.0.0 (hash-stable-1.0.0) + + +"#]]) + .with_stderr(snapbox::str![[""]]) + .is_ok(); +} + #[tokio::test] async fn toolchain_update_is_like_update() { let cx = CliTestContext::new(Scenario::SimpleV2).await; diff --git a/tests/suite/cli_rustup_ui/rustup_toolchain_cmd_install_cmd_help_flag.stdout.term.svg b/tests/suite/cli_rustup_ui/rustup_toolchain_cmd_install_cmd_help_flag.stdout.term.svg index cf66aa826d..fada49de25 100644 --- a/tests/suite/cli_rustup_ui/rustup_toolchain_cmd_install_cmd_help_flag.stdout.term.svg +++ b/tests/suite/cli_rustup_ui/rustup_toolchain_cmd_install_cmd_help_flag.stdout.term.svg @@ -1,4 +1,4 @@ - +