Read configuration from environment variables
authorAlex Crichton <alex@alexcrichton.com>
Fri, 19 Feb 2016 08:07:22 +0000 (00:07 -0800)
committerAlex Crichton <alex@alexcrichton.com>
Fri, 19 Feb 2016 08:18:13 +0000 (00:18 -0800)
This commit adds a more principled system to rationalize what ends up being a
configuration value versus an environment variable. This problem is solved by
just saying that they're one and the same! Similar to Bundler, this commit
supports overriding the `foo.bar` configuration value with the `CARGO_FOO_BAR`
environment variable.

Currently this is used as part of the `get_string` and `get_i64` methods on
`Config`. This means, for example, that the following environment variables can
now be used to configure Cargo:

* CARGO_BUILD_JOBS
* CARGO_HTTP_TIMEOUT
* CARGO_HTTP_PROXY

Currently it's not supported to encode a list in an environment variable, so for
example `CARGO_PATHS` would not be read when reading the global `paths`
configuration value.

cc #2362
cc #2395 -- intended to close this in tandem with #2397

src/cargo/util/config.rs
src/cargo/util/errors.rs
src/doc/config.md
tests/test_cargo_config.rs [new file with mode: 0644]
tests/tests.rs

index b2361e74d1476276bcfda5dfac7cdaedf2993bd0..00df39b703c05b27287c7db283a5000e9f651e92 100644 (file)
@@ -7,12 +7,13 @@ use std::fs::{self, File};
 use std::io::prelude::*;
 use std::mem;
 use std::path::{Path, PathBuf};
+use std::str::FromStr;
 
 use rustc_serialize::{Encodable,Encoder};
 use toml;
 use core::shell::{Verbosity, ColorConfig};
 use core::{MultiShell, Package};
-use util::{CargoResult, ChainError, Rustc, internal, human, paths};
+use util::{CargoResult, CargoError, ChainError, Rustc, internal, human, paths};
 
 use util::toml as cargo_toml;
 
@@ -148,7 +149,29 @@ impl Config {
         Ok(Some(val.clone()))
     }
 
+    fn get_env<V: FromStr>(&self, key: &str) -> CargoResult<Option<Value<V>>>
+        where Box<CargoError>: From<V::Err>
+    {
+        let key = key.replace(".", "_")
+                     .replace("-", "_")
+                     .chars()
+                     .flat_map(|c| c.to_uppercase())
+                     .collect::<String>();
+        match env::var(&format!("CARGO_{}", key)) {
+            Ok(value) => {
+                Ok(Some(Value {
+                    val: try!(value.parse()),
+                    definition: Definition::Environment,
+                }))
+            }
+            Err(..) => Ok(None),
+        }
+    }
+
     pub fn get_string(&self, key: &str) -> CargoResult<Option<Value<String>>> {
+        if let Some(v) = try!(self.get_env(key)) {
+            return Ok(Some(v))
+        }
         match try!(self.get(key)) {
             Some(CV::String(i, path)) => {
                 Ok(Some(Value {
@@ -209,6 +232,9 @@ impl Config {
     }
 
     pub fn get_i64(&self, key: &str) -> CargoResult<Option<Value<i64>>> {
+        if let Some(v) = try!(self.get_env(key)) {
+            return Ok(Some(v))
+        }
         match try!(self.get(key)) {
             Some(CV::Integer(i, path)) => {
                 Ok(Some(Value {
@@ -311,6 +337,7 @@ pub struct Value<T> {
 
 pub enum Definition {
     Path(PathBuf),
+    Environment,
 }
 
 impl fmt::Debug for ConfigValue {
@@ -496,9 +523,10 @@ impl ConfigValue {
 }
 
 impl Definition {
-    pub fn root<'a>(&'a self, _config: &'a Config) -> &'a Path {
+    pub fn root<'a>(&'a self, config: &'a Config) -> &'a Path {
         match *self {
             Definition::Path(ref p) => p.parent().unwrap().parent().unwrap(),
+            Definition::Environment => config.cwd(),
         }
     }
 }
@@ -507,6 +535,7 @@ impl fmt::Display for Definition {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match *self {
             Definition::Path(ref p) => p.display().fmt(f),
+            Definition::Environment => "the environment".fmt(f),
         }
     }
 }
index 255d8699e840c98161c203f3d17493a3fba3db0c..d69d6b3002ac03f3d164af77d7d3d1ce5aeb697c 100644 (file)
@@ -2,8 +2,10 @@ use std::error::Error;
 use std::ffi;
 use std::fmt;
 use std::io;
+use std::num;
 use std::process::{Output, ExitStatus};
 use std::str;
+use std::string;
 
 use curl;
 use git2;
@@ -308,6 +310,13 @@ from_error! {
     toml::DecodeError,
     ffi::NulError,
     term::Error,
+    num::ParseIntError,
+}
+
+impl From<string::ParseError> for Box<CargoError> {
+    fn from(t: string::ParseError) -> Box<CargoError> {
+        match t {}
+    }
 }
 
 impl<E: CargoError> From<Human<E>> for Box<CargoError> {
@@ -328,6 +337,7 @@ impl CargoError for toml::DecodeError {}
 impl CargoError for url::ParseError {}
 impl CargoError for ffi::NulError {}
 impl CargoError for term::Error {}
+impl CargoError for num::ParseIntError {}
 
 // =============================================================================
 // Construction helpers
index d911e87bf2f000bfebe8318cc7b03823871a80be..157f0817259c06ffe9a7f14a38a7d0a024305800 100644 (file)
@@ -90,8 +90,16 @@ target-dir = "target"  # path of where to place all generated artifacts
 
 # Environment Variables
 
-Cargo recognizes a few global [environment variables][env] to configure itself.
-Settings specified via config files take precedence over those specified via
+Cargo can also be configured through environment variables in addition to the
+TOML syntax above. For each configuration key above of the form `foo.bar` the
+environment variable `CARGO_FOO_BAR` can also be used to define the value. For
+example the `build.jobs` key can also be defined by `CARGO_BUILD_JOBS`.
+
+Environment variables will take precedent over TOML configuration, and currently
+only integer, boolean, and string keys are supported to be defined by
 environment variables.
 
+In addition to the system above, Cargo recognizes a few other specific
+[environment variables][env].
+
 [env]: environment-variables.html
diff --git a/tests/test_cargo_config.rs b/tests/test_cargo_config.rs
new file mode 100644 (file)
index 0000000..6de6aad
--- /dev/null
@@ -0,0 +1,26 @@
+use support::{project, execs};
+use hamcrest::assert_that;
+
+fn setup() {
+}
+
+test!(read_env_vars_for_config {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            authors = []
+            version = "0.0.0"
+            build = "build.rs"
+        "#)
+        .file("src/lib.rs", "")
+        .file("build.rs", r#"
+            use std::env;
+            fn main() {
+                assert_eq!(env::var("NUM_JOBS").unwrap(), "100");
+            }
+        "#);
+
+    assert_that(p.cargo_process("build").env("CARGO_BUILD_JOBS", "100"),
+                execs().with_status(0));
+});
index b67e02a0ffb5e707ea127949e37050a39444cf39..6b252a3e2e7f83aec21c40070cd8add2d9e1ff52 100644 (file)
@@ -66,6 +66,7 @@ mod test_cargo_rustdoc;
 mod test_cargo_search;
 mod test_cargo_test;
 mod test_cargo_tool_paths;
+mod test_cargo_config;
 mod test_cargo_verify_project;
 mod test_cargo_version;
 mod test_shell;