diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index ed4b04dc228..3082abecea3 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -21,6 +21,8 @@ use cargo_util::{paths, ProcessBuilder}; use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; +use std::collections::BTreeMap; +use std::ffi::OsString; use std::path::{Path, PathBuf}; use std::str::{self, FromStr}; @@ -588,6 +590,10 @@ impl TargetInfo { .iter() .any(|sup| sup.as_str() == split.as_str()) } + + pub fn get_target_envs(&self) -> &BTreeMap> { + return self.crate_type_process.get_envs(); + } } /// Takes rustc output (using specialized command line args), and calculates the file prefix and diff --git a/src/cargo/core/compiler/fingerprint/mod.rs b/src/cargo/core/compiler/fingerprint/mod.rs index 9d9eb4e4da4..8e972c8d608 100644 --- a/src/cargo/core/compiler/fingerprint/mod.rs +++ b/src/cargo/core/compiler/fingerprint/mod.rs @@ -357,7 +357,9 @@ mod dirty_reason; use std::collections::hash_map::{Entry, HashMap}; +use std::collections::BTreeMap; use std::env; +use std::ffi::OsString; use std::hash::{self, Hash, Hasher}; use std::io; use std::path::{Path, PathBuf}; @@ -768,14 +770,35 @@ pub enum StaleItem { impl LocalFingerprint { /// Read the environment variable of the given env `key`, and creates a new /// [`LocalFingerprint::RerunIfEnvChanged`] for it. - /// - // TODO: This is allowed at this moment. Should figure out if it makes - // sense if permitting to read env from the config system. #[allow(clippy::disallowed_methods)] - fn from_env>(key: K) -> LocalFingerprint { - let key = key.as_ref(); + fn from_env>( + key: K, + envs: &BTreeMap>, + ) -> LocalFingerprint { + fn get_envs_case_insensitive( + key: &str, + envs: &BTreeMap>, + ) -> Option { + let upper_case_key: String = key.to_uppercase(); + for (k, v) in envs { + if k.to_uppercase().eq(&upper_case_key) { + return v.to_owned(); + } + } + None + } + + let key: &str = key.as_ref(); let var = key.to_owned(); - let val = env::var(key).ok(); + + let val: Option = if cfg!(windows) { + get_envs_case_insensitive(key, envs) + } else { + envs.get(key).and_then(|v| v.to_owned()) + } + .or(env::var_os(key)) + .and_then(|os_str| os_str.into_string().ok()); + LocalFingerprint::RerunIfEnvChanged { var, val } } @@ -1608,6 +1631,12 @@ fn build_script_local_fingerprints( bool, ) { assert!(unit.mode.is_run_custom_build()); + let envs = build_runner + .bcx + .target_data + .info(unit.kind) + .get_target_envs() + .clone(); // First up, if this build script is entirely overridden, then we just // return the hash of what we overrode it with. This is the easy case! if let Some(fingerprint) = build_script_override_fingerprint(build_runner, unit) { @@ -1660,7 +1689,12 @@ fn build_script_local_fingerprints( // Ok so now we're in "new mode" where we can have files listed as // dependencies as well as env vars listed as dependencies. Process // them all here. - Ok(Some(local_fingerprints_deps(deps, &target_dir, &pkg_root))) + Ok(Some(local_fingerprints_deps( + deps, + &target_dir, + &pkg_root, + &envs, + ))) }; // Note that `false` == "not overridden" @@ -1695,6 +1729,7 @@ fn local_fingerprints_deps( deps: &BuildDeps, target_root: &Path, pkg_root: &Path, + envs: &BTreeMap>, ) -> Vec { debug!("new local fingerprints deps {:?}", pkg_root); let mut local = Vec::new(); @@ -1719,7 +1754,7 @@ fn local_fingerprints_deps( local.extend( deps.rerun_if_env_changed .iter() - .map(LocalFingerprint::from_env), + .map(|v| LocalFingerprint::from_env(v, &envs)), ); local diff --git a/src/doc/src/reference/build-scripts.md b/src/doc/src/reference/build-scripts.md index 3837ebdc30d..d3f5b207d54 100644 --- a/src/doc/src/reference/build-scripts.md +++ b/src/doc/src/reference/build-scripts.md @@ -387,10 +387,8 @@ The `rerun-if-env-changed` instruction tells Cargo to re-run the build script if the value of an environment variable of the given name has changed. Note that the environment variables here are intended for global environment -variables like `CC` and such, it is not possible to use this for environment -variables like `TARGET` that [Cargo sets for build scripts][build-env]. The -environment variables in use are those received by `cargo` invocations, not -those received by the executable of the build script. +variables like `CC` and such, it is not necessary to use this for environment +variables like `TARGET` that Cargo sets. ## The `links` Manifest Key diff --git a/tests/testsuite/build_script_env.rs b/tests/testsuite/build_script_env.rs index c22bb4f6ac5..0f923500046 100644 --- a/tests/testsuite/build_script_env.rs +++ b/tests/testsuite/build_script_env.rs @@ -5,6 +5,148 @@ use cargo_test_support::project; use cargo_test_support::sleep_ms; use cargo_test_support::str; +#[cargo_test] +fn rerun_if_env_changes_config() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + r#" + [env] + FOO = "good" + "#, + ) + .file( + "build.rs", + r#" +fn main() { + println!("cargo:rerun-if-env-changed=FOO"); + if let Ok(foo) = std::env::var("FOO") { + assert!(&foo != "bad"); + } +} +"#, + ) + .build(); + + p.cargo("check") + .with_stderr_data(str![[r#" +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); + + p.change_file( + ".cargo/config.toml", + r#" + [env] + FOO = "bad" + "#, + ); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[ERROR] failed to run custom build command for `foo v0.1.0 ([ROOT]/foo)` +... +"#]]) + .run(); +} + +#[cfg(windows)] +#[cargo_test] +fn rerun_if_env_changes_config_in_windows() { + let p = project() + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config.toml", + r#" + [env] + Foo = "good" + "#, + ) + .file( + "build.rs", + r#" +fn main() { + println!("cargo:rerun-if-env-changed=foo"); + if let Ok(foo) = std::env::var("FOo") { + assert!(&foo != "bad"); + } +} +"#, + ) + .build(); + + p.cargo("check") + .with_stderr_data(str![[r#" +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); + + p.change_file( + ".cargo/config.toml", + r#" + [env] + FoO = "bad" + "#, + ); + + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[ERROR] failed to run custom build command for `foo v0.1.0 ([ROOT]/foo)` +... +"#]]) + .run(); +} + +#[cargo_test] +fn rerun_if_env_changes_cargo_set_env_variable() { + let p = project() + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#" + fn main() { + println!("cargo:rerun-if-env-changed=OUT_DIR"); + } + "#, + ) + .build(); + + p.cargo("check") + .with_stderr_data(str![[r#" +[COMPILING] foo v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); + + p.change_file( + ".cargo/config.toml", + r#" + [env] + OUT_DIR = "somedir" + "#, + ); + + p.cargo("check") + .with_stderr_data(str![[r#" +[COMPILING] foo v0.0.1 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); +} + #[cargo_test] fn rerun_if_env_changes() { let p = project()