blob: 8e7457fb9548ead60e7fe6d9659b704c61e50947 [file] [log] [blame]
//! Tests for multiple build scripts feature.
use crate::prelude::*;
use cargo_test_support::compare::assert_e2e;
use cargo_test_support::git;
use cargo_test_support::publish::validate_crate_contents;
use cargo_test_support::str;
use cargo_test_support::{Project, project};
use std::fs::File;
#[cargo_test]
fn build_without_feature_enabled_aborts_with_error() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() {}")
.file("build2.rs", "fn main() {}")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml`
Caused by:
feature `multiple-build-scripts` is required
The package requires the Cargo feature called `multiple-build-scripts`, but that feature is not stabilized in this version of Cargo ([..]).
Consider adding `cargo-features = ["multiple-build-scripts"]` 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#multiple-build-scripts for more information about the status of this feature.
"#]])
.run();
}
fn basic_empty_project() -> Project {
project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() {}")
.file("build2.rs", "fn main() {}")
.build()
}
#[cargo_test]
fn empty_multiple_build_script_project() {
let p = basic_empty_project();
p.cargo("check")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stderr_data(str![[r#"
[COMPILING] foo v0.1.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test]
fn multiple_build_scripts_metadata() {
let p = basic_empty_project();
p.cargo("metadata --format-version=1")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stderr_data("")
.with_stdout_data(
str![[r#"
{
"metadata": null,
"packages": [
{
"authors": [],
"categories": [],
"default_run": null,
"dependencies": [],
"description": null,
"documentation": null,
"edition": "2024",
"features": {},
"homepage": null,
"id": "path+[ROOTURL]/foo#0.1.0",
"keywords": [],
"license": null,
"license_file": null,
"links": null,
"manifest_path": "[ROOT]/foo/Cargo.toml",
"metadata": null,
"name": "foo",
"publish": null,
"readme": null,
"repository": null,
"rust_version": null,
"source": null,
"targets": [
{
"crate_types": [
"bin"
],
"doc": true,
"doctest": false,
"edition": "2024",
"kind": [
"bin"
],
"name": "foo",
"src_path": "[ROOT]/foo/src/main.rs",
"test": true
},
{
"crate_types": [
"bin"
],
"doc": false,
"doctest": false,
"edition": "2024",
"kind": [
"custom-build"
],
"name": "build-script-build1",
"src_path": "[ROOT]/foo/build1.rs",
"test": false
},
{
"crate_types": [
"bin"
],
"doc": false,
"doctest": false,
"edition": "2024",
"kind": [
"custom-build"
],
"name": "build-script-build2",
"src_path": "[ROOT]/foo/build2.rs",
"test": false
}
],
"version": "0.1.0"
}
],
"resolve": {
"nodes": [
{
"dependencies": [],
"deps": [],
"features": [],
"id": "path+[ROOTURL]/foo#0.1.0"
}
],
"root": "path+[ROOTURL]/foo#0.1.0"
},
"target_directory": "[ROOT]/foo/target",
"build_directory": "[ROOT]/foo/target",
"version": 1,
"workspace_default_members": [
"path+[ROOTURL]/foo#0.1.0"
],
"workspace_members": [
"path+[ROOTURL]/foo#0.1.0"
],
"workspace_root": "[ROOT]/foo"
}
"#]]
.is_json(),
)
.run();
}
#[cargo_test]
fn verify_package_multiple_build_scripts() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
license = "MIT"
description = "foo"
documentation = "docs.rs/foo"
authors = []
build = ["build1.rs", "build2.rs"]
include = [ "src/main.rs", "build1.rs" ]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() {}")
.file("build2.rs", "fn main() {}")
.build();
p.cargo("package")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stderr_data(str![[r#"
[PACKAGING] foo v0.1.0 ([ROOT]/foo)
[WARNING] ignoring `package.build` entry `build2.rs` as it is not included in the published package
[PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
[VERIFYING] foo v0.1.0 ([ROOT]/foo)
[COMPILING] foo v0.1.0 ([ROOT]/foo/target/package/foo-0.1.0)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
let f = File::open(&p.root().join("target/package/foo-0.1.0.crate")).unwrap();
validate_crate_contents(
f,
"foo-0.1.0.crate",
&[
"Cargo.toml",
"Cargo.toml.orig",
"src/main.rs",
"build1.rs",
"Cargo.lock",
],
[(
"Cargo.toml",
str![[r##"
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
cargo-features = ["multiple-build-scripts"]
[package]
edition = "2024"
name = "foo"
version = "0.1.0"
authors = []
build = "build1.rs"
include = [
"src/main.rs",
"build1.rs",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "foo"
documentation = "docs.rs/foo"
readme = false
license = "MIT"
[[bin]]
name = "foo"
path = "src/main.rs"
"##]],
)],
);
}
fn add_git_vendor_config(p: &Project, git_project: &Project) {
p.change_file(
".cargo/config.toml",
&format!(
r#"
[source."git+{url}"]
git = "{url}"
replace-with = 'vendor'
[source.vendor]
directory = 'vendor'
"#,
url = git_project.url()
),
);
}
#[cargo_test]
fn verify_vendor_multiple_build_scripts() {
let git_project = git::new("dep", |project| {
project
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "dep"
version = "0.1.0"
edition = "2024"
license = "MIT"
description = "dependency of foo"
documentation = "docs.rs/dep"
authors = []
build = ["build1.rs", "build2.rs"]
include = [ "src/main.rs", "build1.rs" ]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() {}")
.file("build2.rs", "fn main() {}")
});
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
[dependencies.dep]
git = '{}'
"#,
git_project.url()
),
)
.file("src/main.rs", "fn main() {}")
.build();
p.cargo("vendor --respect-source-config")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stderr_data(str![[r#"
[UPDATING] git repository `[ROOTURL]/dep`
[LOCKING] 1 package to latest [..] compatible version
Vendoring dep v0.1.0 ([ROOTURL]/dep#[..]) ([ROOT]/home/.cargo/git/checkouts/dep-[HASH]/[..]) to vendor/dep
[WARNING] ignoring `package.build` entry `build2.rs` as it is not included in the published package
To use vendored sources, add this to your .cargo/config.toml for this project:
"#]])
.run();
add_git_vendor_config(&p, &git_project);
assert_e2e().eq(
p.read_file("vendor/dep/Cargo.toml"),
str![[r##"
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
cargo-features = ["multiple-build-scripts"]
[package]
edition = "2024"
name = "dep"
version = "0.1.0"
authors = []
build = "build1.rs"
include = [
"src/main.rs",
"build1.rs",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "dependency of foo"
documentation = "docs.rs/dep"
readme = false
license = "MIT"
[[bin]]
name = "dep"
path = "src/main.rs"
"##]],
);
p.cargo("check")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.run();
}
#[cargo_test]
fn custom_build_script_first_index_script_failed() {
// In this, the script that is at first index in the build script array fails
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() { std::process::exit(101); }")
.file("build2.rs", "fn main() {}")
.build();
p.cargo("check -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(101)
.with_stderr_data(str![[r#"
[COMPILING] foo v0.1.0 ([ROOT]/foo)
...
[ERROR] failed to run custom build command for `foo v0.1.0 ([ROOT]/foo)`
Caused by:
process didn't exit successfully: `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build1` ([EXIT_STATUS]: 101)
...
"#]])
.run();
}
#[cargo_test]
fn custom_build_script_second_index_script_failed() {
// In this, the script that is at second index in the build script array fails
// This test was necessary because earlier, the program failed only if first script failed.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file("build1.rs", "fn main() {}")
.file("build2.rs", "fn main() { std::process::exit(101); }")
.build();
p.cargo("check -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(101)
.with_stderr_data(str![[r#"
[COMPILING] foo v0.1.0 ([ROOT]/foo)
...
[ERROR] failed to run custom build command for `foo v0.1.0 ([ROOT]/foo)`
Caused by:
process didn't exit successfully: `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build2` ([EXIT_STATUS]: 101)
...
"#]])
.run();
}
#[cargo_test]
fn build_script_with_conflicting_environment_variables() {
// In this, multiple scripts set different values to same environment variables
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file(
"src/main.rs",
r#"
const FOO: &'static str = env!("FOO");
fn main() {
println!("{}", FOO);
}
"#,
)
.file(
"build1.rs",
r#"fn main() { println!("cargo::rustc-env=FOO=bar1"); }"#,
)
.file(
"build2.rs",
r#"fn main() { println!("cargo::rustc-env=FOO=bar2"); }"#,
)
.build();
p.cargo("run -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stdout_data(str![[r#"
bar2
"#]])
.run();
}
#[cargo_test]
fn build_script_with_conflicting_out_dirs() {
// In this, multiple scripts create file with same name in their respective OUT_DIR.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
// OUT_DIR is set to the lexicographically largest build script's OUT_DIR by default
.file(
"src/main.rs",
r#"
include!(concat!(env!("OUT_DIR"), "/foo.rs"));
fn main() {
println!("{}", message());
}
"#,
)
.file(
"build1.rs",
r#"
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("foo.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, from Build Script 1!\"
}
"
).unwrap();
}"#,
)
.file(
"build2.rs",
r#"
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("foo.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, from Build Script 2!\"
}
"
).unwrap();
}"#,
)
.build();
p.cargo("run -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stdout_data(str![[r#"
Hello, from Build Script 2!
"#]])
.run();
}
#[cargo_test]
fn build_script_with_conflicts_reverse_sorted() {
// In this, multiple scripts create file with same name in their respective OUT_DIR.
// It is different from above because `package.build` is not sorted in this.
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build2.rs", "build1.rs"]
"#,
)
// OUT_DIR is set to the lexicographically largest build script's OUT_DIR by default
.file(
"src/main.rs",
r#"
include!(concat!(env!("OUT_DIR"), "/foo.rs"));
fn main() {
println!("{}", message());
}
"#,
)
.file(
"build1.rs",
r#"
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("foo.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, from Build Script 1!\"
}
"
).unwrap();
}"#,
)
.file(
"build2.rs",
r#"
use std::env;
use std::fs;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("foo.rs");
fs::write(
&dest_path,
"pub fn message() -> &'static str {
\"Hello, from Build Script 2!\"
}
"
).unwrap();
}"#,
)
.build();
p.cargo("run -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_status(0)
.with_stdout_data(str![[r#"
Hello, from Build Script 1!
"#]])
.run();
}
#[cargo_test]
fn rerun_untracks_other_files() {
let p = project()
.file(
"Cargo.toml",
r#"
cargo-features = ["multiple-build-scripts"]
[package]
name = "foo"
version = "0.1.0"
edition = "2024"
build = ["build1.rs", "build2.rs"]
"#,
)
.file("src/main.rs", "fn main() {}")
.file(
"build1.rs",
r#"
fn main() {
foo();
}
fn foo() {
let _path = "assets/foo.txt";
}
"#,
)
.file(
"build2.rs",
r#"
fn main() {
bar();
}
fn bar() {
let path = "assets/bar.txt";
println!("cargo::rerun-if-changed={path}");
}"#,
)
.file("assets/foo.txt", "foo")
.file("assets/bar.txt", "bar")
.build();
p.cargo("check")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.run();
// Editing foo.txt will also recompile now since they are separate build scripts
p.change_file("assets/foo.txt", "foo updated");
p.cargo("check -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_stderr_data(str![[r#"
[DIRTY] foo v0.1.0 ([ROOT]/foo): the [..]
[COMPILING] foo v0.1.0 ([ROOT]/foo)
[RUNNING] `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build1`
[RUNNING] `rustc --crate-name foo --edition=2024 src/main.rs [..] --crate-type bin [..]
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
// Editing bar.txt will also recompile now since they are separate build scripts
p.change_file("assets/bar.txt", "bar updated");
p.cargo("check -v")
.masquerade_as_nightly_cargo(&["multiple-build-scripts"])
.with_stderr_data(str![[r#"
[DIRTY] foo v0.1.0 ([ROOT]/foo): the [..]
[COMPILING] foo v0.1.0 ([ROOT]/foo)
[RUNNING] `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build[..]`
[RUNNING] `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build[..]`
[RUNNING] `rustc --crate-name foo --edition=2024 src/main.rs [..] --crate-type bin [..]
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}