diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index bcdfac9edbb..e84caddba67 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -1704,12 +1704,22 @@ fn build_deps_args( let mut unstable_opts = false; + // Add `OUT_DIR` environment variables for build scripts + let first_custom_build_dep = deps.iter().find(|dep| dep.unit.mode.is_run_custom_build()); + if let Some(dep) = first_custom_build_dep { + let out_dir = &build_runner.files().build_script_out_dir(&dep.unit); + cmd.env("OUT_DIR", &out_dir); + } + for dep in deps { if dep.unit.mode.is_run_custom_build() { - cmd.env( - "OUT_DIR", - &build_runner.files().build_script_out_dir(&dep.unit), - ); + let out_dir = &build_runner.files().build_script_out_dir(&dep.unit); + let target_name = dep.unit.target.name(); + let out_dir_prefix = target_name + .strip_prefix("build-script-") + .unwrap_or(target_name); + let out_dir_name = format!("{out_dir_prefix}_OUT_DIR"); + cmd.env(&out_dir_name, &out_dir); } } diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 65c4ea70771..5328cafbf4d 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -104,6 +104,7 @@ pub(super) fn to_targets( if metabuild.is_some() { anyhow::bail!("cannot specify both `metabuild` and `build`"); } + validate_unique_build_scripts(custom_build)?; for script in custom_build { let script_path = Path::new(script); let name = format!( @@ -901,6 +902,22 @@ fn validate_unique_names(targets: &[TomlTarget], target_kind: &str) -> CargoResu Ok(()) } +/// Will check a list of build scripts, and make sure script file stems are unique within a vector. +fn validate_unique_build_scripts(scripts: &[String]) -> CargoResult<()> { + let mut seen = HashSet::new(); + for script in scripts { + let stem = Path::new(script).file_stem().unwrap().to_str().unwrap(); + if !seen.insert(stem) { + anyhow::bail!( + "found duplicate build script file stem {}, \ + but all build scripts must have a unique file stem", + stem + ); + } + } + Ok(()) +} + fn configure( toml: &TomlTarget, target: &mut Target, diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs index 8e7457fb954..867d045ee00 100644 --- a/tests/testsuite/build_scripts_multiple.rs +++ b/tests/testsuite/build_scripts_multiple.rs @@ -549,7 +549,7 @@ fn build_script_with_conflicting_out_dirs() { build = ["build1.rs", "build2.rs"] "#, ) - // OUT_DIR is set to the lexicographically largest build script's OUT_DIR by default + // By default, OUT_DIR is set to that of the first build script in the array .file( "src/main.rs", r#" @@ -603,7 +603,7 @@ fn build_script_with_conflicting_out_dirs() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(0) .with_stdout_data(str![[r#" -Hello, from Build Script 2! +Hello, from Build Script 1! "#]]) .run(); @@ -628,7 +628,7 @@ fn build_script_with_conflicts_reverse_sorted() { build = ["build2.rs", "build1.rs"] "#, ) - // OUT_DIR is set to the lexicographically largest build script's OUT_DIR by default + // By default, OUT_DIR is set to that of the first build script in the array .file( "src/main.rs", r#" @@ -682,7 +682,7 @@ fn build_script_with_conflicts_reverse_sorted() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(0) .with_stdout_data(str![[r#" -Hello, from Build Script 1! +Hello, from Build Script 2! "#]]) .run(); @@ -764,3 +764,117 @@ fn bar() { "#]]) .run(); } + +#[cargo_test] +fn multiple_out_dirs() { + // Test to verify access to the `OUT_DIR` of the respective build scripts. + + 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#" + include!(concat!(env!("build1_OUT_DIR"), "/foo.rs")); + include!(concat!(env!("build2_OUT_DIR"), "/foo.rs")); + fn main() { + println!("{}", message1()); + println!("{}", message2()); + } + "#, + ) + .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 message1() -> &'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 message2() -> &'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! +Hello, from Build Script 2! + +"#]]) + .run(); +} + +#[cargo_test] +fn duplicate_build_script_stems() { + // Test to verify that duplicate build script file stems throws error. + + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["multiple-build-scripts"] + + [package] + name = "foo" + version = "0.1.0" + edition = "2024" + build = ["build1.rs", "foo/build1.rs"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("build1.rs", "fn main() {}") + .file("foo/build1.rs", "fn main() {}") + .build(); + + p.cargo("check -v") + .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: + found duplicate build script file stem build1, but all build scripts must have a unique file stem + +"#]]) + .run(); +}