From c0442bb0b3fc74d9a4181b1c0e19e86542ec77d7 Mon Sep 17 00:00:00 2001 From: Matt Harding Date: Sun, 19 Feb 2023 10:54:17 +0000 Subject: [PATCH] Improve `rustup show`, `list toolchains`, `default` 1. Change the output format of `rustup show`, to be in a more logical order and have more pleasing formatting 2. Update the formatting of `rustup show active-toolchain` and `rustup toolchain list` to match the new `rustup show` format. 3. `rustup +nightly show` will no longer install the nightly toolchain if it isn't already installed. Ditto for `rustup show active-toolchain`. 4. Fix a bug where the RUSTUP_TOOLCHAIN environment variable took priority over the +toolchain command line option, and add a test for override priority. 5. `rustup default` no longer errors when there is no default toolchain configured, it prints a message instead. 6. Update the docs, and fix an issue where they incorrectly stated that `rust-toolchain.toml` config files couldn't specify custom names. (Text last touched here, see commit message: https://github.com/rust-lang/rustup/commit/7bfbd3b96c29898de001cbda7730fa440d35bd3b) --- doc/user-guide/src/overrides.md | 11 +- src/cli/common.rs | 105 ++++---- src/cli/rustup_mode.rs | 277 ++++++++++--------- src/config.rs | 452 +++++++++++++++++++------------- tests/suite/cli_exact.rs | 7 + tests/suite/cli_misc.rs | 9 +- tests/suite/cli_rustup.rs | 366 +++++++++++++++++--------- tests/suite/cli_v1.rs | 6 +- tests/suite/cli_v2.rs | 6 +- 9 files changed, 714 insertions(+), 525 deletions(-) diff --git a/doc/user-guide/src/overrides.md b/doc/user-guide/src/overrides.md index d3cfa564808..56923652b96 100644 --- a/doc/user-guide/src/overrides.md +++ b/doc/user-guide/src/overrides.md @@ -19,10 +19,7 @@ the directory tree toward the filesystem root, and a `rust-toolchain.toml` file that is closer to the current directory will be preferred over a directory override that is further away. -To verify which toolchain is active, you can use `rustup show`, -which will also try to install the corresponding -toolchain if the current one has not been installed according to the above rules. -(Please note that this behavior is subject to change, as detailed in issue [#1397].) +To verify which toolchain is active, you can use `rustup show`. [toolchain]: concepts/toolchains.md [toolchain override shorthand]: #toolchain-override-shorthand @@ -123,16 +120,12 @@ The `channel` setting specifies which [toolchain] to use. The value is a string in the following form: ``` -[-] +([-])| = stable|beta|nightly| = YYYY-MM-DD ``` -Note that this is a more restricted form than `rustup` toolchains -generally, and cannot be used to specify custom toolchains or -host-specific toolchains. - [toolchain]: concepts/toolchains.md #### path diff --git a/src/cli/common.rs b/src/cli/common.rs index 9a85aca25b6..8a83c6c9663 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -22,6 +22,7 @@ use crate::currentprocess::{ }; use crate::dist::dist::{TargetTriple, ToolchainDesc}; use crate::install::UpdateStatus; +use crate::toolchain::names::{LocalToolchainName, ToolchainName}; use crate::utils::notifications as util_notifications; use crate::utils::notify::NotificationLevel; use crate::utils::utils; @@ -461,74 +462,72 @@ pub(crate) fn list_installed_components(distributable: DistributableToolchain<'_ Ok(()) } -fn print_toolchain_path( - cfg: &Cfg, - toolchain: &str, - if_default: &str, - if_override: &str, - verbose: bool, -) -> Result<()> { - let toolchain_path = cfg.toolchains_dir.join(toolchain); - let toolchain_meta = fs::symlink_metadata(&toolchain_path)?; - let toolchain_path = if verbose { - if toolchain_meta.is_dir() { - format!("\t{}", toolchain_path.display()) - } else { - format!("\t{}", fs::read_link(toolchain_path)?.display()) - } - } else { - String::new() - }; - writeln!( - process().stdout().lock(), - "{}{}{}{}", - &toolchain, - if_default, - if_override, - toolchain_path - )?; - Ok(()) -} - pub(crate) fn list_toolchains(cfg: &Cfg, verbose: bool) -> Result { - // Work with LocalToolchainName to accommodate path based overrides - let toolchains = cfg - .list_toolchains()? - .iter() - .map(Into::into) - .collect::>(); + let toolchains: Vec = cfg.list_toolchains()?; if toolchains.is_empty() { writeln!(process().stdout().lock(), "no installed toolchains")?; } else { - let def_toolchain_name = cfg.get_default()?.map(|t| (&t).into()); + let default_toolchain_name = cfg.get_default()?; let cwd = utils::current_dir()?; - let ovr_toolchain_name = if let Ok(Some((toolchain, _reason))) = cfg.find_override(&cwd) { - Some(toolchain) - } else { - None - }; - for toolchain in toolchains { - let if_default = if def_toolchain_name.as_ref() == Some(&toolchain) { - " (default)" + let active_toolchain_name: Option = + if let Ok(Some((LocalToolchainName::Named(toolchain), _reason))) = + cfg.find_active_toolchain(&cwd) + { + Some(toolchain) } else { - "" - }; - let if_override = if ovr_toolchain_name.as_ref() == Some(&toolchain) { - " (override)" - } else { - "" + None }; - print_toolchain_path( + for toolchain in toolchains { + let is_default_toolchain = default_toolchain_name.as_ref() == Some(&toolchain); + let is_active_toolchain = active_toolchain_name.as_ref() == Some(&toolchain); + + print_toolchain( cfg, &toolchain.to_string(), - if_default, - if_override, + is_default_toolchain, + is_active_toolchain, verbose, ) .context("Failed to list toolchains' directories")?; } } + + fn print_toolchain( + cfg: &Cfg, + toolchain: &str, + is_default: bool, + is_active: bool, + verbose: bool, + ) -> Result<()> { + let toolchain_path = cfg.toolchains_dir.join(toolchain); + let toolchain_meta = fs::symlink_metadata(&toolchain_path)?; + let toolchain_path = if verbose { + if toolchain_meta.is_dir() { + format!(" {}", toolchain_path.display()) + } else { + format!(" {}", fs::read_link(toolchain_path)?.display()) + } + } else { + String::new() + }; + let status_str = match (is_default, is_active) { + (true, true) => " (default, active)", + (true, false) => " (default)", + (false, true) => " (active)", + (false, false) => "", + }; + + writeln!( + process().stdout().lock(), + "{}{}{}", + &toolchain, + status_str, + toolchain_path + )?; + Ok(()) + } + Ok(utils::ExitCode(0)) } diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 8281e77ebe7..087fefe35b5 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -11,6 +11,7 @@ use clap::{ }; use clap_complete::Shell; +use crate::config::new_toolchain_with_reason; use crate::{ cli::{ common::{self, PackageUpdate}, @@ -20,6 +21,7 @@ use crate::{ topical_doc, }, command, + config::ActiveReason, currentprocess::{ argsource::ArgSource, filesource::{StderrSource, StdoutSource}, @@ -38,8 +40,9 @@ use crate::{ names::{ custom_toolchain_name_parser, maybe_resolvable_toolchainame_parser, partial_toolchain_desc_parser, resolvable_local_toolchainame_parser, - resolvable_toolchainame_parser, CustomToolchainName, MaybeResolvableToolchainName, - ResolvableLocalToolchainName, ResolvableToolchainName, ToolchainName, + resolvable_toolchainame_parser, CustomToolchainName, LocalToolchainName, + MaybeResolvableToolchainName, ResolvableLocalToolchainName, ResolvableToolchainName, + ToolchainName, }, toolchain::Toolchain, }, @@ -110,7 +113,7 @@ pub fn main() -> Result { cfg.set_toolchain_override(&ResolvableToolchainName::try_from(&t[1..])?); } - let toolchain = cfg.find_or_install_override_toolchain_or_default(&cwd)?.0; + let toolchain = cfg.find_or_install_active_toolchain(&cwd)?.0; Ok(toolchain.rustc_version()) } @@ -864,14 +867,21 @@ fn default_(cfg: &Cfg, m: &ArgMatches) -> Result { }; let cwd = utils::current_dir()?; - if let Some((toolchain, reason)) = cfg.find_override(&cwd)? { - info!("note that the toolchain '{toolchain}' is currently in use ({reason})"); + if let Some((toolchain, reason)) = cfg.find_active_toolchain(&cwd)? { + if !matches!(reason, crate::config::ActiveReason::Default) { + info!("note that the toolchain '{toolchain}' is currently in use ({reason})"); + } } } else { - let default_toolchain = cfg - .get_default()? - .ok_or_else(|| anyhow!("no default toolchain configured"))?; - writeln!(process().stdout().lock(), "{default_toolchain} (default)")?; + match cfg.get_default()? { + Some(default_toolchain) => { + writeln!(process().stdout().lock(), "{default_toolchain} (default)")?; + } + None => writeln!( + process().stdout().lock(), + "no default toolchain is configured" + )?, + } } Ok(utils::ExitCode(0)) @@ -1078,133 +1088,112 @@ fn show(cfg: &Cfg, m: &ArgMatches) -> Result { let cwd = utils::current_dir()?; let installed_toolchains = cfg.list_toolchains()?; - // XXX: we may want a find_without_install capability for show. - let active_toolchain = cfg.find_or_install_override_toolchain_or_default(&cwd); - - // active_toolchain will carry the reason we don't have one in its detail. - let active_targets = if let Ok(ref at) = active_toolchain { - if let Ok(distributable) = DistributableToolchain::try_from(&at.0) { - let components = (|| { - let manifestation = distributable.get_manifestation()?; - let config = manifestation.read_config()?.unwrap_or_default(); - let manifest = distributable.get_manifest()?; - manifest.query_components(distributable.desc(), &config) - })(); - - match components { - Ok(cs_vec) => cs_vec - .into_iter() - .filter(|c| c.component.short_name_in_manifest() == "rust-std") - .filter(|c| c.installed) - .collect(), - Err(_) => vec![], - } + let active_toolchain_and_reason: Option<(ToolchainName, ActiveReason)> = + if let Ok(Some((LocalToolchainName::Named(toolchain_name), reason))) = + cfg.find_active_toolchain(&cwd) + { + Some((toolchain_name, reason)) } else { - // These three vec![] could perhaps be reduced with and_then on active_toolchain. - vec![] - } - } else { - vec![] - }; + None + }; + + let (active_toolchain_name, _active_reason) = active_toolchain_and_reason + .as_ref() + .map(|atar| (&atar.0, &atar.1)) + .unzip(); + + let active_targets: Vec = active_toolchain_name + .and_then(|atn| match atn { + ToolchainName::Official(desc) => DistributableToolchain::new(cfg, desc.clone()).ok(), + _ => None, + }) + .and_then(|distributable| { + let manifestation = distributable.get_manifestation().ok()?; + let config = manifestation.read_config().ok()?.unwrap_or_default(); + let manifest = distributable.get_manifest().ok()?; + manifest + .query_components(distributable.desc(), &config) + .ok() + }) + .map(|cs_vec| { + cs_vec + .into_iter() + .filter(|c| c.component.short_name_in_manifest() == "rust-std") + .filter(|c| c.installed) + .collect() + }) + .unwrap_or_default(); - let show_installed_toolchains = installed_toolchains.len() > 1; - let show_active_targets = active_targets.len() > 1; - let show_active_toolchain = true; - - // Only need to display headers if we have multiple sections - let show_headers = [ - show_installed_toolchains, - show_active_targets, - show_active_toolchain, - ] - .iter() - .filter(|x| **x) - .count() - > 1; - - if show_installed_toolchains { + // show installed toolchains + { let mut t = process().stdout().terminal(); - if show_headers { - print_header::(&mut t, "installed toolchains")?; - } - let default_name = cfg - .get_default()? - .ok_or_else(|| anyhow!("no default toolchain configured"))?; - for it in installed_toolchains { - if default_name == it { - writeln!(t.lock(), "{it} (default)")?; - } else { - writeln!(t.lock(), "{it}")?; - } + print_header::(&mut t, "installed toolchains")?; + + let default_toolchain_name = cfg.get_default()?; + + let last_index = installed_toolchains.len().wrapping_sub(1); + for (n, toolchain_name) in installed_toolchains.into_iter().enumerate() { + let is_default_toolchain = default_toolchain_name.as_ref() == Some(&toolchain_name); + let is_active_toolchain = active_toolchain_name == Some(&toolchain_name); + + let status_str = match (is_default_toolchain, is_active_toolchain) { + (true, true) => " (default, active)", + (true, false) => " (default)", + (false, true) => " (active)", + (false, false) => "", + }; + + writeln!(t.lock(), "{toolchain_name}{status_str}")?; + if verbose { - let toolchain = Toolchain::new(cfg, it.into())?; - writeln!(process().stdout().lock(), "{}", toolchain.rustc_version())?; - // To make it easy to see what rustc that belongs to what - // toolchain we separate each pair with an extra newline - writeln!(process().stdout().lock())?; + let toolchain = Toolchain::new(cfg, toolchain_name.into())?; + writeln!(process().stdout().lock(), " {}", toolchain.rustc_version())?; + // To make it easy to see which rustc belongs to which + // toolchain, we separate each pair with an extra newline. + if n != last_index { + writeln!(process().stdout().lock())?; + } } } - if show_headers { - writeln!(t.lock())? - }; } - if show_active_targets { + // show active toolchain + { let mut t = process().stdout().terminal(); - if show_headers { - print_header::(&mut t, "installed targets for active toolchain")?; - } - for at in active_targets { - writeln!( - t.lock(), - "{}", - at.component - .target - .as_ref() - .expect("rust-std should have a target") - )?; - } - if show_headers { - writeln!(t.lock())?; - }; - } - - if show_active_toolchain { - let mut t = process().stdout().terminal(); + writeln!(t.lock())?; - if show_headers { - print_header::(&mut t, "active toolchain")?; - } + print_header::(&mut t, "active toolchain")?; - match active_toolchain { - Ok(atc) => match atc { - (ref toolchain, Some(ref reason)) => { - writeln!(t.lock(), "{} ({})", toolchain.name(), reason)?; - writeln!(t.lock(), "{}", toolchain.rustc_version())?; - } - (ref toolchain, None) => { - writeln!(t.lock(), "{} (default)", toolchain.name())?; - writeln!(t.lock(), "{}", toolchain.rustc_version())?; - } - }, - Err(err) => { - let root_cause = err.root_cause(); - if let Some(RustupError::ToolchainNotSelected) = - root_cause.downcast_ref::() - { - writeln!(t.lock(), "no active toolchain")?; - } else if let Some(cause) = err.source() { - writeln!(t.lock(), "(error: {err}, {cause})")?; - } else { - writeln!(t.lock(), "(error: {err})")?; + match active_toolchain_and_reason { + Some((active_toolchain_name, active_reason)) => { + let active_toolchain = new_toolchain_with_reason( + cfg, + active_toolchain_name.clone().into(), + &active_reason, + )?; + writeln!(t.lock(), "name: {}", active_toolchain.name())?; + writeln!(t.lock(), "compiler: {}", active_toolchain.rustc_version())?; + writeln!(t.lock(), "active because: {}", active_reason.to_string())?; + + // show installed targets for the active toolchain + writeln!(t.lock(), "installed targets:")?; + + for at in active_targets { + writeln!( + t.lock(), + " {}", + at.component + .target + .as_ref() + .expect("rust-std should have a target") + )?; } } - } - - if show_headers { - writeln!(t.lock())? + None => { + writeln!(t.lock(), "no active toolchain")?; + } } } @@ -1213,9 +1202,11 @@ fn show(cfg: &Cfg, m: &ArgMatches) -> Result { E: From, { t.attr(terminalsource::Attr::Bold)?; - writeln!(t.lock(), "{s}")?; - writeln!(t.lock(), "{}", "-".repeat(s.len()))?; - writeln!(t.lock())?; + { + let mut term_lock = t.lock(); + writeln!(term_lock, "{s}")?; + writeln!(term_lock, "{}", "-".repeat(s.len()))?; + } // drop the term_lock t.reset()?; Ok(()) } @@ -1227,31 +1218,27 @@ fn show(cfg: &Cfg, m: &ArgMatches) -> Result { fn show_active_toolchain(cfg: &Cfg, m: &ArgMatches) -> Result { let verbose = m.get_flag("verbose"); let cwd = utils::current_dir()?; - match cfg.find_or_install_override_toolchain_or_default(&cwd) { - Err(e) => { - let root_cause = e.root_cause(); - if let Some(RustupError::ToolchainNotSelected) = - root_cause.downcast_ref::() - { - } else { - return Err(e); - } - } - Ok((toolchain, reason)) => { - if let Some(reason) = reason { + match cfg.find_active_toolchain(&cwd)? { + Some((toolchain_name, reason)) => { + let toolchain = new_toolchain_with_reason(cfg, toolchain_name.clone().into(), &reason)?; + writeln!( + process().stdout().lock(), + "{}\nactive because: {}", + toolchain.name(), + reason + )?; + if verbose { writeln!( process().stdout().lock(), - "{} ({})", - toolchain.name(), - reason + "compiler: {}", + toolchain.rustc_version() )?; - } else { - writeln!(process().stdout().lock(), "{} (default)", toolchain.name())?; - } - if verbose { - writeln!(process().stdout().lock(), "{}", toolchain.rustc_version())?; } } + None => writeln!( + process().stdout().lock(), + "no default toolchain is configured" + )?, } Ok(utils::ExitCode(0)) } @@ -1413,7 +1400,7 @@ fn explicit_or_dir_toolchain2( } None => { let cwd = utils::current_dir()?; - let (toolchain, _) = cfg.find_or_install_override_toolchain_or_default(&cwd)?; + let (toolchain, _) = cfg.find_or_install_active_toolchain(&cwd)?; Ok(toolchain) } diff --git a/src/config.rs b/src/config.rs index ed19012e479..d912af41de0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,8 +28,8 @@ use crate::{ toolchain::{ distributable::DistributableToolchain, names::{ - LocalToolchainName, PathBasedToolchainName, ResolvableLocalToolchainName, - ResolvableToolchainName, ToolchainName, + CustomToolchainName, LocalToolchainName, PathBasedToolchainName, + ResolvableLocalToolchainName, ResolvableToolchainName, ToolchainName, }, toolchain::Toolchain, }, @@ -96,18 +96,21 @@ impl> From for OverrideFile { } } +// Represents the reason why the active toolchain is active. #[derive(Debug)] -pub(crate) enum OverrideReason { +pub(crate) enum ActiveReason { + Default, Environment, CommandLine, OverrideDB(PathBuf), ToolchainFile(PathBuf), } -impl Display for OverrideReason { +impl Display for ActiveReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { match self { - Self::Environment => write!(f, "environment override by RUSTUP_TOOLCHAIN"), + Self::Default => write!(f, "it's the default toolchain"), + Self::Environment => write!(f, "overriden by environment variable RUSTUP_TOOLCHAIN"), Self::CommandLine => write!(f, "overridden by +toolchain on the command line"), Self::OverrideDB(path) => write!(f, "directory override for '{}'", path.display()), Self::ToolchainFile(path) => write!(f, "overridden by '{}'", path.display()), @@ -115,59 +118,153 @@ impl Display for OverrideReason { } } -#[derive(Default, Debug)] -struct OverrideCfg { - toolchain: Option, - components: Vec, - targets: Vec, - profile: Option, +/// Calls Toolchain::new(), but augments the error message with more context +/// from the ActiveReason if the toolchain isn't installed. +pub(crate) fn new_toolchain_with_reason<'a>( + cfg: &'a Cfg, + name: LocalToolchainName, + reason: &ActiveReason, +) -> Result> { + match Toolchain::new(cfg, name.clone()) { + Err(RustupError::ToolchainNotInstalled(_)) => { + let reason_err = match reason { + ActiveReason::Environment => { + "the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain" + .to_string() + } + ActiveReason::CommandLine => { + "the +toolchain on the command line specifies an uninstalled toolchain" + .to_string() + } + ActiveReason::OverrideDB(ref path) => format!( + "the directory override for '{}' specifies an uninstalled toolchain", + utils::canonicalize_path(path, cfg.notify_handler.as_ref()).display(), + ), + ActiveReason::ToolchainFile(ref path) => format!( + "the toolchain file at '{}' specifies an uninstalled toolchain", + utils::canonicalize_path(path, cfg.notify_handler.as_ref()).display(), + ), + ActiveReason::Default => { + "the default toolchain does not describe an installed toolchain".to_string() + } + }; + + Err(anyhow!(reason_err) + .context(format!("override toolchain '{name}' is not installed"))) + } + result => Ok(result?), + } +} + +#[derive(Debug)] +enum OverrideCfg { + PathBased(PathBasedToolchainName), + Custom(CustomToolchainName), + Official { + toolchain: ToolchainDesc, + components: Vec, + targets: Vec, + profile: Option, + }, } impl OverrideCfg { fn from_file(cfg: &Cfg, file: OverrideFile) -> Result { - Ok(Self { - toolchain: match (file.toolchain.channel, file.toolchain.path) { - (Some(name), None) => Some( - (&ResolvableToolchainName::try_from(name)? - .resolve(&cfg.get_default_host_triple()?)?) - .into(), - ), - (None, Some(path)) => { - if file.toolchain.targets.is_some() - || file.toolchain.components.is_some() - || file.toolchain.profile.is_some() - { - bail!( - "toolchain options are ignored for path toolchain ({})", - path.display() - ) - } - // We -do not- support relative paths, they permit trivial - // completely arbitrary code execution in a directory. - // Longer term we'll not support path based toolchains at - // all, because they also permit arbitrary code execution, - // though with more challenges to exploit. - Some((&PathBasedToolchainName::try_from(&path as &Path)?).into()) - } - (Some(channel), Some(path)) => { + let toolchain_name: ToolchainName = match (file.toolchain.channel, file.toolchain.path) { + (Some(name), None) => { + ResolvableToolchainName::try_from(name)?.resolve(&cfg.get_default_host_triple()?)? + } + (None, Some(path)) => { + if file.toolchain.targets.is_some() + || file.toolchain.components.is_some() + || file.toolchain.profile.is_some() + { bail!( - "cannot specify both channel ({}) and path ({}) simultaneously", - channel, + "toolchain options are ignored for path toolchain ({})", path.display() ) } - (None, None) => None, - }, - components: file.toolchain.components.unwrap_or_default(), - targets: file.toolchain.targets.unwrap_or_default(), - profile: file - .toolchain - .profile - .as_deref() - .map(dist::Profile::from_str) - .transpose()?, + // We -do not- support relative paths, they permit trivial + // completely arbitrary code execution in a directory. + // Longer term we'll not support path based toolchains at + // all, because they also permit arbitrary code execution, + // though with more challenges to exploit. + return Ok(Self::PathBased(PathBasedToolchainName::try_from( + &path as &Path, + )?)); + } + (Some(channel), Some(path)) => { + bail!( + "cannot specify both channel ({}) and path ({}) simultaneously", + channel, + path.display() + ) + } + (None, None) => cfg + .get_default()? + .ok_or(RustupError::ToolchainNotSelected)?, + }; + Ok(match toolchain_name { + ToolchainName::Official(desc) => { + let components = file.toolchain.components.unwrap_or_default(); + let targets = file.toolchain.targets.unwrap_or_default(); + Self::Official { + toolchain: desc, + components, + targets, + profile: file + .toolchain + .profile + .as_deref() + .map(dist::Profile::from_str) + .transpose()?, + } + } + ToolchainName::Custom(name) => { + if file.toolchain.targets.is_some() + || file.toolchain.components.is_some() + || file.toolchain.profile.is_some() + { + bail!( + "toolchain options are ignored for a custom toolchain ({})", + name + ) + } + Self::Custom(name) + } }) } + + fn get_local_toolchain_name(self) -> LocalToolchainName { + match self { + Self::PathBased(path_based_name) => path_based_name.into(), + Self::Custom(custom_name) => custom_name.into(), + Self::Official { ref toolchain, .. } => toolchain.into(), + } + } +} + +impl From for OverrideCfg { + fn from(value: ToolchainName) -> Self { + match value { + ToolchainName::Official(desc) => Self::Official { + toolchain: desc, + components: vec![], + targets: vec![], + profile: None, + }, + ToolchainName::Custom(name) => Self::Custom(name), + } + } +} + +impl From for OverrideCfg { + fn from(value: LocalToolchainName) -> Self { + match value { + LocalToolchainName::Named(name) => Self::from(name), + LocalToolchainName::Path(path_name) => Self::PathBased(path_name), + } + } } pub(crate) const UNIX_FALLBACK_SETTINGS: &str = "/etc/rustup/settings.toml"; @@ -391,7 +488,7 @@ impl Cfg { } pub(crate) fn which_binary(&self, path: &Path, binary: &str) -> Result { - let (toolchain, _) = self.find_or_install_override_toolchain_or_default(path)?; + let (toolchain, _) = self.find_or_install_active_toolchain(path)?; Ok(toolchain.binary_file(binary)) } @@ -443,105 +540,62 @@ impl Cfg { .transpose()?) } - pub(crate) fn find_override( + pub(crate) fn find_active_toolchain( &self, path: &Path, - ) -> Result> { - Ok(self - .find_override_config(path)? - .and_then(|(override_cfg, reason)| override_cfg.toolchain.map(|t| (t, reason)))) - } - - fn find_override_config(&self, path: &Path) -> Result> { - let mut override_ = None; - - // First check toolchain override from command - if let Some(ref name) = self.toolchain_override { - override_ = Some((name.to_string().into(), OverrideReason::CommandLine)); - } - - // Check RUSTUP_TOOLCHAIN - if let Some(ref name) = self.env_override { - // Because path based toolchain files exist, this has to support - // custom, distributable, and absolute path toolchains otherwise - // rustup's export of a RUSTUP_TOOLCHAIN when running a process will - // error when a nested rustup invocation occurs - override_ = Some((name.to_string().into(), OverrideReason::Environment)); - } - - // Then walk up the directory tree from 'path' looking for either the - // directory in override database, or a `rust-toolchain` file. - if override_.is_none() { - self.settings_file.with(|s| { - override_ = self.find_override_from_dir_walk(path, s)?; - - Ok(()) - })?; + ) -> Result> { + if let Some((override_config, reason)) = self.find_override_config(path)? { + Ok(Some((override_config.get_local_toolchain_name(), reason))) + } else { + Ok(self + .get_default()? + .map(|x| (x.into(), ActiveReason::Default))) } + } - if let Some((file, reason)) = override_ { - // This is hackishly using the error chain to provide a bit of - // extra context about what went wrong. The CLI will display it - // on a line after the proximate error. - - let reason_err = match reason { - OverrideReason::Environment => { - "the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain" - .to_string() - } - OverrideReason::CommandLine => { - "the +toolchain on the command line specifies an uninstalled toolchain" - .to_string() - } - OverrideReason::OverrideDB(ref path) => format!( - "the directory override for '{}' specifies an uninstalled toolchain", - utils::canonicalize_path(path, self.notify_handler.as_ref()).display(), - ), - OverrideReason::ToolchainFile(ref path) => format!( - "the toolchain file at '{}' specifies an uninstalled toolchain", - utils::canonicalize_path(path, self.notify_handler.as_ref()).display(), - ), - }; - - let override_cfg = OverrideCfg::from_file(self, file)?; - // Overridden toolchains can be literally any string, but only - // distributable toolchains will be auto-installed by the wrapping - // code; provide a nice error for this common case. (default could - // be set badly too, but that is much less common). - match &override_cfg.toolchain { - Some(t @ LocalToolchainName::Named(ToolchainName::Custom(_))) - | Some(t @ LocalToolchainName::Path(_)) => { - if let Err(RustupError::ToolchainNotInstalled(_)) = - Toolchain::new(self, t.to_owned()) - { - // Strip the confusing NotADirectory error and only mention that the - // override toolchain is not installed. - return Err(anyhow!(reason_err)) - .with_context(|| format!("override toolchain '{t}' is not installed")); - } - } - // Either official (can auto install) or no toolchain specified - _ => {} + fn find_override_config(&self, path: &Path) -> Result> { + let override_config: Option<(OverrideCfg, ActiveReason)> = + // First check +toolchain override from the command line + if let Some(ref name) = self.toolchain_override { + let override_config = name.resolve(&self.get_default_host_triple()?)?.into(); + Some((override_config, ActiveReason::CommandLine)) } + // Then check the RUSTUP_TOOLCHAIN environment variable + else if let Some(ref name) = self.env_override { + // Because path based toolchain files exist, this has to support + // custom, distributable, and absolute path toolchains otherwise + // rustup's export of a RUSTUP_TOOLCHAIN when running a process will + // error when a nested rustup invocation occurs + Some((name.clone().into(), ActiveReason::Environment)) + } + // Then walk up the directory tree from 'path' looking for either the + // directory in the override database, or a `rust-toolchain{.toml}` file, + // in that order. + else if let Some((override_file, active_reason)) = self.settings_file.with(|s| { + self.find_override_from_dir_walk(path, s) + })? { + Some((OverrideCfg::from_file(self, override_file)?, active_reason)) + } + // Otherwise, there is no override. + else { + None + }; - Ok(Some((override_cfg, reason))) - } else { - Ok(None) - } + Ok(override_config) } fn find_override_from_dir_walk( &self, dir: &Path, settings: &Settings, - ) -> Result> { + ) -> Result> { let notify = self.notify_handler.as_ref(); let mut dir = Some(dir); while let Some(d) = dir { // First check the override database if let Some(name) = settings.dir_override(d, notify) { - let reason = OverrideReason::OverrideDB(d.to_owned()); + let reason = ActiveReason::OverrideDB(d.to_owned()); return Ok(Some((name.into(), reason))); } @@ -616,7 +670,7 @@ impl Cfg { } } - let reason = OverrideReason::ToolchainFile(toolchain_file); + let reason = ActiveReason::ToolchainFile(toolchain_file); return Ok(Some((override_file, reason))); } @@ -656,66 +710,94 @@ impl Cfg { } } + pub(crate) fn find_or_install_active_toolchain( + &self, + path: &Path, + ) -> Result<(Toolchain<'_>, ActiveReason)> { + self.maybe_find_or_install_active_toolchain(path)? + .ok_or(RustupError::ToolchainNotSelected.into()) + } + #[cfg_attr(feature = "otel", tracing::instrument(skip_all))] - pub(crate) fn find_or_install_override_toolchain_or_default( + pub(crate) fn maybe_find_or_install_active_toolchain( &self, path: &Path, - ) -> Result<(Toolchain<'_>, Option)> { - let (toolchain, components, targets, reason, profile) = - match self.find_override_config(path)? { - Some(( - OverrideCfg { - toolchain, - components, - targets, - profile, - }, - reason, - )) => (toolchain, components, targets, Some(reason), profile), - None => (None, vec![], vec![], None, None), - }; - let toolchain = match toolchain { - t @ Some(_) => t, - None => self.get_default()?.map(Into::into), - }; - match toolchain { - // No override and no default set - None => Err(RustupError::ToolchainNotSelected.into()), - Some(toolchain @ LocalToolchainName::Named(ToolchainName::Custom(_))) - | Some(toolchain @ LocalToolchainName::Path(_)) => { - Ok((Toolchain::new(self, toolchain)?, reason)) + ) -> Result, ActiveReason)>> { + match self.find_override_config(path)? { + Some((override_config, reason)) => match override_config { + OverrideCfg::PathBased(path_based_name) => { + let toolchain = + new_toolchain_with_reason(self, path_based_name.into(), &reason)?; + Ok(Some((toolchain, reason))) + } + OverrideCfg::Custom(custom_name) => { + let toolchain = new_toolchain_with_reason(self, custom_name.into(), &reason)?; + Ok(Some((toolchain, reason))) + } + OverrideCfg::Official { + toolchain, + components, + targets, + profile, + } => { + let toolchain = + self.ensure_installed(toolchain, components, targets, profile)?; + Ok(Some((toolchain, reason))) + } + }, + None => match self.get_default()? { + None => Ok(None), + Some(ToolchainName::Custom(custom_name)) => { + let reason = ActiveReason::Default; + let toolchain = new_toolchain_with_reason(self, custom_name.into(), &reason)?; + Ok(Some((toolchain, reason))) + } + Some(ToolchainName::Official(toolchain_desc)) => { + let reason = ActiveReason::Default; + let toolchain = self.ensure_installed(toolchain_desc, vec![], vec![], None)?; + Ok(Some((toolchain, reason))) + } + }, + } + } + + // Returns a Toolchain matching the given ToolchainDesc, installing it and + // the given components and targets if they aren't already installed. + fn ensure_installed( + &self, + toolchain: ToolchainDesc, + components: Vec, + targets: Vec, + profile: Option, + ) -> Result> { + let components: Vec<_> = components.iter().map(AsRef::as_ref).collect(); + let targets: Vec<_> = targets.iter().map(AsRef::as_ref).collect(); + let toolchain = match DistributableToolchain::new(self, toolchain.clone()) { + Err(RustupError::ToolchainNotInstalled(_)) => { + DistributableToolchain::install( + self, + &toolchain, + &components, + &targets, + profile.unwrap_or(Profile::Default), + false, + )? + .1 } - Some(LocalToolchainName::Named(ToolchainName::Official(desc))) => { - let components: Vec<_> = components.iter().map(AsRef::as_ref).collect(); - let targets: Vec<_> = targets.iter().map(AsRef::as_ref).collect(); - let toolchain = match DistributableToolchain::new(self, desc.clone()) { - Err(RustupError::ToolchainNotInstalled(_)) => { - DistributableToolchain::install( - self, - &desc, - &components, - &targets, - profile.unwrap_or(Profile::Default), - false, - )? - .1 - } - Ok(mut distributable) => { - if !distributable.components_exist(&components, &targets)? { - distributable.update( - &components, - &targets, - profile.unwrap_or(Profile::Default), - )?; - } - distributable - } - Err(e) => return Err(e.into()), + Ok(mut distributable) => { + if !distributable.components_exist(&components, &targets)? { + distributable.update( + &components, + &targets, + profile.unwrap_or(Profile::Default), + )?; } - .into(); - Ok((toolchain, reason)) + distributable } + Err(e) => return Err(e.into()), } + .into(); + Ok(toolchain) } /// Get the configured default toolchain. @@ -829,7 +911,7 @@ impl Cfg { } pub(crate) fn create_command_for_dir(&self, path: &Path, binary: &str) -> Result { - let (toolchain, _) = self.find_or_install_override_toolchain_or_default(path)?; + let (toolchain, _) = self.find_or_install_active_toolchain(path)?; self.create_command_for_toolchain_(toolchain, binary) } diff --git a/tests/suite/cli_exact.rs b/tests/suite/cli_exact.rs index 9c53486809d..7356ab448aa 100644 --- a/tests/suite/cli_exact.rs +++ b/tests/suite/cli_exact.rs @@ -577,6 +577,13 @@ fn default_none() { &["rustup", "default", "none"], "info: default toolchain unset", ); + + config.expect_ok_ex( + &["rustup", "default"], + "no default toolchain is configured\n", + "", + ); + config.expect_err_ex( &["rustc", "--version"], "", diff --git a/tests/suite/cli_misc.rs b/tests/suite/cli_misc.rs index 49982fd2236..9c218178f39 100644 --- a/tests/suite/cli_misc.rs +++ b/tests/suite/cli_misc.rs @@ -477,13 +477,6 @@ fn toolchains_are_resolved_early() { }); } -#[test] -fn no_panic_on_default_toolchain_missing() { - setup(&|config| { - config.expect_err(&["rustup", "default"], "no default toolchain configured"); - }); -} - // #190 #[test] fn proxies_pass_empty_args() { @@ -930,7 +923,7 @@ fn override_by_toolchain_on_the_command_line() { config.expect_stdout_ok(&["rustup", "+nightly", "which", "rustc"], "/bin/rustc"); config.expect_stdout_ok( &["rustup", "+nightly", "show"], - "(overridden by +toolchain on the command line)", + "active because: overridden by +toolchain on the command line", ); config.expect_err( &["rustup", "+foo", "which", "rustc"], diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 9a6adde7a4d..671766afffb 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -505,7 +505,7 @@ fn link() { config.expect_ok(&["rustup", "toolchain", "link", "custom", &path]); config.expect_ok(&["rustup", "default", "custom"]); config.expect_stdout_ok(&["rustc", "--version"], "hash-c-1"); - config.expect_stdout_ok(&["rustup", "show"], "custom (default)"); + config.expect_stdout_ok(&["rustup", "show"], "custom (default, active)"); config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "default", "nightly"]); config.expect_stdout_ok(&["rustup", "show"], "custom"); @@ -630,6 +630,11 @@ fn show_toolchain_none() { r"Default host: {0} rustup home: {1} +installed toolchains +-------------------- + +active toolchain +---------------- no active toolchain " ), @@ -650,8 +655,17 @@ fn show_toolchain_default() { r"Default host: {0} rustup home: {1} -nightly-{0} (default) -1.3.0 (hash-nightly-2) +installed toolchains +-------------------- +nightly-{0} (default, active) + +active toolchain +---------------- +name: nightly-{0} +compiler: 1.3.0 (hash-nightly-2) +active because: it's the default toolchain +installed targets: + {0} " ), r"", @@ -660,6 +674,50 @@ nightly-{0} (default) }); } +#[test] +fn show_no_default() { + test(&|config| { + config.with_scenario(Scenario::SimpleV2, &|config| { + config.expect_ok(&["rustup", "install", "nightly"]); + config.expect_ok(&["rustup", "default", "none"]); + config.expect_stdout_ok( + &["rustup", "show"], + for_host!( + "\ +installed toolchains +-------------------- +nightly-{0} + +active toolchain +" + ), + ); + }) + }); +} + +#[test] +fn show_no_default_active() { + test(&|config| { + config.with_scenario(Scenario::SimpleV2, &|config| { + config.expect_ok(&["rustup", "install", "nightly"]); + config.expect_ok(&["rustup", "default", "none"]); + config.expect_stdout_ok( + &["rustup", "+nightly", "show"], + for_host!( + "\ +installed toolchains +-------------------- +nightly-{0} (active) + +active toolchain +" + ), + ); + }) + }); +} + #[test] fn show_multiple_toolchains() { test(&|config| { @@ -675,16 +733,16 @@ rustup home: {1} installed toolchains -------------------- - stable-{0} -nightly-{0} (default) +nightly-{0} (default, active) active toolchain ---------------- - -nightly-{0} (default) -1.3.0 (hash-nightly-2) - +name: nightly-{0} +compiler: 1.3.0 (hash-nightly-2) +active because: it's the default toolchain +installed targets: + {0} " ), r"", @@ -709,18 +767,18 @@ fn show_multiple_targets() { r"Default host: {2} rustup home: {3} -installed targets for active toolchain --------------------------------------- - -{1} -{0} +installed toolchains +-------------------- +nightly-{0} (default, active) active toolchain ---------------- - -nightly-{0} (default) -1.3.0 (xxxx-nightly-2) - +name: nightly-{0} +compiler: 1.3.0 (xxxx-nightly-2) +active because: it's the default toolchain +installed targets: + {1} + {0} ", clitools::MULTI_ARCH1, clitools::CROSS_ARCH2, @@ -760,22 +818,17 @@ rustup home: {3} installed toolchains -------------------- - stable-{0} -nightly-{0} (default) - -installed targets for active toolchain --------------------------------------- - -{1} -{0} +nightly-{0} (default, active) active toolchain ---------------- - -nightly-{0} (default) -1.3.0 (xxxx-nightly-2) - +name: nightly-{0} +compiler: 1.3.0 (xxxx-nightly-2) +active because: it's the default toolchain +installed targets: + {1} + {0} ", clitools::MULTI_ARCH1, clitools::CROSS_ARCH2, @@ -795,10 +848,22 @@ fn list_default_toolchain() { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], - for_host!( - r"nightly-{0} (default) -" - ), + for_host!("nightly-{0} (default, active)\n"), + r"", + ); + }) + }); +} + +#[test] +fn list_no_default_toolchain() { + test(&|config| { + config.with_scenario(Scenario::SimpleV2, &|config| { + config.expect_ok(&["rustup", "install", "nightly"]); + config.expect_ok(&["rustup", "default", "none"]); + config.expect_ok_ex( + &["rustup", "toolchain", "list"], + for_host!("nightly-{0}\n"), r"", ); }) @@ -806,16 +871,13 @@ fn list_default_toolchain() { } #[test] -fn list_override_toolchain() { +fn list_no_default_override_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "override", "set", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], - for_host!( - r"nightly-{0} (override) -" - ), + for_host!("nightly-{0} (active)\n"), r"", ); }) @@ -830,10 +892,7 @@ fn list_default_and_override_toolchain() { config.expect_ok(&["rustup", "override", "set", "nightly"]); config.expect_ok_ex( &["rustup", "toolchain", "list"], - for_host!( - r"nightly-{0} (default) (override) -" - ), + for_host!("nightly-{0} (default, active)\n"), r"", ); }) @@ -845,32 +904,35 @@ fn heal_damaged_toolchain() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "nightly"]); - config.expect_not_stderr_ok( - &["rustup", "show", "active-toolchain"], - "syncing channel updates", - ); - let path = format!( + config.expect_not_stderr_ok(&["rustup", "which", "rustc"], "syncing channel updates"); + let manifest_path = format!( "toolchains/nightly-{}/lib/rustlib/multirust-channel-manifest.toml", this_host_triple() ); - fs::remove_file(config.rustupdir.join(path)).unwrap(); + + let mut rustc_path = config.rustupdir.join( + [ + "toolchains", + &format!("nightly-{}", this_host_triple()), + "bin", + "rustc", + ] + .iter() + .collect::(), + ); + + if cfg!(windows) { + rustc_path.set_extension("exe"); + } + + fs::remove_file(config.rustupdir.join(manifest_path)).unwrap(); config.expect_ok_ex( - &["rustup", "show", "active-toolchain"], - &format!( - r"nightly-{0} (default) -", - this_host_triple() - ), - for_host!( - r"info: syncing channel updates for 'nightly-{0}' -" - ), + &["rustup", "which", "rustc"], + &format!("{}\n", rustc_path.to_str().unwrap()), + for_host!("info: syncing channel updates for 'nightly-{0}'\n"), ); config.expect_ok(&["rustup", "default", "nightly"]); - config.expect_stderr_ok( - &["rustup", "show", "active-toolchain"], - "syncing channel updates", - ); + config.expect_stderr_ok(&["rustup", "which", "rustc"], "syncing channel updates"); }) }); } @@ -1023,15 +1085,12 @@ fn show_toolchain_override_not_installed() { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "override", "add", "nightly"]); config.expect_ok(&["rustup", "toolchain", "remove", "nightly"]); - let mut cmd = clitools::cmd(config, "rustup", ["show"]); - clitools::env(config, &mut cmd); - let out = cmd.output().unwrap(); - assert!(out.status.success()); - let stdout = String::from_utf8(out.stdout).unwrap(); - let stderr = String::from_utf8(out.stderr).unwrap(); - assert!(!stdout.contains("not a directory")); - assert!(!stdout.contains("is not installed")); - assert!(stderr.contains("info: installing component 'rustc'")); + let out = config.run("rustup", ["show"], &[]); + assert!(!out.ok); + assert!(out + .stderr + .contains("is not installed: the directory override for")); + assert!(!out.stderr.contains("info: installing component 'rustc'")); }) }); } @@ -1078,8 +1137,17 @@ fn show_toolchain_env() { r"Default host: {0} rustup home: {1} -nightly-{0} (environment override by RUSTUP_TOOLCHAIN) -1.3.0 (hash-nightly-2) +installed toolchains +-------------------- +nightly-{0} (default, active) + +active toolchain +---------------- +name: nightly-{0} +compiler: 1.3.0 (hash-nightly-2) +active because: overriden by environment variable RUSTUP_TOOLCHAIN +installed targets: + {0} " ) ); @@ -1091,15 +1159,31 @@ nightly-{0} (environment override by RUSTUP_TOOLCHAIN) fn show_toolchain_env_not_installed() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { - let mut cmd = clitools::cmd(config, "rustup", ["show"]); - clitools::env(config, &mut cmd); - cmd.env("RUSTUP_TOOLCHAIN", "nightly"); - let out = cmd.output().unwrap(); - assert!(out.status.success()); - let stdout = String::from_utf8(out.stdout).unwrap(); - let stderr = String::from_utf8(out.stderr).unwrap(); - assert!(!stdout.contains("is not installed")); - assert!(stderr.contains("info: installing component 'rustc'")); + let out = config.run("rustup", ["show"], &[("RUSTUP_TOOLCHAIN", "nightly")]); + + assert!(!out.ok); + + let expected_out = for_host_and_home!( + config, + r"Default host: {0} +rustup home: {1} + +installed toolchains +-------------------- + +active toolchain +---------------- +" + ); + assert!(&out.stdout == expected_out); + assert!( + out.stderr + == format!( + "error: override toolchain 'nightly-{}' is not installed: \ + the RUSTUP_TOOLCHAIN environment variable specifies an uninstalled toolchain\n", + this_host_triple() + ) + ); }) }); } @@ -1111,10 +1195,7 @@ fn show_active_toolchain() { config.expect_ok(&["rustup", "default", "nightly"]); config.expect_ok_ex( &["rustup", "show", "active-toolchain"], - for_host!( - r"nightly-{0} (default) -" - ), + for_host!("nightly-{0}\nactive because: it's the default toolchain\n"), r"", ); }) @@ -1138,20 +1219,19 @@ rustup home: {1} installed toolchains -------------------- - nightly-2015-01-01-{0} -1.2.0 (hash-nightly-1) - -nightly-{0} (default) -1.3.0 (hash-nightly-2) + 1.2.0 (hash-nightly-1) +nightly-{0} (default, active) + 1.3.0 (hash-nightly-2) active toolchain ---------------- - -nightly-{0} (default) -1.3.0 (hash-nightly-2) - +name: nightly-{0} +compiler: 1.3.0 (hash-nightly-2) +active because: it's the default toolchain +installed targets: + {0} " ), r"", @@ -1168,8 +1248,9 @@ fn show_active_toolchain_with_verbose() { config.expect_ok_ex( &["rustup", "show", "active-toolchain", "--verbose"], for_host!( - r"nightly-{0} (default) -1.3.0 (hash-nightly-2) + r"nightly-{0} +active because: it's the default toolchain +compiler: 1.3.0 (hash-nightly-2) " ), r"", @@ -1187,7 +1268,7 @@ fn show_active_toolchain_with_override() { config.expect_ok(&["rustup", "override", "set", "stable"]); config.expect_stdout_ok( &["rustup", "show", "active-toolchain"], - for_host!("stable-{0} (directory override for"), + for_host!("stable-{0}\nactive because: directory override for"), ); }) }); @@ -1196,7 +1277,11 @@ fn show_active_toolchain_with_override() { #[test] fn show_active_toolchain_none() { test(&|config| { - config.expect_ok_ex(&["rustup", "show", "active-toolchain"], r"", r""); + config.expect_ok_ex( + &["rustup", "show", "active-toolchain"], + "no default toolchain is configured\n", + "", + ); }); } @@ -1921,7 +2006,7 @@ channel = "nightly" } #[test] -fn directory_override_beats_file_override() { +fn close_file_override_beats_far_directory_override() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { config.expect_ok(&["rustup", "default", "stable"]); @@ -1932,36 +2017,79 @@ fn directory_override_beats_file_override() { config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); let cwd = config.current_dir(); - let toolchain_file = cwd.join("rust-toolchain"); + + let subdir = cwd.join("subdir"); + fs::create_dir_all(&subdir).unwrap(); + + let toolchain_file = subdir.join("rust-toolchain"); raw::write_file(&toolchain_file, "nightly").unwrap(); - config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); + config.change_dir(&subdir, &|config| { + config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); + }); }) }); } #[test] -fn close_file_override_beats_far_directory_override() { +// Check that toolchain overrides have the correct priority. +fn override_order() { test(&|config| { - config.with_scenario(Scenario::SimpleV2, &|config| { - config.expect_ok(&["rustup", "default", "stable"]); - config.expect_ok(&["rustup", "toolchain", "install", "beta"]); - config.expect_ok(&["rustup", "toolchain", "install", "nightly"]); + config.with_scenario(Scenario::ArchivesV2, &|config| { + let host = this_host_triple(); + // give each override type a different toolchain + let default_tc = &format!("beta-2015-01-01-{}", host); + let env_tc = &format!("stable-2015-01-01-{}", host); + let dir_tc = &format!("beta-2015-01-02-{}", host); + let file_tc = &format!("stable-2015-01-02-{}", host); + let command_tc = &format!("nightly-2015-01-01-{}", host); + config.expect_ok(&["rustup", "install", default_tc]); + config.expect_ok(&["rustup", "install", env_tc]); + config.expect_ok(&["rustup", "install", dir_tc]); + config.expect_ok(&["rustup", "install", file_tc]); + config.expect_ok(&["rustup", "install", command_tc]); + + // No default + config.expect_ok(&["rustup", "default", "none"]); + config.expect_stdout_ok( + &["rustup", "show", "active-toolchain"], + "no default toolchain is configured\n", + ); - config.expect_ok(&["rustup", "override", "set", "beta"]); - config.expect_stdout_ok(&["rustc", "--version"], "hash-beta-1.2.0"); + // Default + config.expect_ok(&["rustup", "default", default_tc]); + config.expect_stdout_ok(&["rustup", "show", "active-toolchain"], default_tc); - let cwd = config.current_dir(); + // file > default + let toolchain_file = config.current_dir().join("rust-toolchain.toml"); + raw::write_file( + &toolchain_file, + &format!("[toolchain]\nchannel='{}'", file_tc), + ) + .unwrap(); + config.expect_stdout_ok(&["rustup", "show", "active-toolchain"], file_tc); - let subdir = cwd.join("subdir"); - fs::create_dir_all(&subdir).unwrap(); + // dir override > file > default + config.expect_ok(&["rustup", "override", "set", dir_tc]); + config.expect_stdout_ok(&["rustup", "show", "active-toolchain"], dir_tc); - let toolchain_file = subdir.join("rust-toolchain"); - raw::write_file(&toolchain_file, "nightly").unwrap(); + // env > dir override > file > default + let out = config.run( + "rustup", + &["show", "active-toolchain"], + &[("RUSTUP_TOOLCHAIN", env_tc)], + ); + assert!(out.ok); + assert!(out.stdout.contains(env_tc)); - config.change_dir(&subdir, &|config| { - config.expect_stdout_ok(&["rustc", "--version"], "hash-nightly-2"); - }); + // +toolchain > env > dir override > file > default + let out = config.run( + "rustup", + &[&format!("+{}", command_tc), "show", "active-toolchain"], + &[("RUSTUP_TOOLCHAIN", env_tc)], + ); + assert!(out.ok); + assert!(out.stdout.contains(command_tc)); }) }); } @@ -2272,7 +2400,7 @@ fn check_unix_settings_fallback() { test(&|config| { config.with_scenario(Scenario::SimpleV2, &|config| { // No default toolchain specified yet - config.expect_err(&["rustup", "default"], r"no default toolchain configured"); + config.expect_stdout_ok(&["rustup", "default"], "no default toolchain is configured"); // Default toolchain specified in fallback settings file let mock_settings_file = config.current_dir().join("mock_fallback_settings.toml"); diff --git a/tests/suite/cli_v1.rs b/tests/suite/cli_v1.rs index 331b2747f68..cbe466a0165 100644 --- a/tests/suite/cli_v1.rs +++ b/tests/suite/cli_v1.rs @@ -91,11 +91,11 @@ fn list_toolchains() { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "update", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "nightly"); - config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default)\t"); + config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default, active) "); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], - for_host!("\\toolchains\\nightly-{}"), + for_host!(r"\toolchains\nightly-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok( @@ -106,7 +106,7 @@ fn list_toolchains() { #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], - "\\toolchains\\beta-2015-01-01", + r"\toolchains\beta-2015-01-01", ); #[cfg(not(windows))] config.expect_stdout_ok( diff --git a/tests/suite/cli_v2.rs b/tests/suite/cli_v2.rs index 563e6609fca..540513a97fa 100644 --- a/tests/suite/cli_v2.rs +++ b/tests/suite/cli_v2.rs @@ -129,11 +129,11 @@ fn list_toolchains() { config.expect_ok(&["rustup", "update", "nightly"]); config.expect_ok(&["rustup", "update", "beta-2015-01-01"]); config.expect_stdout_ok(&["rustup", "toolchain", "list"], "nightly"); - config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default)\t"); + config.expect_stdout_ok(&["rustup", "toolchain", "list", "-v"], "(default, active) "); #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], - for_host!("\\toolchains\\nightly-{}"), + for_host!(r"\toolchains\nightly-{}"), ); #[cfg(not(windows))] config.expect_stdout_ok( @@ -144,7 +144,7 @@ fn list_toolchains() { #[cfg(windows)] config.expect_stdout_ok( &["rustup", "toolchain", "list", "-v"], - "\\toolchains\\beta-2015-01-01", + r"\toolchains\beta-2015-01-01", ); #[cfg(not(windows))] config.expect_stdout_ok(