Skip to content

Add environment variable query #130883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions compiler/rustc_borrowck/src/nll.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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)))
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_data_structures/src/stable_hasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
28 changes: 27 additions & 1 deletion compiler/rustc_interface/src/passes.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<'_>,
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_lint/src/non_local_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_) => {
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_middle/src/query/erase.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::ffi::OsStr;
use std::intrinsics::transmute_unchecked;
use std::mem::MaybeUninit;

Expand Down Expand Up @@ -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<()>>()];
}
Expand Down Expand Up @@ -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>>>()];
}
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_middle/src/query/keys.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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> {
Expand Down
16 changes: 16 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#![allow(unused_parens)]

use std::ffi::OsStr;
use std::mem;
use std::path::PathBuf;
use std::sync::Arc;
Expand Down Expand Up @@ -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" }
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -1882,6 +1884,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> {
Expand Down
18 changes: 18 additions & 0 deletions tests/incremental/env/env_macro.rs
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions tests/incremental/env/option_env_macro.rs
Original file line number Diff line number Diff line change
@@ -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());
}
Loading