diff --git a/src/cli/common.rs b/src/cli/common.rs index 1db6bd80e8..9766b88950 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -10,13 +10,14 @@ use std::{cmp, env}; use anstyle::Style; use anyhow::{Context, Result, anyhow}; use clap_cargo::style::{CONTEXT, ERROR, UPDATE_ADDED, UPDATE_UNCHANGED, UPDATE_UPGRADED}; +use futures_util::future::join_all; use git_testament::{git_testament, render_testament}; use tracing::{error, info, warn}; use tracing_subscriber::{EnvFilter, Registry, reload::Handle}; use crate::{ config::Cfg, - dist::{DistOptions, TargetTriple, ToolchainDesc}, + dist::{DistOptions, TargetTriple, ToolchainDesc, manifest::Manifest}, errors::RustupError, install::{InstallMethod, UpdateStatus}, process::Process, @@ -213,11 +214,27 @@ fn show_channel_updates( pub(crate) async fn update_all_channels(cfg: &Cfg<'_>, force_update: bool) -> Result { let profile = cfg.get_profile()?; + let channels = cfg.list_channels()?; + + // Run a pre-check to determine which channels have updates available. + let prefetched_manifests: Vec> = + join_all(channels.iter().map(|(_, d)| d.fetch_dist_manifest())) + .await + .into_iter() + .map(|r| r.unwrap_or(None)) + .collect(); + let mut toolchains = Vec::new(); - 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; + for ((desc, distributable), manifest) in channels.into_iter().zip(prefetched_manifests) { + let result = if force_update || manifest.is_some() { + let options = DistOptions::new(&[], &[], &desc, profile, force_update, cfg)? + .for_update(&distributable, false); + InstallMethod::Dist(options) + .install_with_manifest(manifest) + .await + } else { + Ok(UpdateStatus::Unchanged) + }; if let Err(e) = &result { error!("{e}"); diff --git a/src/dist/mod.rs b/src/dist/mod.rs index 0bda9963fe..ad127e1c11 100644 --- a/src/dist/mod.rs +++ b/src/dist/mod.rs @@ -955,7 +955,11 @@ impl<'cfg, 'a> DistOptions<'cfg, 'a> { // // Returns the manifest's hash if anything changed. #[tracing::instrument(level = "trace", err(level = "trace"), skip_all, fields(profile = ?self.profile, prefix = %prefix.path().display()))] - pub(crate) async fn install_into(&self, prefix: &InstallPrefix) -> Result> { + pub(crate) async fn install_into( + &self, + prefix: &InstallPrefix, + manifest: Option<(ManifestV2, String)>, + ) -> 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 if fresh_install && self.update_hash.exists() { @@ -1013,6 +1017,7 @@ impl<'cfg, 'a> DistOptions<'cfg, 'a> { }; let mut toolchain = self.toolchain.clone(); + let mut prefetched_manifest = manifest; let res = loop { let result = try_update_from_dist_( &self.dl_cfg, @@ -1028,6 +1033,7 @@ impl<'cfg, 'a> DistOptions<'cfg, 'a> { self.targets, &mut fetched, self.cfg, + prefetched_manifest.take(), ) .await; @@ -1126,27 +1132,32 @@ async fn try_update_from_dist_( targets: &[&str], fetched: &mut String, cfg: &Cfg<'_>, + prefetched_manifest: Option<(ManifestV2, String)>, ) -> Result> { let toolchain_str = toolchain.to_string(); let manifestation = Manifestation::open(prefix.clone(), toolchain.target.clone())?; // TODO: Add a notification about which manifest version is going to be used info!("syncing channel updates for {toolchain_str}"); - match download - .dl_v2_manifest( - // Even if manifest has not changed, we must continue to install requested components. - // So if components or targets is not empty, we skip passing `update_hash` so that - // we essentially degenerate to `rustup component add` / `rustup target add` - if components.is_empty() && targets.is_empty() { - Some(update_hash) - } else { - None - }, - toolchain, - cfg, - ) - .await - { + let manifest_result = if prefetched_manifest.is_some() { + Ok(prefetched_manifest) + } else { + download + .dl_v2_manifest( + // Even if manifest has not changed, we must continue to install requested components. + // So if components or targets is not empty, we skip passing `update_hash` so that + // we essentially degenerate to `rustup component add` / `rustup target add` + if components.is_empty() && targets.is_empty() { + Some(update_hash) + } else { + None + }, + toolchain, + cfg, + ) + .await + }; + match manifest_result { Ok(Some((m, hash))) => { match m.get_rust_version() { Ok(version) => info!("latest update on {} for version {version}", m.date), diff --git a/src/install.rs b/src/install.rs index 74b0f9bd16..d704463b56 100644 --- a/src/install.rs +++ b/src/install.rs @@ -7,7 +7,7 @@ use tracing::debug; use crate::{ config::Cfg, - dist::{DistOptions, prefix::InstallPrefix}, + dist::{DistOptions, manifest::Manifest, prefix::InstallPrefix}, errors::RustupError, toolchain::{CustomToolchainName, LocalToolchainName, Toolchain}, utils, @@ -38,6 +38,13 @@ impl InstallMethod<'_, '_> { // Install a toolchain #[tracing::instrument(level = "trace", err(level = "trace"), skip_all)] pub(crate) async fn install(self) -> Result { + self.install_with_manifest(None).await + } + + pub(crate) async fn install_with_manifest( + self, + manifest: Option<(Manifest, String)>, + ) -> Result { // Initialize rayon for use by the remove_dir_all crate limiting the number of threads. // This will error if rayon is already initialized but it's fine to ignore that. let _ = rayon::ThreadPoolBuilder::new() @@ -54,7 +61,7 @@ impl InstallMethod<'_, '_> { } debug!("toolchain directory: {}", self.dest_path().display()); - let updated = self.run(&self.dest_path()).await?; + let updated = self.run(&self.dest_path(), manifest).await?; let status = match updated { false => { @@ -82,7 +89,7 @@ impl InstallMethod<'_, '_> { } } - async fn run(&self, path: &Path) -> Result { + async fn run(&self, path: &Path, manifest: Option<(Manifest, String)>) -> Result { if path.exists() { // Don't uninstall first for Dist method match self { @@ -104,7 +111,7 @@ impl InstallMethod<'_, '_> { } InstallMethod::Dist(opts) => { let prefix = &InstallPrefix::from(path.to_owned()); - let maybe_new_hash = opts.install_into(prefix).await?; + let maybe_new_hash = opts.install_into(prefix, manifest).await?; if let Some(hash) = maybe_new_hash { utils::write_file("update hash", &opts.update_hash, &hash)?; diff --git a/src/toolchain/distributable.rs b/src/toolchain/distributable.rs index 74f36b5f28..47b9ab3878 100644 --- a/src/toolchain/distributable.rs +++ b/src/toolchain/distributable.rs @@ -445,15 +445,18 @@ impl<'a> DistributableToolchain<'a> { Ok(()) } - pub async fn show_dist_version(&self) -> anyhow::Result> { - match DownloadCfg::new(self.toolchain.cfg) + pub async fn fetch_dist_manifest(&self) -> anyhow::Result> { + DownloadCfg::new(self.toolchain.cfg) .dl_v2_manifest( Some(&self.toolchain.cfg.get_hash_file(&self.desc, false)?), &self.desc, self.toolchain.cfg, ) - .await? - { + .await + } + + pub async fn show_dist_version(&self) -> anyhow::Result> { + match self.fetch_dist_manifest().await? { Some((manifest, _)) => Ok(Some(manifest.get_rust_version()?.to_string())), None => Ok(None), } diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 229c5650b3..18eb63c305 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -97,7 +97,6 @@ async fn rustup_stable_no_change() { "#]]) .with_stderr(snapbox::str![[r#" -info: syncing channel updates for stable-[HOST_TRIPLE] info: cleaning up downloads & tmp directories "#]]) @@ -210,7 +209,6 @@ info: syncing channel updates for stable-[HOST_TRIPLE] info: latest update on 2015-01-02 for version 1.1.0 (hash-stable-1.1.0) info: removing previous version of component cargo ... -info: syncing channel updates for beta-[HOST_TRIPLE] info: syncing channel updates for nightly-[HOST_TRIPLE] info: latest update on 2015-01-02 for version 1.3.0 (hash-nightly-2) info: removing previous version of component cargo diff --git a/tests/suite/cli_rustup_ui/rustup_update_no_change.stderr.term.svg b/tests/suite/cli_rustup_ui/rustup_update_no_change.stderr.term.svg index 5ce55154fc..e974042abe 100644 --- a/tests/suite/cli_rustup_ui/rustup_update_no_change.stderr.term.svg +++ b/tests/suite/cli_rustup_ui/rustup_update_no_change.stderr.term.svg @@ -1,4 +1,4 @@ - +