diff --git a/src/cli/common.rs b/src/cli/common.rs index 3d2e49cd9b..8e5e7c3db5 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -9,14 +9,11 @@ use rustup::utils::notify::NotificationLevel; use rustup::utils::utils; use rustup::{Cfg, Notification, Toolchain, UpdateStatus}; use std::fs; -use std::io::{BufRead, BufReader, ErrorKind, Write}; +use std::io::{BufRead, ErrorKind, Write}; use std::path::Path; -use std::process::{Command, Stdio}; use std::sync::Arc; -use std::time::Duration; use std::{cmp, env, iter}; use term2::Terminal; -use wait_timeout::ChildExt; pub const WARN_COMPLETE_PROFILE: &str = "downloading with complete profile isn't recommended unless you are a developer of the rust language"; @@ -153,16 +150,18 @@ fn show_channel_updates( ) -> Result<()> { let data = toolchains.into_iter().map(|(name, result)| { let toolchain = cfg.get_toolchain(&name, false).expect(""); - let version = rustc_version(&toolchain); + let version = toolchain.rustc_version(); let banner; let color; + let mut previous_version: Option = None; match result { Ok(UpdateStatus::Installed) => { banner = "installed"; color = Some(term2::color::GREEN); } - Ok(UpdateStatus::Updated) => { + Ok(UpdateStatus::Updated(v)) => { + previous_version = Some(v); banner = "updated"; color = Some(term2::color::GREEN); } @@ -178,7 +177,7 @@ fn show_channel_updates( let width = name.len() + 1 + banner.len(); - (name, banner, width, color, version) + (name, banner, width, color, version, previous_version) }); let mut t = term2::stdout(); @@ -186,9 +185,9 @@ fn show_channel_updates( let data: Vec<_> = data.collect(); let max_width = data .iter() - .fold(0, |a, &(_, _, width, _, _)| cmp::max(a, width)); + .fold(0, |a, &(_, _, width, _, _, _)| cmp::max(a, width)); - for (name, banner, width, color, version) in data { + for (name, banner, width, color, version, previous_version) in data { let padding = max_width - width; let padding: String = iter::repeat(' ').take(padding).collect(); let _ = write!(t, " {}", padding); @@ -199,7 +198,11 @@ fn show_channel_updates( let _ = write!(t, "{} ", name); let _ = write!(t, "{}", banner); let _ = t.reset(); - let _ = writeln!(t, " - {}", version); + let _ = write!(t, " - {}", version); + if let Some(previous_version) = previous_version { + let _ = write!(t, " (from {})", previous_version); + } + let _ = writeln!(t); } let _ = writeln!(t); @@ -313,57 +316,6 @@ where Ok(()) } -pub fn rustc_version(toolchain: &Toolchain<'_>) -> String { - if toolchain.exists() { - let rustc_path = toolchain.binary_file("rustc"); - if utils::is_file(&rustc_path) { - let mut cmd = Command::new(&rustc_path); - cmd.arg("--version"); - cmd.stdin(Stdio::null()); - cmd.stdout(Stdio::piped()); - cmd.stderr(Stdio::piped()); - toolchain.set_ldpath(&mut cmd); - - // some toolchains are faulty with some combinations of platforms and - // may fail to launch but also to timely terminate. - // (known cases include Rust 1.3.0 through 1.10.0 in recent macOS Sierra.) - // we guard against such cases by enforcing a reasonable timeout to read. - let mut line1 = None; - if let Ok(mut child) = cmd.spawn() { - let timeout = Duration::new(10, 0); - match child.wait_timeout(timeout) { - Ok(Some(status)) if status.success() => { - let out = child - .stdout - .expect("Child::stdout requested but not present"); - let mut line = String::new(); - if BufReader::new(out).read_line(&mut line).is_ok() { - let lineend = line.trim_end_matches(&['\r', '\n'][..]).len(); - line.truncate(lineend); - line1 = Some(line); - } - } - Ok(None) => { - let _ = child.kill(); - return String::from("(timeout reading rustc version)"); - } - Ok(Some(_)) | Err(_) => {} - } - } - - if let Some(line1) = line1 { - line1 - } else { - String::from("(error reading rustc version)") - } - } else { - String::from("(rustc does not exist)") - } - } else { - String::from("(toolchain not installed)") - } -} - pub fn list_targets(toolchain: &Toolchain<'_>) -> Result<()> { let mut t = term2::stdout(); for component in toolchain.list_components()? { diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 43a2196055..ea579d1eaa 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -840,7 +840,7 @@ fn update(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result<()> { None }; - if let Some(status) = status { + if let Some(status) = status.clone() { println!(); common::show_channel_update(cfg, toolchain.name(), Ok(status))?; } @@ -1002,11 +1002,11 @@ fn show(cfg: &Cfg) -> Result<()> { Ok(atc) => match atc { Some((ref toolchain, Some(ref reason))) => { writeln!(t, "{} ({})", toolchain.name(), reason)?; - writeln!(t, "{}", common::rustc_version(toolchain))?; + writeln!(t, "{}", toolchain.rustc_version())?; } Some((ref toolchain, None)) => { writeln!(t, "{} (default)", toolchain.name())?; - writeln!(t, "{}", common::rustc_version(toolchain))?; + writeln!(t, "{}", toolchain.rustc_version())?; } None => { writeln!(t, "no active toolchain")?; diff --git a/src/toolchain.rs b/src/toolchain.rs index 8556c84f02..c976c88eb7 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -16,11 +16,14 @@ use std::env; use std::env::consts::EXE_SUFFIX; use std::ffi::OsStr; use std::ffi::OsString; +use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, Stdio}; use std::str::FromStr; +use std::time::Duration; use url::Url; +use wait_timeout::ChildExt; /// A fully resolved reference to a toolchain which may or may not exist pub struct Toolchain<'a> { @@ -38,10 +41,10 @@ pub struct ComponentStatus { pub available: bool, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum UpdateStatus { Installed, - Updated, + Updated(String), // Stores the version of rustc *before* the update Unchanged, } @@ -103,8 +106,12 @@ impl<'a> Toolchain<'a> { } fn install(&self, install_method: InstallMethod<'_>) -> Result { assert!(self.is_valid_install_method(install_method)); - let exists = self.exists(); - if exists { + let previous_version = if self.exists() { + Some(self.rustc_version()) + } else { + None + }; + if previous_version.is_some() { (self.cfg.notify_handler)(Notification::UpdatingToolchain(&self.name)); } else { (self.cfg.notify_handler)(Notification::InstallingToolchain(&self.name)); @@ -118,11 +125,10 @@ impl<'a> Toolchain<'a> { (self.cfg.notify_handler)(Notification::InstalledToolchain(&self.name)); } - let status = match (updated, exists) { - (true, false) => UpdateStatus::Installed, - (true, true) => UpdateStatus::Updated, - (false, true) => UpdateStatus::Unchanged, - (false, false) => UpdateStatus::Unchanged, + let status = match (updated, previous_version) { + (true, None) => UpdateStatus::Installed, + (true, Some(v)) => UpdateStatus::Updated(v), + (false, _) => UpdateStatus::Unchanged, }; Ok(status) @@ -792,4 +798,55 @@ impl<'a> Toolchain<'a> { path.push(name.to_owned() + env::consts::EXE_SUFFIX); path } + + pub fn rustc_version(&self) -> String { + if self.exists() { + let rustc_path = self.binary_file("rustc"); + if utils::is_file(&rustc_path) { + let mut cmd = Command::new(&rustc_path); + cmd.arg("--version"); + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + self.set_ldpath(&mut cmd); + + // some toolchains are faulty with some combinations of platforms and + // may fail to launch but also to timely terminate. + // (known cases include Rust 1.3.0 through 1.10.0 in recent macOS Sierra.) + // we guard against such cases by enforcing a reasonable timeout to read. + let mut line1 = None; + if let Ok(mut child) = cmd.spawn() { + let timeout = Duration::new(10, 0); + match child.wait_timeout(timeout) { + Ok(Some(status)) if status.success() => { + let out = child + .stdout + .expect("Child::stdout requested but not present"); + let mut line = String::new(); + if BufReader::new(out).read_line(&mut line).is_ok() { + let lineend = line.trim_end_matches(&['\r', '\n'][..]).len(); + line.truncate(lineend); + line1 = Some(line); + } + } + Ok(None) => { + let _ = child.kill(); + return String::from("(timeout reading rustc version)"); + } + Ok(Some(_)) | Err(_) => {} + } + } + + if let Some(line1) = line1 { + line1 + } else { + String::from("(error reading rustc version)") + } + } else { + String::from("(rustc does not exist)") + } + } else { + String::from("(toolchain not installed)") + } + } } diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs index 7151a16a0e..a5bebdba76 100644 --- a/tests/cli-rustup.rs +++ b/tests/cli-rustup.rs @@ -40,7 +40,7 @@ fn rustup_stable() { &["rustup", "update", "--no-self-update"], for_host!( r" - stable-{0} updated - 1.1.0 (hash-stable-1.1.0) + stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) " ), @@ -80,7 +80,7 @@ fn rustup_stable_quiet() { &["rustup", "--quiet", "update", "--no-self-update"], for_host!( r" - stable-{0} updated - 1.1.0 (hash-stable-1.1.0) + stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) " ), @@ -142,9 +142,9 @@ fn rustup_all_channels() { &["rustup", "update", "--no-self-update"], for_host!( r" - stable-{0} updated - 1.1.0 (hash-stable-1.1.0) - beta-{0} updated - 1.2.0 (hash-beta-1.2.0) - nightly-{0} updated - 1.3.0 (hash-nightly-2) + stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) + beta-{0} updated - 1.2.0 (hash-beta-1.2.0) (from 1.1.0 (hash-beta-1.1.0)) + nightly-{0} updated - 1.3.0 (hash-nightly-2) (from 1.2.0 (hash-nightly-1)) " ), @@ -212,9 +212,9 @@ fn rustup_some_channels_up_to_date() { &["rustup", "update", "--no-self-update"], for_host!( r" - stable-{0} updated - 1.1.0 (hash-stable-1.1.0) + stable-{0} updated - 1.1.0 (hash-stable-1.1.0) (from 1.0.0 (hash-stable-1.0.0)) beta-{0} unchanged - 1.2.0 (hash-beta-1.2.0) - nightly-{0} updated - 1.3.0 (hash-nightly-2) + nightly-{0} updated - 1.3.0 (hash-nightly-2) (from 1.2.0 (hash-nightly-1)) " ),