diff --git a/src/cli/common.rs b/src/cli/common.rs index 9302272b36..bae45da5f5 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -228,6 +228,7 @@ fn show_channel_updates( pub(crate) async fn update_all_channels( cfg: &Cfg<'_>, force_update: bool, + check: bool, ) -> anyhow::Result { let profile = cfg.get_profile()?; let channels = cfg.list_channels()?; @@ -241,7 +242,9 @@ pub(crate) async fn update_all_channels( })) .await; - let mut toolchains = Vec::new(); + let mut toolchains = Vec::with_capacity(channels_with_manifests.len()); + let mut has_update_error = false; + let mut has_update = false; for (desc, distributable, manifest) in channels_with_manifests { let result = if force_update || manifest.is_some() { let options = DistOptions::new(&[], &[], &desc, profile, force_update, cfg)? @@ -251,16 +254,22 @@ pub(crate) async fn update_all_channels( Ok(UpdateStatus::Unchanged) }; - if let Err(e) = &result { - error!("{e}"); + match &result { + Ok(UpdateStatus::Updated(_)) | Ok(UpdateStatus::Installed) => has_update = true, + Err(e) => { + has_update_error = true; + error!("{e}"); + } + _ => (), } toolchains.push((desc, result)); } - let has_update_error = toolchains.iter().any(|(_, r)| r.is_err()); let exit_code = if has_update_error { ExitCode::FAILURE + } else if check && has_update { + ExitCode::UPDATES_AVAILABLE } else { ExitCode::SUCCESS }; diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 049818a214..824722d8a0 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -202,6 +202,10 @@ enum RustupSubcmd { /// Install toolchains that require an emulator. See https://github.com/rust-lang/rustup/wiki/Non-host-toolchains #[arg(long)] force_non_host: bool, + + /// Indicate update status via exit code + #[arg(long)] + check: bool, }, /// Check for updates to Rust toolchains and rustup @@ -493,6 +497,10 @@ struct UpdateOpts { /// Set the installed toolchain as the default toolchain #[arg(long)] default: bool, + + /// Indicate update status via exit code + #[arg(long)] + check: bool, } #[derive(Debug, Default, Args)] @@ -765,6 +773,7 @@ pub async fn main( allow_downgrade, force, force_non_host, + check, } => { update( cfg, @@ -774,6 +783,7 @@ pub async fn main( allow_downgrade, force, force_non_host, + check, ..UpdateOpts::default() }, false, @@ -1185,7 +1195,7 @@ async fn update( info!("it's active because: {}", source.to_reason()); exit_code &= self_update_mode.update(should_self_update, &dl_cfg).await?; } else { - exit_code &= common::update_all_channels(cfg, opts.force).await?; + exit_code &= common::update_all_channels(cfg, opts.force, opts.check).await?; exit_code &= self_update_mode.update(should_self_update, &dl_cfg).await?; info!("cleaning up downloads & tmp directories"); diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 9ffba60f28..1621e64847 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -16,6 +16,57 @@ use rustup::{ utils::raw, }; +#[tokio::test] +async fn update_check_no_updates() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect(["rustup", "toolchain", "add", "stable"]) + .await + .is_ok(); + + cx.config + .expect(["rustup", "update", "--check"]) + .await + .is_ok() + .with_stdout(snapbox::str![[r#" + + stable-[HOST_TUPLE] unchanged - 1.1.0 (hash-stable-1.1.0) + + +"#]]); +} + +#[tokio::test] +async fn update_check_with_partial_updates() { + 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", "toolchain", "add", "beta"]) + .await + .is_ok(); + + cx.config + .expect(["rustup", "update", "--check"]) + .await + .has_code(100) + .with_stdout(snapbox::str![[r#" + + stable-[HOST_TUPLE] updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) + beta-[HOST_TUPLE] unchanged - 1.2.0 (hash-beta-1.2.0) + + +"#]]); +} + #[tokio::test] async fn rustup_stable() { let mut cx = CliTestContext::new(Scenario::None).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 d42f1d8930..57498171b1 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 @@ - +