diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index bcb3933790..ee303003e7 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -49,6 +49,7 @@ pub fn main() -> Result<()> { }, ("install", Some(m)) => update(cfg, m)?, ("update", Some(m)) => update(cfg, m)?, + ("check", Some(_)) => check_updates(cfg)?, ("uninstall", Some(m)) => toolchain_remove(cfg, m)?, ("default", Some(m)) => default_(cfg, m)?, ("toolchain", Some(c)) => match c.subcommand() { @@ -200,6 +201,10 @@ pub fn cli() -> App<'static, 'static> { .takes_value(false), ), ) + .subcommand( + SubCommand::with_name("check") + .about("Check for updates to Rust toolchains") + ) .subcommand( SubCommand::with_name("default") .about("Set the default toolchain") @@ -668,6 +673,48 @@ fn default_(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { Ok(()) } +fn check_updates(cfg: &Cfg) -> Result<()> { + let mut t = term2::stdout(); + let channels = cfg.list_channels()?; + + for channel in channels { + match channel { + (ref name, Ok(ref toolchain)) => { + let current_version = toolchain.show_version()?; + let dist_version = toolchain.show_dist_version()?; + let _ = t.attr(term2::Attr::Bold); + write!(t, "{} - ", name)?; + match (current_version, dist_version) { + (None, None) => { + let _ = t.fg(term2::color::BRIGHT_RED); + writeln!(t, "Cannot identify installed or update versions")?; + } + (Some(cv), None) => { + let _ = t.fg(term2::color::BRIGHT_GREEN); + write!(t, "Up to date")?; + let _ = t.reset(); + writeln!(t, " : {}", cv)?; + } + (Some(cv), Some(dv)) => { + let _ = t.fg(term2::color::BRIGHT_YELLOW); + write!(t, "Update available")?; + let _ = t.reset(); + writeln!(t, " : {} -> {}", cv, dv)?; + } + (None, Some(dv)) => { + let _ = t.fg(term2::color::BRIGHT_YELLOW); + write!(t, "Update available")?; + let _ = t.reset(); + writeln!(t, " : (Unknown version) -> {}", dv)?; + } + } + } + (_, Err(err)) => return Err(err.into()), + } + } + Ok(()) +} + fn update(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { let self_update = !m.is_present("no-self-update") && !self_update::NEVER_SELF_UPDATE; if let Some(names) = m.values_of("toolchain") { diff --git a/src/config.rs b/src/config.rs index d33d8fe1e8..bb065f9469 100644 --- a/src/config.rs +++ b/src/config.rs @@ -390,10 +390,7 @@ impl Cfg { } } - pub fn update_all_channels( - &self, - force_update: bool, - ) -> Result)>> { + pub fn list_channels(&self) -> Result>)>> { let toolchains = self.list_toolchains()?; // Convert the toolchain strings to Toolchain values @@ -401,11 +398,20 @@ impl Cfg { let toolchains = toolchains.map(|n| (n.clone(), self.get_toolchain(&n, true))); // Filter out toolchains that don't track a release channel - let toolchains = toolchains - .filter(|&(_, ref t)| t.as_ref().map(Toolchain::is_tracking).unwrap_or(false)); + Ok(toolchains + .filter(|&(_, ref t)| t.as_ref().map(Toolchain::is_tracking).unwrap_or(false)) + .collect()) + } + + pub fn update_all_channels( + &self, + force_update: bool, + ) -> Result)>> { + let channels = self.list_channels()?; + let channels = channels.into_iter(); // Update toolchains and collect the results - let toolchains = toolchains.map(|(n, t)| { + let channels = channels.map(|(n, t)| { let t = t.and_then(|t| { let t = t.install_from_dist(force_update); if let Err(ref e) = t { @@ -417,7 +423,7 @@ impl Cfg { (n, t) }); - Ok(toolchains.collect()) + Ok(channels.collect()) } pub fn check_metadata_version(&self) -> Result<()> { diff --git a/src/dist/dist.rs b/src/dist/dist.rs index b21eb8c35d..173abbbb0a 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -679,7 +679,7 @@ fn update_from_dist_<'a>( } } -fn dl_v2_manifest<'a>( +pub fn dl_v2_manifest<'a>( download: DownloadCfg<'a>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, diff --git a/src/toolchain.rs b/src/toolchain.rs index bc66ad319e..2888be5bd0 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -470,6 +470,36 @@ impl<'a> Toolchain<'a> { }) } + pub fn show_version(&self) -> Result> { + if !self.exists() { + return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); + } + + let toolchain = &self.name; + let toolchain = ToolchainDesc::from_str(toolchain)?; + + let prefix = InstallPrefix::from(self.path.to_owned()); + let manifestation = Manifestation::open(prefix, toolchain.target.clone())?; + + match manifestation.load_manifest()? { + Some(manifest) => Ok(Some(manifest.get_rust_version()?.to_string())), + None => Ok(None), + } + } + + pub fn show_dist_version(&self) -> Result> { + let update_hash = self.update_hash()?; + + match crate::dist::dist::dl_v2_manifest( + self.download_cfg(), + update_hash.as_ref().map(|p| &**p), + &self.desc()?, + )? { + Some((manifest, _)) => Ok(Some(manifest.get_rust_version()?.to_string())), + None => Ok(None), + } + } + pub fn list_components(&self) -> Result> { if !self.exists() { return Err(ErrorKind::ToolchainNotInstalled(self.name.to_owned()).into()); diff --git a/tests/cli-exact.rs b/tests/cli-exact.rs index 01f8c0bec2..554c9a8ff6 100644 --- a/tests/cli-exact.rs +++ b/tests/cli-exact.rs @@ -4,7 +4,8 @@ pub mod mock; use crate::mock::clitools::{ - self, expect_err_ex, expect_ok, expect_ok_ex, this_host_triple, Config, Scenario, + self, expect_err_ex, expect_ok, expect_ok_ex, expect_stdout_ok, set_current_dist_date, + this_host_triple, Config, Scenario, }; macro_rules! for_host { @@ -14,7 +15,7 @@ macro_rules! for_host { } fn setup(f: &dyn Fn(&mut Config)) { - clitools::setup(Scenario::SimpleV2, f); + clitools::setup(Scenario::ArchivesV2, f); } #[test] @@ -31,7 +32,7 @@ fn update() { ), for_host!( r"info: syncing channel updates for 'nightly-{0}' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -68,6 +69,89 @@ fn update_again() { }); } +#[test] +fn check_updates_none() { + setup(&|config| { + set_current_dist_date(config, "2015-01-01"); + expect_ok(config, &["rustup", "update", "stable", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "beta", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); + expect_stdout_ok( + config, + &["rustup", "check"], + for_host!( + r"stable-{0} - Up to date : 1.0.0 (hash-s-1) +beta-{0} - Up to date : 1.1.0 (hash-b-1) +nightly-{0} - Up to date : 1.2.0 (hash-n-1) +" + ), + ); + }) +} + +#[test] +fn check_updates_some() { + setup(&|config| { + set_current_dist_date(config, "2015-01-01"); + expect_ok(config, &["rustup", "update", "stable", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "beta", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); + set_current_dist_date(config, "2015-01-02"); + expect_stdout_ok( + config, + &["rustup", "check"], + for_host!( + r"stable-{0} - Update available : 1.0.0 (hash-s-1) -> 1.1.0 (hash-s-2) +beta-{0} - Update available : 1.1.0 (hash-b-1) -> 1.2.0 (hash-b-2) +nightly-{0} - Update available : 1.2.0 (hash-n-1) -> 1.3.0 (hash-n-2) +" + ), + ); + }) +} + +#[test] +fn check_updates_with_update() { + setup(&|config| { + set_current_dist_date(config, "2015-01-01"); + expect_ok(config, &["rustup", "update", "stable", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "beta", "--no-self-update"]); + expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); + expect_stdout_ok( + config, + &["rustup", "check"], + for_host!( + r"stable-{0} - Up to date : 1.0.0 (hash-s-1) +beta-{0} - Up to date : 1.1.0 (hash-b-1) +nightly-{0} - Up to date : 1.2.0 (hash-n-1) +" + ), + ); + set_current_dist_date(config, "2015-01-02"); + expect_stdout_ok( + config, + &["rustup", "check"], + for_host!( + r"stable-{0} - Update available : 1.0.0 (hash-s-1) -> 1.1.0 (hash-s-2) +beta-{0} - Update available : 1.1.0 (hash-b-1) -> 1.2.0 (hash-b-2) +nightly-{0} - Update available : 1.2.0 (hash-n-1) -> 1.3.0 (hash-n-2) +" + ), + ); + expect_ok(config, &["rustup", "update", "beta", "--no-self-update"]); + expect_stdout_ok( + config, + &["rustup", "check"], + for_host!( + r"stable-{0} - Update available : 1.0.0 (hash-s-1) -> 1.1.0 (hash-s-2) +beta-{0} - Up to date : 1.2.0 (hash-b-2) +nightly-{0} - Update available : 1.2.0 (hash-n-1) -> 1.3.0 (hash-n-2) +" + ), + ); + }) +} + #[test] fn default() { setup(&|config| { @@ -82,7 +166,7 @@ fn default() { ), for_host!( r"info: syncing channel updates for 'nightly-{0}' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -342,7 +426,7 @@ fn update_invalid_toolchain() { &["rustup", "update", "nightly-2016-03-1"], r"", r"info: syncing channel updates for 'nightly-2016-03-1' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) error: target '2016-03-1' not found in channel. Perhaps check https://forge.rust-lang.org/platform-support.html for available targets ", ); @@ -357,7 +441,7 @@ fn default_invalid_toolchain() { &["rustup", "default", "nightly-2016-03-1"], r"", r"info: syncing channel updates for 'nightly-2016-03-1' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) error: target '2016-03-1' not found in channel. Perhaps check https://forge.rust-lang.org/platform-support.html for available targets ", ); diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs index dd19120e16..a1c3bcdba9 100644 --- a/tests/cli-rustup.rs +++ b/tests/cli-rustup.rs @@ -46,7 +46,7 @@ fn rustup_stable() { ), for_host!( r"info: syncing channel updates for 'stable-{0}' -info: latest update on 2015-01-02, rust version 1.1.0 +info: latest update on 2015-01-02, rust version 1.1.0 (hash-s-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -108,7 +108,7 @@ fn rustup_all_channels() { ), for_host!( r"info: syncing channel updates for 'stable-{0}' -info: latest update on 2015-01-02, rust version 1.1.0 +info: latest update on 2015-01-02, rust version 1.1.0 (hash-s-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -122,7 +122,7 @@ info: installing component 'cargo' info: installing component 'rust-std' info: installing component 'rust-docs' info: syncing channel updates for 'beta-{0}' -info: latest update on 2015-01-02, rust version 1.2.0 +info: latest update on 2015-01-02, rust version 1.2.0 (hash-b-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -136,7 +136,7 @@ info: installing component 'cargo' info: installing component 'rust-std' info: installing component 'rust-docs' info: syncing channel updates for 'nightly-{0}' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -177,7 +177,7 @@ fn rustup_some_channels_up_to_date() { ), for_host!( r"info: syncing channel updates for 'stable-{0}' -info: latest update on 2015-01-02, rust version 1.1.0 +info: latest update on 2015-01-02, rust version 1.1.0 (hash-s-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -192,7 +192,7 @@ info: installing component 'rust-std' info: installing component 'rust-docs' info: syncing channel updates for 'beta-{0}' info: syncing channel updates for 'nightly-{0}' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' @@ -240,7 +240,7 @@ fn default() { ), for_host!( r"info: syncing channel updates for 'nightly-{0}' -info: latest update on 2015-01-02, rust version 1.3.0 +info: latest update on 2015-01-02, rust version 1.3.0 (hash-n-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' diff --git a/tests/cli-self-upd.rs b/tests/cli-self-upd.rs index 5f908318ee..2b5104a274 100644 --- a/tests/cli-self-upd.rs +++ b/tests/cli-self-upd.rs @@ -869,7 +869,7 @@ fn first_install_exact() { ", for_host!( r"info: syncing channel updates for 'stable-{0}' -info: latest update on 2015-01-02, rust version 1.1.0 +info: latest update on 2015-01-02, rust version 1.1.0 (hash-s-2) info: downloading component 'rustc' info: downloading component 'cargo' info: downloading component 'rust-std' diff --git a/tests/dist.rs b/tests/dist.rs index 9920a169fe..de93d6223c 100644 --- a/tests/dist.rs +++ b/tests/dist.rs @@ -50,7 +50,7 @@ pub fn create_mock_channel( packages.push(MockPackage { name: "rust", - version: "1.0.0", + version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), @@ -113,7 +113,7 @@ pub fn create_mock_channel( let pkg = &bin[4..]; packages.push(MockPackage { name: pkg, - version: "1.0.0", + version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), @@ -138,7 +138,7 @@ pub fn create_mock_channel( packages.push(MockPackage { name: "rust-std", - version: "1.0.0", + version: "1.0.0".to_string(), targets: vec![ MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), @@ -203,7 +203,7 @@ pub fn create_mock_channel( fn bonus_component(name: &'static str, contents: Arc>) -> MockPackage { MockPackage { name, - version: "1.0.0", + version: "1.0.0".to_string(), targets: vec![MockTargetedPackage { target: "x86_64-apple-darwin".to_string(), available: true, diff --git a/tests/mock/clitools.rs b/tests/mock/clitools.rs index f0464c22e6..a47ec06af4 100644 --- a/tests/mock/clitools.rs +++ b/tests/mock/clitools.rs @@ -485,7 +485,7 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { chans.extend(vec![c1, c2, c3]); } let c4 = if s == Scenario::Unavailable { - build_mock_unavailable_channel("nightly", "2015-01-02", "1.3.0") + build_mock_unavailable_channel("nightly", "2015-01-02", "1.3.0", "hash-n-2") } else { build_mock_channel( s, @@ -693,7 +693,7 @@ fn build_mock_channel( MockPackage { name, - version, + version: format!("{} ({})", version, version_hash), targets: target_pkgs.collect(), } }); @@ -773,7 +773,12 @@ fn build_mock_channel( } } -fn build_mock_unavailable_channel(channel: &str, date: &str, version: &'static str) -> MockChannel { +fn build_mock_unavailable_channel( + channel: &str, + date: &str, + version: &str, + version_hash: &str, +) -> MockChannel { let host_triple = this_host_triple(); let packages = [ @@ -789,7 +794,7 @@ fn build_mock_unavailable_channel(channel: &str, date: &str, version: &'static s .iter() .map(|name| MockPackage { name, - version, + version: format!("{} ({})", version, version_hash), targets: vec![MockTargetedPackage { target: host_triple.clone(), available: false, diff --git a/tests/mock/dist.rs b/tests/mock/dist.rs index 2c82f66872..a0c65a98f5 100644 --- a/tests/mock/dist.rs +++ b/tests/mock/dist.rs @@ -80,7 +80,7 @@ pub struct MockChannel { pub struct MockPackage { // rust, rustc, rust-std-$triple, rust-doc, etc. pub name: &'static str, - pub version: &'static str, + pub version: String, pub targets: Vec, }