Auto merge of #9175 - wickerwaka:env-section, r=alexcrichton

Add support for [env] section in .cargo/config.toml

This adds support for an `[env]` section in the config.toml files. Environment variables set in this section will be applied to the environment of any processes executed by cargo.

This is implemented to follow the recommendations in https://github.com/rust-lang/cargo/pull/8839#issuecomment-725678657

Variables have optional `force` and `relative` flags. `force` means the variable can override an existing environment variable. `relative` means the variable represents a path relative to the location of the directory that contains the `.cargo/` directory that contains the `config.toml` file. A relative variable will have an absolute path prepended to it before setting it in the environment.

```
[env]
FOOBAR = "Apple"
PATH_TO_SOME_TOOL = { value = "bin/tool", relative = true }
USERNAME = { value = "test_user", force = true }
```

Fixes #4121
This commit is contained in:
bors 2021-02-23 15:22:59 +00:00
commit 4742e82957
5 changed files with 203 additions and 0 deletions

View file

@ -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)
}
}

View file

@ -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

View file

@ -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<Vec<(String, TargetCfgConfig)>>,
doc_extern_map: LazyCell<RustdocExternMap>,
progress_config: ProgressConfig,
env_config: LazyCell<EnvConfig>,
}
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::<EnvConfig>("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<EnvConfigValueInner>,
}
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<String, EnvConfigValue>;
/// A type to deserialize a list of strings from a toml file.
///
/// Supports deserializing either a whitespace-separated list of arguments in a

View file

@ -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();
}

View file

@ -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;