about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_borrowck/src/nll.rs7
-rw-r--r--compiler/rustc_data_structures/src/stable_hasher.rs2
-rw-r--r--compiler/rustc_interface/src/passes.rs28
-rw-r--r--compiler/rustc_lint/src/non_local_def.rs6
-rw-r--r--compiler/rustc_middle/src/query/erase.rs9
-rw-r--r--compiler/rustc_middle/src/query/keys.rs10
-rw-r--r--compiler/rustc_middle/src/query/mod.rs16
-rw-r--r--compiler/rustc_middle/src/ty/context.rs11
-rw-r--r--tests/incremental/env/env_macro.rs18
-rw-r--r--tests/incremental/env/option_env_macro.rs18
10 files changed, 118 insertions, 7 deletions
diff --git a/compiler/rustc_borrowck/src/nll.rs b/compiler/rustc_borrowck/src/nll.rs
index d0bd364425a..8e7b6f083ac 100644
--- a/compiler/rustc_borrowck/src/nll.rs
+++ b/compiler/rustc_borrowck/src/nll.rs
@@ -1,9 +1,9 @@
 //! The entry point of the NLL borrow checker.
 
+use std::io;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::str::FromStr;
-use std::{env, io};
 
 use polonius_engine::{Algorithm, Output};
 use rustc_index::IndexSlice;
@@ -162,9 +162,8 @@ pub(crate) fn compute_regions<'a, 'tcx>(
         }
 
         if polonius_output {
-            let algorithm =
-                env::var("POLONIUS_ALGORITHM").unwrap_or_else(|_| String::from("Hybrid"));
-            let algorithm = Algorithm::from_str(&algorithm).unwrap();
+            let algorithm = infcx.tcx.env_var("POLONIUS_ALGORITHM").unwrap_or("Hybrid");
+            let algorithm = Algorithm::from_str(algorithm).unwrap();
             debug!("compute_regions: using polonius algorithm {:?}", algorithm);
             let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis");
             Some(Box::new(Output::compute(polonius_facts, algorithm, false)))
diff --git a/compiler/rustc_data_structures/src/stable_hasher.rs b/compiler/rustc_data_structures/src/stable_hasher.rs
index ffbe54d6206..3a64c924cc2 100644
--- a/compiler/rustc_data_structures/src/stable_hasher.rs
+++ b/compiler/rustc_data_structures/src/stable_hasher.rs
@@ -564,6 +564,8 @@ where
     }
 }
 
+impl_stable_traits_for_trivial_type!(::std::ffi::OsStr);
+
 impl_stable_traits_for_trivial_type!(::std::path::Path);
 impl_stable_traits_for_trivial_type!(::std::path::PathBuf);
 
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 8be7ba7455e..2440f0639c8 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -1,5 +1,5 @@
 use std::any::Any;
-use std::ffi::OsString;
+use std::ffi::{OsStr, OsString};
 use std::io::{self, BufWriter, Write};
 use std::path::{Path, PathBuf};
 use std::sync::{Arc, LazyLock, OnceLock};
@@ -361,6 +361,31 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) {
     )
 }
 
