diff --git a/doc/user-guide/src/environment-variables.md b/doc/user-guide/src/environment-variables.md index a9669be902..a8240903dd 100644 --- a/doc/user-guide/src/environment-variables.md +++ b/doc/user-guide/src/environment-variables.md @@ -72,6 +72,8 @@ - `RUSTUP_CONCURRENT_DOWNLOADS` *unstable* (default: the number of components to download). Controls the number of downloads made concurrently. +- `RUSTUP_TOOLCHAIN_SOURCE`. Set by rustup to tell downstream tools how `RUSTUP_TOOLCHAIN` was determined. + [directive syntax]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives [dc]: https://docs.docker.com/storage/storagedriver/overlayfs-driver/#modifying-files-or-directories [override]: overrides.md diff --git a/src/cli/proxy_mode.rs b/src/cli/proxy_mode.rs index 1cb24f8252..67829a6058 100644 --- a/src/cli/proxy_mode.rs +++ b/src/cli/proxy_mode.rs @@ -5,8 +5,9 @@ use anyhow::Result; use crate::{ cli::{common::set_globals, job, self_update}, command::run_command_for_dir, + config::ActiveReason, process::Process, - toolchain::ResolvableLocalToolchainName, + toolchain::{ResolvableLocalToolchainName, maybe_set_env_source}, }; #[tracing::instrument(level = "trace", skip(process))] @@ -24,6 +25,7 @@ pub async fn main(arg0: &str, current_dir: PathBuf, process: &Process) -> Result .filter(|arg| arg.starts_with('+')) .map(|name| ResolvableLocalToolchainName::try_from(&name.as_ref()[1..])) .transpose()?; + let toolchain_specified = toolchain.is_some(); // Build command args now while we know whether or not to skip arg 1. let cmd_args: Vec<_> = process @@ -32,9 +34,10 @@ pub async fn main(arg0: &str, current_dir: PathBuf, process: &Process) -> Result .collect(); let cfg = set_globals(current_dir, true, process)?; - let cmd = cfg - .resolve_local_toolchain(toolchain) - .await? - .command(arg0)?; + let toolchain = cfg.resolve_local_toolchain(toolchain).await?; + let mut cmd = toolchain.command(arg0)?; + if toolchain_specified { + maybe_set_env_source(&mut cmd, || Some(ActiveReason::CommandLine)); + } run_command_for_dir(cmd, arg0, &cmd_args) } diff --git a/src/config.rs b/src/config.rs index b4c574fdc7..bab759ea0b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,6 +102,19 @@ pub(crate) enum ActiveReason { ToolchainFile(PathBuf), } +impl ActiveReason { + /// Format `ActiveReason` for setting the `RUSTUP_TOOLCHAIN_SOURCE` environment variable. + pub fn to_source(&self) -> &str { + match self { + Self::Default => "default", + Self::Environment => "env", + Self::CommandLine => "cli", + Self::OverrideDB(_) => "override", + Self::ToolchainFile(_) => "config", + } + } +} + impl Display for ActiveReason { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { match self { diff --git a/src/test/mock_bin_src.rs b/src/test/mock_bin_src.rs index d85b8002cd..5ca84dba18 100644 --- a/src/test/mock_bin_src.rs +++ b/src/test/mock_bin_src.rs @@ -106,6 +106,14 @@ fn main() { let mut out = io::stderr(); writeln!(out, "{}", std::env::current_exe().unwrap().display()).unwrap(); } + Some("--echo-rustup-toolchain-source") => { + let mut out = io::stderr(); + if let Ok(rustup_toolchain_source) = std::env::var("RUSTUP_TOOLCHAIN_SOURCE") { + writeln!(out, "{rustup_toolchain_source}").unwrap(); + } else { + panic!("RUSTUP_TOOLCHAIN_SOURCE environment variable not set"); + } + } arg => panic!("bad mock proxy commandline: {:?}", arg), } } diff --git a/src/toolchain.rs b/src/toolchain.rs index ad7183cfce..4599e39bb8 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -181,6 +181,14 @@ impl<'a> Toolchain<'a> { env_var::inc("RUST_RECURSION_COUNT", cmd, self.cfg.process); + maybe_set_env_source(cmd, || { + if let Ok(Some((_, reason))) = self.cfg.active_toolchain() { + Some(reason) + } else { + None + } + }); + cmd.env("RUSTUP_TOOLCHAIN", format!("{}", self.name)); cmd.env("RUSTUP_HOME", &self.cfg.rustup_dir); } @@ -605,3 +613,16 @@ impl<'a> Toolchain<'a> { .collect()) } } + +/// Set the `RUSTUP_TOOLCHAIN_SOURCE` environment variable if not already set. +/// +/// `RUSTUP_TOOLCHAIN_SOURCE` indicates how the toolchain was determined. The environment +/// variable could have been set in proxy_mode.rs, in which case it should not be changed. +pub(crate) fn maybe_set_env_source(cmd: &mut Command, f: impl FnOnce() -> Option) { + if env::var_os("RUSTUP_TOOLCHAIN_SOURCE").is_some() { + return; + } + if let Some(reason) = f() { + cmd.env("RUSTUP_TOOLCHAIN_SOURCE", reason.to_source()); + } +} diff --git a/tests/suite/cli_rustup.rs b/tests/suite/cli_rustup.rs index 7b1b0787dc..f59b92da95 100644 --- a/tests/suite/cli_rustup.rs +++ b/tests/suite/cli_rustup.rs @@ -3038,6 +3038,88 @@ error: no active toolchain .is_ok(); } +#[tokio::test] +async fn rustup_toolchain_source_cli() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect(&["rustup", "install", "nightly"]) + .await + .is_ok(); + cx.config + .expect(["cargo", "+nightly", "--echo-rustup-toolchain-source"]) + .await + .with_stderr(snapbox::str![[r#" +... +cli + +"#]]); +} + +#[tokio::test] +async fn rustup_toolchain_source_env() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect_with_env( + ["cargo", "--echo-rustup-toolchain-source"], + [("RUSTUP_TOOLCHAIN", "nightly")], + ) + .await + .with_stderr(snapbox::str![[r#" +... +env + +"#]]); +} + +#[tokio::test] +async fn rustup_toolchain_source_override() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect(["rustup", "override", "set", "nightly"]) + .await + .is_ok(); + cx.config + .expect(["cargo", "--echo-rustup-toolchain-source"]) + .await + .with_stderr(snapbox::str![[r#" +... +override + +"#]]); +} + +#[tokio::test] +async fn rustup_toolchain_source_config() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + let toolchain_file = cx.config.current_dir().join("rust-toolchain.toml"); + raw::write_file(&toolchain_file, "[toolchain]\nchannel='nightly'").unwrap(); + cx.config + .expect(["cargo", "--echo-rustup-toolchain-source"]) + .await + .with_stderr(snapbox::str![[r#" +... +config + +"#]]); +} + +#[tokio::test] +async fn rustup_toolchain_source_default() { + let cx = CliTestContext::new(Scenario::SimpleV2).await; + cx.config + .expect(&["rustup", "default", "stable"]) + .await + .is_ok(); + cx.config + .expect(["cargo", "--echo-rustup-toolchain-source"]) + .await + .with_stderr(snapbox::str![[r#" +... +default + +"#]]); +} + #[tokio::test] async fn directory_override_doesnt_need_to_exist_unless_it_is_selected() { let cx = CliTestContext::new(Scenario::SimpleV2).await;