Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,15 +1704,34 @@ fn build_deps_args(

let mut unstable_opts = false;

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),
);
// 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);
}

// Adding output directory for each build script
let is_multiple_build_scripts_enabled = unit
.pkg
.manifest()
.unstable_features()
.require(Feature::multiple_build_scripts())
.is_ok();

if is_multiple_build_scripts_enabled {
for dep in deps {
if dep.unit.mode.is_run_custom_build() {
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);
}
}
}

for arg in extern_args(build_runner, unit, &mut unstable_opts)? {
cmd.arg(arg);
}
Expand Down
29 changes: 28 additions & 1 deletion src/cargo/util/toml/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
//! It is a bit tricky because we need match explicit information from `Cargo.toml`
//! with implicit info in directory layout.

use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::fs::{self, DirEntry};
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -104,6 +105,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!(
Expand Down Expand Up @@ -901,6 +903,31 @@ 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 = HashMap::new();
for script in scripts {
let stem = Path::new(script).file_stem().unwrap().to_str().unwrap();
seen.entry(stem)
.or_insert_with(Vec::new)
.push(script.as_str());
}
let mut conflict_file_stem = false;
let mut err_msg = String::from(
"found build scripts with duplicate file stems, but all build scripts must have a unique file stem",
);
for (stem, paths) in seen {
if paths.len() > 1 {
conflict_file_stem = true;
write!(&mut err_msg, "\n for stem `{stem}`: {}", paths.join(", "))?;
}
}
if conflict_file_stem {
anyhow::bail!(err_msg);
}
Ok(())
}

fn configure(
toml: &TomlTarget,
target: &mut Target,
Expand Down
4 changes: 4 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ version = "0.0.1"
build = ["foo.rs", "bar.rs"]
```

**Accessing Output Directories**: Output directory of each build script can be accessed by using `<script-name>_OUT_DIR`
where the `<script-name>` is the file-stem of the build script, exactly as-is.
For example, `bar_OUT_DIR` for script at `foo/bar.rs`. (Only set during compilation, can be accessed via `env!` macro)

## public-dependency
* Tracking Issue: [#44663](https://github.com/rust-lang/rust/issues/44663)

Expand Down
123 changes: 119 additions & 4 deletions tests/testsuite/build_scripts_multiple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down Expand Up @@ -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();
Expand All @@ -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#"
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -764,3 +764,118 @@ 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 build scripts with duplicate file stems, but all build scripts must have a unique file stem
for stem `build1`: build1.rs, foo/build1.rs

"#]])
.run();
}