diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index ba4f0f8f576..40389e7ae7b 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -339,6 +339,16 @@ impl<'cfg> Compilation<'cfg> { ) .env("CARGO_PKG_AUTHORS", &pkg.authors().join(":")) .cwd(pkg.root()); + + if self.config.cli_unstable().configurable_env { + // Apply any environment variables from the config + for (key, value) in self.config.env_config()?.iter() { + if value.is_force() || cmd.get_env(&key).is_none() { + cmd.env(&key, value.resolve(&self.config)); + } + } + } + Ok(cmd) } } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 494e3399300..3b6c4e9d7d3 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -444,6 +444,7 @@ pub struct CliUnstable { pub weak_dep_features: bool, pub extra_link_arg: bool, pub credential_process: bool, + pub configurable_env: bool, } const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \ @@ -598,6 +599,7 @@ impl CliUnstable { "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, + "configurable-env" => self.configurable_env = parse_empty(k, v)?, "features" => { // For now this is still allowed (there are still some // unstable options like "compare"). This should be removed at diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs index 7cea1bcecc3..a0dc0963c17 100644 --- a/src/cargo/util/config/mod.rs +++ b/src/cargo/util/config/mod.rs @@ -49,10 +49,12 @@ //! translate from `ConfigValue` and environment variables to the caller's //! desired type. +use std::borrow::Cow; use std::cell::{RefCell, RefMut}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; use std::env; +use std::ffi::OsStr; use std::fmt; use std::fs::{self, File}; use std::io::prelude::*; @@ -181,6 +183,7 @@ pub struct Config { target_cfgs: LazyCell>, doc_extern_map: LazyCell, progress_config: ProgressConfig, + env_config: LazyCell, } impl Config { @@ -264,6 +267,7 @@ impl Config { target_cfgs: LazyCell::new(), doc_extern_map: LazyCell::new(), progress_config: ProgressConfig::default(), + env_config: LazyCell::new(), } } @@ -1244,6 +1248,11 @@ impl Config { &self.progress_config } + pub fn env_config(&self) -> CargoResult<&EnvConfig> { + self.env_config + .try_borrow_with(|| self.get::("env")) + } + /// This is used to validate the `term` table has valid syntax. /// /// This is necessary because loading the term settings happens very @@ -1953,6 +1962,54 @@ where deserializer.deserialize_option(ProgressVisitor) } +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum EnvConfigValueInner { + Simple(String), + WithOptions { + value: String, + #[serde(default)] + force: bool, + #[serde(default)] + relative: bool, + }, +} + +#[derive(Debug, Deserialize)] +#[serde(transparent)] +pub struct EnvConfigValue { + inner: Value, +} + +impl EnvConfigValue { + pub fn is_force(&self) -> bool { + match self.inner.val { + EnvConfigValueInner::Simple(_) => false, + EnvConfigValueInner::WithOptions { force, .. } => force, + } + } + + pub fn resolve<'a>(&'a self, config: &Config) -> Cow<'a, OsStr> { + match self.inner.val { + EnvConfigValueInner::Simple(ref s) => Cow::Borrowed(OsStr::new(s.as_str())), + EnvConfigValueInner::WithOptions { + ref value, + relative, + .. + } => { + if relative { + let p = self.inner.definition.root(config).join(&value); + Cow::Owned(p.into_os_string()) + } else { + Cow::Borrowed(OsStr::new(value.as_str())) + } + } + } + } +} + +pub type EnvConfig = HashMap; + /// A type to deserialize a list of strings from a toml file. /// /// Supports deserializing either a whitespace-separated list of arguments in a diff --git a/tests/testsuite/cargo_env_config.rs b/tests/testsuite/cargo_env_config.rs new file mode 100644 index 00000000000..83ebdbb18f6 --- /dev/null +++ b/tests/testsuite/cargo_env_config.rs @@ -0,0 +1,133 @@ +//! Tests for `[env]` config. + +use cargo_test_support::{basic_bin_manifest, project}; + +#[cargo_test] +fn env_basic() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file( + "src/main.rs", + r#" + use std::env; + fn main() { + println!( "compile-time:{}", env!("ENV_TEST_1233") ); + println!( "run-time:{}", env::var("ENV_TEST_1233").unwrap()); + } + "#, + ) + .file( + ".cargo/config", + r#" + [env] + ENV_TEST_1233 = "Hello" + "#, + ) + .build(); + + p.cargo("run -Zconfigurable-env") + .masquerade_as_nightly_cargo() + .with_stdout_contains("compile-time:Hello") + .with_stdout_contains("run-time:Hello") + .run(); +} + +#[cargo_test] +fn env_invalid() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file( + "src/main.rs", + r#" + fn main() { + } + "#, + ) + .file( + ".cargo/config", + r#" + [env] + ENV_TEST_BOOL = false + "#, + ) + .build(); + + p.cargo("build -Zconfigurable-env") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr_contains("[..]could not load config key `env.ENV_TEST_BOOL`") + .run(); +} + +#[cargo_test] +fn env_force() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file( + "src/main.rs", + r#" + use std::env; + fn main() { + println!( "ENV_TEST_FORCED:{}", env!("ENV_TEST_FORCED") ); + println!( "ENV_TEST_UNFORCED:{}", env!("ENV_TEST_UNFORCED") ); + println!( "ENV_TEST_UNFORCED_DEFAULT:{}", env!("ENV_TEST_UNFORCED_DEFAULT") ); + } + "#, + ) + .file( + ".cargo/config", + r#" + [env] + ENV_TEST_UNFORCED_DEFAULT = "from-config" + ENV_TEST_UNFORCED = { value = "from-config", force = false } + ENV_TEST_FORCED = { value = "from-config", force = true } + "#, + ) + .build(); + + p.cargo("run -Zconfigurable-env") + .masquerade_as_nightly_cargo() + .env("ENV_TEST_FORCED", "from-env") + .env("ENV_TEST_UNFORCED", "from-env") + .env("ENV_TEST_UNFORCED_DEFAULT", "from-env") + .with_stdout_contains("ENV_TEST_FORCED:from-config") + .with_stdout_contains("ENV_TEST_UNFORCED:from-env") + .with_stdout_contains("ENV_TEST_UNFORCED_DEFAULT:from-env") + .run(); +} + +#[cargo_test] +fn env_relative() { + let p = project() + .file("Cargo.toml", &basic_bin_manifest("foo2")) + .file( + "src/main.rs", + r#" + use std::env; + use std::path::Path; + fn main() { + println!( "ENV_TEST_REGULAR:{}", env!("ENV_TEST_REGULAR") ); + println!( "ENV_TEST_REGULAR_DEFAULT:{}", env!("ENV_TEST_REGULAR_DEFAULT") ); + println!( "ENV_TEST_RELATIVE:{}", env!("ENV_TEST_RELATIVE") ); + + assert!( Path::new(env!("ENV_TEST_RELATIVE")).is_absolute() ); + assert!( !Path::new(env!("ENV_TEST_REGULAR")).is_absolute() ); + assert!( !Path::new(env!("ENV_TEST_REGULAR_DEFAULT")).is_absolute() ); + } + "#, + ) + .file( + ".cargo/config", + r#" + [env] + ENV_TEST_REGULAR = { value = "Cargo.toml", relative = false } + ENV_TEST_REGULAR_DEFAULT = "Cargo.toml" + ENV_TEST_RELATIVE = { value = "Cargo.toml", relative = true } + "#, + ) + .build(); + + p.cargo("run -Zconfigurable-env") + .masquerade_as_nightly_cargo() + .run(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 4df5679189b..3f4c9653eb7 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -24,6 +24,7 @@ mod build_script_extra_link_arg; mod cache_messages; mod cargo_alias_config; mod cargo_command; +mod cargo_env_config; mod cargo_features; mod cargo_targets; mod cfg;