diff --git a/Cargo.lock b/Cargo.lock index ef61e89e26..3bfaf15ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1220,6 +1220,7 @@ name = "rustup" version = "1.19.0" dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "download 0.6.3", "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 13b772b81a..554e44e535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ no-self-update = [] # Sorted by alphabetic order [dependencies] +chrono = "0.4" clap = "2" download = { path = "download" } error-chain = "0.12" diff --git a/src/dist/dist.rs b/src/dist/dist.rs index 173abbbb0a..8650c822eb 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -7,6 +7,7 @@ use crate::dist::temp; use crate::errors::*; use crate::utils::utils; +use chrono::prelude::*; use lazy_static::lazy_static; use regex::Regex; @@ -599,6 +600,77 @@ fn update_from_dist_<'a>( profile: Option, prefix: &InstallPrefix, force_update: bool, +) -> Result> { + let mut toolchain = toolchain.clone(); + let mut fetched = String::new(); + let mut first_err = None; + let backtrack = toolchain.channel == "nightly" && toolchain.date.is_none(); + loop { + match try_update_from_dist_( + download, + update_hash, + &toolchain, + profile, + prefix, + force_update, + &mut fetched, + ) { + Ok(v) => break Ok(v), + Err(e) => { + if !backtrack { + break Err(e); + } + + if let ErrorKind::RequestedComponentsUnavailable(components, ..) = e.kind() { + (download.notify_handler)(Notification::SkippingNightlyMissingComponent( + components, + )); + + if first_err.is_none() { + first_err = Some(e); + } + } else if let Some(e) = first_err { + // if we fail to find a suitable nightly, we abort the search and give the + // original "components unavailable for download" error. + break Err(e); + } else { + break Err(e); + } + + // The user asked to update their nightly, but the latest nightly does not have all + // the components that the user currently has installed. Let's try the previous + // nightlies in reverse chronological order until we find a nightly that does, + // starting at one date earlier than the current manifest's date. + // + // NOTE: we don't need to explicitly check for the case where the next nightly to + // try is _older_ than the current nightly, since we know that the user's current + // nightlys supports the components they have installed, and thus would always + // terminate the search. + toolchain.date = Some( + Utc.from_utc_date( + &NaiveDate::parse_from_str( + toolchain.date.as_ref().unwrap_or(&fetched), + "%Y-%m-%d", + ) + .expect("Malformed manifest date"), + ) + .pred() + .format("%Y-%m-%d") + .to_string(), + ); + } + } + } +} + +fn try_update_from_dist_<'a>( + download: DownloadCfg<'a>, + update_hash: Option<&Path>, + toolchain: &ToolchainDesc, + profile: Option, + prefix: &InstallPrefix, + force_update: bool, + fetched: &mut String, ) -> Result> { let toolchain_str = toolchain.to_string(); let manifestation = Manifestation::open(prefix.clone(), toolchain.target.clone())?; @@ -622,6 +694,8 @@ fn update_from_dist_<'a>( remove_components: Vec::new(), }; + *fetched = m.date.clone(); + return match manifestation.update( &m, changes, diff --git a/src/dist/notifications.rs b/src/dist/notifications.rs index d87dd4bce4..604021e460 100644 --- a/src/dist/notifications.rs +++ b/src/dist/notifications.rs @@ -1,4 +1,5 @@ use crate::dist::dist::TargetTriple; +use crate::dist::manifest::Component; use crate::dist::temp; use crate::errors::*; use crate::utils::notify::NotificationLevel; @@ -29,6 +30,7 @@ pub enum Notification<'a> { DownloadingManifest(&'a str), DownloadedManifest(&'a str, Option<&'a str>), DownloadingLegacyManifest, + SkippingNightlyMissingComponent(&'a [Component]), ManifestChecksumFailedHack, ComponentUnavailable(&'a str, Option<&'a TargetTriple>), StrayHash(&'a Path), @@ -66,6 +68,7 @@ impl<'a> Notification<'a> { | ManifestChecksumFailedHack | RollingBack | DownloadingManifest(_) + | SkippingNightlyMissingComponent(_) | DownloadedManifest(_, _) => NotificationLevel::Info, CantReadUpdateHash(_) | ExtensionNotInstalled(_) @@ -158,6 +161,11 @@ impl<'a> Display for Notification<'a> { "removing stray hash found at '{}' in order to continue", path.display() ), + SkippingNightlyMissingComponent(components) => write!( + f, + "skipping nightly which is missing installed component '{}'", + components[0].short_name_in_manifest() + ), } } } diff --git a/tests/cli-misc.rs b/tests/cli-misc.rs index 6510dab2f2..0f7eee2778 100644 --- a/tests/cli-misc.rs +++ b/tests/cli-misc.rs @@ -8,7 +8,6 @@ use crate::mock::clitools::{ expect_ok_eq, expect_ok_ex, expect_stderr_ok, expect_stdout_ok, run, set_current_dist_date, this_host_triple, Config, Scenario, }; -use rustup::errors::TOOLSTATE_MSG; use rustup::utils::{raw, utils}; use std::env::consts::EXE_SUFFIX; @@ -741,18 +740,32 @@ fn update_unavailable_rustc() { expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1"); + // latest nightly is unavailable set_current_dist_date(config, "2015-01-02"); - expect_err( - config, - &["rustup", "update", "nightly"], - format!( - "some components unavailable for download for channel nightly: 'rustc', 'cargo', 'rust-std', 'rust-docs'\n{}", - TOOLSTATE_MSG - ) - .as_str(), - ); + // update should do nothing + expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); + expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1"); + }); +} + +#[test] +fn update_nightly_even_with_incompat() { + clitools::setup(Scenario::MissingComponent, &|config| { + set_current_dist_date(config, "2019-09-12"); + expect_ok(config, &["rustup", "default", "nightly"]); expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1"); + expect_ok(config, &["rustup", "component", "add", "rls"]); + expect_component_executable(config, "rls"); + + // latest nightly is now one that does not have RLS + set_current_dist_date(config, "2019-09-14"); + + expect_component_executable(config, "rls"); + // update should bring us to latest nightly that does + expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); + expect_stdout_ok(config, &["rustc", "--version"], "hash-n-2"); + expect_component_executable(config, "rls"); }); } diff --git a/tests/mock/clitools.rs b/tests/mock/clitools.rs index a47ec06af4..7a532389c0 100644 --- a/tests/mock/clitools.rs +++ b/tests/mock/clitools.rs @@ -43,14 +43,15 @@ pub struct Config { // Building the mock server is slow, so use simple scenario when possible. #[derive(PartialEq, Copy, Clone)] pub enum Scenario { - Full, // Two dates, two manifests - ArchivesV2, // Two dates, v2 manifests - ArchivesV1, // Two dates, v1 manifests - SimpleV2, // One date, v2 manifests - SimpleV1, // One date, v1 manifests - MultiHost, // One date, v2 manifests, MULTI_ARCH1 host - Unavailable, // Two dates, v2 manifests, everything unavailable in second date. - UnavailableRls, // Two dates, v2 manifests, RLS unavailable in first date, restored on second. + Full, // Two dates, two manifests + ArchivesV2, // Two dates, v2 manifests + ArchivesV1, // Two dates, v1 manifests + SimpleV2, // One date, v2 manifests + SimpleV1, // One date, v1 manifests + MultiHost, // One date, v2 manifests, MULTI_ARCH1 host + Unavailable, // Two dates, v2 manifests, everything unavailable in second date. + UnavailableRls, // Two dates, v2 manifests, RLS unavailable in first date, restored on second. + MissingComponent, // Three dates, v2 manifests, RLS available in first and last, not middle } pub static CROSS_ARCH1: &str = "x86_64-unknown-linux-musl"; @@ -451,9 +452,36 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { | Scenario::ArchivesV2 | Scenario::Unavailable | Scenario::UnavailableRls => 2, + Scenario::MissingComponent => 3, }; - if dates_count > 1 { + if let Scenario::MissingComponent = s { + let c1 = build_mock_channel( + s, + "nightly", + "2019-09-12", + "1.37.0", + "hash-n-1", + RlsStatus::Available, + ); + let c2 = build_mock_channel( + s, + "nightly", + "2019-09-13", + "1.37.0", + "hash-n-2", + RlsStatus::Available, + ); + let c3 = build_mock_channel( + s, + "nightly", + "2019-09-14", + "1.37.0", + "hash-n-3", + RlsStatus::Unavailable, + ); + chans.extend(vec![c1, c2, c3]); + } else if dates_count > 1 { let c1 = build_mock_channel( s, "nightly", @@ -484,35 +512,37 @@ 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", "hash-n-2") - } else { - build_mock_channel( + if s != Scenario::MissingComponent { + let c4 = if s == Scenario::Unavailable { + build_mock_unavailable_channel("nightly", "2015-01-02", "1.3.0", "hash-n-2") + } else { + build_mock_channel( + s, + "nightly", + "2015-01-02", + "1.3.0", + "hash-n-2", + RlsStatus::Renamed, + ) + }; + let c5 = build_mock_channel( s, - "nightly", + "beta", "2015-01-02", - "1.3.0", - "hash-n-2", - RlsStatus::Renamed, - ) - }; - let c5 = build_mock_channel( - s, - "beta", - "2015-01-02", - "1.2.0", - "hash-b-2", - RlsStatus::Available, - ); - let c6 = build_mock_channel( - s, - "stable", - "2015-01-02", - "1.1.0", - "hash-s-2", - RlsStatus::Available, - ); - chans.extend(vec![c4, c5, c6]); + "1.2.0", + "hash-b-2", + RlsStatus::Available, + ); + let c6 = build_mock_channel( + s, + "stable", + "2015-01-02", + "1.1.0", + "hash-s-2", + RlsStatus::Available, + ); + chans.extend(vec![c4, c5, c6]); + } let vs = match s { Scenario::Full => vec![ManifestVersion::V1, ManifestVersion::V2], @@ -521,7 +551,8 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { | Scenario::ArchivesV2 | Scenario::MultiHost | Scenario::Unavailable - | Scenario::UnavailableRls => vec![ManifestVersion::V2], + | Scenario::UnavailableRls + | Scenario::MissingComponent => vec![ManifestVersion::V2], }; MockDistServer { @@ -530,62 +561,64 @@ fn create_mock_dist_server(path: &Path, s: Scenario) { } .write(&vs, true); - // Also create the manifests for stable releases by version - if dates_count > 1 { + if s != Scenario::MissingComponent { + // Also create the manifests for stable releases by version + if dates_count > 1 { + let _ = hard_link( + path.join("dist/2015-01-01/channel-rust-stable.toml"), + path.join("dist/channel-rust-1.0.0.toml"), + ); + let _ = hard_link( + path.join("dist/2015-01-01/channel-rust-stable.toml.sha256"), + path.join("dist/channel-rust-1.0.0.toml.sha256"), + ); + } let _ = hard_link( - path.join("dist/2015-01-01/channel-rust-stable.toml"), - path.join("dist/channel-rust-1.0.0.toml"), + path.join("dist/2015-01-02/channel-rust-stable.toml"), + path.join("dist/channel-rust-1.1.0.toml"), ); let _ = hard_link( - path.join("dist/2015-01-01/channel-rust-stable.toml.sha256"), - path.join("dist/channel-rust-1.0.0.toml.sha256"), + path.join("dist/2015-01-02/channel-rust-stable.toml.sha256"), + path.join("dist/channel-rust-1.1.0.toml.sha256"), ); - } - let _ = hard_link( - path.join("dist/2015-01-02/channel-rust-stable.toml"), - path.join("dist/channel-rust-1.1.0.toml"), - ); - let _ = hard_link( - path.join("dist/2015-01-02/channel-rust-stable.toml.sha256"), - path.join("dist/channel-rust-1.1.0.toml.sha256"), - ); - // Same for v1 manifests. These are just the installers. - let host_triple = this_host_triple(); - if dates_count > 1 { + // Same for v1 manifests. These are just the installers. + let host_triple = this_host_triple(); + if dates_count > 1 { + hard_link( + path.join(format!( + "dist/2015-01-01/rust-stable-{}.tar.gz", + host_triple + )), + path.join(format!("dist/rust-1.0.0-{}.tar.gz", host_triple)), + ) + .unwrap(); + hard_link( + path.join(format!( + "dist/2015-01-01/rust-stable-{}.tar.gz.sha256", + host_triple + )), + path.join(format!("dist/rust-1.0.0-{}.tar.gz.sha256", host_triple)), + ) + .unwrap(); + } hard_link( path.join(format!( - "dist/2015-01-01/rust-stable-{}.tar.gz", + "dist/2015-01-02/rust-stable-{}.tar.gz", host_triple )), - path.join(format!("dist/rust-1.0.0-{}.tar.gz", host_triple)), + path.join(format!("dist/rust-1.1.0-{}.tar.gz", host_triple)), ) .unwrap(); hard_link( path.join(format!( - "dist/2015-01-01/rust-stable-{}.tar.gz.sha256", + "dist/2015-01-02/rust-stable-{}.tar.gz.sha256", host_triple )), - path.join(format!("dist/rust-1.0.0-{}.tar.gz.sha256", host_triple)), + path.join(format!("dist/rust-1.1.0-{}.tar.gz.sha256", host_triple)), ) .unwrap(); } - hard_link( - path.join(format!( - "dist/2015-01-02/rust-stable-{}.tar.gz", - host_triple - )), - path.join(format!("dist/rust-1.1.0-{}.tar.gz", host_triple)), - ) - .unwrap(); - hard_link( - path.join(format!( - "dist/2015-01-02/rust-stable-{}.tar.gz.sha256", - host_triple - )), - path.join(format!("dist/rust-1.1.0-{}.tar.gz.sha256", host_triple)), - ) - .unwrap(); } fn build_mock_channel(