//! Tests for the metabuild feature (declarative build scripts). use std::str; use cargo_test_support::prelude::*; use cargo_test_support::{ basic_lib_manifest, basic_manifest, is_coarse_mtime, project, registry::Package, rustc_host, str, Project, }; #[cargo_test] fn metabuild_gated() { let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" metabuild = ["mb"] "#, ) .file("src/lib.rs", "") .build(); p.cargo("check") .masquerade_as_nightly_cargo(&["metabuild"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: feature `metabuild` is required The package requires the Cargo feature called `metabuild`, but that feature is not stabilized in this version of Cargo ([..]). Consider adding `cargo-features = ["metabuild"]` to the top of Cargo.toml (above the [package] table) to tell Cargo you are opting in to use this unstable feature. See https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#metabuild for more information about the status of this feature. "#]]) .run(); } fn basic_project() -> Project { project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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" edition = "2015" "#, ) .file( "mb-other/src/lib.rs", r#"pub fn metabuild() { println!("Hello mb-other"); }"#, ) .build() } #[cargo_test] fn metabuild_basic() { let p = basic_project(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_data(str![[r#" [foo 0.0.1] Hello mb [foo 0.0.1] Hello mb-other "#]]) .run(); } #[cargo_test] fn metabuild_error_both() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: cannot specify both `metabuild` and `build` "#]]) .run(); } #[cargo_test] fn metabuild_missing_dep() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" metabuild = "mb" "#, ) .file("src/lib.rs", "") .build(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_status(101) .with_stderr_data(str![[r#" [ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` Caused by: metabuild package `mb` must be specified in `build-dependencies` "#]]) .run(); } #[allow(deprecated)] #[cargo_test] fn metabuild_optional_dep() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_does_not_contain("[foo 0.0.1] Hello mb") .run(); p.cargo("check -vv --features mb") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_contains("[foo 0.0.1] Hello mb") .run(); } #[cargo_test] fn metabuild_lib_name() { // Test when setting `name` on [lib]. let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" metabuild = "mb" [build-dependencies] mb = {path="mb"} "#, ) .file("src/lib.rs", "") .file( "mb/Cargo.toml", r#" [package] name = "mb" version = "0.0.1" edition = "2015" [lib] name = "other" "#, ) .file( "mb/src/lib.rs", r#"pub fn metabuild() { println!("Hello mb"); }"#, ) .build(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_data(str![[r#" [foo 0.0.1] Hello mb "#]]) .run(); } #[allow(deprecated)] #[cargo_test] fn metabuild_fresh() { if is_coarse_mtime() { // This test doesn't work on coarse mtimes very well. Because the // metabuild script is created at build time, its mtime is almost // always equal to the mtime of the output. The second call to `build` // will then think it needs to be rebuilt when it should be fresh. return; } // Check that rebuild is fresh. let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_contains("[foo 0.0.1] Hello mb") .run(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_does_not_contain("[foo 0.0.1] Hello mb") .with_stderr_data(str![[r#" [FRESH] mb v0.5.0 ([ROOT]/foo/mb) [FRESH] foo v0.0.1 ([ROOT]/foo) [FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); } #[cargo_test] fn metabuild_links() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_data(str![[r#" [foo 0.0.1] Hello mb "#]]) .run(); } #[cargo_test] fn metabuild_override() { let p = project() .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "foo" version = "0.0.1" edition = "2015" 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.toml", &format!( r#" [target.{}.cat] rustc-link-lib = ["a"] "#, rustc_host() ), ) .build(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .run(); } #[allow(deprecated)] #[cargo_test] fn metabuild_workspace() { let p = project() .file( "Cargo.toml", r#" [workspace] members = ["member1", "member2"] "#, ) .file( "member1/Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "member1" version = "0.0.1" edition = "2015" 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" edition = "2015" metabuild = ["mb1"] [build-dependencies] mb1 = {path="../../mb1"} "#, ) .file("member2/src/lib.rs", "") .build(); project() .at("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() .at("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(); p.cargo("check -vv --workspace") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_contains("[member1 0.0.1] Hello mb1 [..]member1") .with_stdout_contains("[member1 0.0.1] Hello mb2 [..]member1") .with_stdout_contains("[member2 0.0.1] Hello mb1 [..]member2") .with_stdout_does_not_contain("[member2 0.0.1] Hello mb2 [..]member2") .run(); } #[cargo_test] fn metabuild_metadata() { // The metabuild Target is filtered out of the `metadata` results. let p = basic_project(); let meta = p .cargo("metadata --format-version=1") .masquerade_as_nightly_cargo(&["metabuild"]) .run_json(); let mb_info: Vec<&str> = meta["packages"] .as_array() .unwrap() .iter() .find(|p| p["name"].as_str().unwrap() == "foo") .unwrap()["metabuild"] .as_array() .unwrap() .iter() .map(|s| s.as_str().unwrap()) .collect(); assert_eq!(mb_info, ["mb", "mb-other"]); } #[cargo_test] fn metabuild_build_plan() { let p = basic_project(); p.cargo("build --build-plan -Zunstable-options") .masquerade_as_nightly_cargo(&["metabuild", "build-plan"]) .with_stdout_data( str![[r#" { "inputs": [ "[ROOT]/foo/Cargo.toml", "[ROOT]/foo/mb/Cargo.toml", "[ROOT]/foo/mb-other/Cargo.toml" ], "invocations": [ { "args": "{...}", "compile_mode": "build", "cwd": "[ROOT]/foo", "deps": [], "env": "{...}", "kind": null, "links": {}, "outputs": [ "[ROOT]/foo/target/debug/deps/libmb-[HASH].rlib", "[ROOT]/foo/target/debug/deps/libmb-[HASH].rmeta" ], "package_name": "mb", "package_version": "0.5.0", "program": "rustc", "target_kind": [ "lib" ] }, { "args": "{...}", "compile_mode": "build", "cwd": "[ROOT]/foo", "deps": [], "env": "{...}", "kind": null, "links": {}, "outputs": [ "[ROOT]/foo/target/debug/deps/libmb_other-[HASH].rlib", "[ROOT]/foo/target/debug/deps/libmb_other-[HASH].rmeta" ], "package_name": "mb-other", "package_version": "0.0.1", "program": "rustc", "target_kind": [ "lib" ] }, { "args": "{...}", "compile_mode": "build", "cwd": "[ROOT]/foo", "deps": [ 0, 1 ], "env": "{...}", "kind": null, "links": "{...}", "outputs": "{...}", "package_name": "foo", "package_version": "0.0.1", "program": "rustc", "target_kind": [ "custom-build" ] }, { "args": "{...}", "compile_mode": "run-custom-build", "cwd": "[ROOT]/foo", "deps": [ 2 ], "env": "{...}", "kind": null, "links": {}, "outputs": [], "package_name": "foo", "package_version": "0.0.1", "program": "[ROOT]/foo/target/debug/build/foo-[HASH]/metabuild-foo", "target_kind": [ "custom-build" ] }, { "args": "{...}", "compile_mode": "build", "cwd": "[ROOT]/foo", "deps": [ 3 ], "env": "{...}", "kind": null, "links": "{...}", "outputs": [ "[ROOT]/foo/target/debug/deps/libfoo-[HASH].rlib", "[ROOT]/foo/target/debug/deps/libfoo-[HASH].rmeta" ], "package_name": "foo", "package_version": "0.0.1", "program": "rustc", "target_kind": [ "lib" ] } ] } "#]] .json(), ) .run(); assert_eq!(p.glob("target/.metabuild/metabuild-foo-*.rs").count(), 1); } #[cargo_test] fn metabuild_two_versions() { // Two versions of a metabuild dep with the same name. let p = project() .at("ws") .file( "Cargo.toml", r#" [workspace] members = ["member1", "member2"] "#, ) .file( "member1/Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "member1" version = "0.0.1" edition = "2015" 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" edition = "2015" metabuild = ["mb"] [build-dependencies] mb = {path="../../mb2"} "#, ) .file("member2/src/lib.rs", "") .build(); project().at("mb1") .file("Cargo.toml", r#" [package] name = "mb" version = "0.0.1" edition = "2015" "#) .file( "src/lib.rs", r#"pub fn metabuild() { println!("Hello mb1 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, ) .build(); project().at("mb2") .file("Cargo.toml", r#" [package] name = "mb" version = "0.0.2" edition = "2015" "#) .file( "src/lib.rs", r#"pub fn metabuild() { println!("Hello mb2 {}", std::env::var("CARGO_MANIFEST_DIR").unwrap()); }"#, ) .build(); p.cargo("check -vv --workspace") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_data( str![[r#" [member2 0.0.1] Hello mb2 [ROOT]/ws/member2 [member1 0.0.1] Hello mb1 [ROOT]/ws/member1 "#]] .unordered(), ) .run(); assert_eq!( p.glob("target/.metabuild/metabuild-member?-*.rs").count(), 2 ); } #[cargo_test] fn metabuild_external_dependency() { Package::new("mb", "1.0.0") .file("Cargo.toml", &basic_manifest("mb", "1.0.0")) .file( "src/lib.rs", r#"pub fn metabuild() { println!("Hello mb"); }"#, ) .publish(); Package::new("dep", "1.0.0") .file( "Cargo.toml", r#" cargo-features = ["metabuild"] [package] name = "dep" version = "1.0.0" edition = "2015" metabuild = ["mb"] [build-dependencies] mb = "1.0" "#, ) .file("src/lib.rs", "") .build_dep("mb", "1.0.0") .publish(); let p = project() .file( "Cargo.toml", r#" [package] name = "foo" version = "0.0.1" edition = "2015" [dependencies] dep = "1.0" "#, ) .file("src/lib.rs", "extern crate dep;") .build(); p.cargo("check -vv") .masquerade_as_nightly_cargo(&["metabuild"]) .with_stdout_data(str![[r#" [dep 1.0.0] Hello mb "#]]) .run(); assert_eq!(p.glob("target/.metabuild/metabuild-dep-*.rs").count(), 1); } #[allow(deprecated)] #[cargo_test] fn metabuild_json_artifact() { let p = basic_project(); p.cargo("check --message-format=json") .masquerade_as_nightly_cargo(&["metabuild"]) .with_json_contains_unordered( r#" { "executable": null, "features": [], "filenames": "{...}", "fresh": false, "package_id": "path+file:///[..]/foo#0.0.1", "manifest_path": "[..]", "profile": "{...}", "reason": "compiler-artifact", "target": { "crate_types": [ "bin" ], "doc": false, "doctest": false, "edition": "2018", "kind": [ "custom-build" ], "name": "metabuild-foo", "src_path": "[..]/foo/target/.metabuild/metabuild-foo-[..].rs", "test": false } } { "cfgs": [], "env": [], "linked_libs": [], "linked_paths": [], "package_id": "path+file:///[..]/foo#0.0.1", "out_dir": "[..]", "reason": "build-script-executed" } "#, ) .run(); } #[allow(deprecated)] #[cargo_test] fn metabuild_failed_build_json() { let p = basic_project(); // Modify the metabuild dep so that it fails to compile. p.change_file("mb/src/lib.rs", ""); p.cargo("check --message-format=json") .masquerade_as_nightly_cargo(&["metabuild"]) .with_status(101) .with_json_contains_unordered( r#" { "message": { "$message_type": "diagnostic", "children": "{...}", "code": "{...}", "level": "error", "message": "cannot find function `metabuild`[..]", "rendered": "{...}", "spans": "{...}" }, "package_id": "path+file:///[..]/foo#0.0.1", "manifest_path": "[..]", "reason": "compiler-message", "target": { "crate_types": [ "bin" ], "doc": false, "doctest": false, "edition": "2018", "kind": [ "custom-build" ], "name": "metabuild-foo", "src_path": null, "test": false } } "#, ) .run(); }