Skip to content

Commit 95de3e9

Browse files
committed
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. ``` [env] FOOBAR = "Apple" PATH_TO_SOME_TOOL = { value = "bin/tool", relative = true } USERNAME = { value = "test_user", force = true } ```
1 parent af564b2 commit 95de3e9

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed

src/cargo/core/compiler/compilation.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,15 @@ impl<'cfg> Compilation<'cfg> {
339339
)
340340
.env("CARGO_PKG_AUTHORS", &pkg.authors().join(":"))
341341
.cwd(pkg.root());
342+
343+
344+
// Apply any environment variables from the config
345+
for (key, value) in self.config.env_config()?.iter() {
346+
if value.is_force() || cmd.get_env(&key).is_none() {
347+
cmd.env(&key, value.resolve(&self.config));
348+
}
349+
}
350+
342351
Ok(cmd)
343352
}
344353
}

src/cargo/util/config/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ use std::path::{Path, PathBuf};
6262
use std::str::FromStr;
6363
use std::sync::Once;
6464
use std::time::Instant;
65+
use std::borrow::Cow;
66+
use std::ffi::OsStr;
6567

6668
use anyhow::{anyhow, bail, format_err};
6769
use curl::easy::Easy;
@@ -181,6 +183,7 @@ pub struct Config {
181183
target_cfgs: LazyCell<Vec<(String, TargetCfgConfig)>>,
182184
doc_extern_map: LazyCell<RustdocExternMap>,
183185
progress_config: ProgressConfig,
186+
env_config: LazyCell<EnvConfig>,
184187
}
185188

186189
impl Config {
@@ -264,6 +267,7 @@ impl Config {
264267
target_cfgs: LazyCell::new(),
265268
doc_extern_map: LazyCell::new(),
266269
progress_config: ProgressConfig::default(),
270+
env_config: LazyCell::new(),
267271
}
268272
}
269273

@@ -1244,6 +1248,41 @@ impl Config {
12441248
&self.progress_config
12451249
}
12461250

1251+
/// Create an EnvConfigValue hashmap from the "env" table
1252+
fn get_env_config(&self) -> CargoResult<EnvConfig> {
1253+
// We cannot use pure serde handling for this. The Value<_> type does not
1254+
// work when parsing the env table to a hashmap. So iterator through each
1255+
// entry in the "env" table, determine it's type and then use `get` method
1256+
// to deserialize it.
1257+
let env_table = &self.get_table(&ConfigKey::from_str("env"))?;
1258+
let mut vars = EnvConfig::new();
1259+
1260+
if env_table.is_none() {
1261+
return Ok(vars);
1262+
}
1263+
1264+
let env_table = &env_table.as_ref().unwrap().val;
1265+
1266+
for (key, value) in env_table.iter() {
1267+
let full_key = format!("env.{}", key);
1268+
let e = match value {
1269+
ConfigValue::Table(..) => self.get::<EnvConfigValue>(&full_key)?,
1270+
_ => {
1271+
let v = self.get::<Value<String>>(&full_key)?;
1272+
EnvConfigValue::from_value(v)
1273+
}
1274+
};
1275+
vars.insert(key.clone(), e);
1276+
}
1277+
Ok(vars)
1278+
}
1279+
1280+
pub fn env_config(&self) -> CargoResult<&EnvConfig> {
1281+
self.env_config.try_borrow_with(|| {
1282+
self.get_env_config()
1283+
})
1284+
}
1285+
12471286
/// This is used to validate the `term` table has valid syntax.
12481287
///
12491288
/// This is necessary because loading the term settings happens very
@@ -1953,6 +1992,40 @@ where
19531992
deserializer.deserialize_option(ProgressVisitor)
19541993
}
19551994

1995+
#[derive(Debug, Deserialize)]
1996+
pub struct EnvConfigValue {
1997+
value: Value<String>,
1998+
#[serde(default)]
1999+
force: bool,
2000+
#[serde(default)]
2001+
relative: bool,
2002+
}
2003+
2004+
impl EnvConfigValue {
2005+
fn from_value(value: Value<String>) -> EnvConfigValue {
2006+
EnvConfigValue {
2007+
value,
2008+
force: false,
2009+
relative: false
2010+
}
2011+
}
2012+
2013+
pub fn is_force(&self) -> bool {
2014+
self.force
2015+
}
2016+
2017+
pub fn resolve<'a>(&'a self, config: &Config) -> Cow<'a, OsStr> {
2018+
if self.relative {
2019+
let p = self.value.definition.root(config).join(&self.value.val);
2020+
Cow::Owned(p.into_os_string())
2021+
} else {
2022+
Cow::Borrowed(OsStr::new(&self.value.val))
2023+
}
2024+
}
2025+
}
2026+
2027+
pub type EnvConfig = HashMap<String, EnvConfigValue>;
2028+
19562029
/// A type to deserialize a list of strings from a toml file.
19572030
///
19582031
/// Supports deserializing either a whitespace-separated list of arguments in a

