Skip to content

Commit eabb4cd

Browse files
authored
feat: Implementation and tests for multiple-build-scripts (#15704)
Hi Everyone! This is PR for the implementation of the first milestone of [GSoC Project : Build Script Delegation](https://summerofcode.withgoogle.com/programs/2025/projects/nUt4PdAA) This will provide actual implementation for #15630 ### What does this PR try to resolve? Now, multiple build scripts are parsed, this PR aims to implement the functioning the feature. This PR will allow users to use multiple build scripts, and is backward compatible with single script as well as boolean values. **Motivation :** This will help users to maintain separate smaller and cleaner build scripts instead of one large build script. This is also necessary for build script delegation. Deferred - Accessing each build script's `OUT_DIR`: This will be handled in a follow up PR. For now, each build script writes to its own `OUT_DIR` and `OUT_DIR` for the regular build targets is set to the build script with the **lexicographically largest** name.. - User control over which build script wins in a conflict. This will be handled in a follow up PR. If two build scripts write to the same env variable, which gets applied to the binary? Currently, its the build script with the **lexicographically largest** name. This makes it deterministic. With some futzing, users can control this for now. However, with build script delegation, users won't be able to control this. We likely want it based off of the order the user assigns into the build script array. - Something about linking a C library is actually preferring **lexicographically smallest** name. We should handle conflicts consistently. We need to dig into what parts are doing it based on smallest and make sure that whatever priority scheme we use for env variables applies here as well. ### How to test and review this PR? There is a feature gate `multiple-build-scripts` that can be passed via `cargo-features` in `Cargo.toml`. So, you have to add ```toml cargo-features = ["multiple-build-scripts"] ``` Preferably on the top of the `Cargo.toml` and use nightly toolchain to use the feature
2 parents f9ee73d + bfb8697 commit eabb4cd

File tree

10 files changed

+409
-158
lines changed

10 files changed

+409
-158
lines changed

src/cargo/core/compiler/build_runner/mod.rs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,25 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
248248
args.extend(compiler::features_args(unit));
249249
args.extend(compiler::check_cfg_args(unit));
250250

251-
let script_meta = self.find_build_script_metadata(unit);
252-
if let Some(meta) = script_meta {
253-
if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
254-
for cfg in &output.cfgs {
255-
args.push("--cfg".into());
256-
args.push(cfg.into());
257-
}
251+
let script_metas = self.find_build_script_metadatas(unit);
252+
if let Some(meta_vec) = script_metas.clone() {
253+
for meta in meta_vec {
254+
if let Some(output) = self.build_script_outputs.lock().unwrap().get(meta) {
255+
for cfg in &output.cfgs {
256+
args.push("--cfg".into());
257+
args.push(cfg.into());
258+
}
258259

259-
for check_cfg in &output.check_cfgs {
260-
args.push("--check-cfg".into());
261-
args.push(check_cfg.into());
262-
}
260+
for check_cfg in &output.check_cfgs {
261+
args.push("--check-cfg".into());
262+
args.push(check_cfg.into());
263+
}
263264

264-
for (lt, arg) in &output.linker_args {
265-
if lt.applies_to(&unit.target, unit.mode) {
266-
args.push("-C".into());
267-
args.push(format!("link-arg={}", arg).into());
265+
for (lt, arg) in &output.linker_args {
266+
if lt.applies_to(&unit.target, unit.mode) {
267+
args.push("-C".into());
268+
args.push(format!("link-arg={}", arg).into());
269+
}
268270
}
269271
}
270272
}
@@ -285,7 +287,7 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
285287
args,
286288
unstable_opts,
287289
linker: self.compilation.target_linker(unit.kind).clone(),
288-
script_meta,
290+
script_metas,
289291
env: artifact::get_env(&self, self.unit_deps(unit))?,
290292
});
291293
}
@@ -420,29 +422,40 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
420422
&self.bcx.unit_graph[unit]
421423
}
422424

423-
/// Returns the `RunCustomBuild` Unit associated with the given Unit.
425+
/// Returns the `RunCustomBuild` Units associated with the given Unit.
424426
///
425427
/// If the package does not have a build script, this returns None.
426-
pub fn find_build_script_unit(&self, unit: &Unit) -> Option<Unit> {
428+
pub fn find_build_script_units(&self, unit: &Unit) -> Option<Vec<Unit>> {
427429
if unit.mode.is_run_custom_build() {
428-
return Some(unit.clone());
430+
return Some(vec![unit.clone()]);
429431
}
430-
self.bcx.unit_graph[unit]
432+
433+
let build_script_units: Vec<Unit> = self.bcx.unit_graph[unit]
431434
.iter()
432-
.find(|unit_dep| {
435+
.filter(|unit_dep| {
433436
unit_dep.unit.mode.is_run_custom_build()
434437
&& unit_dep.unit.pkg.package_id() == unit.pkg.package_id()
435438
})
436439
.map(|unit_dep| unit_dep.unit.clone())
440+
.collect();
441+
if build_script_units.is_empty() {
442+
None
443+
} else {
444+
Some(build_script_units)
445+
}
437446
}
438447

439448
/// Returns the metadata hash for the `RunCustomBuild` Unit associated with
440449
/// the given unit.
441450
///
442451
/// If the package does not have a build script, this returns None.
443-
pub fn find_build_script_metadata(&self, unit: &Unit) -> Option<UnitHash> {
444-
let script_unit = self.find_build_script_unit(unit)?;
445-
Some(self.get_run_build_script_metadata(&script_unit))
452+
pub fn find_build_script_metadatas(&self, unit: &Unit) -> Option<Vec<UnitHash>> {
453+
self.find_build_script_units(unit).map(|units| {
454+
units
455+
.iter()
456+
.map(|u| self.get_run_build_script_metadata(u))
457+
.collect()
458+
})
446459
}
447460

448461
/// Returns the metadata hash for a `RunCustomBuild` unit.
@@ -480,11 +493,11 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
480493
/// Returns a [`UnitOutput`] which represents some information about the
481494
/// output of a unit.
482495
pub fn unit_output(&self, unit: &Unit, path: &Path) -> UnitOutput {
483-
let script_meta = self.find_build_script_metadata(unit);
496+
let script_metas = self.find_build_script_metadatas(unit);
484497
UnitOutput {
485498
unit: unit.clone(),
486499
path: path.to_path_buf(),
487-
script_meta,
500+
script_metas,
488501
}
489502
}
490503

src/cargo/core/compiler/compilation.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct Doctest {
4545
/// The script metadata, if this unit's package has a build script.
4646
///
4747
/// This is used for indexing [`Compilation::extra_env`].
48-
pub script_meta: Option<UnitHash>,
48+
pub script_metas: Option<Vec<UnitHash>>,
4949

5050
/// Environment variables to set in the rustdoc process.
5151
pub env: HashMap<String, OsString>,
@@ -61,7 +61,7 @@ pub struct UnitOutput {
6161
/// The script metadata, if this unit's package has a build script.
6262
///
6363
/// This is used for indexing [`Compilation::extra_env`].
64-
pub script_meta: Option<UnitHash>,
64+
pub script_metas: Option<Vec<UnitHash>>,
6565
}
6666

6767
/// A structure returning the result of a compilation.
@@ -197,14 +197,14 @@ impl<'gctx> Compilation<'gctx> {
197197
pub fn rustdoc_process(
198198
&self,
199199
unit: &Unit,
200-
script_meta: Option<UnitHash>,
200+
script_metas: Option<&Vec<UnitHash>>,
201201
) -> CargoResult<ProcessBuilder> {
202202
let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
203203
if self.gctx.extra_verbose() {
204204
rustdoc.display_env_vars();
205205
}
206206
let cmd = fill_rustc_tool_env(rustdoc, unit);
207-
let mut cmd = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, ToolKind::Rustdoc)?;
207+
let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
208208
cmd.retry_with_argfile(true);
209209
unit.target.edition().cmd_edition_arg(&mut cmd);
210210

@@ -248,15 +248,15 @@ impl<'gctx> Compilation<'gctx> {
248248
/// target platform. This is typically used for `cargo run` and `cargo
249249
/// test`.
250250
///
251-
/// `script_meta` is the metadata for the `RunCustomBuild` unit that this
251+
/// `script_metas` is the metadata for the `RunCustomBuild` unit that this
252252
/// unit used for its build script. Use `None` if the package did not have
253253
/// a build script.
254254
pub fn target_process<T: AsRef<OsStr>>(
255255
&self,
256256
cmd: T,
257257
kind: CompileKind,
258258
pkg: &Package,
259-
script_meta: Option<UnitHash>,
259+
script_metas: Option<&Vec<UnitHash>>,
260260
) -> CargoResult<ProcessBuilder> {
261261
let builder = if let Some((runner, args)) = self.target_runner(kind) {
262262
let mut builder = ProcessBuilder::new(runner);
@@ -267,7 +267,7 @@ impl<'gctx> Compilation<'gctx> {
267267
ProcessBuilder::new(cmd)
268268
};
269269
let tool_kind = ToolKind::TargetProcess;
270-
let mut builder = self.fill_env(builder, pkg, script_meta, kind, tool_kind)?;
270+
let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
271271

272272
if let Some(client) = self.gctx.jobserver_from_env() {
273273
builder.inherit_jobserver(client);
@@ -285,7 +285,7 @@ impl<'gctx> Compilation<'gctx> {
285285
&self,
286286
mut cmd: ProcessBuilder,
287287
pkg: &Package,
288-
script_meta: Option<UnitHash>,
288+
script_metas: Option<&Vec<UnitHash>>,
289289
kind: CompileKind,
290290
tool_kind: ToolKind,
291291
) -> CargoResult<ProcessBuilder> {
@@ -343,10 +343,12 @@ impl<'gctx> Compilation<'gctx> {
343343
let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
344344

345345
cmd.env(paths::dylib_path_envvar(), &search_path);
346-
if let Some(meta) = script_meta {
347-
if let Some(env) = self.extra_env.get(&meta) {
348-
for (k, v) in env {
349-
cmd.env(k, v);
346+
if let Some(meta_vec) = script_metas {
347+
for meta in meta_vec {
348+
if let Some(env) = self.extra_env.get(meta) {
349+
for (k, v) in env {
350+
cmd.env(k, v);
351+
}
350352
}
351353
}
352354
}

src/cargo/core/compiler/custom_build.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,10 +1286,12 @@ pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
12861286

12871287
// If a package has a build script, add itself as something to inspect for linking.
12881288
if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1289-
let script_meta = build_runner
1290-
.find_build_script_metadata(unit)
1289+
let script_metas = build_runner
1290+
.find_build_script_metadatas(unit)
12911291
.expect("has_custom_build should have RunCustomBuild");
1292-
add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1292+
for script_meta in script_metas {
1293+
add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1294+
}
12931295
}
12941296

12951297
if unit.mode.is_run_custom_build() {

src/cargo/core/compiler/job_queue/mod.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -981,28 +981,30 @@ impl<'gctx> DrainState<'gctx> {
981981
show_warnings: bool,
982982
) -> CargoResult<()> {
983983
let outputs = build_runner.build_script_outputs.lock().unwrap();
984-
let Some(metadata) = build_runner.find_build_script_metadata(unit) else {
984+
let Some(metadata_vec) = build_runner.find_build_script_metadatas(unit) else {
985985
return Ok(());
986986
};
987987
let bcx = &mut build_runner.bcx;
988-
if let Some(output) = outputs.get(metadata) {
989-
if !output.log_messages.is_empty()
990-
&& (show_warnings
991-
|| output
992-
.log_messages
993-
.iter()
994-
.any(|(severity, _)| *severity == Severity::Error))
995-
{
996-
let msg_with_package =
997-
|msg: &str| format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), msg);
998-
999-
for (severity, message) in output.log_messages.iter() {
1000-
match severity {
1001-
Severity::Error => {
1002-
bcx.gctx.shell().error(msg_with_package(message))?;
1003-
}
1004-
Severity::Warning => {
1005-
bcx.gctx.shell().warn(msg_with_package(message))?;
988+
for metadata in metadata_vec {
989+
if let Some(output) = outputs.get(metadata) {
990+
if !output.log_messages.is_empty()
991+
&& (show_warnings
992+
|| output
993+
.log_messages
994+
.iter()
995+
.any(|(severity, _)| *severity == Severity::Error))
996+
{
997+
let msg_with_package =
998+
|msg: &str| format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), msg);
999+
1000+
for (severity, message) in output.log_messages.iter() {
1001+
match severity {
1002+
Severity::Error => {
1003+
bcx.gctx.shell().error(msg_with_package(message))?;
1004+
}
1005+
Severity::Warning => {
1006+
bcx.gctx.shell().warn(msg_with_package(message))?;
1007+
}
10061008
}
10071009
}
10081010
}

src/cargo/core/compiler/mod.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ fn rustc(
313313
.unwrap_or_else(|| build_runner.bcx.gctx.cwd())
314314
.to_path_buf();
315315
let fingerprint_dir = build_runner.files().fingerprint_dir(unit);
316-
let script_metadata = build_runner.find_build_script_metadata(unit);
316+
let script_metadatas = build_runner.find_build_script_metadatas(unit);
317317
let is_local = unit.is_local();
318318
let artifact = unit.artifact;
319319
let sbom_files = build_runner.sbom_output_files(unit)?;
@@ -371,7 +371,7 @@ fn rustc(
371371
)?;
372372
add_plugin_deps(&mut rustc, &script_outputs, &build_scripts, &root_output)?;
373373
}
374-
add_custom_flags(&mut rustc, &script_outputs, script_metadata)?;
374+
add_custom_flags(&mut rustc, &script_outputs, script_metadatas)?;
375375
}
376376

377377
for output in outputs.iter() {
@@ -920,7 +920,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
920920
let rustdoc_depinfo_enabled = build_runner.bcx.gctx.cli_unstable().rustdoc_depinfo;
921921

922922
let mut output_options = OutputOptions::new(build_runner, unit);
923-
let script_metadata = build_runner.find_build_script_metadata(unit);
923+
let script_metadatas = build_runner.find_build_script_metadatas(unit);
924924
let scrape_outputs = if should_include_scrape_units(build_runner.bcx, unit) {
925925
Some(
926926
build_runner
@@ -960,7 +960,7 @@ fn rustdoc(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<W
960960
add_custom_flags(
961961
&mut rustdoc,
962962
&build_script_outputs.lock().unwrap(),
963-
script_metadata,
963+
script_metadatas,
964964
)?;
965965

966966
// Add the output of scraped examples to the rustdoc command.
@@ -1686,18 +1686,20 @@ fn build_deps_args(
16861686
fn add_custom_flags(
16871687
cmd: &mut ProcessBuilder,
16881688
build_script_outputs: &BuildScriptOutputs,
1689-
metadata: Option<UnitHash>,
1689+
metadata_vec: Option<Vec<UnitHash>>,
16901690
) -> CargoResult<()> {
1691-
if let Some(metadata) = metadata {
1692-
if let Some(output) = build_script_outputs.get(metadata) {
1693-
for cfg in output.cfgs.iter() {
1694-
cmd.arg("--cfg").arg(cfg);
1695-
}
1696-
for check_cfg in &output.check_cfgs {
1697-
cmd.arg("--check-cfg").arg(check_cfg);
1698-
}
1699-
for (name, value) in output.env.iter() {
1700-
cmd.env(name, value);
1691+
if let Some(metadata_vec) = metadata_vec {
1692+
for metadata in metadata_vec {
1693+
if let Some(output) = build_script_outputs.get(metadata) {
1694+
for cfg in output.cfgs.iter() {
1695+
cmd.arg("--cfg").arg(cfg);
1696+
}
1697+
for check_cfg in &output.check_cfgs {
1698+
cmd.arg("--check-cfg").arg(check_cfg);
1699+
}
1700+
for (name, value) in output.env.iter() {
1701+
cmd.env(name, value);
1702+
}
17011703
}
17021704
}
17031705
}

src/cargo/core/compiler/output_depinfo.rs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,20 @@ fn add_deps_for_unit(
7575
}
7676

7777
// Add rerun-if-changed dependencies
78-
if let Some(metadata) = build_runner.find_build_script_metadata(unit) {
79-
if let Some(output) = build_runner
80-
.build_script_outputs
81-
.lock()
82-
.unwrap()
83-
.get(metadata)
84-
{
85-
for path in &output.rerun_if_changed {
86-
// The paths we have saved from the unit are of arbitrary relativeness and may be
87-
// relative to the crate root of the dependency.
88-
let path = unit.pkg.root().join(path);
89-
deps.insert(path);
78+
if let Some(metadata_vec) = build_runner.find_build_script_metadatas(unit) {
79+
for metadata in metadata_vec {
80+
if let Some(output) = build_runner
81+
.build_script_outputs
82+
.lock()
83+
.unwrap()
84+
.get(metadata)
85+
{
86+
for path in &output.rerun_if_changed {
87+
// The paths we have saved from the unit are of arbitrary relativeness and may be
88+
// relative to the crate root of the dependency.
89+
let path = unit.pkg.root().join(path);
90+
deps.insert(path);
91+
}
9092
}
9193
}
9294
}

0 commit comments

Comments
 (0)