diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 1256787f4f8..861f95bd726 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -670,6 +670,7 @@ fn scrape_target_config(config: &Config, triple: &str) library_paths: Vec::new(), library_links: Vec::new(), cfgs: Vec::new(), + env: Vec::new(), metadata: Vec::new(), rerun_if_changed: Vec::new(), warnings: Vec::new(), @@ -708,6 +709,12 @@ fn scrape_target_config(config: &Config, triple: &str) let list = value.list(&k)?; output.cfgs.extend(list.iter().map(|v| v.0.clone())); } + "rustc-env" => { + for (name, val) in value.table(&k)?.0 { + let val = val.string(name)?.0; + output.env.push((name.clone(), val.to_string())); + } + } "warning" | "rerun-if-changed" => { bail!("`{}` is not supported in build script overrides", k); } diff --git a/src/cargo/ops/cargo_rustc/custom_build.rs b/src/cargo/ops/cargo_rustc/custom_build.rs index 4c1dca592fe..64f05f918e8 100644 --- a/src/cargo/ops/cargo_rustc/custom_build.rs +++ b/src/cargo/ops/cargo_rustc/custom_build.rs @@ -21,6 +21,8 @@ pub struct BuildOutput { pub library_links: Vec, /// Various `--cfg` flags to pass to the compiler pub cfgs: Vec, + /// Additional environment variables to run the compiler with. + pub env: Vec<(String, String)>, /// Metadata to pass to the immediate dependencies pub metadata: Vec<(String, String)>, /// Paths to trigger a rerun of this build script. @@ -255,6 +257,7 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) linked_libs: &parsed_output.library_links, linked_paths: &library_paths, cfgs: &parsed_output.cfgs, + env: &parsed_output.env, }); } @@ -321,6 +324,7 @@ impl BuildOutput { let mut library_paths = Vec::new(); let mut library_links = Vec::new(); let mut cfgs = Vec::new(); + let mut env = Vec::new(); let mut metadata = Vec::new(); let mut rerun_if_changed = Vec::new(); let mut warnings = Vec::new(); @@ -361,6 +365,7 @@ impl BuildOutput { "rustc-link-lib" => library_links.push(value.to_string()), "rustc-link-search" => library_paths.push(PathBuf::from(value)), "rustc-cfg" => cfgs.push(value.to_string()), + "rustc-env" => env.push(BuildOutput::parse_rustc_env(value, &whence)?), "warning" => warnings.push(value.to_string()), "rerun-if-changed" => rerun_if_changed.push(value.to_string()), _ => metadata.push((key.to_string(), value.to_string())), @@ -371,6 +376,7 @@ impl BuildOutput { library_paths: library_paths, library_links: library_links, cfgs: cfgs, + env: env, metadata: metadata, rerun_if_changed: rerun_if_changed, warnings: warnings, @@ -407,6 +413,17 @@ impl BuildOutput { } Ok((library_paths, library_links)) } + + pub fn parse_rustc_env(value: &str, whence: &str) + -> CargoResult<(String, String)> { + let mut iter = value.splitn(2, '='); + let name = iter.next(); + let val = iter.next(); + match (name, val) { + (Some(n), Some(v)) => Ok((n.to_owned(), v.to_owned())), + _ => bail!("Variable rustc-env has no value in {}: {}", whence, value), + } + } } /// Compute the `build_scripts` map in the `Context` which tracks what build diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 439046eb123..470f700f590 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -195,6 +195,10 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, .or_insert_with(HashSet::new) .extend(output.cfgs.iter().cloned()); + cx.compilation.extra_env.entry(pkg.clone()) + .or_insert_with(Vec::new) + .extend(output.env.iter().cloned()); + for dir in output.library_paths.iter() { cx.compilation.native_dirs.insert(dir.clone()); } @@ -265,6 +269,7 @@ fn rustc(cx: &mut Context, unit: &Unit, exec: Arc) -> CargoResult) -> CargoResult) -> CargoResult CargoResult<()> { + let key = (current_id.clone(), kind); + if let Some(output) = build_state.get(&key) { + for &(ref name, ref value) in output.env.iter() { + rustc.env(name, value); + } + } + Ok(()) + } } /// Link the compiled target (often of form foo-{metadata_hash}) to the @@ -599,6 +622,9 @@ fn rustdoc(cx: &mut Context, unit: &Unit) -> CargoResult { for cfg in output.cfgs.iter() { rustdoc.arg("--cfg").arg(cfg); } + for &(ref name, ref value) in output.env.iter() { + rustdoc.env(name, value); + } } state.running(&rustdoc); rustdoc.exec().chain_error(|| { diff --git a/src/cargo/util/machine_message.rs b/src/cargo/util/machine_message.rs index 1d4f33a86da..5f917f85a74 100644 --- a/src/cargo/util/machine_message.rs +++ b/src/cargo/util/machine_message.rs @@ -48,6 +48,7 @@ pub struct BuildScript<'a> { pub linked_libs: &'a [String], pub linked_paths: &'a [String], pub cfgs: &'a [String], + pub env: &'a [(String, String)], } impl<'a> Message for BuildScript<'a> { diff --git a/src/doc/build-script.md b/src/doc/build-script.md index 586dd29f056..b747ed0c095 100644 --- a/src/doc/build-script.md +++ b/src/doc/build-script.md @@ -51,6 +51,7 @@ All the lines printed to stdout by a build script are written to a file like `ta cargo:rustc-link-lib=static=foo cargo:rustc-link-search=native=/path/to/foo cargo:rustc-cfg=foo +cargo:rustc-env=FOO=bar # arbitrary user-defined metadata cargo:root=/path/to/foo cargo:libdir=/path/to/foo/lib @@ -73,6 +74,12 @@ crate is built: * `rustc-cfg=FEATURE` indicates that the specified feature will be passed as a `--cfg` flag to the compiler. This is often useful for performing compile-time detection of various features. +* `rustc-env=VAR=VALUE` indicates that the specified environment variable + will be added to the environment which the compiler is run within. + The value can be then retrieved by the `env!` macro in the compiled crate. + This is useful for embedding additional metadata in crate's code, + such as the hash of Git HEAD or the unique identifier of a continuous + integration server. * `rerun-if-changed=PATH` is a path to a file or directory which indicates that the build script should be re-run if it changes (detected by a more-recent last-modified timestamp on the file). Normally build scripts are re-run if diff --git a/tests/build-script.rs b/tests/build-script.rs index 8d40474d217..7356af284c1 100644 --- a/tests/build-script.rs +++ b/tests/build-script.rs @@ -1575,6 +1575,114 @@ fn cfg_override_doc() { assert_that(&p.root().join("target/doc/bar/fn.bar.html"), existing_file()); } +#[test] +fn env_build() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#) + .file("src/main.rs", r#" + const FOO: &'static str = env!("FOO"); + fn main() { + println!("{}", FOO); + } + "#) + .file("build.rs", r#" + fn main() { + println!("cargo:rustc-env=FOO=foo"); + } + "#); + assert_that(p.cargo_process("build").arg("-v"), + execs().with_status(0)); + assert_that(p.cargo("run").arg("-v"), + execs().with_status(0).with_stdout("foo\n")); +} + +#[test] +fn env_test() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#) + .file("build.rs", r#" + fn main() { + println!("cargo:rustc-env=FOO=foo"); + } + "#) + .file("src/lib.rs", r#" + pub const FOO: &'static str = env!("FOO"); + "#) + .file("tests/test.rs", r#" + extern crate foo; + + #[test] + fn test_foo() { + assert_eq!("foo", foo::FOO); + } + "#); + assert_that(p.cargo_process("test").arg("-v"), + execs().with_stderr(format!("\ +[COMPILING] foo v0.0.1 ({dir}) +[RUNNING] [..] build.rs [..] +[RUNNING] `[..][/]build-script-build` +[RUNNING] [..] --crate-name foo[..] +[RUNNING] [..] --crate-name foo[..] +[RUNNING] [..] --crate-name test[..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[RUNNING] `[..][/]foo-[..][EXE]` +[RUNNING] `[..][/]test-[..][EXE]` +[DOCTEST] foo +[RUNNING] [..] --crate-name foo[..]", dir = p.url())) + .with_stdout(" +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + + +running 1 test +test test_foo ... ok + +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured + + +running 0 tests + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured + +")); +} + +#[test] +fn env_doc() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + build = "build.rs" + "#) + .file("src/main.rs", r#" + const FOO: &'static str = env!("FOO"); + fn main() {} + "#) + .file("build.rs", r#" + fn main() { + println!("cargo:rustc-env=FOO=foo"); + } + "#); + assert_that(p.cargo_process("doc").arg("-v"), + execs().with_status(0)); +} + #[test] fn flags_go_into_tests() { let p = project("foo") @@ -1816,7 +1924,7 @@ fn fresh_builds_possible_with_link_libs() { rustc-flags = \"-l z -L ./\" ", target)) .file("build.rs", ""); - + assert_that(p.cargo_process("build").arg("-v"), execs().with_status(0).with_stderr("\ [COMPILING] foo v0.5.0 ([..] @@ -1857,7 +1965,7 @@ fn fresh_builds_possible_with_multiple_metadata_overrides() { e = \"\" ", target)) .file("build.rs", ""); - + assert_that(p.cargo_process("build").arg("-v"), execs().with_status(0).with_stderr("\ [COMPILING] foo v0.5.0 ([..]