+fn env_var_os<'tcx>(tcx: TyCtxt<'tcx>, key: &'tcx OsStr) -> Option<&'tcx OsStr> {
+    let value = env::var_os(key);
+
+    let value_tcx = value.as_ref().map(|value| {
+        let encoded_bytes = tcx.arena.alloc_slice(value.as_encoded_bytes());
+        debug_assert_eq!(value.as_encoded_bytes(), encoded_bytes);
+        // SAFETY: The bytes came from `as_encoded_bytes`, and we assume that
+        // `alloc_slice` is implemented correctly, and passes the same bytes
+        // back (debug asserted above).
+        unsafe { OsStr::from_encoded_bytes_unchecked(encoded_bytes) }
+    });
+
+    // Also add the variable to Cargo's dependency tracking
+    //
+    // NOTE: This only works for passes run before `write_dep_info`. See that
+    // for extension points for configuring environment variables to be
+    // properly change-tracked.
+    tcx.sess.psess.env_depinfo.borrow_mut().insert((
+        Symbol::intern(&key.to_string_lossy()),
+        value.as_ref().and_then(|value| value.to_str()).map(|value| Symbol::intern(&value)),
+    ));
+
+    value_tcx
+}
+
 // Returns all the paths that correspond to generated files.
 fn generated_output_paths(
     tcx: TyCtxt<'_>,
@@ -725,6 +750,7 @@ pub static DEFAULT_QUERY_PROVIDERS: LazyLock<Providers> = LazyLock::new(|| {
         |tcx, _| tcx.arena.alloc_from_iter(tcx.resolutions(()).stripped_cfg_items.steal());
     providers.resolutions = |tcx, ()| tcx.resolver_for_lowering_raw(()).1;
     providers.early_lint_checks = early_lint_checks;
+    providers.env_var_os = env_var_os;
     limits::provide(providers);
     proc_macro_decls::provide(providers);
     rustc_const_eval::provide(providers);
diff --git a/compiler/rustc_lint/src/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs
index f8e0a94f9ec..9ed11d9cc82 100644
--- a/compiler/rustc_lint/src/non_local_def.rs
+++ b/compiler/rustc_lint/src/non_local_def.rs
@@ -104,8 +104,10 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions {
         // determining if we are in a doctest context can't currently be determined
         // by the code itself (there are no specific attributes), but fortunately rustdoc
         // sets a perma-unstable env var for libtest so we just reuse that for now
-        let is_at_toplevel_doctest =
-            || self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok();
+        let is_at_toplevel_doctest = || {
+            self.body_depth == 2
+                && cx.tcx.env_var_os("UNSTABLE_RUSTDOC_TEST_PATH".as_ref()).is_some()
+        };
 
         match item.kind {
             ItemKind::Impl(impl_) => {
diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs
index 7bbaa0496d5..6c6b9a5510c 100644
--- a/compiler/rustc_middle/src/query/erase.rs
+++ b/compiler/rustc_middle/src/query/erase.rs
@@ -1,3 +1,4 @@
+use std::ffi::OsStr;
 use std::intrinsics::transmute_unchecked;
 use std::mem::MaybeUninit;
 
@@ -67,6 +68,10 @@ impl<T> EraseType for &'_ [T] {
     type Result = [u8; size_of::<&'static [()]>()];
 }
 
+impl EraseType for &'_ OsStr {
+    type Result = [u8; size_of::<&'static OsStr>()];
+}
+
 impl<T> EraseType for &'_ ty::List<T> {
     type Result = [u8; size_of::<&'static ty::List<()>>()];
 }
@@ -174,6 +179,10 @@ impl<T> EraseType for Option<&'_ [T]> {
     type Result = [u8; size_of::<Option<&'static [()]>>()];
 }
 
+impl EraseType for Option<&'_ OsStr> {
+    type Result = [u8; size_of::<Option<&'static OsStr>>()];
+}
+
 impl EraseType for Option<mir::DestructuredConstant<'_>> {
     type Result = [u8; size_of::<Option<mir::DestructuredConstant<'static>>>()];
 }
diff --git a/compiler/rustc_middle/src/query/keys.rs b/compiler/rustc_middle/src/query/keys.rs
index 98314b5abfd..c382bcd726f 100644
--- a/compiler/rustc_middle/src/query/keys.rs
+++ b/compiler/rustc_middle/src/query/keys.rs
@@ -1,5 +1,7 @@
 //! Defines the set of legal keys that can be used in queries.
 
+use std::ffi::OsStr;
+
 use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId, LocalModDefId, ModDefId};
 use rustc_hir::hir_id::{HirId, OwnerId};
 use rustc_query_system::dep_graph::DepNodeIndex;
@@ -498,6 +500,14 @@ impl Key for Option<Symbol> {
     }
 }
 
+impl<'tcx> Key for &'tcx OsStr {
+    type Cache<V> = DefaultCache<Self, V>;
+
+    fn default_span(&self, _tcx: TyCtxt<'_>) -> Span {
+        DUMMY_SP
+    }
+}
+
 /// Canonical query goals correspond to abstract trait operations that
 /// are not tied to any crate in particular.
 impl<'tcx, T: Clone> Key for CanonicalQueryInput<'tcx, T> {
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 527c18addbe..850de3f0c2a 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -6,6 +6,7 @@
 
 #![allow(unused_parens)]
 
+use std::ffi::OsStr;
 use std::mem;
 use std::path::PathBuf;
 use std::sync::Arc;
@@ -119,6 +120,21 @@ rustc_queries! {
         desc { "perform lints prior to AST lowering" }
     }
 
+    /// Tracked access to environment variables.
+    ///
+    /// Useful for the implementation of `std::env!`, `proc-macro`s change
+    /// detection and other changes in the compiler's behaviour that is easier
+    /// to control with an environment variable than a flag.
+    ///
+    /// NOTE: This currently does not work with dependency info in the
+    /// analysis, codegen and linking passes, place extra code at the top of
+    /// `rustc_interface::passes::write_dep_info` to make that work.
+    query env_var_os(key: &'tcx OsStr) -> Option<&'tcx OsStr> {
+        // Environment variables are global state
+        eval_always
+        desc { "get the value of an environment variable" }
+    }
+
     query resolutions(_: ()) -> &'tcx ty::ResolverGlobalCtxt {
         no_hash
         desc { "getting the resolver outputs" }
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 834d1f2a4a8..08d4c1f9cf2 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -7,6 +7,8 @@ pub mod tls;
 use std::assert_matches::{assert_matches, debug_assert_matches};
 use std::borrow::Borrow;
 use std::cmp::Ordering;
+use std::env::VarError;
+use std::ffi::OsStr;
 use std::hash::{Hash, Hasher};
 use std::marker::PhantomData;
 use std::ops::{Bound, Deref};
@@ -1883,6 +1885,15 @@ impl<'tcx> TyCtxt<'tcx> {
         }
         None
     }
+
+    /// Helper to get a tracked environment variable via. [`TyCtxt::env_var_os`] and converting to
+    /// UTF-8 like [`std::env::var`].
+    pub fn env_var<K: ?Sized + AsRef<OsStr>>(self, key: &'tcx K) -> Result<&'tcx str, VarError> {
+        match self.env_var_os(key.as_ref()) {
+            Some(value) => value.to_str().ok_or_else(|| VarError::NotUnicode(value.to_os_string())),
+            None => Err(VarError::NotPresent),
+        }
+    }
 }
 
 impl<'tcx> TyCtxtAt<'tcx> {
diff --git a/tests/incremental/env/env_macro.rs b/tests/incremental/env/env_macro.rs
new file mode 100644
index 00000000000..0c026328874
--- /dev/null
+++ b/tests/incremental/env/env_macro.rs
@@ -0,0 +1,18 @@
+// Check that changes to environment variables are propagated to `env!`.
+//
+// This test is intentionally written to not use any `#[cfg(rpass*)]`, to
+// _really_ test that we re-compile if the environment variable changes.
+
+//@ revisions: cfail1 rpass2 rpass3 cfail4
+//@ [cfail1]unset-rustc-env:EXAMPLE_ENV
+//@ [rpass2]rustc-env:EXAMPLE_ENV=one
+//@ [rpass2]exec-env:EXAMPLE_ENV=one
+//@ [rpass3]rustc-env:EXAMPLE_ENV=two
+//@ [rpass3]exec-env:EXAMPLE_ENV=two
+//@ [cfail4]unset-rustc-env:EXAMPLE_ENV
+
+fn main() {
+    assert_eq!(env!("EXAMPLE_ENV"), std::env::var("EXAMPLE_ENV").unwrap());
+    //[cfail1]~^ ERROR environment variable `EXAMPLE_ENV` not defined at compile time
+    //[cfail4]~^^ ERROR environment variable `EXAMPLE_ENV` not defined at compile time
+}
diff --git a/tests/incremental/env/option_env_macro.rs b/tests/incremental/env/option_env_macro.rs
new file mode 100644
index 00000000000..44c3bfd69e0
--- /dev/null
+++ b/tests/incremental/env/option_env_macro.rs
@@ -0,0 +1,18 @@
+// Check that changes to environment variables are propagated to `option_env!`.
+//
+// This test is intentionally written to not use any `#[cfg(rpass*)]`, to
+// _really_ test that we re-compile if the environment variable changes.
+
+//@ revisions: rpass1 rpass2 rpass3 rpass4
+//@ [rpass1]unset-rustc-env:EXAMPLE_ENV
+//@ [rpass1]unset-exec-env:EXAMPLE_ENV
+//@ [rpass2]rustc-env:EXAMPLE_ENV=one
+//@ [rpass2]exec-env:EXAMPLE_ENV=one
+//@ [rpass3]rustc-env:EXAMPLE_ENV=two
+//@ [rpass3]exec-env:EXAMPLE_ENV=two
+//@ [rpass4]unset-rustc-env:EXAMPLE_ENV
+//@ [rpass4]unset-exec-env:EXAMPLE_ENV
+
+fn main() {
+    assert_eq!(option_env!("EXAMPLE_ENV"), std::env::var("EXAMPLE_ENV").ok().as_deref());
+}