tests/testsuite/cargo_env_config.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Tests for `[env]` config.
2+
3+
use cargo_test_support::{basic_bin_manifest, project};
4+
5+
#[cargo_test]
6+
fn env_basic() {
7+
let p = project()
8+
.file("Cargo.toml", &basic_bin_manifest("foo"))
9+
.file("src/main.rs", r#"
10+
use std::env;
11+
fn main() {
12+
println!( "compile-time:{}", env!("ENV_TEST_1233") );
13+
println!( "run-time:{}", env::var("ENV_TEST_1233").unwrap());
14+
}
15+
"#)
16+
.file(
17+
".cargo/config",
18+
r#"
19+
[env]
20+
ENV_TEST_1233 = "Hello"
21+
"#,
22+
)
23+
.build();
24+
25+
p.cargo("run")
26+
.with_stdout_contains("compile-time:Hello")
27+
.with_stdout_contains("run-time:Hello")
28+
.run();
29+
}
30+
31+
#[cargo_test]
32+
fn env_invalid() {
33+
let p = project()
34+
.file("Cargo.toml", &basic_bin_manifest("foo"))
35+
.file("src/main.rs", r#"
36+
fn main() {
37+
}
38+
"#)
39+
.file(
40+
".cargo/config",
41+
r#"
42+
[env]
43+
ENV_TEST_BOOL = false
44+
"#,
45+
)
46+
.build();
47+
48+
p.cargo("build")
49+
.with_status(101)
50+
.with_stderr_contains("[..]`env.ENV_TEST_BOOL` expected a string, but found a boolean")
51+
.run();
52+
}
53+
54+
#[cargo_test]
55+
fn env_force() {
56+
let p = project()
57+
.file("Cargo.toml", &basic_bin_manifest("foo"))
58+
.file("src/main.rs", r#"
59+
use std::env;
60+
fn main() {
61+
println!( "ENV_TEST_FORCED:{}", env!("ENV_TEST_FORCED") );
62+
println!( "ENV_TEST_UNFORCED:{}", env!("ENV_TEST_UNFORCED") );
63+
}
64+
"#)
65+
.file(
66+
".cargo/config",
67+
r#"
68+
[env]
69+
ENV_TEST_UNFORCED = "from-config"
70+
ENV_TEST_FORCED = { value = "from-config", force = true }
71+
"#,
72+
)
73+
.build();
74+
75+
p.cargo("run")
76+
.env("ENV_TEST_FORCED", "from-env")
77+
.env("ENV_TEST_UNFORCED", "from-env")
78+
.with_stdout_contains("ENV_TEST_FORCED:from-config")
79+
.with_stdout_contains("ENV_TEST_UNFORCED:from-env")
80+
.run();
81+
}
82+
83+
#[cargo_test]
84+
fn env_relative() {
85+
let p = project()
86+
.file("Cargo.toml", &basic_bin_manifest("foo2"))
87+
.file("src/main.rs", r#"
88+
use std::env;
89+
use std::path::Path;
90+
fn main() {
91+
println!( "ENV_TEST_RELATIVE:{}", env!("ENV_TEST_RELATIVE") );
92+
println!( "ENV_TEST_ABSOLUTE:{}", env!("ENV_TEST_ABSOLUTE") );
93+
94+
assert!( Path::new(env!("ENV_TEST_ABSOLUTE")).is_absolute() );
95+
assert!( !Path::new(env!("ENV_TEST_RELATIVE")).is_absolute() );
96+
}
97+
"#)
98+
.file(
99+
".cargo/config",
100+
r#"
101+
[env]
102+
ENV_TEST_RELATIVE = "Cargo.toml"
103+
ENV_TEST_ABSOLUTE = { value = "Cargo.toml", relative = true }
104+
"#,
105+
)
106+
.build();
107+
108+
p.cargo("run")
109+
.run();
110+
}

tests/testsuite/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ mod build_script_extra_link_arg;
2424
mod cache_messages;
2525
mod cargo_alias_config;
2626
mod cargo_command;
27+
mod cargo_env_config;
2728
mod cargo_features;
2829
mod cargo_targets;
2930
mod cfg;

0 commit comments

Comments
 (0)