diff --git a/compiler/rustc_builtin_macros/src/env.rs b/compiler/rustc_builtin_macros/src/env.rs index 8c2fa6ee95f33..d772642b88b26 100644 --- a/compiler/rustc_builtin_macros/src/env.rs +++ b/compiler/rustc_builtin_macros/src/env.rs @@ -13,6 +13,16 @@ use thin_vec::thin_vec; use crate::errors; +fn lookup_env<'cx>(cx: &'cx ExtCtxt<'_>, var: Symbol) -> Option { + let var = var.as_str(); + if let Some(value) = cx.sess.opts.logical_env.get(var) { + return Some(Symbol::intern(value)); + } + // If the environment variable was not defined with the `--env` option, we try to retrieve it + // from rustc's environment. + env::var(var).ok().as_deref().map(Symbol::intern) +} + pub fn expand_option_env<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, @@ -23,7 +33,7 @@ pub fn expand_option_env<'cx>( }; let sp = cx.with_def_site_ctxt(sp); - let value = env::var(var.as_str()).ok().as_deref().map(Symbol::intern); + let value = lookup_env(cx, var); cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value)); let e = match value { None => { @@ -77,7 +87,7 @@ pub fn expand_env<'cx>( }; let span = cx.with_def_site_ctxt(sp); - let value = env::var(var.as_str()).ok().as_deref().map(Symbol::intern); + let value = lookup_env(cx, var); cx.sess.parse_sess.env_depinfo.borrow_mut().insert((var, value)); let e = match value { None => { diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index 1ea3ab0d5ecbb..6a8427616e7ac 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -8,7 +8,7 @@ use crate::search_paths::SearchPath; use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; use crate::{lint, HashStableContext}; use crate::{EarlyErrorHandler, Session}; -use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey}; use rustc_errors::emitter::HumanReadableErrorType; use rustc_errors::{ColorConfig, DiagnosticArgValue, HandlerFlags, IntoDiagnosticArg}; @@ -1114,6 +1114,7 @@ impl Default for Options { pretty: None, working_dir: RealFileName::LocalPath(std::env::current_dir().unwrap()), color: ColorConfig::Auto, + logical_env: FxIndexMap::default(), } } } @@ -1810,6 +1811,7 @@ pub fn rustc_optgroups() -> Vec { "Remap source names in all output (compiler messages and output files)", "FROM=TO", ), + opt::multi("", "env", "Inject an environment variable", "VAR=VALUE"), ]); opts } @@ -2589,6 +2591,23 @@ fn parse_remap_path_prefix( mapping } +fn parse_logical_env( + handler: &mut EarlyErrorHandler, + matches: &getopts::Matches, +) -> FxIndexMap { + let mut vars = FxIndexMap::default(); + + for arg in matches.opt_strs("env") { + if let Some((name, val)) = arg.split_once('=') { + vars.insert(name.to_string(), val.to_string()); + } else { + handler.early_error(format!("`--env`: specify value for variable `{arg}`")); + } + } + + vars +} + // JUSTIFICATION: before wrapper fn is available #[allow(rustc::bad_opt_access)] pub fn build_session_options( @@ -2827,6 +2846,8 @@ pub fn build_session_options( handler.early_error("can't dump dependency graph without `-Z query-dep-graph`"); } + let logical_env = parse_logical_env(handler, matches); + // Try to find a directory containing the Rust `src`, for more details see // the doc comment on the `real_rust_source_base_dir` field. let tmp_buf; @@ -2907,6 +2928,7 @@ pub fn build_session_options( pretty, working_dir, color, + logical_env, } } @@ -3181,6 +3203,7 @@ pub(crate) mod dep_tracking { }; use crate::lint; use crate::utils::NativeLib; + use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::stable_hasher::Hash64; use rustc_errors::LanguageIdentifier; use rustc_feature::UnstableFeatures; @@ -3339,6 +3362,21 @@ pub(crate) mod dep_tracking { } } + impl DepTrackingHash for FxIndexMap { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.len(), hasher); + for (key, value) in self.iter() { + DepTrackingHash::hash(key, hasher, error_format, for_crate_hash); + DepTrackingHash::hash(value, hasher, error_format, for_crate_hash); + } + } + } + impl DepTrackingHash for OutputTypes { fn hash( &self, diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 7a6108bfbe24c..982cbe3bd9514 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -3,6 +3,7 @@ use crate::config::*; use crate::search_paths::SearchPath; use crate::utils::NativeLib; use crate::{lint, EarlyErrorHandler}; +use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::profiling::TimePassesFormat; use rustc_data_structures::stable_hasher::Hash64; use rustc_errors::ColorConfig; @@ -150,6 +151,9 @@ top_level_options!( target_triple: TargetTriple [TRACKED], + /// Effective logical environment used by `env!`/`option_env!` macros + logical_env: FxIndexMap [TRACKED], + test: bool [TRACKED], error_format: ErrorOutputType [UNTRACKED], diagnostic_width: Option [UNTRACKED], diff --git a/src/doc/unstable-book/src/compiler-flags/env.md b/src/doc/unstable-book/src/compiler-flags/env.md new file mode 100644 index 0000000000000..df0547dd24b6c --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/env.md @@ -0,0 +1,26 @@ +# `env` + +The tracking issue for this feature is: [#118372](https://github.com/rust-lang/rust/issues/118372). + +------------------------ + +This option flag allows to specify environment variables value at compile time to be +used by `env!` and `option_env!` macros. + +When retrieving an environment variable value, the one specified by `--env` will take +precedence. For example, if you want have `PATH=a` in your environment and pass: + +```bash +rustc --env PATH=env +``` + +Then you will have: + +```rust,no_run +assert_eq!(env!("PATH"), "env"); +``` + +Please note that on Windows, environment variables are case insensitive but case +preserving whereas `rustc`'s environment variables are case sensitive. For example, +having `Path` in your environment (case insensitive) is different than using +`rustc --env Path=...` (case sensitive). diff --git a/tests/ui/extenv/extenv-env-overload.rs b/tests/ui/extenv/extenv-env-overload.rs new file mode 100644 index 0000000000000..b82bb2fe9661a --- /dev/null +++ b/tests/ui/extenv/extenv-env-overload.rs @@ -0,0 +1,9 @@ +// run-pass +// rustc-env:MY_VAR=tadam +// compile-flags: --env MY_VAR=123abc -Zunstable-options + +// This test ensures that variables provided with `--env` take precedence over +// variables from environment. +fn main() { + assert_eq!(env!("MY_VAR"), "123abc"); +} diff --git a/tests/ui/extenv/extenv-env.rs b/tests/ui/extenv/extenv-env.rs new file mode 100644 index 0000000000000..9fda52b894111 --- /dev/null +++ b/tests/ui/extenv/extenv-env.rs @@ -0,0 +1,5 @@ +// compile-flags: --env FOO=123abc -Zunstable-options +// run-pass +fn main() { + assert_eq!(env!("FOO"), "123abc"); +} diff --git a/tests/ui/extenv/extenv-not-env.rs b/tests/ui/extenv/extenv-not-env.rs new file mode 100644 index 0000000000000..d6c4a43b0032a --- /dev/null +++ b/tests/ui/extenv/extenv-not-env.rs @@ -0,0 +1,7 @@ +// run-pass +// rustc-env:MY_ENV=/ +// Ensures that variables not defined through `--env` are still available. + +fn main() { + assert!(!env!("MY_ENV").is_empty()); +} diff --git a/tests/ui/feature-gates/env-flag.rs b/tests/ui/feature-gates/env-flag.rs new file mode 100644 index 0000000000000..9dfda2584fbc2 --- /dev/null +++ b/tests/ui/feature-gates/env-flag.rs @@ -0,0 +1,3 @@ +// compile-flags: --env A=B + +fn main() {} diff --git a/tests/ui/feature-gates/env-flag.stderr b/tests/ui/feature-gates/env-flag.stderr new file mode 100644 index 0000000000000..5cb18cef9fbc2 --- /dev/null +++ b/tests/ui/feature-gates/env-flag.stderr @@ -0,0 +1,2 @@ +error: the `-Z unstable-options` flag must also be passed to enable the flag `env` +