diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 4e26f191952..b86185a6a1b 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -319,6 +319,12 @@ pub fn cli() -> App<'static, 'static> { .help("Force an update, even if some components are missing") .long("force") .takes_value(false), + ) + .arg( + Arg::with_name("allow-downgrade") + .help("Installs the most recent toolchain which supports all required components, even than this is older than the currently installed toolchain") + .long("allow-downgrade") + .takes_value(false), ), ) .subcommand( @@ -819,7 +825,12 @@ fn update(cfg: &mut Cfg, m: &ArgMatches<'_>) -> Result<()> { .values_of("targets") .map(|v| v.collect()) .unwrap_or_else(Vec::new); - Some(toolchain.install_from_dist(m.is_present("force"), &components, &targets)?) + Some(toolchain.install_from_dist( + m.is_present("force"), + m.is_present("allow-downgrade"), + &components, + &targets, + )?) } else if !toolchain.exists() { return Err(ErrorKind::InvalidToolchainName(toolchain.name().to_string()).into()); } else { diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index cdb7bd12a6e..7b8173d917f 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -782,7 +782,7 @@ fn maybe_install_rust( println!(); } else if cfg.find_default()?.is_none() { let toolchain = cfg.get_toolchain(toolchain_str, false)?; - let status = toolchain.install_from_dist(true, components, targets)?; + let status = toolchain.install_from_dist(true, false, components, targets)?; cfg.set_default(toolchain_str)?; println!(); common::show_channel_update(&cfg, toolchain_str, Ok(status))?; diff --git a/src/config.rs b/src/config.rs index 9d6ad5d59bf..88aa367fb40 100644 --- a/src/config.rs +++ b/src/config.rs @@ -438,7 +438,7 @@ impl Cfg { ErrorKind::OverrideToolchainNotInstalled(name.to_string()) }) } else { - toolchain.install_from_dist(true, &[], &[])?; + toolchain.install_from_dist(true, false, &[], &[])?; Ok(Some((toolchain, reason))) } } @@ -547,7 +547,7 @@ impl Cfg { // Update toolchains and collect the results let channels = channels.map(|(n, t)| { let t = t.and_then(|t| { - let t = t.install_from_dist(force_update, &[], &[]); + let t = t.install_from_dist(force_update, false, &[], &[]); if let Err(ref e) = t { (self.notify_handler)(Notification::NonFatalError(e)); } @@ -599,7 +599,7 @@ impl Cfg { ) -> Result { let toolchain = self.get_toolchain(toolchain, false)?; if install_if_missing && !toolchain.exists() { - toolchain.install_from_dist(true, &[], &[])?; + toolchain.install_from_dist(true, false, &[], &[])?; } if let Some(cmd) = self.maybe_do_cargo_fallback(&toolchain, binary)? { diff --git a/src/dist/dist.rs b/src/dist/dist.rs index a9d241bcf0d..d8dc1f8c77e 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -580,6 +580,7 @@ pub fn update_from_dist<'a>( profile: Option, prefix: &InstallPrefix, force_update: bool, + allow_downgrade: bool, old_date: Option<&str>, components: &[&str], targets: &[&str], @@ -601,6 +602,7 @@ pub fn update_from_dist<'a>( profile, prefix, force_update, + allow_downgrade, old_date, components, targets, @@ -622,6 +624,7 @@ fn update_from_dist_<'a>( profile: Option, prefix: &InstallPrefix, force_update: bool, + allow_downgrade: bool, old_date: Option<&str>, components: &[&str], targets: &[&str], @@ -644,7 +647,8 @@ fn update_from_dist_<'a>( Some(if provided < 1 { 1 } else { provided }) }; - // We never want to backtrack further back than the nightly that's already installed. + // In case there is no allow-downgrade option set + // we never want to backtrack further back than the nightly that's already installed. // // If no nightly is installed, it makes no sense to backtrack beyond the first ever manifest, // which is 2014-12-20 according to @@ -652,13 +656,12 @@ fn update_from_dist_<'a>( // // We could arguably use the date of the first rustup release here, but that would break a // bunch of the tests, which (inexplicably) use 2015-01-01 as their manifest dates. - let first_manifest = old_date - .map(|date| { - Utc.from_utc_date( - &NaiveDate::parse_from_str(date, "%Y-%m-%d").expect("Malformed manifest date"), - ) - }) - .unwrap_or_else(|| Utc.from_utc_date(&NaiveDate::from_ymd(2014, 12, 20))); + let first_manifest = Utc.from_utc_date(&NaiveDate::from_ymd(2014, 12, 20)); + let old_manifest = if allow_downgrade || old_date == None { + first_manifest + } else { + utc_from_manifest_date(old_date.unwrap()) + }; loop { match try_update_from_dist_( @@ -713,22 +716,10 @@ fn update_from_dist_<'a>( // 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. - let try_next = Utc - .from_utc_date( - &NaiveDate::parse_from_str( - toolchain.date.as_ref().unwrap_or(&fetched), - "%Y-%m-%d", - ) - .unwrap_or_else(|_| { - panic!( - "Malformed manifest date: {:?}", - toolchain.date.as_ref().unwrap_or(&fetched) - ) - }), - ) - .pred(); - - if try_next < first_manifest { + let toolchain_date = toolchain.date.as_ref().unwrap_or(&fetched); + let try_next = utc_from_manifest_date(toolchain_date).pred(); + + if try_next < old_manifest { // Wouldn't be an update if we go further back than the user's current nightly. if let Some(e) = first_err { break Err(e); @@ -926,3 +917,9 @@ fn dl_v1_manifest<'a>(download: DownloadCfg<'a>, toolchain: &ToolchainDesc) -> R Ok(urls) } + +fn utc_from_manifest_date(date_str: &str) -> Date { + let msg = format!("Malformed manifest date: {:?}", date_str); + let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d").expect(&msg); + Utc.from_utc_date(&date) +} diff --git a/src/install.rs b/src/install.rs index 1d6eadd84e4..cbdd36f8252 100644 --- a/src/install.rs +++ b/src/install.rs @@ -24,6 +24,8 @@ pub enum InstallMethod<'a> { DownloadCfg<'a>, // --force bool, + // --allow-downgrade + bool, // toolchain already exists bool, // currently installed date @@ -66,6 +68,7 @@ impl<'a> InstallMethod<'a> { update_hash, dl_cfg, force_update, + allow_downgrade, exists, old_date, components, @@ -79,6 +82,7 @@ impl<'a> InstallMethod<'a> { if exists { None } else { Some(profile) }, prefix, force_update, + allow_downgrade, old_date, components, targets, diff --git a/src/toolchain.rs b/src/toolchain.rs index b0780e90691..8556c84f022 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -166,6 +166,7 @@ impl<'a> Toolchain<'a> { pub fn install_from_dist( &self, force_update: bool, + allow_downgrade: bool, components: &[&str], targets: &[&str], ) -> Result { @@ -177,6 +178,7 @@ impl<'a> Toolchain<'a> { update_hash.as_ref().map(|p| &**p), self.download_cfg(), force_update, + allow_downgrade, self.exists(), old_date.as_ref().map(|s| &**s), components, @@ -193,6 +195,7 @@ impl<'a> Toolchain<'a> { self.download_cfg(), false, false, + false, None, &[], &[], diff --git a/tests/cli-v2.rs b/tests/cli-v2.rs index 9f412672154..25b22ae51d9 100644 --- a/tests/cli-v2.rs +++ b/tests/cli-v2.rs @@ -1374,3 +1374,71 @@ fn check_pgp_keys() { ); }) } + +#[test] +fn install_fails_on_missing_component() { + clitools::setup(Scenario::MissingComponent, &|config| { + let trip = TargetTriple::from_build(); + let expected = format!( + "component 'rls' for target '{}' is unavailable for download for channel nightly", + trip, + ); + + // this dist has no rls and there is no newer one + set_current_dist_date(config, "2019-09-14"); + expect_ok( + config, + &[ + "rustup", + "toolchain", + "install", + "nightly", + "--no-self-update", + ], + ); + expect_err( + config, + &[ + "rustup", + "toolchain", + "install", + "nightly", + "--no-self-update", + "-c", + "rls", + ], + &expected, + ); + }); +} + +#[test] +fn install_allow_downgrade() { + clitools::setup(Scenario::MissingComponent, &|config| { + // this dist has no rls, but rls is available on 2019-09-13 + set_current_dist_date(config, "2019-09-14"); + expect_ok( + config, + &[ + "rustup", + "toolchain", + "install", + "nightly", + "--no-self-update", + ], + ); + expect_ok( + config, + &[ + "rustup", + "toolchain", + "install", + "nightly", + "--no-self-update", + "-c", + "rls", + "--allow-downgrade", + ], + ); + }); +}