Skip to content
Open
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
18 changes: 14 additions & 4 deletions src/cargo/core/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +1716 to +1722
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't is need a feature gate?

}
}

Expand Down
32 changes: 31 additions & 1 deletion src/cargo/util/toml/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//! 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::fs::{self, DirEntry};
use std::path::{Path, PathBuf};

Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -901,6 +902,35 @@ 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;
err_msg += &format!(
"\nBuild Scripts : {} have the same file stem, that is {}",
paths.join(", "),
stem
);
Comment on lines +921 to +925
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
err_msg += &format!(
"\nBuild Scripts : {} have the same file stem, that is {}",
paths.join(", "),
stem
);
write!(&mut err_msg, " for stem `{stem}`: {}", paths.join(", "))?;
  • generally better to use write! to append formatted text
  • duplicating the error message
  • needs indentation to set apart from the rest of the message

}
}
if conflict_file_stem {
anyhow::bail!(err_msg);
}
Ok(())
}

fn configure(
toml: &TomlTarget,
target: &mut Target,
Expand Down
3 changes: 3 additions & 0 deletions src/doc/src/reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ corresponding environment variable is set to the empty string, `""`.
* `OUT_DIR` --- If the package has a build script, this is set to the folder
where the build script should place its output. See below for more information.
(Only set during compilation.)
* `<script-name>_OUT_DIR` --- If the package has build script(s), this is set to the folder where the
build script should place its output. 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.)
Comment on lines +259 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be on unstable.md

* `CARGO_BIN_EXE_<name>` --- The absolute path to a binary target's executable.
This is only set when building an [integration test] or benchmark. This may
be used with the [`env` macro] to find the executable to run for testing
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this test be added before the validation?

// 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
Build Scripts : build1.rs, foo/build1.rs have the same file stem, that is build1

"#]])
.run();
}
Loading