Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/cargo/core/compiler/compilation.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
71 changes: 71 additions & 0 deletions src/cargo/util/config/mod.rs
Original file line number Diff line number Diff line change
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,39 @@ impl Config {
&self.progress_config
}

/// Create an EnvConfigValue hashmap from the "env" table
fn get_env_config(&self) -> CargoResult<EnvConfig> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to do custom serde deserialization (e.g. via #[serde(untagged)]) instead of having custom deserialization logic here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I tried that originally and could not get it to work. I had something like:

#[derive(Debug, Deserialize)]
#[serde(transparent)]
pub struct EnvConfig {
    vars: HashMap<String, EnvConfigValue>
}

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EnvConfigValue {
    Simple(String),
    WithOptions{
        value: Value<String>,
        force: bool,
        relative: bool, 
    }
}

The problem was serde would never parse the Value<T> type in the WithOptions variant, it worked if it was a regular String. I'm not sure why, maybe the entire "path" from the root of the deserialization needs to be Value<T> types in order for them to be handle correctly in some inner struct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this structure may work instead?

#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EnvConfigValue {
    Simple(String),
    WithOptions(WithOptions),
}

#[derive(Debug, Deserialize)]
pub struct WithOptions {
        value: Value<String>,
        force: bool,
        relative: bool, 
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that doesn't work either. It seems like untagged and the Value<T> types do not play nicely together, it will never select a variant with that type. Latest commit does contain a struct/enum layout that does work though. So no more custom logic.

// We cannot use pure serde handling for this. The Value<_> type does not
// work when parsing the env table to a hashmap. So iterator through each
// entry in the "env" table, determine it's type and then use `get` method
// to deserialize it.
let env_table = &self.get_table(&ConfigKey::from_str("env"))?;
let mut vars = EnvConfig::new();

if env_table.is_none() {
return Ok(vars);
}

let env_table = &env_table.as_ref().unwrap().val;

for (key, value) in env_table.iter() {
let full_key = format!("env.{}", key);
let e = match value {
ConfigValue::Table(..) => self.get::<EnvConfigValue>(&full_key)?,
_ => {
let v = self.get::<Value<String>>(&full_key)?;
EnvConfigValue::from_value(v)
}
};
vars.insert(key.clone(), e);
}
Ok(vars)
}

pub fn env_config(&self) -> CargoResult<&EnvConfig> {
self.env_config.try_borrow_with(|| self.get_env_config())
}

/// 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 +1990,40 @@ where
deserializer.deserialize_option(ProgressVisitor)
}

#[derive(Debug, Deserialize)]
pub struct EnvConfigValue {
value: Value<String>,
#[serde(default)]
force: bool,
#[serde(default)]
relative: bool,
}

impl EnvConfigValue {
fn from_value(value: Value<String>) -> EnvConfigValue {
EnvConfigValue {
value,
force: false,
relative: false,
}
}

pub fn is_force(&self) -> bool {
self.force
}

pub fn resolve<'a>(&'a self, config: &Config) -> Cow<'a, OsStr> {
if self.relative {
let p = self.value.definition.root(config).join(&self.value.val);
Cow::Owned(p.into_os_string())
} else {
Cow::Borrowed(OsStr::new(&self.value.val))
}
}
}

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
126 changes: 126 additions & 0 deletions tests/testsuite/cargo_env_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! 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("[..]`env.ENV_TEST_BOOL` expected a string, but found a boolean")
.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") );
}
"#,
)
.file(
".cargo/config",
r#"
[env]
ENV_TEST_UNFORCED = "from-config"
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")
.with_stdout_contains("ENV_TEST_FORCED:from-config")
.with_stdout_contains("ENV_TEST_UNFORCED: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_RELATIVE:{}", env!("ENV_TEST_RELATIVE") );
println!( "ENV_TEST_ABSOLUTE:{}", env!("ENV_TEST_ABSOLUTE") );

assert!( Path::new(env!("ENV_TEST_ABSOLUTE")).is_absolute() );
assert!( !Path::new(env!("ENV_TEST_RELATIVE")).is_absolute() );
}
"#,
)
.file(
".cargo/config",
r#"
[env]
ENV_TEST_RELATIVE = "Cargo.toml"
ENV_TEST_ABSOLUTE = { 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
Original file line number Diff line number Diff line change
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