diff --git a/rustup-init.sh b/rustup-init.sh index 753f93ea9b..9c26cff21c 100755 --- a/rustup-init.sh +++ b/rustup-init.sh @@ -29,6 +29,7 @@ OPTIONS: --default-host Choose a default host triple --default-toolchain Choose a default toolchain to install --default-toolchain none Do not install any toolchains + --profile [minimal|default|complete] Choose a profile EOF } diff --git a/src/cli/common.rs b/src/cli/common.rs index 12e0eca4e1..03ed42208a 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -368,11 +368,7 @@ pub fn list_targets(toolchain: &Toolchain<'_>) -> Result<()> { .target .as_ref() .expect("rust-std should have a target"); - if component.required { - let _ = t.attr(term2::Attr::Bold); - let _ = writeln!(t, "{} (default)", target); - let _ = t.reset(); - } else if component.installed { + if component.installed { let _ = t.attr(term2::Attr::Bold); let _ = writeln!(t, "{} (installed)", target); let _ = t.reset(); @@ -406,11 +402,7 @@ pub fn list_components(toolchain: &Toolchain<'_>) -> Result<()> { let mut t = term2::stdout(); for component in toolchain.list_components()? { let name = component.name; - if component.required { - t.attr(term2::Attr::Bold)?; - writeln!(t, "{} (default)", name)?; - t.reset()?; - } else if component.installed { + if component.installed { t.attr(term2::Attr::Bold)?; writeln!(t, "{} (installed)", name)?; t.reset()?; diff --git a/src/cli/download_tracker.rs b/src/cli/download_tracker.rs index 57e3250506..214e844505 100644 --- a/src/cli/download_tracker.rs +++ b/src/cli/download_tracker.rs @@ -251,7 +251,6 @@ impl<'a> fmt::Display for Size<'a> { #[cfg(test)] mod tests { - #[test] fn download_tracker_from_seconds_test() { use crate::download_tracker::DownloadTracker; @@ -267,5 +266,4 @@ mod tests { assert_eq!(DownloadTracker::from_seconds(222_292), (2, 13, 44, 52)); } - } diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index cbd625d004..bcb3933790 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -5,7 +5,7 @@ use crate::self_update; use crate::term2; use crate::term2::Terminal; use clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, Shell, SubCommand}; -use rustup::dist::dist::{PartialTargetTriple, PartialToolchainDesc, TargetTriple}; +use rustup::dist::dist::{PartialTargetTriple, PartialToolchainDesc, Profile, TargetTriple}; use rustup::dist::manifest::Component; use rustup::utils::utils::{self, ExitCode}; use rustup::{command, Cfg, Toolchain}; @@ -44,6 +44,7 @@ pub fn main() -> Result<()> { ("show", Some(c)) => match c.subcommand() { ("active-toolchain", Some(_)) => handle_epipe(show_active_toolchain(cfg))?, ("home", Some(_)) => handle_epipe(show_rustup_home(cfg))?, + ("profile", Some(_)) => handle_epipe(show_profile(cfg))?, (_, _) => handle_epipe(show(cfg))?, }, ("install", Some(m)) => update(cfg, m)?, @@ -86,6 +87,7 @@ pub fn main() -> Result<()> { }, ("set", Some(c)) => match c.subcommand() { ("default-host", Some(m)) => set_default_host_triple(&cfg, m)?, + ("profile", Some(m)) => set_profile(&cfg, m)?, (_, _) => unreachable!(), }, ("completions", Some(c)) => { @@ -125,7 +127,7 @@ pub fn cli() -> App<'static, 'static> { ) .subcommand( SubCommand::with_name("show") - .about("Show the active and installed toolchains") + .about("Show the active and installed toolchains or profiles") .after_help(SHOW_HELP) .setting(AppSettings::VersionlessSubcommands) .setting(AppSettings::DeriveDisplayOrder) @@ -138,6 +140,7 @@ pub fn cli() -> App<'static, 'static> { SubCommand::with_name("home") .about("Display the computed value of RUSTUP_HOME"), ) + .subcommand(SubCommand::with_name("profile").about("Show the current profile")), ) .subcommand( SubCommand::with_name("install") @@ -501,6 +504,16 @@ pub fn cli() -> App<'static, 'static> { SubCommand::with_name("default-host") .about("The triple used to identify toolchains when not specified") .arg(Arg::with_name("host_triple").required(true)), + ) + .subcommand( + SubCommand::with_name("profile") + .about("The default components installed") + .arg( + Arg::with_name("profile-name") + .required(true) + .possible_values(Profile::names()) + .default_value(Profile::default_name()), + ), ), ); @@ -919,7 +932,11 @@ fn target_add(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { } for target in &targets { - let new_component = Component::new("rust-std".to_string(), Some(TargetTriple::new(target))); + let new_component = Component::new( + "rust-std".to_string(), + Some(TargetTriple::new(target)), + false, + ); toolchain.add_component(new_component)?; } @@ -930,7 +947,11 @@ fn target_remove(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { let toolchain = explicit_or_dir_toolchain(cfg, m)?; for target in m.values_of("target").expect("") { - let new_component = Component::new("rust-std".to_string(), Some(TargetTriple::new(target))); + let new_component = Component::new( + "rust-std".to_string(), + Some(TargetTriple::new(target)), + false, + ); toolchain.remove_component(new_component)?; } @@ -959,7 +980,7 @@ fn component_add(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { }); for component in m.values_of("component").expect("") { - let new_component = Component::new(component.to_string(), target.clone()); + let new_component = Component::new(component.to_string(), target.clone(), true); toolchain.add_component(new_component)?; } @@ -978,7 +999,7 @@ fn component_remove(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { }); for component in m.values_of("component").expect("") { - let new_component = Component::new(component.to_string(), target.clone()); + let new_component = Component::new(component.to_string(), target.clone(), true); toolchain.remove_component(new_component)?; } @@ -1159,6 +1180,19 @@ fn set_default_host_triple(cfg: &Cfg, m: &ArgMatches<'_>) -> Result<()> { Ok(()) } +fn set_profile(cfg: &Cfg, m: &ArgMatches) -> Result<()> { + cfg.set_profile(&m.value_of("profile-name").unwrap())?; + Ok(()) +} + +fn show_profile(cfg: &Cfg) -> Result<()> { + match cfg.get_profile()? { + Some(p) => println!("{}", p), + None => println!("No profile set"), + } + Ok(()) +} + #[derive(Copy, Clone, Debug, PartialEq)] pub enum CompletionCommand { Rustup, diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index 4d31dc88fc..834441e39d 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -34,7 +34,7 @@ use crate::common::{self, Confirm}; use crate::errors::*; use crate::markdown::md; use crate::term2; -use rustup::dist::dist; +use rustup::dist::dist::{self, Profile}; use rustup::utils::utils; use rustup::utils::Notification; use rustup::{DUP_TOOLS, TOOLS}; @@ -48,6 +48,7 @@ use std::process::{self, Command}; pub struct InstallOpts { pub default_host_triple: String, pub default_toolchain: String, + pub profile: String, pub no_modify_path: bool, } @@ -278,7 +279,12 @@ pub fn install(no_prompt: bool, verbose: bool, mut opts: InstallOpts) -> Result< do_add_to_path(&get_add_path_methods())?; } utils::create_rustup_home()?; - maybe_install_rust(&opts.default_toolchain, &opts.default_host_triple, verbose)?; + maybe_install_rust( + &opts.default_toolchain, + &opts.profile, + &opts.default_host_triple, + verbose, + )?; if cfg!(unix) { let env_file = utils::cargo_home()?.join("env"); @@ -590,10 +596,12 @@ fn current_install_opts(opts: &InstallOpts) -> String { - ` `default host triple: `{}` - ` `default toolchain: `{}` +- ` `profile: `{}` - modify PATH variable: `{}` ", opts.default_host_triple, opts.default_toolchain, + opts.profile, if !opts.no_modify_path { "yes" } else { "no" } ) } @@ -615,6 +623,14 @@ fn customize_install(mut opts: InstallOpts) -> Result { &opts.default_toolchain, )?; + opts.profile = common::question_str( + &format!( + "Profile (which tools and data to install)? ({})", + Profile::names().join("/") + ), + &opts.profile, + )?; + opts.no_modify_path = !common::question_bool("Modify PATH variable? (y/n)", !opts.no_modify_path)?; @@ -716,8 +732,14 @@ pub fn install_proxies() -> Result<()> { Ok(()) } -fn maybe_install_rust(toolchain_str: &str, default_host_triple: &str, verbose: bool) -> Result<()> { +fn maybe_install_rust( + toolchain_str: &str, + profile_str: &str, + default_host_triple: &str, + verbose: bool, +) -> Result<()> { let cfg = common::set_globals(verbose)?; + cfg.set_profile(profile_str)?; // If there is already an install, then `toolchain_str` may not be // a toolchain the user actually wants. Don't do anything. FIXME: @@ -787,7 +809,8 @@ pub fn uninstall(no_prompt: bool) -> Result<()> { // Delete everything in CARGO_HOME *except* the rustup bin // First everything except the bin directory - for dirent in fs::read_dir(&cargo_home).chain_err(|| read_dir_err)? { + let diriter = fs::read_dir(&cargo_home).chain_err(|| read_dir_err)?; + for dirent in diriter { let dirent = dirent.chain_err(|| read_dir_err)?; if dirent.file_name().to_str() != Some("bin") { if dirent.path().is_dir() { @@ -805,7 +828,8 @@ pub fn uninstall(no_prompt: bool) -> Result<()> { .chain(DUP_TOOLS.iter()) .map(|t| format!("{}{}", t, EXE_SUFFIX)); let tools: Vec<_> = tools.chain(vec![format!("rustup{}", EXE_SUFFIX)]).collect(); - for dirent in fs::read_dir(&cargo_home.join("bin")).chain_err(|| read_dir_err)? { + let diriter = fs::read_dir(&cargo_home.join("bin")).chain_err(|| read_dir_err)?; + for dirent in diriter { let dirent = dirent.chain_err(|| read_dir_err)?; let name = dirent.file_name(); let file_is_tool = name.to_str().map(|n| tools.iter().any(|t| *t == n)); diff --git a/src/cli/setup_mode.rs b/src/cli/setup_mode.rs index 6a5437d2f1..06e239770c 100644 --- a/src/cli/setup_mode.rs +++ b/src/cli/setup_mode.rs @@ -2,7 +2,7 @@ use crate::common; use crate::errors::*; use crate::self_update::{self, InstallOpts}; use clap::{App, AppSettings, Arg}; -use rustup::dist::dist::TargetTriple; +use rustup::dist::dist::{Profile, TargetTriple}; use std::env; pub fn main() -> Result<()> { @@ -48,6 +48,12 @@ pub fn main() -> Result<()> { .takes_value(true) .help("Choose a default toolchain to install"), ) + .arg( + Arg::with_name("profile") + .long("profile") + .possible_values(Profile::names()) + .default_value(Profile::default_name()), + ) .arg( Arg::with_name("no-modify-path") .long("no-modify-path") @@ -62,11 +68,15 @@ pub fn main() -> Result<()> { .map(std::borrow::ToOwned::to_owned) .unwrap_or_else(|| TargetTriple::from_host_or_build().to_string()); let default_toolchain = matches.value_of("default-toolchain").unwrap_or("stable"); + let profile = matches + .value_of("profile") + .expect("Unreachable: Clap should supply a default"); let no_modify_path = matches.is_present("no-modify-path"); let opts = InstallOpts { default_host_triple: default_host, default_toolchain: default_toolchain.to_owned(), + profile: profile.to_owned(), no_modify_path, }; diff --git a/src/config.rs b/src/config.rs index a2aaa5ada3..d33d8fe1e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -125,6 +125,36 @@ impl Cfg { Ok(()) } + pub fn set_profile(&self, profile: &str) -> Result<()> { + if !dist::Profile::names().contains(&profile) { + return Err(ErrorKind::UnknownProfile(profile.to_owned()).into()); + } + self.settings_file.with_mut(|s| { + s.profile = Some(profile.to_owned()); + Ok(()) + })?; + (self.notify_handler)(Notification::SetProfile(profile)); + Ok(()) + } + + // Returns a profile, if one exists in the settings file. + // + // Returns `Err` if the settings file could not be read or the profile is + // invalid. Returns `Ok(Some(...))` if there is a valid profile, and `Ok(None)` + // if there is no profile in the settings file. The last variant happens when + // a user upgrades from a version of Rustup without profiles to a version of + // Rustup with profiles. + pub fn get_profile(&self) -> Result> { + self.settings_file.with(|s| { + let p = match &s.profile { + Some(p) => p, + None => return Ok(None), + }; + let p = dist::Profile::from_str(p)?; + Ok(Some(p)) + }) + } + pub fn get_toolchain(&self, name: &str, create_parent: bool) -> Result> { if create_parent { utils::ensure_dir_exists("toolchains", &self.toolchains_dir, &|n| { diff --git a/src/dist/config.rs b/src/dist/config.rs index 6a0279f55e..8f37e143ee 100644 --- a/src/dist/config.rs +++ b/src/dist/config.rs @@ -13,9 +13,9 @@ pub struct Config { impl Config { pub fn from_toml(mut table: toml::value::Table, path: &str) -> Result { - let version = get_string(&mut table, "config_version", path)?; - if !SUPPORTED_CONFIG_VERSIONS.contains(&&*version) { - return Err(ErrorKind::UnsupportedVersion(version).into()); + let config_version = get_string(&mut table, "config_version", path)?; + if !SUPPORTED_CONFIG_VERSIONS.contains(&&*config_version) { + return Err(ErrorKind::UnsupportedVersion(config_version).into()); } let components = get_array(&mut table, "components", path)?; @@ -23,7 +23,7 @@ impl Config { Self::toml_to_components(components, &format!("{}{}.", path, "components"))?; Ok(Self { - config_version: version, + config_version, components, }) } @@ -55,7 +55,7 @@ impl Config { for (i, v) in arr.into_iter().enumerate() { if let toml::Value::Table(t) = v { let path = format!("{}[{}]", path, i); - result.push(Component::from_toml(t, &path)?); + result.push(Component::from_toml(t, &path, false)?); } } diff --git a/src/dist/dist.rs b/src/dist/dist.rs index 25187c63a8..b21eb8c35d 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -1,5 +1,4 @@ use crate::dist::download::DownloadCfg; -use crate::dist::manifest::Component; use crate::dist::manifest::Manifest as ManifestV2; use crate::dist::manifestation::{Changes, Manifestation, UpdateStatus}; use crate::dist::notifications::*; @@ -466,6 +465,42 @@ impl<'a> Manifest<'a> { } } +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum Profile { + Minimal, + Default, + Complete, +} + +impl FromStr for Profile { + type Err = Error; + + fn from_str(name: &str) -> Result { + match name { + "minimal" | "m" => Ok(Profile::Minimal), + "default" | "d" | "" => Ok(Profile::Default), + "complete" | "c" => Ok(Profile::Complete), + _ => Err(ErrorKind::InvalidProfile(name.to_owned()).into()), + } + } +} + +impl Profile { + pub fn names() -> &'static [&'static str] { + &["minimal", "default", "complete"] + } + + pub fn default_name() -> &'static str { + "default" + } +} + +impl Default for Profile { + fn default() -> Self { + Profile::Default + } +} + impl fmt::Display for TargetTriple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -506,6 +541,16 @@ impl fmt::Display for ToolchainDesc { } } +impl fmt::Display for Profile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Profile::Minimal => write!(f, "minimal"), + Profile::Default => write!(f, "default"), + Profile::Complete => write!(f, "complete"), + } + } +} + // Installs or updates a toolchain from a dist server. If an initial // install then it will be installed with the default components. If // an upgrade then all the existing components will be upgraded. @@ -515,9 +560,8 @@ pub fn update_from_dist<'a>( download: DownloadCfg<'a>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, + profile: Option, prefix: &InstallPrefix, - add: &[Component], - remove: &[Component], force_update: bool, ) -> Result> { let fresh_install = !prefix.path().exists(); @@ -534,9 +578,8 @@ pub fn update_from_dist<'a>( download, update_hash, toolchain, + profile, prefix, - add, - remove, force_update, ); @@ -549,23 +592,17 @@ pub fn update_from_dist<'a>( res } -pub fn update_from_dist_<'a>( +fn update_from_dist_<'a>( download: DownloadCfg<'a>, update_hash: Option<&Path>, toolchain: &ToolchainDesc, + profile: Option, prefix: &InstallPrefix, - add: &[Component], - remove: &[Component], force_update: bool, ) -> Result> { let toolchain_str = toolchain.to_string(); let manifestation = Manifestation::open(prefix.clone(), toolchain.target.clone())?; - let changes = Changes { - add_extensions: add.to_owned(), - remove_extensions: remove.to_owned(), - }; - // TODO: Add a notification about which manifest version is going to be used (download.notify_handler)(Notification::DownloadingManifest(&toolchain_str)); match dl_v2_manifest(download, update_hash, toolchain) { @@ -574,6 +611,17 @@ pub fn update_from_dist_<'a>( &m.date, m.get_rust_version().ok(), )); + + let profile_components = match profile { + Some(profile) => m.get_profile_components(profile, &toolchain.target)?, + None => Vec::new(), + }; + + let changes = Changes { + explicit_add_components: profile_components, + remove_components: Vec::new(), + }; + return match manifestation.update( &m, changes, diff --git a/src/dist/manifest.rs b/src/dist/manifest.rs index 3afdb81dff..4db405c7ab 100644 --- a/src/dist/manifest.rs +++ b/src/dist/manifest.rs @@ -13,8 +13,9 @@ use crate::errors::*; use crate::utils::toml_utils::*; -use crate::dist::dist::TargetTriple; +use crate::dist::dist::{Profile, TargetTriple}; use std::collections::HashMap; +use std::str::FromStr; pub const SUPPORTED_MANIFEST_VERSIONS: [&str; 1] = ["2"]; pub const DEFAULT_MANIFEST_VERSION: &str = "2"; @@ -26,6 +27,7 @@ pub struct Manifest { pub packages: HashMap, pub renames: HashMap, pub reverse_renames: HashMap, + pub profiles: HashMap>, } #[derive(Clone, Debug, PartialEq)] @@ -44,7 +46,6 @@ pub enum PackageTargets { pub struct TargetedPackage { pub bins: Option, pub components: Vec, - pub extensions: Vec, } #[derive(Clone, Debug, PartialEq)] @@ -55,10 +56,19 @@ pub struct PackageBins { pub xz_hash: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Debug, Eq, Ord, PartialOrd)] pub struct Component { pkg: String, pub target: Option, + // Older Rustup distinguished between components (which are essential) and + // extensions (which are not). + is_extension: bool, +} + +impl PartialEq for Component { + fn eq(&self, other: &Self) -> bool { + self.pkg == other.pkg && self.target == other.target + } } impl Manifest { @@ -85,6 +95,7 @@ impl Manifest { packages: Self::table_to_packages(&mut table, path)?, renames, reverse_renames, + profiles: Self::table_to_profiles(&mut table, path)?, }) } pub fn into_toml(self) -> toml::value::Table { @@ -102,6 +113,9 @@ impl Manifest { let packages = Self::packages_to_table(self.packages); result.insert("pkg".to_owned(), toml::Value::Table(packages)); + let profiles = Self::profiles_to_table(self.profiles); + result.insert("profiles".to_owned(), toml::Value::Table(profiles)); + result } @@ -156,6 +170,40 @@ impl Manifest { result } + fn table_to_profiles( + table: &mut toml::value::Table, + path: &str, + ) -> Result>> { + let mut result = HashMap::new(); + let profile_table = match get_table(table, "profiles", path) { + Ok(t) => t, + Err(_) => return Ok(result), + }; + + for (k, v) in profile_table { + if let toml::Value::Array(a) = v { + let values = a + .into_iter() + .filter_map(|v| match v { + toml::Value::String(s) => Some(s), + _ => None, + }) + .collect(); + result.insert(Profile::from_str(&k)?, values); + } + } + + Ok(result) + } + fn profiles_to_table(profiles: HashMap>) -> toml::value::Table { + let mut result = toml::value::Table::new(); + for (profile, values) in profiles { + let array = values.into_iter().map(toml::Value::String).collect(); + result.insert(profile.to_string(), toml::Value::Array(array)); + } + result + } + pub fn get_package(&self, name: &str) -> Result<&Package> { self.packages .get(name) @@ -166,8 +214,50 @@ impl Manifest { self.get_package("rust").map(|p| &*p.version) } + pub fn get_legacy_components(&self, target: &TargetTriple) -> Result> { + // Build a profile from the components/extensions. + let result = self + .get_package("rust")? + .get_target(Some(target))? + .components + .iter() + .filter(|c| !c.is_extension && c.target.as_ref().map(|t| t == target).unwrap_or(true)) + .cloned() + .collect(); + + Ok(result) + } + pub fn get_profile_components( + &self, + profile: Profile, + target: &TargetTriple, + ) -> Result> { + // An older manifest with no profiles section. + if self.profiles.is_empty() { + return self.get_legacy_components(target); + } + + let profile = self + .profiles + .get(&profile) + .ok_or_else(|| format!("profile not found: '{}'", profile))?; + + let rust_pkg = self.get_package("rust")?.get_target(Some(target))?; + let result = profile + .iter() + .filter(|s| { + rust_pkg + .components + .iter() + .any(|c| &c.pkg == *s && c.target.as_ref().map(|t| t == target).unwrap_or(true)) + }) + .map(|s| Component::new(s.to_owned(), Some(target.clone()), false)) + .collect(); + Ok(result) + } + fn validate_targeted_package(&self, tpkg: &TargetedPackage) -> Result<()> { - for c in tpkg.components.iter().chain(tpkg.extensions.iter()) { + for c in tpkg.components.iter() { let cpkg = self .get_package(&c.pkg) .chain_err(|| ErrorKind::MissingPackageForComponent(c.short_name(self)))?; @@ -302,6 +392,14 @@ impl TargetedPackage { let components = get_array(&mut table, "components", path)?; let extensions = get_array(&mut table, "extensions", path)?; + let mut components = + Self::toml_to_components(components, &format!("{}{}.", path, "components"), false)?; + components.append(&mut Self::toml_to_components( + extensions, + &format!("{}{}.", path, "extensions"), + true, + )?); + if get_bool(&mut table, "available", path)? { Ok(Self { bins: Some(PackageBins { @@ -310,33 +408,24 @@ impl TargetedPackage { xz_url: get_string(&mut table, "xz_url", path).ok(), xz_hash: get_string(&mut table, "xz_hash", path).ok(), }), - components: Self::toml_to_components( - components, - &format!("{}{}.", path, "components"), - )?, - extensions: Self::toml_to_components( - extensions, - &format!("{}{}.", path, "extensions"), - )?, + components, }) } else { Ok(Self { bins: None, components: vec![], - extensions: vec![], }) } } pub fn into_toml(self) -> toml::value::Table { - let extensions = Self::components_to_toml(self.extensions); - let components = Self::components_to_toml(self.components); let mut result = toml::value::Table::new(); - if !extensions.is_empty() { - result.insert("extensions".to_owned(), toml::Value::Array(extensions)); - } + let (components, extensions) = Self::components_to_toml(self.components); if !components.is_empty() { result.insert("components".to_owned(), toml::Value::Array(components)); } + if !extensions.is_empty() { + result.insert("extensions".to_owned(), toml::Value::Array(extensions)); + } if let Some(bins) = self.bins.clone() { result.insert("hash".to_owned(), toml::Value::String(bins.hash)); result.insert("url".to_owned(), toml::Value::String(bins.url)); @@ -355,38 +444,56 @@ impl TargetedPackage { self.bins.is_some() } - fn toml_to_components(arr: toml::value::Array, path: &str) -> Result> { + fn toml_to_components( + arr: toml::value::Array, + path: &str, + is_extension: bool, + ) -> Result> { let mut result = Vec::new(); for (i, v) in arr.into_iter().enumerate() { if let toml::Value::Table(t) = v { let path = format!("{}[{}]", path, i); - result.push(Component::from_toml(t, &path)?); + result.push(Component::from_toml(t, &path, is_extension)?); } } Ok(result) } - fn components_to_toml(components: Vec) -> toml::value::Array { - let mut result = toml::value::Array::new(); - for v in components { - result.push(toml::Value::Table(v.into_toml())); + fn components_to_toml(data: Vec) -> (toml::value::Array, toml::value::Array) { + let mut components = toml::value::Array::new(); + let mut extensions = toml::value::Array::new(); + for v in data { + if v.is_extension { + extensions.push(toml::Value::Table(v.into_toml())); + } else { + components.push(toml::Value::Table(v.into_toml())); + } } - result + (components, extensions) } } impl Component { - pub fn new(pkg: String, target: Option) -> Self { - Self { pkg, target } + pub fn new(pkg: String, target: Option, is_extension: bool) -> Self { + Self { + pkg, + target, + is_extension, + } } pub fn wildcard(&self) -> Self { Self { pkg: self.pkg.clone(), target: None, + is_extension: false, } } - pub fn from_toml(mut table: toml::value::Table, path: &str) -> Result { + pub fn from_toml( + mut table: toml::value::Table, + path: &str, + is_extension: bool, + ) -> Result { Ok(Self { pkg: get_string(&mut table, "pkg", path)?, target: get_string(&mut table, "target", path).map(|s| { @@ -396,6 +503,7 @@ impl Component { Some(TargetTriple::new(&s)) } })?, + is_extension, }) } pub fn into_toml(self) -> toml::value::Table { diff --git a/src/dist/manifestation.rs b/src/dist/manifestation.rs index e467308c94..e482749954 100644 --- a/src/dist/manifestation.rs +++ b/src/dist/manifestation.rs @@ -3,7 +3,7 @@ use crate::dist::component::{Components, Package, TarGzPackage, TarXzPackage, Transaction}; use crate::dist::config::Config; -use crate::dist::dist::{TargetTriple, DEFAULT_DIST_SERVER}; +use crate::dist::dist::{Profile, TargetTriple, DEFAULT_DIST_SERVER}; use crate::dist::download::{DownloadCfg, File}; use crate::dist::manifest::{Component, Manifest, TargetedPackage}; use crate::dist::notifications::*; @@ -29,37 +29,33 @@ pub struct Manifestation { #[derive(Debug)] pub struct Changes { - pub add_extensions: Vec, - pub remove_extensions: Vec, + pub explicit_add_components: Vec, + pub remove_components: Vec, } impl Changes { pub fn none() -> Self { Self { - add_extensions: Vec::new(), - remove_extensions: Vec::new(), + explicit_add_components: Vec::new(), + remove_components: Vec::new(), } } - fn check_invariants(&self, rust_target_package: &TargetedPackage, config: &Option) { - for component_to_add in &self.add_extensions { - assert!( - rust_target_package.extensions.contains(component_to_add), - "package must contain extension to add" - ); + fn iter_add_components(&self) -> impl Iterator { + self.explicit_add_components.iter() + } + + fn check_invariants(&self, config: &Option) { + for component_to_add in self.iter_add_components() { assert!( - !self.remove_extensions.contains(component_to_add), - "can't both add and remove extensions" + !self.remove_components.contains(component_to_add), + "can't both add and remove components" ); } - for component_to_remove in &self.remove_extensions { - assert!( - rust_target_package.extensions.contains(component_to_remove), - "package must contain extension to remove" - ); + for component_to_remove in &self.remove_components { let config = config .as_ref() - .expect("removing extension on fresh install?"); + .expect("removing component on fresh install?"); assert!( config.components.contains(component_to_remove), "removing package that isn't installed" @@ -94,10 +90,10 @@ impl Manifestation { /// /// `update` takes a manifest describing a release of Rust (which /// may be either a freshly-downloaded one, or the same one used - /// for the previous install), as well as lists off extension + /// for the previous install), as well as lists of extension /// components to add and remove. - /// From that it schedules a list of components to uninstall and + /// From that it schedules a list of components to install and /// to uninstall to bring the installation up to date. It /// downloads the components' packages. Then in a Transaction /// uninstalls old packages and installs new packages, writes the @@ -120,15 +116,13 @@ impl Manifestation { let installed_manifest_path = prefix.path().join(&rel_installed_manifest_path); // Create the lists of components needed for installation - let update = Update::build_update(self, new_manifest, changes, notify_handler)?; + let config = self.read_config()?; + let update = Update::build_update(self, new_manifest, &changes, &config, notify_handler)?; if update.nothing_changes() { return Ok(UpdateStatus::Unchanged); } - // Make sure we don't accidentally uninstall the essential components! (see #1297) - update.missing_essential_components(&self.target_triple, new_manifest, toolchain_str)?; - // Validate that the requested components are available match update.unavailable_components(new_manifest, toolchain_str) { Ok(_) => {} @@ -141,7 +135,8 @@ impl Manifestation { // Download component packages and validate hashes let mut things_to_install: Vec<(Component, Format, File)> = Vec::new(); let mut things_downloaded: Vec = Vec::new(); - for (component, format, url, hash) in update.components_urls_and_hashes(new_manifest)? { + let components = update.components_urls_and_hashes(new_manifest)?; + for (component, format, url, hash) in components { notify_handler(Notification::DownloadingComponent( &component.short_name(new_manifest), &self.target_triple, @@ -169,11 +164,10 @@ impl Manifestation { // If the previous installation was from a v1 manifest we need // to uninstall it first. - let config = self.read_config()?; tx = self.maybe_handle_v2_upgrade(&config, tx)?; // Uninstall components - for component in update.components_to_uninstall { + for component in &update.components_to_uninstall { let notification = if altered { Notification::RemovingOldComponent } else { @@ -242,9 +236,9 @@ impl Manifestation { // that identify installed components. The rust-installer metadata maintained by // `Components` *also* tracks what is installed, but it only tracks names, not // name/target. Needs to be fixed in rust-installer. - let mut config = Config::new(); - config.components = update.final_component_list; - let config_str = config.stringify(); + let mut new_config = Config::new(); + new_config.components = update.final_component_list; + let config_str = new_config.stringify(); let rel_config_path = prefix.rel_manifest_file(CONFIG_FILE); let config_path = prefix.path().join(&rel_config_path); tx.modify_file(rel_config_path.to_owned())?; @@ -397,7 +391,8 @@ impl Manifestation { let mut tx = Transaction::new(prefix.clone(), temp_cfg, notify_handler); // Uninstall components - for component in self.installation.list()? { + let components = self.installation.list()?; + for component in components { tx = component.uninstall(tx)?; } @@ -444,6 +439,7 @@ impl Manifestation { } } +#[derive(Debug)] struct Update { components_to_uninstall: Vec, components_to_install: Vec, @@ -457,24 +453,30 @@ impl Update { fn build_update( manifestation: &Manifestation, new_manifest: &Manifest, - changes: Changes, + changes: &Changes, + config: &Option, notify_handler: &dyn Fn(Notification<'_>), ) -> Result { - // Load the configuration and list of installed components. - let config = manifestation.read_config()?; - // The package to install. let rust_package = new_manifest.get_package("rust")?; let rust_target_package = rust_package.get_target(Some(&manifestation.target_triple))?; - changes.check_invariants(rust_target_package, &config); + changes.check_invariants(&config); // The list of components already installed, empty if a new install - let starting_list = config + let mut starting_list = config .as_ref() .map(|c| c.components.clone()) .unwrap_or_default(); + let installed_components = manifestation.installation.list()?; + let looks_like_v1 = config.is_none() && !installed_components.is_empty(); + if looks_like_v1 { + let mut profile_components = new_manifest + .get_profile_components(Profile::Default, &manifestation.target_triple)?; + starting_list.append(&mut profile_components); + } + let mut result = Self { components_to_uninstall: vec![], components_to_install: vec![], @@ -483,14 +485,13 @@ impl Update { }; // Find the final list of components we want to be left with when - // we're done: required components, added extensions, and existing - // installed extensions. + // we're done: required components, added components, and existing + // installed components. result.build_final_component_list( &starting_list, rust_target_package, new_manifest, &changes, - notify_handler, ); // If this is a full upgrade then the list of components to @@ -515,7 +516,7 @@ impl Update { for component in &result.final_component_list { if !starting_list.contains(component) { result.components_to_install.push(component.clone()); - } else if changes.add_extensions.contains(&component) { + } else if changes.explicit_add_components.contains(&component) { notify_handler(Notification::ComponentAlreadyInstalled( &component.description(new_manifest), )); @@ -536,24 +537,17 @@ impl Update { rust_target_package: &TargetedPackage, new_manifest: &Manifest, changes: &Changes, - notify_handler: &dyn Fn(Notification<'_>), ) { - // Add components required by the package, according to the - // manifest - for required_component in &rust_target_package.components { - self.final_component_list.push(required_component.clone()); - } - - // Add requested extension components - for extension in &changes.add_extensions { - self.final_component_list.push(extension.clone()); + // Add requested components + for component in &changes.explicit_add_components { + self.final_component_list.push(component.clone()); } - // Add extensions that are already installed + // Add components that are already installed for existing_component in starting_list { - let is_removed = changes.remove_extensions.contains(existing_component); + let removed = changes.remove_components.contains(existing_component); - if !is_removed { + if !removed { // If there is a rename in the (new) manifest, then we uninstall the component with the // old name and install a component with the new name if let Some(renamed_component) = new_manifest.rename_component(&existing_component) @@ -568,21 +562,12 @@ impl Update { self.final_component_list.contains(existing_component); if !is_already_included { let component_is_present = - rust_target_package.extensions.contains(existing_component) - || rust_target_package.components.contains(existing_component); + rust_target_package.components.contains(existing_component); if component_is_present { self.final_component_list.push(existing_component.clone()); } else { - // If a component is not available anymore for the target remove it - // This prevents errors when trying to update to a newer version with - // a removed component. - self.components_to_uninstall - .push(existing_component.clone()); - notify_handler(Notification::ComponentUnavailable( - &existing_component.short_name(new_manifest), - existing_component.target.as_ref(), - )); + self.missing_components.push(existing_component.clone()); } } } @@ -594,39 +579,6 @@ impl Update { self.components_to_uninstall.is_empty() && self.components_to_install.is_empty() } - fn missing_essential_components( - &self, - target_triple: &TargetTriple, - manifest: &Manifest, - toolchain_str: &str, - ) -> Result<()> { - let missing_essential_components = ["rustc", "cargo"] - .iter() - .filter_map(|pkg| { - if self - .final_component_list - .iter() - .any(|c| c.short_name_in_manifest() == pkg) - { - None - } else { - Some(Component::new(pkg.to_string(), Some(target_triple.clone()))) - } - }) - .collect::>(); - - if !missing_essential_components.is_empty() { - return Err(ErrorKind::RequestedComponentsUnavailable( - missing_essential_components, - manifest.clone(), - toolchain_str.to_owned(), - ) - .into()); - } - - Ok(()) - } - fn unavailable_components(&self, new_manifest: &Manifest, toolchain_str: &str) -> Result<()> { let mut unavailable_components: Vec = self .components_to_install diff --git a/src/dist/notifications.rs b/src/dist/notifications.rs index 0f94b5a64b..d87dd4bce4 100644 --- a/src/dist/notifications.rs +++ b/src/dist/notifications.rs @@ -148,13 +148,9 @@ impl<'a> Display for Notification<'a> { } ComponentUnavailable(pkg, toolchain) => { if let Some(tc) = toolchain { - write!( - f, - "component '{}' is not available anymore on target '{}'", - pkg, tc - ) + write!(f, "component '{}' is not available on target '{}'", pkg, tc) } else { - write!(f, "component '{}' is not available anymore", pkg) + write!(f, "component '{}' is not available", pkg) } } StrayHash(path) => write!( diff --git a/src/errors.rs b/src/errors.rs index ec9b84c2a2..bc32464c98 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,6 +1,7 @@ #![allow(clippy::large_enum_variant)] use crate::component_for_bin; +use crate::dist::dist::Profile; use crate::dist::manifest::{Component, Manifest}; use crate::dist::temp; use error_chain::error_chain; @@ -193,6 +194,10 @@ error_chain! { description("invalid custom toolchain name") display("invalid custom toolchain name: '{}'", t) } + InvalidProfile(t: String) { + description("invalid profile name") + display("invalid profile name: '{}'; valid names are: {}", t, valid_profile_names()) + } ChecksumFailed { url: String, expected: String, @@ -272,7 +277,7 @@ error_chain! { } RequestedComponentsUnavailable(c: Vec, manifest: Manifest, toolchain: String) { description("some requested components are unavailable to download") - display("{} for channel '{}'\n{}", component_unavailable_msg(&c, &manifest), toolchain, TOOLSTATE_MSG) + display("{}", component_unavailable_msg(&c, &manifest, &toolchain)) } UnknownMetadataVersion(v: String) { description("unknown metadata version") @@ -312,6 +317,14 @@ error_chain! { "".to_string() }) } + UnknownProfile(p: String) { + description("unknown profile name") + display( + "unknown profile name: '{}'; valid profile names are {}", + p, + valid_profile_names(), + ) + } AddingRequiredComponent(t: String, c: String) { description("required component cannot be added") display("component {} was automatically added because it is required for toolchain '{}'", @@ -320,11 +333,6 @@ error_chain! { ParsingSettings(e: toml::de::Error) { description("error parsing settings") } - RemovingRequiredComponent(t: String, c: String) { - description("required component cannot be removed") - display("component {} is required for toolchain '{}' and cannot be removed", - c, t) - } NoExeName { description("couldn't determine self executable name") } @@ -339,7 +347,15 @@ error_chain! { } } -fn component_unavailable_msg(cs: &[Component], manifest: &Manifest) -> String { +fn valid_profile_names() -> String { + Profile::names() + .iter() + .map(|s| format!("'{}'", s)) + .collect::>() + .join(", ") +} + +fn component_unavailable_msg(cs: &[Component], manifest: &Manifest, toolchain: &str) -> String { assert!(!cs.is_empty()); let mut buf = vec![]; @@ -347,8 +363,9 @@ fn component_unavailable_msg(cs: &[Component], manifest: &Manifest) -> String { if cs.len() == 1 { let _ = write!( buf, - "component {} is unavailable for download", - &cs[0].description(manifest) + "component {} is unavailable for download for channel {}", + &cs[0].description(manifest), + toolchain ); } else { let same_target = cs @@ -360,14 +377,22 @@ fn component_unavailable_msg(cs: &[Component], manifest: &Manifest) -> String { .map(|c| format!("'{}'", c.short_name(manifest))) .collect::>() .join(", "); - let _ = write!(buf, "some components unavailable for download: {}", cs_str,); + let _ = write!( + buf, + "some components unavailable for download for channel {}: {}\n{}", + toolchain, cs_str, TOOLSTATE_MSG, + ); } else { let cs_str = cs .iter() .map(|c| c.description(manifest)) .collect::>() .join(", "); - let _ = write!(buf, "some components unavailable for download: {}", cs_str,); + let _ = write!( + buf, + "some components unavailable for download for channel {}: {}\n{}", + toolchain, cs_str, TOOLSTATE_MSG, + ); } } diff --git a/src/install.rs b/src/install.rs index 292a23c930..082f18cc7b 100644 --- a/src/install.rs +++ b/src/install.rs @@ -19,8 +19,12 @@ pub enum InstallMethod<'a> { // bool is whether to force an update Dist( &'a dist::ToolchainDesc, + Option, Option<&'a Path>, DownloadCfg<'a>, + // --force + bool, + // toolchain already exists bool, ), } @@ -50,15 +54,14 @@ impl<'a> InstallMethod<'a> { InstallMethod::tar_gz(src, path, &temp_cfg, notify_handler)?; Ok(true) } - InstallMethod::Dist(toolchain, update_hash, dl_cfg, force_update) => { + InstallMethod::Dist(toolchain, profile, update_hash, dl_cfg, force_update, exists) => { let prefix = &InstallPrefix::from(path.to_owned()); let maybe_new_hash = dist::update_from_dist( dl_cfg, update_hash, toolchain, + if exists { None } else { profile }, prefix, - &[], - &[], force_update, )?; diff --git a/src/lib.rs b/src/lib.rs index 9d802cdb66..75f67b7d35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![deny(rust_2018_idioms)] -#![warn(clippy::use_self)] +#![allow(clippy::too_many_arguments)] #![recursion_limit = "1024"] pub use crate::config::*; diff --git a/src/notifications.rs b/src/notifications.rs index f3e72a8486..cfbd0a8167 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -14,6 +14,7 @@ pub enum Notification<'a> { SetDefaultToolchain(&'a str), SetOverrideToolchain(&'a Path, &'a str), + SetProfile(&'a str), LookingForToolchain(&'a str), ToolchainDirectory(&'a Path, &'a str), UpdatingToolchain(&'a str), @@ -66,6 +67,7 @@ impl<'a> Notification<'a> { | UpdateHashMatches => NotificationLevel::Verbose, SetDefaultToolchain(_) | SetOverrideToolchain(_, _) + | SetProfile(_) | UsingExistingToolchain(_) | UninstallingToolchain(_) | UninstalledToolchain(_) @@ -92,6 +94,7 @@ impl<'a> Display for Notification<'a> { path.display(), name ), + SetProfile(name) => write!(f, "profile set to '{}'", name), LookingForToolchain(name) => write!(f, "looking for installed toolchain '{}'", name), ToolchainDirectory(path, _) => write!(f, "toolchain directory: '{}'", path.display()), UpdatingToolchain(name) => write!(f, "updating existing install for '{}'", name), diff --git a/src/settings.rs b/src/settings.rs index 9ccdb6a81b..89f688edb2 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -67,6 +67,7 @@ pub struct Settings { pub version: String, pub default_host_triple: Option, pub default_toolchain: Option, + pub profile: Option, pub overrides: BTreeMap, } @@ -76,6 +77,7 @@ impl Default for Settings { version: DEFAULT_METADATA_VERSION.to_owned(), default_host_triple: None, default_toolchain: None, + profile: Some("default".to_owned()), overrides: BTreeMap::new(), } } @@ -138,6 +140,7 @@ impl Settings { version, default_host_triple: get_opt_string(&mut table, "default_host_triple", path)?, default_toolchain: get_opt_string(&mut table, "default_toolchain", path)?, + profile: get_opt_string(&mut table, "profile", path)?, overrides: Self::table_to_overrides(&mut table, path)?, }) } @@ -154,6 +157,10 @@ impl Settings { result.insert("default_toolchain".to_owned(), toml::Value::String(v)); } + if let Some(v) = self.profile { + result.insert("profile".to_owned(), toml::Value::String(v)); + } + let overrides = Self::overrides_to_table(self.overrides); result.insert("overrides".to_owned(), toml::Value::Table(overrides)); diff --git a/src/toolchain.rs b/src/toolchain.rs index 19c9b32e43..bc66ad319e 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -34,7 +34,6 @@ pub struct Toolchain<'a> { pub struct ComponentStatus { pub component: Component, pub name: String, - pub required: bool, pub installed: bool, pub available: bool, } @@ -167,9 +166,11 @@ impl<'a> Toolchain<'a> { let update_hash = self.update_hash()?; self.install(InstallMethod::Dist( &self.desc()?, + self.cfg.get_profile()?, update_hash.as_ref().map(|p| &**p), self.download_cfg(), force_update, + self.exists(), )) } @@ -177,9 +178,11 @@ impl<'a> Toolchain<'a> { let update_hash = self.update_hash()?; self.install_if_not_installed(InstallMethod::Dist( &self.desc()?, + self.cfg.get_profile()?, update_hash.as_ref().map(|p| &**p), self.download_cfg(), false, + false, )) } pub fn is_custom(&self) -> bool { @@ -501,6 +504,8 @@ impl<'a> Toolchain<'a> { .map(|c| c.components.contains(component)) .unwrap_or(false); + let component_target = TargetTriple::new(&component.target()); + // Get the component so we can check if it is available let component_pkg = manifest .get_package(&component.short_name_in_manifest()) @@ -512,49 +517,17 @@ impl<'a> Toolchain<'a> { }); let component_target_pkg = component_pkg .targets - .get(&toolchain.target) + .get(&component_target) .expect("component should have target toolchain"); res.push(ComponentStatus { component: component.clone(), name: component.name(&manifest), - required: true, installed, available: component_target_pkg.available(), }); } - for extension in &targ_pkg.extensions { - let installed = config - .as_ref() - .map(|c| c.components.contains(extension)) - .unwrap_or(false); - - let extension_target = TargetTriple::new(&extension.target()); - - // Get the component so we can check if it is available - let extension_pkg = manifest - .get_package(&extension.short_name_in_manifest()) - .unwrap_or_else(|_| { - panic!( - "manifest should contain extension {}", - &extension.short_name(&manifest) - ) - }); - let extension_target_pkg = extension_pkg - .targets - .get(&extension_target) - .expect("extension should have listed toolchain"); - - res.push(ComponentStatus { - component: extension.clone(), - name: extension.name(&manifest), - required: false, - installed, - available: extension_target_pkg.available(), - }); - } - res.sort_by(|a, b| a.component.cmp(&b.component)); Ok(res) @@ -674,21 +647,9 @@ impl<'a> Toolchain<'a> { .get(&toolchain.target) .expect("installed manifest should have a known target"); - if targ_pkg.components.contains(&component) { - // Treat it as a warning, see https://github.com/rust-lang/rustup.rs/issues/441 - eprintln!( - "{}", - ErrorKind::AddingRequiredComponent( - self.name.to_string(), - component.description(&manifest), - ), - ); - return Ok(()); - } - - if !targ_pkg.extensions.contains(&component) { + if !targ_pkg.components.contains(&component) { let wildcard_component = component.wildcard(); - if targ_pkg.extensions.contains(&wildcard_component) { + if targ_pkg.components.contains(&wildcard_component) { component = wildcard_component; } else { return Err(ErrorKind::UnknownComponent( @@ -701,8 +662,8 @@ impl<'a> Toolchain<'a> { } let changes = Changes { - add_extensions: vec![component], - remove_extensions: vec![], + explicit_add_components: vec![component], + remove_components: vec![], }; manifestation.update( @@ -738,24 +699,6 @@ impl<'a> Toolchain<'a> { component = c; } - // Validate the component name - let rust_pkg = manifest - .packages - .get("rust") - .expect("manifest should cantain a rust package"); - let targ_pkg = rust_pkg - .targets - .get(&toolchain.target) - .expect("installed manifest should have a known target"); - - if targ_pkg.components.contains(&component) { - return Err(ErrorKind::RemovingRequiredComponent( - self.name.to_string(), - component.description(&manifest), - ) - .into()); - } - let dist_config = manifestation.read_config()?.unwrap(); if !dist_config.components.contains(&component) { let wildcard_component = component.wildcard(); @@ -772,8 +715,8 @@ impl<'a> Toolchain<'a> { } let changes = Changes { - add_extensions: vec![], - remove_extensions: vec![component], + explicit_add_components: vec![], + remove_components: vec![component], }; manifestation.update( diff --git a/src/utils/toml_utils.rs b/src/utils/toml_utils.rs index a1f111f264..a50b6e89f6 100644 --- a/src/utils/toml_utils.rs +++ b/src/utils/toml_utils.rs @@ -67,7 +67,7 @@ pub fn get_array( if let toml::Value::Array(s) = v { Ok(s) } else { - Err(ErrorKind::ExpectedType("table", path.to_owned() + key).into()) + Err(ErrorKind::ExpectedType("array", path.to_owned() + key).into()) } } else { Ok(toml::value::Array::new()) diff --git a/src/utils/utils.rs b/src/utils/utils.rs index dd167b0d06..50a5a96f59 100644 --- a/src/utils/utils.rs +++ b/src/utils/utils.rs @@ -14,9 +14,6 @@ use url::Url; use retry::delay::{jitter, Fibonacci}; use retry::{retry, OperationResult}; -#[cfg(windows)] -use winapi::shared::minwindef::DWORD; - pub use crate::utils::utils::raw::{ find_cmd, has_cmd, if_not_empty, is_directory, is_file, path_exists, prefix_arg, random_string, }; diff --git a/tests/cli-exact.rs b/tests/cli-exact.rs index 1c44b99d08..01f8c0bec2 100644 --- a/tests/cli-exact.rs +++ b/tests/cli-exact.rs @@ -32,13 +32,13 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' info: default toolchain set to 'nightly-{0}' " @@ -83,13 +83,13 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' info: default toolchain set to 'nightly-{0}' " @@ -369,7 +369,7 @@ fn list_targets() { setup(&|config| { let trip = this_host_triple(); let mut sorted = vec![ - format!("{} (default)", &*trip), + format!("{} (installed)", &*trip), format!("{} (installed)", clitools::CROSS_ARCH1), clitools::CROSS_ARCH2.to_string(), ]; @@ -412,6 +412,7 @@ fn list_installed_targets() { fn cross_install_indicates_target() { setup(&|config| { expect_ok(config, &["rustup", "default", "nightly"]); + // TODO error 'nightly-x86_64-apple-darwin' is not installed expect_ok_ex( config, &["rustup", "target", "add", clitools::CROSS_ARCH1], diff --git a/tests/cli-inst-interactive.rs b/tests/cli-inst-interactive.rs index 3ea6a3ede0..80c25ba3c0 100644 --- a/tests/cli-inst-interactive.rs +++ b/tests/cli-inst-interactive.rs @@ -179,7 +179,7 @@ fn with_non_release_channel_non_default_toolchain() { #[test] fn set_nightly_toolchain() { setup(&|config| { - let out = run_input(config, &["rustup-init"], "2\n\nnightly\n\n\n\n"); + let out = run_input(config, &["rustup-init"], "2\n\nnightly\n\n\n\n\n"); assert!(out.ok); expect_stdout_ok(config, &["rustup", "show"], "nightly"); @@ -189,7 +189,7 @@ fn set_nightly_toolchain() { #[test] fn set_no_modify_path() { setup(&|config| { - let out = run_input(config, &["rustup-init"], "2\n\n\nno\n\n\n"); + let out = run_input(config, &["rustup-init"], "2\n\n\n\nno\n\n\n"); assert!(out.ok); if cfg!(unix) { @@ -204,7 +204,7 @@ fn set_nightly_toolchain_and_unset() { let out = run_input( config, &["rustup-init"], - "2\n\nnightly\n\n2\n\nbeta\n\n\n\n", + "2\n\nnightly\n\n\n2\n\nbeta\n\n\n\n\n", ); assert!(out.ok); @@ -215,7 +215,7 @@ fn set_nightly_toolchain_and_unset() { #[test] fn user_says_nope_after_advanced_install() { setup(&|config| { - let out = run_input(config, &["rustup-init"], "2\n\n\n\nn\n\n"); + let out = run_input(config, &["rustup-init"], "2\n\n\n\n\nn\n\n\n"); assert!(out.ok); assert!(!config.cargodir.join("bin").exists()); }); diff --git a/tests/cli-misc.rs b/tests/cli-misc.rs index 2fadaa948e..6510dab2f2 100644 --- a/tests/cli-misc.rs +++ b/tests/cli-misc.rs @@ -4,8 +4,9 @@ pub mod mock; use crate::mock::clitools::{ - self, expect_err, expect_ok, expect_ok_eq, expect_ok_ex, expect_stderr_ok, expect_stdout_ok, - run, set_current_dist_date, this_host_triple, Config, Scenario, + self, expect_component_executable, expect_component_not_executable, expect_err, expect_ok, + 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}; @@ -568,6 +569,32 @@ fn rename_rls_remove() { }); } +#[test] +fn install_profile() { + let temp_dir = tempfile::Builder::new() + .prefix("fakebin") + .tempdir() + .unwrap(); + let temp_dir_path = temp_dir.path().to_str().unwrap(); + + setup(&|config| { + let args: Vec<&str> = vec!["-y", "--profile", "minimal"]; + run( + config, + "rustup-init", + &args, + &[ + ("RUSTUP_INIT_SKIP_PATH_CHECK", "no"), + ("PATH", &temp_dir_path), + ], + ); + + expect_component_executable(config, "rustup"); + expect_component_executable(config, "rustc"); + expect_component_not_executable(config, "cargo"); + }); +} + #[test] fn install_stops_if_rustc_exists() { let temp_dir = tempfile::Builder::new() @@ -719,7 +746,7 @@ fn update_unavailable_rustc() { config, &["rustup", "update", "nightly"], format!( - "some components unavailable for download: 'rustc', 'cargo' for channel 'nightly'\n{}", + "some components unavailable for download for channel nightly: 'rustc', 'cargo', 'rust-std', 'rust-docs'\n{}", TOOLSTATE_MSG ) .as_str(), @@ -791,3 +818,15 @@ fn completion_cargo_unsupported_shell() { ); }); } + +#[test] +fn add_remove_component() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_component_executable(config, "rustc"); + expect_ok(config, &["rustup", "component", "remove", "rustc"]); + expect_component_not_executable(config, "rustc"); + expect_ok(config, &["rustup", "component", "add", "rustc"]); + expect_component_executable(config, "rustc"); + }); +} diff --git a/tests/cli-rustup.rs b/tests/cli-rustup.rs index 674b4ea096..dd19120e16 100644 --- a/tests/cli-rustup.rs +++ b/tests/cli-rustup.rs @@ -47,17 +47,17 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' " ), @@ -109,45 +109,45 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' " ), @@ -178,32 +178,32 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: removing previous version of component 'rust-std' info: removing previous version of component 'rustc' info: removing previous version of component 'cargo' +info: removing previous version of component 'rust-std' info: removing previous version of component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' " ), @@ -241,13 +241,13 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' info: default toolchain set to 'nightly-{0}' " @@ -991,6 +991,20 @@ fn show_active_toolchain_none() { }); } +#[test] +fn show_profile() { + setup(&|config| { + expect_ok(config, &["rustup", "default", "nightly"]); + expect_stdout_ok(config, &["rustup", "show", "profile"], "default"); + + // Check we get the same thing after we add or remove a component. + expect_ok(config, &["rustup", "component", "add", "rust-src"]); + expect_stdout_ok(config, &["rustup", "show", "profile"], "default"); + expect_ok(config, &["rustup", "component", "remove", "rustc"]); + expect_stdout_ok(config, &["rustup", "show", "profile"], "default"); + }); +} + // #846 #[test] fn set_default_host() { diff --git a/tests/cli-self-upd.rs b/tests/cli-self-upd.rs index ea871cc124..5f908318ee 100644 --- a/tests/cli-self-upd.rs +++ b/tests/cli-self-upd.rs @@ -870,13 +870,13 @@ 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: downloading component 'rust-std' info: downloading component 'rustc' info: downloading component 'cargo' +info: downloading component 'rust-std' info: downloading component 'rust-docs' -info: installing component 'rust-std' info: installing component 'rustc' info: installing component 'cargo' +info: installing component 'rust-std' info: installing component 'rust-docs' info: default toolchain set to 'stable' " diff --git a/tests/cli-v2.rs b/tests/cli-v2.rs index 1f6178fc54..4dd5c7813f 100644 --- a/tests/cli-v2.rs +++ b/tests/cli-v2.rs @@ -472,6 +472,7 @@ fn upgrade_v1_to_v2() { // Delete the v2 manifest so the first day we install from the v1s fs::remove_file(config.distdir.join("dist/channel-rust-nightly.toml.sha256")).unwrap(); expect_ok(config, &["rustup", "default", "nightly"]); + expect_stdout_ok(config, &["rustc", "--version"], "hash-n-1"); set_current_dist_date(config, "2015-01-02"); expect_ok(config, &["rustup", "update", "nightly", "--no-self-update"]); expect_stdout_ok(config, &["rustc", "--version"], "hash-n-2"); @@ -715,8 +716,7 @@ fn add_target_host() { setup(&|config| { let trip = TargetTriple::from_build(); expect_ok(config, &["rustup", "default", "nightly"]); - expect_stderr_ok(config, &["rustup", "target", "add", &trip.to_string()], - for_host!("component 'rust-std' for target '{0}' was automatically added because it is required for toolchain 'nightly-{0}'")); + expect_ok(config, &["rustup", "target", "add", &trip.to_string()]); }); } @@ -857,8 +857,7 @@ fn remove_target_host() { setup(&|config| { let trip = TargetTriple::from_build(); expect_ok(config, &["rustup", "default", "nightly"]); - expect_err(config, &["rustup", "target", "remove", &trip.to_string()], - for_host!("component 'rust-std' for target '{0}' is required for toolchain 'nightly-{0}' and cannot be removed")); + expect_ok(config, &["rustup", "target", "remove", &trip.to_string()]); }); } @@ -938,7 +937,7 @@ fn update_unavailable_std() { config, &["rustup", "update", "nightly", "--no-self-update"], &format!( - "component 'rust-std' for target '{}' is unavailable for download for channel 'nightly'", + "component 'rust-std' for target '{}' is unavailable for download for channel nightly", trip, ), ); @@ -966,7 +965,7 @@ fn update_unavailable_force() { config, &["rustup", "update", "nightly", "--no-self-update"], &format!( - "component 'rls' for target '{}' is unavailable for download for channel 'nightly'", + "component 'rls' for target '{}' is unavailable for download for channel nightly", trip, ), ); diff --git a/tests/dist.rs b/tests/dist.rs index 9e428d850f..9920a169fe 100644 --- a/tests/dist.rs +++ b/tests/dist.rs @@ -5,7 +5,7 @@ pub mod mock; use crate::mock::dist::*; use crate::mock::{MockComponentBuilder, MockFile, MockInstallerBuilder}; -use rustup::dist::dist::{TargetTriple, ToolchainDesc, DEFAULT_DIST_SERVER}; +use rustup::dist::dist::{Profile, TargetTriple, ToolchainDesc, DEFAULT_DIST_SERVER}; use rustup::dist::download::DownloadCfg; use rustup::dist::manifest::{Component, Manifest}; use rustup::dist::manifestation::{Changes, Manifestation, UpdateStatus}; @@ -27,7 +27,7 @@ use url::Url; // Creates a mock dist server populated with some test data pub fn create_mock_dist_server( path: &Path, - edit: Option<&dyn Fn(&str, &mut [MockPackage])>, + edit: Option<&dyn Fn(&str, &mut MockChannel)>, ) -> MockDistServer { MockDistServer { path: path.to_owned(), @@ -41,7 +41,7 @@ pub fn create_mock_dist_server( pub fn create_mock_channel( channel: &str, date: &str, - edit: Option<&dyn Fn(&str, &mut [MockPackage])>, + edit: Option<&dyn Fn(&str, &mut MockChannel)>, ) -> MockChannel { // Put the date in the files so they can be differentiated let contents = Arc::new(date.as_bytes().to_vec()); @@ -59,24 +59,27 @@ pub fn create_mock_channel( MockComponent { name: "rustc".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: false, }, MockComponent { name: "cargo".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: false, }, - ], - extensions: vec![ MockComponent { name: "rust-std".to_string(), target: "i686-apple-darwin".to_string(), + is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "i686-unknown-linux-gnu".to_string(), + is_extension: false, }, ], installer: MockInstallerBuilder { components: vec![] }, @@ -88,17 +91,19 @@ pub fn create_mock_channel( MockComponent { name: "rustc".to_string(), target: "i686-apple-darwin".to_string(), + is_extension: false, }, MockComponent { name: "cargo".to_string(), target: "i686-apple-darwin".to_string(), + is_extension: false, }, MockComponent { name: "rust-std".to_string(), target: "i686-apple-darwin".to_string(), + is_extension: false, }, ], - extensions: vec![], installer: MockInstallerBuilder { components: vec![] }, }, ], @@ -114,7 +119,6 @@ pub fn create_mock_channel( target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: pkg.to_string(), @@ -126,7 +130,6 @@ pub fn create_mock_channel( target: "i686-apple-darwin".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![] }, }, ], @@ -141,7 +144,6 @@ pub fn create_mock_channel( target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-x86_64-apple-darwin".to_string(), @@ -153,7 +155,6 @@ pub fn create_mock_channel( target: "i686-apple-darwin".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-i686-apple-darwin".to_string(), @@ -168,7 +169,6 @@ pub fn create_mock_channel( target: "i686-unknown-linux-gnu".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: "rust-std-i686-unknown-linux-gnu".to_string(), @@ -186,16 +186,18 @@ pub fn create_mock_channel( // for various tests packages.push(bonus_component("bonus", contents.clone())); - if let Some(edit) = edit { - edit(date, &mut packages); - } - - MockChannel { + let mut channel = MockChannel { name: channel.to_string(), date: date.to_string(), packages, renames: HashMap::new(), + }; + + if let Some(edit) = edit { + edit(date, &mut channel); } + + channel } fn bonus_component(name: &'static str, contents: Arc>) -> MockPackage { @@ -206,7 +208,6 @@ fn bonus_component(name: &'static str, contents: Arc>) -> MockPackage { target: "x86_64-apple-darwin".to_string(), available: true, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![MockComponentBuilder { name: format!("{}-x86_64-apple-darwin", name), @@ -263,8 +264,8 @@ fn rename_component() { let dist_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let url = Url::parse(&format!("file://{}", dist_tempdir.path().to_string_lossy())).unwrap(); - let edit_1 = &|_: &str, pkgs: &mut [MockPackage]| { - let tpkg = pkgs[0] + let edit_1 = &|_: &str, chan: &mut MockChannel| { + let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") @@ -272,10 +273,11 @@ fn rename_component() { tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: true, }); }; - let edit_2 = &|_: &str, pkgs: &mut [MockPackage]| { - let tpkg = pkgs[0] + let edit_2 = &|_: &str, chan: &mut MockChannel| { + let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") @@ -283,6 +285,7 @@ fn rename_component() { tpkg.components.push(MockComponent { name: "bobo".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: true, }); }; @@ -305,12 +308,38 @@ fn rename_component() { &url, false, &|url, toolchain, prefix, download_cfg, temp_cfg| { + let adds = [Component::new( + "bonus".to_string(), + Some(TargetTriple::new("x86_64-apple-darwin")), + true, + )]; + change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(&prefix.path().join("bin/bobo"))); change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(&prefix.path().join("bin/bobo"))); }, @@ -323,21 +352,11 @@ fn rename_component_new() { let dist_tempdir = tempfile::Builder::new().prefix("rustup").tempdir().unwrap(); let url = Url::parse(&format!("file://{}", dist_tempdir.path().to_string_lossy())).unwrap(); - let edit_2 = &|_: &str, pkgs: &mut [MockPackage]| { - let tpkg = pkgs[0] - .targets - .iter_mut() - .find(|p| p.target == "x86_64-apple-darwin") - .unwrap(); - tpkg.components.push(MockComponent { - name: "bobo".to_string(), - target: "x86_64-apple-darwin".to_string(), - }); - }; - let date_2 = "2016-02-02"; - let mut channel_2 = create_mock_channel("nightly", date_2, Some(edit_2)); + let mut channel_2 = create_mock_channel("nightly", date_2, None); + // Replace the `bonus` component with a `bobo` component channel_2.packages[4] = bonus_component("bobo", Arc::new(date_2.as_bytes().to_vec())); + // And allow a rename from `bonus` to `bobo` channel_2 .renames .insert("bonus".to_owned(), "bobo".to_owned()); @@ -354,12 +373,44 @@ fn rename_component_new() { &url, false, &|url, toolchain, prefix, download_cfg, temp_cfg| { + let adds = [Component::new( + "bobo".to_string(), + Some(TargetTriple::new("x86_64-apple-darwin")), + true, + )]; + // Install the basics from day 1 change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); + // Neither bonus nor bobo are installed at this point. assert!(!utils::path_exists(&prefix.path().join("bin/bonus"))); assert!(!utils::path_exists(&prefix.path().join("bin/bobo"))); + // Now we move to day 2, where bobo is part of the set of things we want + // to have installed change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); + // As a result `bin/bonus` is present but not `bin/bobo` which we'd + // expect since the bonus component installs `bin/bonus` regardless of + // its name being `bobo` assert!(!utils::path_exists(&prefix.path().join("bin/bobo"))); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); }, @@ -378,6 +429,7 @@ fn update_from_dist( remove: &[Component], download_cfg: &DownloadCfg<'_>, temp_cfg: &temp::Cfg, + force: bool, ) -> Result { // Download the dist manifest and place it into the installation prefix let manifest_url = make_manifest_url(dist_server, toolchain)?; @@ -388,20 +440,25 @@ fn update_from_dist( // Read the manifest to update the components let trip = toolchain.target.clone(); - let manifestation = Manifestation::open(prefix.clone(), trip)?; + let manifestation = Manifestation::open(prefix.clone(), trip.clone())?; + + // TODO on install, need to add profile components (but I guess we shouldn't test that logic here) + let mut profile_components = manifest.get_profile_components(Profile::Default, &trip)?; + let mut add_components = add.to_owned(); + add_components.append(&mut profile_components); let changes = Changes { - add_extensions: add.to_owned(), - remove_extensions: remove.to_owned(), + explicit_add_components: add_components, + remove_components: remove.to_owned(), }; manifestation.update( &manifest, changes, - false, + force, download_cfg, download_cfg.notify_handler, - &toolchain.to_string(), + &toolchain.manifest_name(), ) } @@ -430,7 +487,7 @@ fn uninstall( } fn setup( - edit: Option<&dyn Fn(&str, &mut [MockPackage])>, + edit: Option<&dyn Fn(&str, &mut MockChannel)>, enable_xz: bool, f: &dyn Fn(&Url, &ToolchainDesc, &InstallPrefix, &DownloadCfg<'_>, &temp::Cfg), ) { @@ -476,7 +533,17 @@ fn initial_install() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/rustc"))); assert!(utils::path_exists(&prefix.path().join("lib/libstd.rlib"))); @@ -490,7 +557,17 @@ fn initial_install_xz() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/rustc"))); assert!(utils::path_exists(&prefix.path().join("lib/libstd.rlib"))); @@ -504,7 +581,17 @@ fn test_uninstall() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); uninstall(toolchain, prefix, temp_cfg, &|_| ()).unwrap(); assert!(!utils::path_exists(&prefix.path().join("bin/rustc"))); @@ -519,7 +606,17 @@ fn uninstall_removes_config_file() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.manifest_file("multirust-config.toml") )); @@ -538,13 +635,33 @@ fn upgrade() { download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert_eq!( "2016-02-01", fs::read_to_string(&prefix.path().join("bin/rustc")).unwrap() ); change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert_eq!( "2016-02-02", fs::read_to_string(&prefix.path().join("bin/rustc")).unwrap() @@ -555,10 +672,10 @@ fn upgrade() { #[test] fn unavailable_component() { // On day 2 the bonus component is no longer available - let edit = &|date: &str, pkgs: &mut [MockPackage]| { - // Require the bonus component every dat + let edit = &|date: &str, chan: &mut MockChannel| { + // Require the bonus component every day. { - let tpkg = pkgs[0] + let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") @@ -566,12 +683,17 @@ fn unavailable_component() { tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: true, }); } // Mark the bonus package as unavailable in 2016-02-02 if date == "2016-02-02" { - let bonus_pkg = pkgs.iter_mut().find(|p| p.name == "bonus").unwrap(); + let bonus_pkg = chan + .packages + .iter_mut() + .find(|p| p.name == "bonus") + .unwrap(); for target in &mut bonus_pkg.targets { target.available = false; @@ -583,15 +705,40 @@ fn unavailable_component() { Some(edit), false, &|url, toolchain, prefix, download_cfg, temp_cfg| { + let adds = [Component::new( + "bonus".to_string(), + Some(TargetTriple::new("x86_64-apple-darwin")), + true, + )]; + change_channel_date(url, "nightly", "2016-02-01"); // Update with bonus. - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); change_channel_date(url, "nightly", "2016-02-02"); // Update without bonus, should fail. - let err = update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg) - .unwrap_err(); + let err = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap_err(); match *err.kind() { ErrorKind::RequestedComponentsUnavailable(..) => {} _ => panic!(), @@ -600,12 +747,83 @@ fn unavailable_component() { ); } +// As unavailable_component, but the unavailable component is part of the profile. +#[test] +fn unavailable_component_from_profile() { + // On day 2 the rustc component is no longer available + let edit = &|date: &str, chan: &mut MockChannel| { + // Mark the rustc package as unavailable in 2016-02-02 + if date == "2016-02-02" { + let rustc_pkg = chan + .packages + .iter_mut() + .find(|p| p.name == "rustc") + .unwrap(); + + for target in &mut rustc_pkg.targets { + target.available = false; + } + } + }; + + setup( + Some(edit), + false, + &|url, toolchain, prefix, download_cfg, temp_cfg| { + change_channel_date(url, "nightly", "2016-02-01"); + // Update with rustc. + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); + assert!(utils::path_exists(&prefix.path().join("bin/rustc"))); + change_channel_date(url, "nightly", "2016-02-02"); + + // Update without rustc, should fail. + let err = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap_err(); + match *err.kind() { + ErrorKind::RequestedComponentsUnavailable(..) => {} + _ => panic!(), + } + + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + true, + ) + .unwrap(); + }, + ); +} + #[test] fn removed_component() { // On day 1 install the 'bonus' component, on day 2 its no longer a component - let edit = &|date: &str, pkgs: &mut [MockPackage]| { + let edit = &|date: &str, chan: &mut MockChannel| { if date == "2016-02-01" { - let tpkg = pkgs[0] + let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") @@ -613,7 +831,10 @@ fn removed_component() { tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: true, }); + } else { + chan.packages.retain(|p| p.name != "bonus"); } }; @@ -621,30 +842,40 @@ fn removed_component() { Some(edit), false, &|url, toolchain, prefix, download_cfg, temp_cfg| { - let received_notification = Arc::new(Cell::new(false)); - - let download_cfg = DownloadCfg { - dist_root: download_cfg.dist_root, - temp_cfg: download_cfg.temp_cfg, - download_dir: download_cfg.download_dir, - notify_handler: &|n| { - if let Notification::ComponentUnavailable("bonus", Some(_)) = n { - received_notification.set(true); - } - }, - }; + let adds = [Component::new( + "bonus".to_string(), + Some(TargetTriple::new("x86_64-apple-darwin")), + true, + )]; - change_channel_date(url, "nightly", "2016-02-01"); // Update with bonus. - update_from_dist(url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg).unwrap(); + change_channel_date(url, "nightly", "2016-02-01"); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + &download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); - change_channel_date(url, "nightly", "2016-02-02"); - - // Update without bonus, should emit a notify and remove the bonus component - update_from_dist(url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg).unwrap(); - assert!(!utils::path_exists(&prefix.path().join("bin/bonus"))); - assert!(received_notification.get()); + // Update without bonus, should fail with RequestedComponentsUnavailable + change_channel_date(url, "nightly", "2016-02-02"); + assert!(update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + &download_cfg, + temp_cfg, + false + ) + .is_err()); }, ); } @@ -660,15 +891,27 @@ fn update_preserves_extensions() { Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.path().join("lib/i686-apple-darwin/libstd.rlib") @@ -678,102 +921,25 @@ fn update_preserves_extensions() { )); change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.path().join("lib/i686-apple-darwin/libstd.rlib") - )); - assert!(utils::path_exists( - &prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") - )); - }); -} - -#[test] -fn update_preserves_extensions_that_became_components() { - let edit = &|date: &str, pkgs: &mut [MockPackage]| { - if date == "2016-02-01" { - let tpkg = pkgs[0] - .targets - .iter_mut() - .find(|p| p.target == "x86_64-apple-darwin") - .unwrap(); - tpkg.extensions.push(MockComponent { - name: "bonus".to_string(), - target: "x86_64-apple-darwin".to_string(), - }); - } - if date == "2016-02-02" { - let tpkg = pkgs[0] - .targets - .iter_mut() - .find(|p| p.target == "x86_64-apple-darwin") - .unwrap(); - tpkg.components.push(MockComponent { - name: "bonus".to_string(), - target: "x86_64-apple-darwin".to_string(), - }); - } - }; - setup( - Some(edit), - false, - &|url, toolchain, prefix, download_cfg, temp_cfg| { - let adds = vec![Component::new( - "bonus".to_string(), - Some(TargetTriple::new("x86_64-apple-darwin")), - )]; - - change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); - - assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); - - change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); - assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); - }, - ); -} - -#[test] -fn update_preserves_components_that_became_extensions() { - let edit = &|date: &str, pkgs: &mut [MockPackage]| { - if date == "2016-02-01" { - let tpkg = pkgs[0] - .targets - .iter_mut() - .find(|p| p.target == "x86_64-apple-darwin") - .unwrap(); - tpkg.components.push(MockComponent { - name: "bonus".to_string(), - target: "x86_64-apple-darwin".to_string(), - }); - } - if date == "2016-02-02" { - let tpkg = pkgs[0] - .targets - .iter_mut() - .find(|p| p.target == "x86_64-apple-darwin") - .unwrap(); - tpkg.extensions.push(MockComponent { - name: "bonus".to_string(), - target: "x86_64-apple-darwin".to_string(), - }); - } - }; - setup( - Some(edit), - false, - &|url, toolchain, prefix, download_cfg, temp_cfg| { - change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); - assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); - change_channel_date(url, "nightly", "2016-02-02"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); - assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); - }, - ); + )); + assert!(utils::path_exists( + &prefix.path().join("lib/i686-unknown-linux-gnu/libstd.rlib") + )); + }); } #[test] @@ -783,11 +949,29 @@ fn update_makes_no_changes_for_identical_manifest() { prefix, download_cfg, temp_cfg| { - let status = - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + let status = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert_eq!(status, UpdateStatus::Changed); - let status = - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + let status = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert_eq!(status, UpdateStatus::Unchanged); }); } @@ -803,14 +987,26 @@ fn add_extensions_for_initial_install() { Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.path().join("lib/i686-apple-darwin/libstd.rlib") )); @@ -827,20 +1023,42 @@ fn add_extensions_for_same_manifest() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let adds = vec![ Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.path().join("lib/i686-apple-darwin/libstd.rlib") @@ -860,7 +1078,17 @@ fn add_extensions_for_upgrade() { temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); @@ -868,14 +1096,26 @@ fn add_extensions_for_upgrade() { Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists( &prefix.path().join("lib/i686-apple-darwin/libstd.rlib") @@ -897,9 +1137,20 @@ fn add_extension_not_in_manifest() { let adds = vec![Component::new( "rust-bogus".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + true, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); }); } @@ -914,9 +1165,20 @@ fn add_extension_that_is_required_component() { let adds = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), + false, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); }); } @@ -935,14 +1197,35 @@ fn add_extensions_does_not_remove_other_components() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/rustc"))); }); @@ -960,6 +1243,7 @@ fn remove_extensions_for_initial_install() { let removes = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), + false, )]; update_from_dist( @@ -970,6 +1254,7 @@ fn remove_extensions_for_initial_install() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }); @@ -986,18 +1271,31 @@ fn remove_extensions_for_same_manifest() { Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; update_from_dist( @@ -1008,6 +1306,7 @@ fn remove_extensions_for_same_manifest() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); @@ -1033,20 +1332,33 @@ fn remove_extensions_for_upgrade() { Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, ), Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, ), ]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; update_from_dist( @@ -1057,6 +1369,7 @@ fn remove_extensions_for_upgrade() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); @@ -1079,13 +1392,24 @@ fn remove_extension_not_in_manifest() { temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let removes = vec![Component::new( "rust-bogus".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + true, )]; update_from_dist( @@ -1096,28 +1420,30 @@ fn remove_extension_not_in_manifest() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }); } // Extensions that don't exist in the manifest may still exist on disk -// from a previous manifest. The can't be requested to be removed though; -// only things in the manifest can. +// from a previous manifest. #[test] -#[should_panic] fn remove_extension_not_in_manifest_but_is_already_installed() { - let edit = &|date: &str, pkgs: &mut [MockPackage]| { + let edit = &|date: &str, chan: &mut MockChannel| { if date == "2016-02-01" { - let tpkg = pkgs[0] + let tpkg = chan.packages[0] .targets .iter_mut() .find(|p| p.target == "x86_64-apple-darwin") .unwrap(); - tpkg.extensions.push(MockComponent { + tpkg.components.push(MockComponent { name: "bonus".to_string(), target: "x86_64-apple-darwin".to_string(), + is_extension: true, }); + } else { + chan.packages.retain(|p| p.name != "bonus"); } }; setup( @@ -1126,11 +1452,22 @@ fn remove_extension_not_in_manifest_but_is_already_installed() { &|url, toolchain, prefix, download_cfg, temp_cfg| { change_channel_date(url, "nightly", "2016-02-01"); - let adds = vec![Component::new( + let ref adds = vec![Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), + true, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(utils::path_exists(&prefix.path().join("bin/bonus"))); change_channel_date(url, "nightly", "2016-02-02"); @@ -1138,6 +1475,7 @@ fn remove_extension_not_in_manifest_but_is_already_installed() { let removes = vec![Component::new( "bonus".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), + true, )]; update_from_dist( url, @@ -1147,6 +1485,7 @@ fn remove_extension_not_in_manifest_but_is_already_installed() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }, @@ -1161,11 +1500,22 @@ fn remove_extension_that_is_required_component() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let removes = vec![Component::new( "rustc".to_string(), Some(TargetTriple::new("x86_64-apple-darwin")), + false, )]; update_from_dist( @@ -1176,6 +1526,7 @@ fn remove_extension_that_is_required_component() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }); @@ -1189,11 +1540,22 @@ fn remove_extension_not_installed() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; update_from_dist( @@ -1204,6 +1566,7 @@ fn remove_extension_not_installed() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }); @@ -1223,13 +1586,25 @@ fn remove_extensions_does_not_remove_other_components() { let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; update_from_dist( @@ -1240,6 +1615,7 @@ fn remove_extensions_does_not_remove_other_components() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); @@ -1259,20 +1635,33 @@ fn add_and_remove_for_upgrade() { let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); change_channel_date(url, "nightly", "2016-02-02"); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, )]; update_from_dist( @@ -1283,6 +1672,7 @@ fn add_and_remove_for_upgrade() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); @@ -1305,18 +1695,31 @@ fn add_and_remove() { let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, )]; - update_from_dist(url, toolchain, prefix, &adds, &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &adds, + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-unknown-linux-gnu")), + false, )]; update_from_dist( @@ -1327,6 +1730,7 @@ fn add_and_remove() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); @@ -1347,16 +1751,28 @@ fn add_and_remove_same_component() { prefix, download_cfg, temp_cfg| { - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap(); let adds = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple-darwin")), + false, )]; let removes = vec![Component::new( "rust-std".to_string(), Some(TargetTriple::new("i686-apple_darwin")), + false, )]; update_from_dist( @@ -1367,6 +1783,7 @@ fn add_and_remove_same_component() { &removes, download_cfg, temp_cfg, + false, ) .unwrap(); }); @@ -1383,8 +1800,17 @@ fn bad_component_hash() { let path = path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz"); utils_raw::write_file(&path, "bogus").unwrap(); - let err = - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap_err(); + let err = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap_err(); match *err.kind() { ErrorKind::ComponentDownloadFailed(_) => (), @@ -1404,8 +1830,17 @@ fn unable_to_download_component() { let path = path.join("dist/2016-02-02/rustc-nightly-x86_64-apple-darwin.tar.gz"); fs::remove_file(&path).unwrap(); - let err = - update_from_dist(url, toolchain, prefix, &[], &[], download_cfg, temp_cfg).unwrap_err(); + let err = update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + download_cfg, + temp_cfg, + false, + ) + .unwrap_err(); match *err.kind() { ErrorKind::ComponentDownloadFailed(..) => (), @@ -1452,12 +1887,32 @@ fn reuse_downloaded_file() { }, }; - update_from_dist(url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg).unwrap_err(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + &download_cfg, + temp_cfg, + false, + ) + .unwrap_err(); assert!(!reuse_notification_fired.get()); allow_installation(&prefix); - update_from_dist(url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + &download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(reuse_notification_fired.get()); }) @@ -1499,7 +1954,17 @@ fn checks_files_hashes_before_reuse() { }, }; - update_from_dist(url, toolchain, prefix, &[], &[], &download_cfg, temp_cfg).unwrap(); + update_from_dist( + url, + toolchain, + prefix, + &[], + &[], + &download_cfg, + temp_cfg, + false, + ) + .unwrap(); assert!(noticed_bad_checksum.get()); }) diff --git a/tests/dist_manifest.rs b/tests/dist_manifest.rs index e8a5afd249..cce452027c 100644 --- a/tests/dist_manifest.rs +++ b/tests/dist_manifest.rs @@ -34,7 +34,7 @@ fn parse_smoke_test() { assert_eq!(component.short_name_in_manifest(), "rustc"); assert_eq!(component.target.as_ref(), Some(&x86_64_unknown_linux_gnu)); - let component = &rust_target_pkg.extensions[0]; + let component = &rust_target_pkg.components[4]; assert_eq!(component.short_name_in_manifest(), "rust-std"); assert_eq!(component.target.as_ref(), Some(&x86_64_unknown_linux_musl)); diff --git a/tests/mock/clitools.rs b/tests/mock/clitools.rs index 40e5415617..e46b7ea969 100644 --- a/tests/mock/clitools.rs +++ b/tests/mock/clitools.rs @@ -43,13 +43,14 @@ 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. + 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. } pub static CROSS_ARCH1: &str = "x86_64-unknown-linux-musl"; @@ -286,6 +287,24 @@ pub fn expect_ok_eq(config: &Config, args1: &[&str], args2: &[&str]) { } } +pub fn expect_component_executable(config: &Config, cmd: &str) { + let out1 = run(config, cmd, &["--version"], &[]); + if !out1.ok { + print_command(&[cmd, "--version"], &out1); + println!("expected.ok: true"); + panic!() + } +} + +pub fn expect_component_not_executable(config: &Config, cmd: &str) { + let out1 = run(config, cmd, &["--version"], &[]); + if out1.ok { + print_command(&[cmd, "--version"], &out1); + println!("expected.ok: false"); + panic!() + } +} + fn print_command(args: &[&str], out: &SanitizedOutput) { print!("\n>"); for arg in args { @@ -410,36 +429,95 @@ where output } +#[derive(Copy, Clone, Eq, PartialEq)] +enum RlsStatus { + Available, + Renamed, + Unavailable, +} + // Creates a mock dist server populated with some test data fn create_mock_dist_server(path: &Path, s: Scenario) { let mut chans = Vec::new(); let dates_count = match s { Scenario::SimpleV1 | Scenario::SimpleV2 | Scenario::MultiHost => 1, - Scenario::Full | Scenario::ArchivesV1 | Scenario::ArchivesV2 | Scenario::Unavailable => 2, + Scenario::Full + | Scenario::ArchivesV1 + | Scenario::ArchivesV2 + | Scenario::Unavailable + | Scenario::UnavailableRls => 2, }; if dates_count > 1 { - let c1 = build_mock_channel(s, "nightly", "2015-01-01", "1.2.0", "hash-n-1", false); - let c2 = build_mock_channel(s, "beta", "2015-01-01", "1.1.0", "hash-b-1", false); - let c3 = build_mock_channel(s, "stable", "2015-01-01", "1.0.0", "hash-s-1", false); + let c1 = build_mock_channel( + s, + "nightly", + "2015-01-01", + "1.2.0", + "hash-n-1", + if s == Scenario::UnavailableRls { + RlsStatus::Unavailable + } else { + RlsStatus::Available + }, + ); + let c2 = build_mock_channel( + s, + "beta", + "2015-01-01", + "1.1.0", + "hash-b-1", + RlsStatus::Available, + ); + let c3 = build_mock_channel( + s, + "stable", + "2015-01-01", + "1.0.0", + "hash-s-1", + RlsStatus::Available, + ); chans.extend(vec![c1, c2, c3]); } let c4 = if s == Scenario::Unavailable { build_mock_unavailable_channel("nightly", "2015-01-02", "1.3.0") } else { - build_mock_channel(s, "nightly", "2015-01-02", "1.3.0", "hash-n-2", true) + build_mock_channel( + s, + "nightly", + "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", false); - let c6 = build_mock_channel(s, "stable", "2015-01-02", "1.1.0", "hash-s-2", false); + 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]); let vs = match s { Scenario::Full => vec![ManifestVersion::V1, ManifestVersion::V2], Scenario::SimpleV1 | Scenario::ArchivesV1 => vec![ManifestVersion::V1], - Scenario::SimpleV2 | Scenario::ArchivesV2 | Scenario::MultiHost | Scenario::Unavailable => { - vec![ManifestVersion::V2] - } + Scenario::SimpleV2 + | Scenario::ArchivesV2 + | Scenario::MultiHost + | Scenario::Unavailable + | Scenario::UnavailableRls => vec![ManifestVersion::V2], }; MockDistServer { @@ -512,7 +590,7 @@ fn build_mock_channel( date: &str, version: &'static str, version_hash: &str, - rename_rls: bool, + rls: RlsStatus, ) -> MockChannel { // Build the mock installers let host_triple = this_host_triple(); @@ -541,10 +619,10 @@ fn build_mock_channel( ("cargo", vec![(cargo, host_triple.clone())]), ]; - if rename_rls { + if rls == RlsStatus::Renamed { let rls = build_mock_rls_installer(version, version_hash, true); all.push(("rls-preview", vec![(rls, host_triple.clone())])); - } else { + } else if rls == RlsStatus::Available { let rls = build_mock_rls_installer(version, version_hash, false); all.push(("rls", vec![(rls, host_triple.clone())])); } @@ -582,10 +660,10 @@ fn build_mock_channel( ]; all.extend(more); - if rename_rls { + if rls == RlsStatus::Renamed { let rls = build_mock_rls_installer(version, version_hash, true); all.push(("rls-preview", vec![(rls, triple.clone())])); - } else { + } else if rls == RlsStatus::Available { let rls = build_mock_rls_installer(version, version_hash, false); all.push(("rls", vec![(rls, triple.clone())])); } @@ -606,7 +684,6 @@ fn build_mock_channel( target: triple, available: true, components: vec![], - extensions: vec![], installer, }); @@ -626,51 +703,61 @@ fn build_mock_channel( target_pkg.components.push(MockComponent { name: "rust-std".to_string(), target: target.to_string(), + is_extension: false, }); target_pkg.components.push(MockComponent { name: "rustc".to_string(), target: target.to_string(), + is_extension: false, }); target_pkg.components.push(MockComponent { name: "cargo".to_string(), target: target.to_string(), + is_extension: false, }); target_pkg.components.push(MockComponent { name: "rust-docs".to_string(), target: target.to_string(), + is_extension: false, }); - if rename_rls { - target_pkg.extensions.push(MockComponent { + if rls == RlsStatus::Renamed { + target_pkg.components.push(MockComponent { name: "rls-preview".to_string(), target: target.to_string(), + is_extension: true, }); - } else { - target_pkg.extensions.push(MockComponent { + } else if rls == RlsStatus::Available { + target_pkg.components.push(MockComponent { name: "rls".to_string(), target: target.to_string(), + is_extension: true, }); } - target_pkg.extensions.push(MockComponent { + target_pkg.components.push(MockComponent { name: "rust-std".to_string(), target: CROSS_ARCH1.to_string(), + is_extension: false, }); - target_pkg.extensions.push(MockComponent { + target_pkg.components.push(MockComponent { name: "rust-std".to_string(), target: CROSS_ARCH2.to_string(), + is_extension: false, }); - target_pkg.extensions.push(MockComponent { + target_pkg.components.push(MockComponent { name: "rust-src".to_string(), target: "*".to_string(), + is_extension: true, }); - target_pkg.extensions.push(MockComponent { + target_pkg.components.push(MockComponent { name: "rust-analysis".to_string(), target: target.to_string(), + is_extension: true, }); } } let mut renames = HashMap::new(); - if rename_rls { + if rls == RlsStatus::Renamed { renames.insert("rls".to_owned(), "rls-preview".to_owned()); } @@ -703,7 +790,6 @@ fn build_mock_unavailable_channel(channel: &str, date: &str, version: &'static s target: host_triple.clone(), available: false, components: vec![], - extensions: vec![], installer: MockInstallerBuilder { components: vec![] }, }], }) diff --git a/tests/mock/dist.rs b/tests/mock/dist.rs index ff529da962..2c82f66872 100644 --- a/tests/mock/dist.rs +++ b/tests/mock/dist.rs @@ -90,10 +90,7 @@ pub struct MockTargetedPackage { pub target: String, // Whether the file actually exists (could be due to build failure) pub available: bool, - // Required components pub components: Vec, - // Optional components - pub extensions: Vec, // The mock rust-installer pub installer: MockInstallerBuilder, } @@ -102,6 +99,7 @@ pub struct MockTargetedPackage { pub struct MockComponent { pub name: String, pub target: String, + pub is_extension: bool, } #[derive(Clone)] @@ -152,6 +150,7 @@ impl MockDistServer { let component = MockComponent { name: package.name.to_string(), target: target_package.target.to_string(), + is_extension: false, }; hashes.insert( component, @@ -331,6 +330,7 @@ impl MockDistServer { let component = MockComponent { name: package.name.to_owned(), target: target.target.to_owned(), + is_extension: false, }; let hash = hashes[&component].clone(); toml_target.insert(String::from("hash"), toml::Value::String(hash.gz)); @@ -343,8 +343,9 @@ impl MockDistServer { toml_target.insert(String::from("xz_hash"), toml::Value::String(xz_hash)); } - // [pkg.*.target.*.components.*] + // [pkg.*.target.*.components.*] and [pkg.*.target.*.extensions.*] let mut toml_components = toml::value::Array::new(); + let mut toml_extensions = toml::value::Array::new(); for component in &target.components { let mut toml_component = toml::value::Table::new(); toml_component.insert( @@ -355,27 +356,16 @@ impl MockDistServer { String::from("target"), toml::Value::String(component.target.to_owned()), ); - toml_components.push(toml::Value::Table(toml_component)); + if component.is_extension { + toml_extensions.push(toml::Value::Table(toml_component)); + } else { + toml_components.push(toml::Value::Table(toml_component)); + } } toml_target.insert( String::from("components"), toml::Value::Array(toml_components), ); - - // [pkg.*.target.*.extensions.*] - let mut toml_extensions = toml::value::Array::new(); - for extension in &target.extensions { - let mut toml_extension = toml::value::Table::new(); - toml_extension.insert( - String::from("pkg"), - toml::Value::String(extension.name.to_owned()), - ); - toml_extension.insert( - String::from("target"), - toml::Value::String(extension.target.to_owned()), - ); - toml_extensions.push(toml::Value::Table(toml_extension)); - } toml_target.insert( String::from("extensions"), toml::Value::Array(toml_extensions), @@ -397,6 +387,21 @@ impl MockDistServer { } toml_manifest.insert(String::from("renames"), toml::Value::Table(toml_renames)); + let mut toml_profiles = toml::value::Table::new(); + let profiles = &[ + ("minimal", vec!["rustc"]), + ("default", vec!["rustc", "cargo", "rust-std", "rust-docs"]), + ("complete", vec!["rustc", "cargo", "rust-std"]), + ]; + for (profile, values) in profiles { + let array = values + .iter() + .map(|v| toml::Value::String((**v).to_owned())) + .collect(); + toml_profiles.insert(profile.to_string(), toml::Value::Array(array)); + } + toml_manifest.insert(String::from("profiles"), toml::Value::Table(toml_profiles)); + let manifest_name = format!("dist/channel-rust-{}", channel.name); let manifest_path = self.path.join(format!("{}.toml", manifest_name)); write_file(&manifest_path, &toml::to_string(&toml_manifest).unwrap());