From a225f5f3ecfe4c2cf637f855bb63d12cc30b1625 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 11 Jun 2018 10:44:55 -0700 Subject: [PATCH] Metabuild (RFC 2196) --- src/cargo/core/compiler/custom_build.rs | 36 ++ src/cargo/core/features.rs | 3 + src/cargo/core/manifest.rs | 15 + src/cargo/core/package.rs | 3 + src/cargo/core/workspace.rs | 32 ++ src/cargo/util/paths.rs | 21 + src/cargo/util/toml/mod.rs | 45 ++ src/cargo/util/toml/targets.rs | 26 +- src/doc/src/reference/unstable.md | 33 ++ tests/testsuite/main.rs | 1 + tests/testsuite/metabuild.rs | 638 ++++++++++++++++++++++++ 11 files changed, 851 insertions(+), 2 deletions(-) create mode 100644 tests/testsuite/metabuild.rs diff --git a/src/cargo/core/compiler/custom_build.rs b/src/cargo/core/compiler/custom_build.rs index 880fcd49c51..c00a5eb5ecb 100644 --- a/src/cargo/core/compiler/custom_build.rs +++ b/src/cargo/core/compiler/custom_build.rs @@ -134,6 +134,10 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes let build_plan = bcx.build_config.build_plan; let invocation_name = unit.buildkey(); + if let Some(deps) = unit.pkg.manifest().metabuild() { + prepare_metabuild(cx, build_script_unit, deps)?; + } + // Building the command to execute let to_exec = script_output.join(unit.target.name()); @@ -536,6 +540,38 @@ impl BuildOutput { } } +fn prepare_metabuild<'a, 'cfg>( + cx: &Context<'a, 'cfg>, + unit: &Unit<'a>, + deps: &[String], +) -> CargoResult<()> { + let mut output = Vec::new(); + let available_deps = cx.dep_targets(unit); + // Filter out optional dependencies, and look up the actual lib name. + let meta_deps: Vec<_> = deps + .iter() + .filter_map(|name| { + available_deps + .iter() + .find(|u| u.pkg.name().as_str() == name.as_str()) + .map(|dep| dep.target.crate_name()) + }) + .collect(); + for dep in &meta_deps { + output.push(format!("extern crate {};\n", dep)); + } + output.push("fn main() {\n".to_string()); + for dep in &meta_deps { + output.push(format!(" {}::metabuild();\n", dep)); + } + output.push("}\n".to_string()); + let output = output.join(""); + let path = unit.target.src_path(); + fs::create_dir_all(path.parent().unwrap())?; + paths::write_if_changed(path, &output)?; + Ok(()) +} + impl BuildDeps { pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps { BuildDeps { diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 06f9d5741f4..d221b869056 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -189,6 +189,9 @@ features! { // "default-run" manifest option, [unstable] default_run: bool, + + // Declarative build scripts. + [unstable] metabuild: bool, } } diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index 1f49bddc902..9489ac719b7 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -44,6 +44,7 @@ pub struct Manifest { edition: Edition, im_a_teapot: Option, default_run: Option, + metabuild: Option>, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -300,6 +301,7 @@ impl Manifest { im_a_teapot: Option, default_run: Option, original: Rc, + metabuild: Option>, ) -> Manifest { Manifest { summary, @@ -321,6 +323,7 @@ impl Manifest { im_a_teapot, default_run, publish_lockfile, + metabuild, } } @@ -348,6 +351,9 @@ impl Manifest { pub fn targets(&self) -> &[Target] { &self.targets } + pub fn targets_mut(&mut self) -> &mut[Target] { + &mut self.targets + } pub fn version(&self) -> &Version { self.package_id().version() } @@ -443,6 +449,10 @@ impl Manifest { pub fn default_run(&self) -> Option<&str> { self.default_run.as_ref().map(|s| &s[..]) } + + pub fn metabuild(&self) -> Option<&Vec> { + self.metabuild.as_ref() + } } impl VirtualManifest { @@ -746,6 +756,11 @@ impl Target { self.doc = doc; self } + pub fn set_src_path(&mut self, src_path: PathBuf) -> &mut Target { + assert!(src_path.is_absolute()); + self.src_path = NonHashedPathBuf { path: src_path }; + self + } } impl fmt::Display for Target { diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index c6c2e028481..71337ff1ee9 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -105,6 +105,9 @@ impl Package { pub fn manifest(&self) -> &Manifest { &self.manifest } + pub fn manifest_mut(&mut self) -> &mut Manifest { + &mut self.manifest + } /// Get the path to the manifest pub fn manifest_path(&self) -> &Path { &self.manifest_path diff --git a/src/cargo/core/workspace.rs b/src/cargo/core/workspace.rs index 7439bf3195f..2dee10900f8 100644 --- a/src/cargo/core/workspace.rs +++ b/src/cargo/core/workspace.rs @@ -1,6 +1,9 @@ +#![allow(deprecated)] // for SipHasher + use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; use std::collections::BTreeMap; +use std::hash::{Hash, Hasher, SipHasher}; use std::path::{Path, PathBuf}; use std::slice; @@ -150,6 +153,7 @@ impl<'cfg> Workspace<'cfg> { }; ws.root_manifest = ws.find_root(manifest_path)?; ws.find_members()?; + ws.fixup()?; ws.validate()?; Ok(ws) } @@ -513,6 +517,11 @@ impl<'cfg> Workspace<'cfg> { Ok(()) } + fn fixup(&mut self) -> CargoResult<()> { + let target_dir = self.target_dir(); + self.packages.fixup(target_dir) + } + /// Validates a workspace, ensuring that a number of invariants are upheld: /// /// 1. A workspace only has one root. @@ -773,6 +782,29 @@ impl<'cfg> Packages<'cfg> { } } } + + fn fixup(&mut self, target_dir: Filesystem) -> CargoResult<()> { + for maybe_pkg in self.packages.values_mut() { + if let MaybePackage::Package(pkg) = maybe_pkg { + let mut hasher = SipHasher::new_with_keys(0, 0); + pkg.hash(&mut hasher); + let hash = hasher.finish(); + let name = pkg.name(); + for target in pkg.manifest_mut().targets_mut().iter_mut() { + // TODO: Don't rely on magic name? + if target.is_custom_build() + && target.src_path().file_name().unwrap() == "metabuild.rs" + { + let path = target_dir + .join(".metabuild") + .join(format!("metabuild-{}-{:016x}.rs", name, hash)); + target.set_src_path(path.into_path_unlocked()); + } + } + } + } + Ok(()) + } } impl<'a, 'cfg> Members<'a, 'cfg> { diff --git a/src/cargo/util/paths.rs b/src/cargo/util/paths.rs index de87a6a3cb0..985be326cdc 100644 --- a/src/cargo/util/paths.rs +++ b/src/cargo/util/paths.rs @@ -142,6 +142,27 @@ pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> { Ok(()) } +pub fn write_if_changed, C: AsRef<[u8]>>(path: P, contents: C) -> CargoResult<()> { + (|| -> CargoResult<()> { + let contents = contents.as_ref(); + let mut f = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&path)?; + let mut orig = Vec::new(); + f.read_to_end(&mut orig)?; + if orig != contents { + f.set_len(0)?; + f.seek(io::SeekFrom::Start(0))?; + f.write_all(contents)?; + } + Ok(()) + })() + .chain_err(|| format!("failed to write `{}`", path.as_ref().display()))?; + Ok(()) +} + pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> { (|| -> CargoResult<()> { let mut f = OpenOptions::new() diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 4c29512fc41..e322b855e58 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -484,6 +484,44 @@ impl TomlProfile { } } +#[derive(Clone, Debug, Serialize, Eq, PartialEq)] +pub struct StringOrVec(Vec); + +impl<'de> de::Deserialize<'de> for StringOrVec { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = StringOrVec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or list of strings") + } + + fn visit_str(self, s: &str) -> Result + where + E: de::Error, + { + Ok(StringOrVec(vec![s.to_string()])) + } + + fn visit_seq(self, v: V) -> Result + where + V: de::SeqAccess<'de>, + { + let seq = de::value::SeqAccessDeserializer::new(v); + Vec::deserialize(seq).map(StringOrVec) + + } + } + + deserializer.deserialize_any(Visitor) + } +} + #[derive(Clone, Debug, Serialize, Eq, PartialEq)] #[serde(untagged)] pub enum StringOrBool { @@ -571,6 +609,7 @@ pub struct TomlProject { version: semver::Version, authors: Option>, build: Option, + metabuild: Option, links: Option, exclude: Option>, include: Option>, @@ -775,6 +814,10 @@ impl TomlManifest { Edition::Edition2015 }; + if project.metabuild.is_some() { + features.require(Feature::metabuild())?; + } + // If we have no lib at all, use the inferred lib if available // If we have a lib with a path, we're done // If we have a lib with no path, use the inferred lib or_else package name @@ -784,6 +827,7 @@ impl TomlManifest { package_root, edition, &project.build, + &project.metabuild, &mut warnings, &mut errors, )?; @@ -974,6 +1018,7 @@ impl TomlManifest { project.im_a_teapot, project.default_run.clone(), Rc::clone(me), + project.metabuild.clone().map(|sov| sov.0), ); if project.license_file.is_some() && project.license.is_some() { manifest.warnings_mut().add_warning( diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 1bbdf20e754..0672dce9fa8 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -16,8 +16,8 @@ use std::collections::HashSet; use core::{compiler, Edition, Target}; use util::errors::CargoResult; -use super::{LibKind, PathValue, StringOrBool, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, - TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget}; +use super::{LibKind, PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, + TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget}; pub fn targets( manifest: &TomlManifest, @@ -25,6 +25,7 @@ pub fn targets( package_root: &Path, edition: Edition, custom_build: &Option, + metabuild: &Option, warnings: &mut Vec, errors: &mut Vec, ) -> CargoResult> { @@ -91,6 +92,9 @@ pub fn targets( // processing the custom build script if let Some(custom_build) = manifest.maybe_custom_build(custom_build, package_root) { + if metabuild.is_some() { + bail!("cannot specify both `metabuild` and `build`"); + } let name = format!( "build-script-{}", custom_build @@ -103,6 +107,24 @@ pub fn targets( package_root.join(custom_build), )); } + if let Some(metabuild) = metabuild { + // Verify names match available build deps. + let bdeps = manifest.build_dependencies.as_ref(); + for name in &metabuild.0 { + if !bdeps.map_or(false, |bd| bd.contains_key(name)) { + bail!( + "metabuild package `{}` must be specified in `build-dependencies`", + name + ); + } + } + + targets.push(Target::custom_build_target( + &format!("metabuild-{}", package.name), + // TODO: This "magic" name is kinda weird. + package_root.join("metabuild.rs"), + )); + } Ok(targets) } diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index fe137b84201..d9f559e731d 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -320,3 +320,36 @@ both `src/bin/a.rs` and `src/bin/b.rs`: [project] default-run = "a" ``` + +### Metabuild +* Tracking Issue: [rust-lang/rust#49803](https://github.com/rust-lang/rust/issues/49803) +* RFC: [#2196](https://github.com/rust-lang/rfcs/blob/master/text/2196-metabuild.md) + +Metabuild is a feature to have declarative build scripts. Instead of writing +a `build.rs` script, you specify a list of build dependencies in the +`metabuild` key in `Cargo.toml`. A build script is automatically generated +that runs each build dependency in order. Metabuild packages can then read +metadata from `Cargo.toml` to specify their behavior. + +Include `cargo-features` at the top of `Cargo.toml`, a `metadata` key in the +`package`, list the dependencies in `build-dependencies`, and add any metadata +that the metabuild packages require. Example: + +```toml +cargo-features = ["metabuild"] + +[package] +name = "mypackage" +version = "0.0.1" +metabuild = ["foo", "bar"] + +[build-dependencies] +foo = "1.0" +bar = "1.0" + +[package.metadata.foo] +extra-info = "qwerty" +``` + +Metabuild packages should have a public function called `metabuild` that +performs the same actions as a regular `build.rs` script would perform. diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 22747b65de3..6940c65a75a 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -61,6 +61,7 @@ mod jobserver; mod local_registry; mod lockfile_compat; mod login; +mod metabuild; mod metadata; mod net_config; mod new; diff --git a/tests/testsuite/metabuild.rs b/tests/testsuite/metabuild.rs new file mode 100644 index 00000000000..6a6ab8e2f8d --- /dev/null +++ b/tests/testsuite/metabuild.rs @@ -0,0 +1,638 @@ +use cargotest::support::{basic_lib_manifest, execs, lines_match, project, Project}; +use cargotest::{rustc_host, ChannelChanger}; +use glob::glob; +use hamcrest::assert_that; +use serde_json; +use std::str; + +#[test] +fn metabuild_gated() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + metabuild = ["mb"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr_contains( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + feature `metabuild` is required + +consider adding `cargo-features = [\"metabuild\"]` to the manifest +", + ), + ); +} + +fn basic_project() -> Project { + project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = ["mb", "mb-other"] + + [build-dependencies] + mb = {path="mb"} + mb-other = {path="mb-other"} + "#, + ) + .file("src/lib.rs", "") + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb"); }"#, + ) + .file( + "mb-other/Cargo.toml", + r#" + [package] + name = "mb-other" + version = "0.0.1" + "#, + ) + .file( + "mb-other/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb-other"); }"#, + ) + .build() +} + +#[test] +fn metabuild_basic() { + let p = basic_project(); + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs() + .with_status(0) + .with_stdout_contains("Hello mb") + .with_stdout_contains("Hello mb-other"), + ); +} + +#[test] +fn metabuild_error_both() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb"} + "#, + ) + .file("src/lib.rs", "") + .file("build.rs", r#"fn main() {}"#) + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb"); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr_contains( + "\ +error: failed to parse manifest at [..] + +Caused by: + cannot specify both `metabuild` and `build` +", + ), + ); +} + +#[test] +fn metabuild_missing_dep() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = "mb" + "#, + ) + .file("src/lib.rs", "") + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(101).with_stderr_contains( + "\ +error: failed to parse manifest at [..] + +Caused by: + metabuild package `mb` must be specified in `build-dependencies`", + ), + ); +} + +#[test] +fn metabuild_optional_dep() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb", optional=true} + "#, + ) + .file("src/lib.rs", "") + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb"); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs() + .with_status(0) + .with_stdout_does_not_contain("Hello mb"), + ); + + assert_that( + p.cargo("build -vv --features mb") + .masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout_contains("Hello mb"), + ); +} + +#[test] +fn metabuild_lib_name() { + // Test when setting `name` on [lib]. + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb"} + "#, + ) + .file("src/lib.rs", "") + .file( + "mb/Cargo.toml", + r#" + [package] + name = "mb" + version = "0.0.1" + [lib] + name = "other" + "#, + ) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb"); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout_contains("Hello mb"), + ); +} + +#[test] +fn metabuild_fresh() { + // Check that rebuild is fresh. + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb"} + "#, + ) + .file("src/lib.rs", "") + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb"); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout_contains("Hello mb"), + ); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs() + .with_status(0) + .with_stdout_does_not_contain("Hello mb") + .with_stderr( + "\ +[FRESH] mb [..] +[FRESH] foo [..] +[FINISHED] dev [..] +", + ), + ); +} + +#[test] +fn metabuild_links() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + links = "cat" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb"} + "#, + ) + .file("src/lib.rs", "") + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { + assert_eq!(std::env::var("CARGO_MANIFEST_LINKS"), + Ok("cat".to_string())); + println!("Hello mb"); + }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(0).with_stdout_contains("Hello mb"), + ); +} + +#[test] +fn metabuild_override() { + let p = project("foo") + .file( + "Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "foo" + version = "0.0.1" + links = "cat" + metabuild = "mb" + + [build-dependencies] + mb = {path="mb"} + "#, + ) + .file("src/lib.rs", "") + .file("mb/Cargo.toml", &basic_lib_manifest("mb")) + .file( + "mb/src/lib.rs", + r#"pub fn metabuild() { panic!("should not run"); }"#, + ) + .file( + ".cargo/config", + &format!( + r#" + [target.{}.cat] + rustc-link-lib = ["a"] + "#, + rustc_host() + ), + ) + .build(); + + assert_that( + p.cargo("build -vv").masquerade_as_nightly_cargo(), + execs().with_status(0), + ); +} + +#[test] +fn metabuild_workspace() { + let p = project("ws") + .file( + "Cargo.toml", + r#" + [workspace] + members = ["member1", "member2"] + "#, + ) + .file( + "member1/Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "member1" + version = "0.0.1" + metabuild = ["mb1", "mb2"] + + [build-dependencies] + mb1 = {path="../../mb1"} + mb2 = {path="../../mb2"} + "#, + ) + .file("member1/src/lib.rs", "") + .file( + "member2/Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "member2" + version = "0.0.1" + metabuild = ["mb1"] + + [build-dependencies] + mb1 = {path="../../mb1"} + "#, + ) + .file("member2/src/lib.rs", "") + .build(); + + project("mb1") + .file("Cargo.toml", &basic_lib_manifest("mb1")) + .file( + "src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb1 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, + ) + .build(); + + project("mb2") + .file("Cargo.toml", &basic_lib_manifest("mb2")) + .file( + "src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb2 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv --all").masquerade_as_nightly_cargo(), + execs() + .with_status(0) + .with_stdout_contains("Hello mb1 [..]member1") + .with_stdout_contains("Hello mb2 [..]member1") + .with_stdout_contains("Hello mb1 [..]member2") + .with_stdout_does_not_contain("Hello mb2 [..]member2"), + ); +} + +#[test] +fn metabuild_metadata() { + let p = basic_project(); + + let output = p + .cargo("metadata --format-version=1") + .masquerade_as_nightly_cargo() + .exec_with_output() + .expect("cargo metadata failed"); + let stdout = str::from_utf8(&output.stdout).unwrap(); + let meta: serde_json::Value = serde_json::from_str(stdout).expect("failed to parse json"); + let src_path = meta["packages"] + .as_array() + .unwrap() + .iter() + .filter(|p| p["name"].as_str().unwrap() == "foo") + .next() + .unwrap()["targets"] + .as_array() + .unwrap() + .iter() + .filter(|t| t["name"].as_str().unwrap() == "metabuild-foo") + .next() + .expect("expected metabuild-foo target")["src_path"] + .as_str() + .expect("expected src_path"); + assert!( + lines_match( + "[..][/]foo[/]target[/].metabuild[/]metabuild-foo-[..].rs", + src_path + ), + "{} did not match", + src_path + ); +} + +#[test] +fn metabuild_build_plan() { + let p = basic_project(); + + assert_that( + p.cargo("build --build-plan -Zunstable-options") + .masquerade_as_nightly_cargo(), + execs().with_status(0).with_json( + r#" +{ + "invocations": [ + { + "package_name": "mb", + "package_version": "0.5.0", + "target_kind": ["lib"], + "kind": "Host", + "deps": [], + "outputs": ["[..][/]target[/]debug[/]deps[/]libmb-[..].rlib"], + "links": {}, + "program": "rustc", + "args": "{...}", + "env": "{...}", + "cwd": "[..]" + }, + { + "package_name": "mb-other", + "package_version": "0.0.1", + "target_kind": ["lib"], + "kind": "Host", + "deps": [], + "outputs": ["[..][/]target[/]debug[/]deps[/]libmb_other-[..].rlib"], + "links": {}, + "program": "rustc", + "args": "{...}", + "env": "{...}", + "cwd": "[..]" + }, + { + "package_name": "foo", + "package_version": "0.0.1", + "target_kind": ["custom-build"], + "kind": "Host", + "deps": [0, 1], + "outputs": ["[..][/]target[/]debug[/]build[/]foo-[..][/]metabuild_foo-[..][EXE]"], + "links": "{...}", + "program": "rustc", + "args": "{...}", + "env": "{...}", + "cwd": "[..]" + }, + { + "package_name": "foo", + "package_version": "0.0.1", + "target_kind": ["custom-build"], + "kind": "Host", + "deps": [2], + "outputs": [], + "links": {}, + "program": "[..][/]foo[/]target[/]debug[/]build[/]foo-[..][/]metabuild-foo", + "args": [], + "env": "{...}", + "cwd": "[..]" + }, + { + "package_name": "foo", + "package_version": "0.0.1", + "target_kind": ["lib"], + "kind": "Host", + "deps": [3], + "outputs": ["[..][/]foo[/]target[/]debug[/]deps[/]libfoo-[..].rlib"], + "links": "{...}", + "program": "rustc", + "args": "{...}", + "env": "{...}", + "cwd": "[..]" + } + ], + "inputs": [ + "[..][/]foo[/]Cargo.toml", + "[..][/]foo[/]mb[/]Cargo.toml", + "[..][/]foo[/]mb-other[/]Cargo.toml" + ] +} +"#, + ), + ); + + assert_eq!( + glob( + &p.root() + .join("target/.metabuild/metabuild-foo-*.rs") + .to_str() + .unwrap() + ).unwrap() + .count(), + 1 + ); +} + +#[test] +fn metabuild_two_versions() { + // Two versions of a metabuild dep with the same name. + let p = project("ws") + .file( + "Cargo.toml", + r#" + [workspace] + members = ["member1", "member2"] + "#, + ) + .file( + "member1/Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "member1" + version = "0.0.1" + metabuild = ["mb"] + + [build-dependencies] + mb = {path="../../mb1"} + "#, + ) + .file("member1/src/lib.rs", "") + .file( + "member2/Cargo.toml", + r#" + cargo-features = ["metabuild"] + [package] + name = "member2" + version = "0.0.1" + metabuild = ["mb"] + + [build-dependencies] + mb = {path="../../mb2"} + "#, + ) + .file("member2/src/lib.rs", "") + .build(); + + project("mb1") + .file("Cargo.toml", r#" + [package] + name = "mb" + version = "0.0.1" + "#) + .file( + "src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb1 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, + ) + .build(); + + project("mb2") + .file("Cargo.toml", r#" + [package] + name = "mb" + version = "0.0.2" + "#) + .file( + "src/lib.rs", + r#"pub fn metabuild() { println!("Hello mb2 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, + ) + .build(); + + assert_that( + p.cargo("build -vv --all").masquerade_as_nightly_cargo(), + execs() + .with_status(0) + .with_stdout_contains("Hello mb1 [..]member1") + .with_stdout_contains("Hello mb2 [..]member2"), + ); + + assert_eq!( + glob( + &p.root() + .join("target/.metabuild/metabuild-member?-*.rs") + .to_str() + .unwrap() + ).unwrap() + .count(), + 2 + ); +}