Skip to content

Commit

Permalink
Auto merge of #9175 - wickerwaka:env-section, r=alexcrichton
Browse files Browse the repository at this point in the history
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 #8839 (comment)

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
  • Loading branch information
bors committed Feb 23, 2021
2 parents 7442c14 + 05acf73 commit 4742e82
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/cargo/core/compiler/compilation.rs
Expand Up @@ -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)
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/cargo/core/features.rs
Expand Up @@ -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 \
Expand Down Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions src/cargo/util/config/mod.rs
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -264,6 +267,7 @@ impl Config {
target_cfgs: LazyCell::new(),
doc_extern_map: LazyCell::new(),
progress_config: ProgressConfig::default(),
env_config: LazyCell::new(),
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
133 changes: 133 additions & 0 deletions 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();
}
1 change: 1 addition & 0 deletions tests/testsuite/main.rs
Expand Up @@ -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;
Expand Down

0 comments on commit 4742e82

Please sign in to comment.