diff --git a/src/bin/rustup-init.rs b/src/bin/rustup-init.rs index 7b13d00198..57b9057be5 100644 --- a/src/bin/rustup-init.rs +++ b/src/bin/rustup-init.rs @@ -13,7 +13,7 @@ #![recursion_limit = "1024"] -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use cfg_if::cfg_if; // Public macros require availability of the internal symbols use rs_tracing::{ @@ -29,6 +29,7 @@ use rustup::cli::self_update; use rustup::cli::setup_mode; use rustup::currentprocess::{process, with_runtime, Process}; use rustup::env_var::RUST_RECURSION_COUNT_MAX; +use rustup::errors::RustupError; use rustup::is_proxyable_tools; use rustup::utils::utils::{self, ExitCode}; @@ -115,17 +116,19 @@ async fn run_rustup_inner() -> Result { // Before we do anything else, ensure we know where we are and who we // are because otherwise we cannot proceed usefully. - utils::current_dir()?; + let current_dir = process() + .current_dir() + .context(RustupError::LocatingWorkingDir)?; utils::current_exe()?; match process().name().as_deref() { - Some("rustup") => rustup_mode::main().await, + Some("rustup") => rustup_mode::main(current_dir).await, Some(n) if n.starts_with("rustup-setup") || n.starts_with("rustup-init") => { // NB: The above check is only for the prefix of the file // name. Browsers rename duplicates to // e.g. rustup-setup(2), and this allows all variations // to work. - setup_mode::main().await + setup_mode::main(current_dir).await } Some(n) if n.starts_with("rustup-gc-") => { // This is the final uninstallation stage on windows where @@ -140,7 +143,7 @@ async fn run_rustup_inner() -> Result { } Some(n) => { is_proxyable_tools(n)?; - proxy_mode::main(n).await.map(ExitCode::from) + proxy_mode::main(n, current_dir).await.map(ExitCode::from) } None => { // Weird case. No arg0, or it's unparsable. diff --git a/src/cli/common.rs b/src/cli/common.rs index 45eae5760a..d65c28b4df 100644 --- a/src/cli/common.rs +++ b/src/cli/common.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::fmt::Display; use std::fs; use std::io::{BufRead, ErrorKind, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use std::{cmp, env}; @@ -168,19 +168,22 @@ impl NotifyOnConsole { } #[cfg_attr(feature = "otel", tracing::instrument)] -pub(crate) fn set_globals(verbose: bool, quiet: bool) -> Result { +pub(crate) fn set_globals(current_dir: PathBuf, verbose: bool, quiet: bool) -> Result { let download_tracker = DownloadTracker::new_with_display_progress(!quiet); let console_notifier = RefCell::new(NotifyOnConsole { verbose, ..Default::default() }); - Cfg::from_env(Arc::new(move |n: Notification<'_>| { - if download_tracker.lock().unwrap().handle_notification(&n) { - return; - } - console_notifier.borrow_mut().handle(n); - })) + Cfg::from_env( + current_dir, + Arc::new(move |n: Notification<'_>| { + if download_tracker.lock().unwrap().handle_notification(&n) { + return; + } + console_notifier.borrow_mut().handle(n); + }), + ) } pub(crate) fn show_channel_update( diff --git a/src/cli/proxy_mode.rs b/src/cli/proxy_mode.rs index 362de80d42..1a81999e5f 100644 --- a/src/cli/proxy_mode.rs +++ b/src/cli/proxy_mode.rs @@ -1,18 +1,17 @@ -use std::ffi::OsString; -use std::process::ExitStatus; +use std::{path::PathBuf, process::ExitStatus}; use anyhow::Result; +use crate::toolchain::toolchain::Toolchain; use crate::{ cli::{common::set_globals, job, self_update}, command::run_command_for_dir, - config::Cfg, currentprocess::process, - toolchain::names::{LocalToolchainName, ResolvableLocalToolchainName}, + toolchain::names::ResolvableLocalToolchainName, }; #[cfg_attr(feature = "otel", tracing::instrument)] -pub async fn main(arg0: &str) -> Result { +pub async fn main(arg0: &str, current_dir: PathBuf) -> Result { self_update::cleanup_self_updater()?; let _setup = job::setup(); @@ -35,24 +34,17 @@ pub async fn main(arg0: &str) -> Result { .skip(1 + toolchain.is_some() as usize) .collect(); - let cfg = set_globals(false, true)?; + let cfg = set_globals(current_dir, false, true)?; cfg.check_metadata_version()?; let toolchain = toolchain .map(|t| t.resolve(&cfg.get_default_host_triple()?)) .transpose()?; - direct_proxy(&cfg, arg0, toolchain, &cmd_args).await -} -#[cfg_attr(feature = "otel", tracing::instrument(skip(cfg)))] -async fn direct_proxy( - cfg: &Cfg, - arg0: &str, - toolchain: Option, - args: &[OsString], -) -> Result { - let cmd = match toolchain { - None => cfg.create_command_for_dir(arg0).await?, - Some(tc) => cfg.create_command_for_toolchain(&tc, false, arg0).await?, + let toolchain = match toolchain { + None => cfg.find_or_install_active_toolchain().await?.0, + Some(tc) => Toolchain::from_local(&tc, false, &cfg).await?, }; - run_command_for_dir(cmd, arg0, args) + + let cmd = toolchain.command(arg0)?; + run_command_for_dir(cmd, arg0, &cmd_args) } diff --git a/src/cli/rustup_mode.rs b/src/cli/rustup_mode.rs index 6b760dc6a9..ae8212b408 100644 --- a/src/cli/rustup_mode.rs +++ b/src/cli/rustup_mode.rs @@ -22,7 +22,7 @@ use crate::{ topical_doc, }, command, - config::{new_toolchain_with_reason, ActiveReason, Cfg}, + config::{ActiveReason, Cfg}, currentprocess::{ process, terminalsource::{self, ColorableTerminal}, @@ -524,7 +524,7 @@ enum SetSubcmd { } #[cfg_attr(feature = "otel", tracing::instrument(fields(args = format!("{:?}", process().args_os().collect::>()))))] -pub async fn main() -> Result { +pub async fn main(current_dir: PathBuf) -> Result { self_update::cleanup_self_updater()?; use clap::error::ErrorKind::*; @@ -539,8 +539,10 @@ pub async fn main() -> Result { info!("This is the version for the rustup toolchain manager, not the rustc compiler."); #[cfg_attr(feature = "otel", tracing::instrument)] - async fn rustc_version() -> std::result::Result> { - let cfg = &mut common::set_globals(false, true)?; + async fn rustc_version( + current_dir: PathBuf, + ) -> std::result::Result> { + let cfg = &mut common::set_globals(current_dir, false, true)?; if let Some(t) = process().args().find(|x| x.starts_with('+')) { debug!("Fetching rustc version from toolchain `{}`", t); @@ -552,7 +554,7 @@ pub async fn main() -> Result { Ok(toolchain.rustc_version()) } - match rustc_version().await { + match rustc_version(current_dir).await { Ok(version) => info!("The currently active `rustc` version is `{}`", version), Err(err) => debug!("Wanted to tell you the current rustc version, too, but ran into this error: {}", err), } @@ -577,7 +579,7 @@ pub async fn main() -> Result { Err(err) } }?; - let cfg = &mut common::set_globals(matches.verbose, matches.quiet)?; + let cfg = &mut common::set_globals(current_dir, matches.verbose, matches.quiet)?; if let Some(t) = &matches.plus_toolchain { cfg.set_toolchain_override(t); @@ -898,9 +900,8 @@ async fn run( install: bool, ) -> Result { let toolchain = toolchain.resolve(&cfg.get_default_host_triple()?)?; - let cmd = cfg - .create_command_for_toolchain(&toolchain, install, &command[0]) - .await?; + let toolchain = Toolchain::from_local(&toolchain, install, cfg).await?; + let cmd = toolchain.command(&command[0])?; command::run_command_for_dir(cmd, &command[0], &command[1..]) } @@ -913,7 +914,8 @@ async fn which( let desc = toolchain.resolve(&cfg.get_default_host_triple()?)?; Toolchain::new(cfg, desc.into())?.binary_file(binary) } else { - cfg.which_binary(binary).await? + let (toolchain, _) = cfg.find_or_install_active_toolchain().await?; + toolchain.binary_file(binary) }; utils::assert_is_file(&binary_path)?; @@ -1020,7 +1022,7 @@ fn show(cfg: &Cfg, verbose: bool) -> Result { match active_toolchain_and_reason { Some((active_toolchain_name, active_reason)) => { - let active_toolchain = new_toolchain_with_reason( + let active_toolchain = Toolchain::with_reason( cfg, active_toolchain_name.clone().into(), &active_reason, @@ -1063,7 +1065,7 @@ fn show(cfg: &Cfg, verbose: bool) -> Result { fn show_active_toolchain(cfg: &Cfg, verbose: bool) -> Result { match cfg.find_active_toolchain()? { Some((toolchain_name, reason)) => { - let toolchain = new_toolchain_with_reason(cfg, toolchain_name.clone(), &reason)?; + let toolchain = Toolchain::with_reason(cfg, toolchain_name.clone(), &reason)?; writeln!( process().stdout().lock(), "{}\nactive because: {}", @@ -1265,7 +1267,7 @@ async fn toolchain_link( if true { InstallMethod::Link { - src: &utils::to_absolute(src)?, + src: &cfg.current_dir.join(src), dest, cfg, } diff --git a/src/cli/self_update.rs b/src/cli/self_update.rs index cdcd6ac603..a81158ef0e 100644 --- a/src/cli/self_update.rs +++ b/src/cli/self_update.rs @@ -365,6 +365,7 @@ fn canonical_cargo_home() -> Result> { /// `CARGO_HOME`/bin, hard-linking the various Rust tools to it, /// and adding `CARGO_HOME`/bin to PATH. pub(crate) async fn install( + current_dir: PathBuf, no_prompt: bool, verbose: bool, quiet: bool, @@ -458,7 +459,7 @@ pub(crate) async fn install( } } - let install_res = || async { + let install_res = move || async move { install_bins()?; #[cfg(unix)] @@ -468,7 +469,16 @@ pub(crate) async fn install( do_add_to_programs()?; do_add_to_path()?; } - utils::create_rustup_home()?; + + // If RUSTUP_HOME is not set, make sure it exists + if process().var_os("RUSTUP_HOME").is_none() { + let home = utils::home_dir() + .map(|p| p.join(".rustup")) + .ok_or_else(|| anyhow::anyhow!("could not find home dir to put .rustup in"))?; + + fs::create_dir_all(home).context("unable to create ~/.rustup")?; + } + maybe_install_rust( opts.default_toolchain, &opts.profile, @@ -476,6 +486,7 @@ pub(crate) async fn install( !opts.no_update_toolchain, opts.components, opts.targets, + current_dir, verbose, quiet, ) @@ -847,10 +858,11 @@ async fn maybe_install_rust( update_existing_toolchain: bool, components: &[&str], targets: &[&str], + current_dir: PathBuf, verbose: bool, quiet: bool, ) -> Result<()> { - let mut cfg = common::set_globals(verbose, quiet)?; + let mut cfg = common::set_globals(current_dir, verbose, quiet)?; let toolchain = _install_selection( &mut cfg, @@ -1329,7 +1341,7 @@ mod tests { currentprocess::with(tp.clone().into(), || -> Result<()> { // TODO: we could pass in a custom cfg to get notification // callbacks rather than output to the tp sink. - let mut cfg = common::set_globals(false, false).unwrap(); + let mut cfg = common::set_globals(tp.cwd.clone(), false, false).unwrap(); assert_eq!( "stable" .parse::() diff --git a/src/cli/setup_mode.rs b/src/cli/setup_mode.rs index 5dd32b49be..49dcbb3bb3 100644 --- a/src/cli/setup_mode.rs +++ b/src/cli/setup_mode.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use anyhow::Result; use clap::{builder::PossibleValuesParser, Parser}; @@ -74,7 +76,7 @@ struct RustupInit { } #[cfg_attr(feature = "otel", tracing::instrument)] -pub async fn main() -> Result { +pub async fn main(current_dir: PathBuf) -> Result { use clap::error::ErrorKind; let RustupInit { @@ -122,5 +124,5 @@ pub async fn main() -> Result { targets: &target.iter().map(|s| &**s).collect::>(), }; - self_update::install(no_prompt, verbose, quiet, opts).await + self_update::install(current_dir, no_prompt, verbose, quiet, opts).await } diff --git a/src/config.rs b/src/config.rs index 15a525f98b..3356ac28da 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,6 @@ use std::borrow::Cow; use std::fmt::{self, Debug, Display}; use std::io; use std::path::{Path, PathBuf}; -use std::process::Command; use std::str::FromStr; use std::sync::Arc; @@ -15,7 +14,7 @@ use crate::{ cli::self_update::SelfUpdateMode, currentprocess::process, dist::{ - dist::{self, PartialToolchainDesc, Profile, ToolchainDesc}, + dist::{self, Profile, ToolchainDesc}, download::DownloadCfg, temp, }, @@ -117,44 +116,6 @@ impl Display for ActiveReason { } } -/// 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(_)) => (), - result => { - return Ok(result?); - } - } - - 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"))) -} - // Represents a toolchain override from a +toolchain command line option, // RUSTUP_TOOLCHAIN environment variable, or rust-toolchain.toml file etc. Can // include components and targets from a rust-toolchain.toml that should be @@ -289,7 +250,10 @@ pub(crate) struct Cfg { } impl Cfg { - pub(crate) fn from_env(notify_handler: Arc)>) -> Result { + pub(crate) fn from_env( + current_dir: PathBuf, + notify_handler: Arc)>, + ) -> Result { // Set up the rustup home directory let rustup_dir = utils::rustup_home()?; @@ -367,7 +331,7 @@ impl Cfg { toolchain_override: None, env_override, dist_root_url: dist_root, - current_dir: utils::current_dir()?, + current_dir, }; // Run some basic checks against the constructed configuration @@ -493,11 +457,6 @@ impl Cfg { Ok(self.update_hash_dir.join(toolchain.to_string())) } - pub(crate) async fn which_binary(&self, binary: &str) -> Result { - let (toolchain, _) = self.find_or_install_active_toolchain().await?; - Ok(toolchain.binary_file(binary)) - } - #[cfg_attr(feature = "otel", tracing::instrument(skip_all))] pub(crate) fn upgrade_data(&self) -> Result<()> { let current_version = self.settings_file.with(|s| Ok(s.version.clone()))?; @@ -744,12 +703,11 @@ impl Cfg { 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)?; + let toolchain = 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)?; + let toolchain = Toolchain::with_reason(self, custom_name.into(), &reason)?; Ok(Some((toolchain, reason))) } OverrideCfg::Official { @@ -768,7 +726,7 @@ impl Cfg { None => Ok(None), Some(ToolchainName::Custom(custom_name)) => { let reason = ActiveReason::Default; - let toolchain = new_toolchain_with_reason(self, custom_name.into(), &reason)?; + let toolchain = Toolchain::with_reason(self, custom_name.into(), &reason)?; Ok(Some((toolchain, reason))) } Some(ToolchainName::Official(toolchain_desc)) => { @@ -931,110 +889,6 @@ impl Cfg { }) } - pub(crate) async fn create_command_for_dir(&self, binary: &str) -> Result { - let (toolchain, _) = self.find_or_install_active_toolchain().await?; - self.create_command_for_toolchain_(toolchain, binary) - } - - pub(crate) async fn create_command_for_toolchain( - &self, - toolchain_name: &LocalToolchainName, - install_if_missing: bool, - binary: &str, - ) -> Result { - match toolchain_name { - LocalToolchainName::Named(ToolchainName::Official(desc)) => { - match DistributableToolchain::new(self, desc.clone()) { - Err(RustupError::ToolchainNotInstalled(_)) => { - if install_if_missing { - DistributableToolchain::install( - self, - desc, - &[], - &[], - self.get_profile()?, - true, - ) - .await?; - } - } - o => { - o?; - } - } - } - n => { - if !Toolchain::exists(self, n)? { - return Err(RustupError::ToolchainNotInstallable(n.to_string()).into()); - } - } - } - - let toolchain = Toolchain::new(self, toolchain_name.clone())?; - - // NB this can only fail in race conditions since we handle existence above - // for dir. - self.create_command_for_toolchain_(toolchain, binary) - } - - fn create_command_for_toolchain_( - &self, - toolchain: Toolchain<'_>, - binary: &str, - ) -> Result { - // Should push the cargo fallback into a custom toolchain type? And then - // perhaps a trait that create command layers on? - if !matches!( - toolchain.name(), - LocalToolchainName::Named(ToolchainName::Official(_)) - ) { - if let Some(cmd) = self.maybe_do_cargo_fallback(&toolchain, binary)? { - return Ok(cmd); - } - } - - toolchain.create_command(binary) - } - - // Custom toolchains don't have cargo, so here we detect that situation and - // try to find a different cargo. - fn maybe_do_cargo_fallback( - &self, - toolchain: &Toolchain<'_>, - binary: &str, - ) -> Result> { - if binary != "cargo" && binary != "cargo.exe" { - return Ok(None); - } - - let cargo_path = toolchain.binary_file("cargo"); - - // breadcrumb in case of regression: we used to get the cargo path and - // cargo.exe path separately, not using the binary_file helper. This may - // matter if calling a binary with some personality that allows .exe and - // not .exe to coexist (e.g. wine) - but that's not something we aim to - // support : the host should always be correct. - if cargo_path.exists() { - return Ok(None); - } - - let default_host_triple = self.get_default_host_triple()?; - // XXX: This could actually consider all installed distributable - // toolchains in principle. - for fallback in ["nightly", "beta", "stable"] { - let resolved = - PartialToolchainDesc::from_str(fallback)?.resolve(&default_host_triple)?; - if let Ok(fallback) = - crate::toolchain::distributable::DistributableToolchain::new(self, resolved) - { - let cmd = fallback.create_fallback_command("cargo", toolchain)?; - return Ok(Some(cmd)); - } - } - - Ok(None) - } - pub(crate) fn set_default_host_triple(&self, host_triple: String) -> Result<()> { // Ensure that the provided host_triple is capable of resolving // against the 'stable' toolchain. This provides early errors diff --git a/src/currentprocess.rs b/src/currentprocess.rs index ba3d982e3d..27660bc85f 100644 --- a/src/currentprocess.rs +++ b/src/currentprocess.rs @@ -106,7 +106,7 @@ impl Process { } } - pub(crate) fn current_dir(&self) -> io::Result { + pub fn current_dir(&self) -> io::Result { match self { Process::OSProcess(_) => env::current_dir(), #[cfg(feature = "test")] diff --git a/src/dist/dist.rs b/src/dist/dist.rs index e3e0711b95..7a2139fa00 100644 --- a/src/dist/dist.rs +++ b/src/dist/dist.rs @@ -96,7 +96,7 @@ fn components_missing_msg(cs: &[Component], manifest: &ManifestV2, toolchain: &s } #[derive(Debug, ThisError)] -pub(crate) enum DistError { +pub enum DistError { #[error("{}", components_missing_msg(.0, .1, .2))] ToolchainComponentsMissing(Vec, Box, String), #[error("no release found for '{0}'")] diff --git a/src/errors.rs b/src/errors.rs index dafc05b857..3ed45506c3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -25,7 +25,7 @@ use crate::{ pub struct OperationError(pub anyhow::Error); #[derive(ThisError, Debug)] -pub(crate) enum RustupError { +pub enum RustupError { #[error("partially downloaded file may have been damaged and was removed, please try again")] BrokenPartialFile, #[error("component download failed for {0}")] diff --git a/src/test/mock/clitools.rs b/src/test/mock/clitools.rs index 4a47ecb5dd..efd093ffef 100644 --- a/src/test/mock/clitools.rs +++ b/src/test/mock/clitools.rs @@ -731,8 +731,11 @@ impl Config { .enable_all() .worker_threads(2) .max_blocking_threads(2); - let process_res = - currentprocess::with_runtime(tp.clone().into(), builder, rustup_mode::main()); + let process_res = currentprocess::with_runtime( + tp.clone().into(), + builder, + rustup_mode::main(tp.cwd.clone()), + ); // convert Err's into an ec let ec = match process_res { Ok(process_res) => process_res, diff --git a/src/toolchain/names.rs b/src/toolchain/names.rs index 3dece6e5fb..ad1147acc2 100644 --- a/src/toolchain/names.rs +++ b/src/toolchain/names.rs @@ -249,7 +249,7 @@ impl Display for MaybeOfficialToolchainName { /// like setting overrides, or that depend on configuration, like calculating /// the toolchain directory. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) enum ToolchainName { +pub enum ToolchainName { Custom(CustomToolchainName), Official(ToolchainDesc), } @@ -396,7 +396,7 @@ impl Display for LocalToolchainName { /// A custom toolchain name, but not an official toolchain name /// (e.g. my-custom-toolchain) #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) struct CustomToolchainName(String); +pub struct CustomToolchainName(String); impl CustomToolchainName { fn validate(candidate: &str) -> Result { @@ -433,7 +433,7 @@ impl Display for CustomToolchainName { /// code execution in a rust dir, so as a partial mitigation is limited to /// absolute paths. #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) struct PathBasedToolchainName(PathBuf, String); +pub struct PathBasedToolchainName(PathBuf, String); impl Display for PathBasedToolchainName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/src/toolchain/toolchain.rs b/src/toolchain/toolchain.rs index a011226e5d..42bb6e6536 100644 --- a/src/toolchain/toolchain.rs +++ b/src/toolchain/toolchain.rs @@ -6,6 +6,7 @@ use std::{ io::{self, BufRead, BufReader}, path::{Path, PathBuf}, process::{Command, Stdio}, + str::FromStr, time::Duration, }; @@ -14,7 +15,7 @@ use fs_at::OpenOptions; use wait_timeout::ChildExt; use crate::{ - config::Cfg, + config::{ActiveReason, Cfg}, currentprocess::process, dist::dist::PartialToolchainDesc, env_var, install, @@ -37,6 +38,42 @@ pub(crate) struct Toolchain<'a> { } impl<'a> Toolchain<'a> { + pub(crate) async fn from_local( + toolchain_name: &LocalToolchainName, + install_if_missing: bool, + cfg: &'a Cfg, + ) -> anyhow::Result> { + match toolchain_name { + LocalToolchainName::Named(ToolchainName::Official(desc)) => { + match DistributableToolchain::new(cfg, desc.clone()) { + Err(RustupError::ToolchainNotInstalled(_)) => { + if install_if_missing { + DistributableToolchain::install( + cfg, + desc, + &[], + &[], + cfg.get_profile()?, + true, + ) + .await?; + } + } + o => { + o?; + } + } + } + n => { + if !Self::exists(cfg, n)? { + return Err(RustupError::ToolchainNotInstallable(n.to_string()).into()); + } + } + } + + Ok(Self::new(cfg, toolchain_name.clone())?) + } + pub(crate) async fn from_partial( toolchain: Option, cfg: &'a Cfg, @@ -50,6 +87,44 @@ impl<'a> Toolchain<'a> { } } + /// Calls Toolchain::new(), but augments the error message with more context + /// from the ActiveReason if the toolchain isn't installed. + pub(crate) fn with_reason( + cfg: &'a Cfg, + name: LocalToolchainName, + reason: &ActiveReason, + ) -> anyhow::Result { + match Self::new(cfg, name.clone()) { + Err(RustupError::ToolchainNotInstalled(_)) => (), + result => { + return Ok(result?); + } + } + + 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"))) + } + pub(crate) fn new(cfg: &'a Cfg, name: LocalToolchainName) -> Result { let path = cfg.toolchain_path(&name); if !Toolchain::exists(cfg, &name)? { @@ -250,11 +325,56 @@ impl<'a> Toolchain<'a> { } } + pub(crate) fn command(&self, binary: &str) -> anyhow::Result { + // Should push the cargo fallback into a custom toolchain type? And then + // perhaps a trait that create command layers on? + if !matches!( + self.name(), + LocalToolchainName::Named(ToolchainName::Official(_)) + ) { + if let Some(cmd) = self.maybe_do_cargo_fallback(binary)? { + return Ok(cmd); + } + } + + self.create_command(binary) + } + + // Custom toolchains don't have cargo, so here we detect that situation and + // try to find a different cargo. + pub(crate) fn maybe_do_cargo_fallback(&self, binary: &str) -> anyhow::Result> { + if binary != "cargo" && binary != "cargo.exe" { + return Ok(None); + } + + let cargo_path = self.binary_file("cargo"); + + // breadcrumb in case of regression: we used to get the cargo path and + // cargo.exe path separately, not using the binary_file helper. This may + // matter if calling a binary with some personality that allows .exe and + // not .exe to coexist (e.g. wine) - but that's not something we aim to + // support : the host should always be correct. + if cargo_path.exists() { + return Ok(None); + } + + let default_host_triple = self.cfg.get_default_host_triple()?; + // XXX: This could actually consider all installed distributable + // toolchains in principle. + for fallback in ["nightly", "beta", "stable"] { + let resolved = + PartialToolchainDesc::from_str(fallback)?.resolve(&default_host_triple)?; + if let Ok(fallback) = DistributableToolchain::new(self.cfg, resolved) { + let cmd = fallback.create_fallback_command("cargo", self)?; + return Ok(Some(cmd)); + } + } + + Ok(None) + } + #[cfg_attr(feature="otel", tracing::instrument(err,fields(binary, recursion=process().var("RUST_RECURSION_COUNT").ok())))] - pub fn create_command + Debug>( - &self, - binary: T, - ) -> Result { + fn create_command + Debug>(&self, binary: T) -> Result { // Create the path to this binary within the current toolchain sysroot let binary = if let Some(binary_str) = binary.as_ref().to_str() { if binary_str.to_lowercase().ends_with(EXE_SUFFIX) { diff --git a/src/utils/utils.rs b/src/utils/utils.rs index c93ee899d2..5dc3d53b60 100644 --- a/src/utils/utils.rs +++ b/src/utils/utils.rs @@ -491,23 +491,10 @@ pub(crate) fn make_executable(path: &Path) -> Result<()> { inner(path) } -pub fn current_dir() -> Result { - process() - .current_dir() - .context(RustupError::LocatingWorkingDir) -} - pub fn current_exe() -> Result { env::current_exe().context(RustupError::LocatingWorkingDir) } -pub(crate) fn to_absolute>(path: P) -> Result { - current_dir().map(|mut v| { - v.push(path); - v - }) -} - pub(crate) fn home_dir() -> Option { home::home_dir_with_env(&home_process()) } @@ -516,29 +503,6 @@ pub(crate) fn cargo_home() -> Result { home::cargo_home_with_env(&home_process()).context("failed to determine cargo home") } -// Creates a ~/.rustup folder -pub(crate) fn create_rustup_home() -> Result<()> { - // If RUSTUP_HOME is set then don't make any assumptions about where it's - // ok to put ~/.rustup - if process().var_os("RUSTUP_HOME").is_some() { - return Ok(()); - } - - let home = rustup_home_in_user_dir()?; - fs::create_dir_all(home).context("unable to create ~/.rustup")?; - - Ok(()) -} - -fn dot_dir(name: &str) -> Option { - home_dir().map(|p| p.join(name)) -} - -fn rustup_home_in_user_dir() -> Result { - // XXX: This error message seems wrong/bogus. - dot_dir(".rustup").ok_or_else(|| anyhow::anyhow!("couldn't find value of RUSTUP_HOME")) -} - pub(crate) fn rustup_home() -> Result { home::rustup_home_with_env(&home_process()).context("failed to determine rustup home dir") }