From 93fd488f948afecd466afdbb02ea666eb7bea89f Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Sat, 7 Jun 2025 02:56:12 +0530 Subject: [PATCH 1/9] Rename readme variable to build --- crates/cargo-util-schemas/src/manifest/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index b28aa38fb..73a9952e8 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -255,8 +255,8 @@ impl TomlPackage { } pub fn normalized_build(&self) -> Result, UnresolvedError> { - let readme = self.build.as_ref().ok_or(UnresolvedError)?; - match readme { + let build = self.build.as_ref().ok_or(UnresolvedError)?; + match build { StringOrBool::Bool(false) => Ok(None), StringOrBool::Bool(true) => Err(UnresolvedError), StringOrBool::String(value) => Ok(Some(value)), From 79730837cb7d1b7874508296cb87afae742f065d Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Sat, 7 Jun 2025 03:24:11 +0530 Subject: [PATCH 2/9] Add feature gate for multiple build scripts --- src/cargo/core/features.rs | 3 +++ src/doc/src/reference/unstable.md | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index a79695e87..a0a1a9371 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -577,6 +577,9 @@ features! { /// Allows use of editions that are not yet stable. (unstable, unstable_editions, "", "reference/unstable.html#unstable-editions"), + + /// Allows use of multiple build scripts. + (unstable, multiple_build_scripts, "", "reference/unstable.html#multiple-build-scripts"), } /// Status and metadata for a single unstable feature. diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index d9022d761..54fe74e11 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -66,6 +66,7 @@ Each new feature described below should explain how to use it. * [-Z allow-features](#allow-features) --- Provides a way to restrict which unstable features are used. * Build scripts and linking * [Metabuild](#metabuild) --- Provides declarative build scripts. + * [Multiple Build Scripts](#multiple-build-scripts) --- Allows use of multiple build scripts. * Resolver and features * [no-index-update](#no-index-update) --- Prevents cargo from updating the index cache. * [avoid-dev-deps](#avoid-dev-deps) --- Prevents the resolver from including dev-dependencies during resolution. @@ -332,6 +333,24 @@ extra-info = "qwerty" Metabuild packages should have a public function called `metabuild` that performs the same actions as a regular `build.rs` script would perform. +## Multiple Build Scripts +* Tracking Issue: [#14903](https://github.com/rust-lang/cargo/issues/14903) +* Original Pull Request: [#15630](https://github.com/rust-lang/cargo/pull/15630) + +Multiple Build Scripts feature allows you to have multiple build scripts in your package. + +Include `cargo-features` at the top of `Cargo.toml` and add `multiple-build-scripts` to enable feature. +Add the paths of the build scripts as an array in `package.build`. For example: + +```toml +cargo-features = ["multiple-build-scripts"] + +[package] +name = "mypackage" +version = "0.0.1" +build = ["foo.rs", "bar.rs"] +``` + ## public-dependency * Tracking Issue: [#44663](https://github.com/rust-lang/rust/issues/44663) From 92b00aefc3d35ec81659134fdc6072239bad48b8 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Sat, 7 Jun 2025 04:14:44 +0530 Subject: [PATCH 3/9] Fork StringOrBool into new TomlPackageBuild type and update manifest.schema.json --- .../cargo-util-schemas/manifest.schema.json | 20 +++++++++--- crates/cargo-util-schemas/src/manifest/mod.rs | 32 ++++++++++++++++--- src/cargo/ops/vendor.rs | 8 +++-- src/cargo/util/toml/mod.rs | 11 ++++--- src/cargo/util/toml/targets.rs | 23 +++++++------ 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/crates/cargo-util-schemas/manifest.schema.json b/crates/cargo-util-schemas/manifest.schema.json index 2024fea84..318abcca0 100644 --- a/crates/cargo-util-schemas/manifest.schema.json +++ b/crates/cargo-util-schemas/manifest.schema.json @@ -269,7 +269,7 @@ "build": { "anyOf": [ { - "$ref": "#/$defs/StringOrBool" + "$ref": "#/$defs/TomlPackageBuild" }, { "type": "null" @@ -540,13 +540,15 @@ } ] }, - "StringOrBool": { + "TomlPackageBuild": { "anyOf": [ { - "type": "string" + "description": "If build scripts are disabled or enabled.\n If true, `build.rs` in the root folder will be the build script.", + "type": "boolean" }, { - "type": "boolean" + "description": "Path of Build Script if there's just one script.", + "type": "string" } ] }, @@ -596,6 +598,16 @@ } ] }, + "StringOrBool": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + } + ] + }, "TomlValue": true, "TomlTarget": { "type": "object", diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index 73a9952e8..aa0248893 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -182,7 +182,7 @@ pub struct TomlPackage { pub name: Option, pub version: Option, pub authors: Option, - pub build: Option, + pub build: Option, pub metabuild: Option, pub default_target: Option, pub forced_target: Option, @@ -257,9 +257,9 @@ impl TomlPackage { pub fn normalized_build(&self) -> Result, UnresolvedError> { let build = self.build.as_ref().ok_or(UnresolvedError)?; match build { - StringOrBool::Bool(false) => Ok(None), - StringOrBool::Bool(true) => Err(UnresolvedError), - StringOrBool::String(value) => Ok(Some(value)), + TomlPackageBuild::Auto(false) => Ok(None), + TomlPackageBuild::Auto(true) => Err(UnresolvedError), + TomlPackageBuild::SingleScript(value) => Ok(Some(value)), } } @@ -1702,6 +1702,30 @@ impl<'de> Deserialize<'de> for StringOrBool { } } +#[derive(Clone, Debug, Serialize, Eq, PartialEq)] +#[serde(untagged)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +pub enum TomlPackageBuild { + /// If build scripts are disabled or enabled. + /// If true, `build.rs` in the root folder will be the build script. + Auto(bool), + + /// Path of Build Script if there's just one script. + SingleScript(String), +} + +impl<'de> Deserialize<'de> for TomlPackageBuild { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + UntaggedEnumVisitor::new() + .bool(|b| Ok(TomlPackageBuild::Auto(b))) + .string(|s| Ok(TomlPackageBuild::SingleScript(s.to_owned()))) + .deserialize(deserializer) + } +} + #[derive(PartialEq, Clone, Debug, Serialize)] #[serde(untagged)] #[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs index ce1f688d1..fde6cee40 100644 --- a/src/cargo/ops/vendor.rs +++ b/src/cargo/ops/vendor.rs @@ -12,6 +12,7 @@ use crate::util::{try_canonicalize, CargoResult, GlobalContext}; use anyhow::{bail, Context as _}; use cargo_util::{paths, Sha256}; use cargo_util_schemas::core::SourceKind; +use cargo_util_schemas::manifest::TomlPackageBuild; use serde::Serialize; use walkdir::WalkDir; @@ -513,7 +514,8 @@ fn prepare_toml_for_vendor( .package .as_mut() .expect("venedored manifests must have packages"); - if let Some(cargo_util_schemas::manifest::StringOrBool::String(path)) = &package.build { + // Validates if build script file exists. If not, warn and ignore. + if let Some(TomlPackageBuild::SingleScript(path)) = &package.build { let path = paths::normalize_path(Path::new(path)); let included = packaged_files.contains(&path); let build = if included { @@ -522,13 +524,13 @@ fn prepare_toml_for_vendor( .into_string() .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; let path = crate::util::toml::normalize_path_string_sep(path); - cargo_util_schemas::manifest::StringOrBool::String(path) + TomlPackageBuild::SingleScript(path) } else { gctx.shell().warn(format!( "ignoring `package.build` as `{}` is not included in the published package", path.display() ))?; - cargo_util_schemas::manifest::StringOrBool::Bool(false) + TomlPackageBuild::Auto(false) }; package.build = Some(build); } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 83d106783..efff0b50c 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -13,7 +13,7 @@ use cargo_platform::Platform; use cargo_util::paths; use cargo_util_schemas::manifest::{ self, PackageName, PathBaseName, TomlDependency, TomlDetailedDependency, TomlManifest, - TomlWorkspace, + TomlPackageBuild, TomlWorkspace, }; use cargo_util_schemas::manifest::{RustVersion, StringOrBool}; use itertools::Itertools; @@ -670,7 +670,7 @@ fn normalize_package_toml<'a>( .transpose()? .map(manifest::InheritableField::Value); let build = if is_embedded { - Some(StringOrBool::Bool(false)) + Some(TomlPackageBuild::Auto(false)) } else { targets::normalize_build(original_package.build.as_ref(), package_root) }; @@ -2885,7 +2885,8 @@ fn prepare_toml_for_publish( let mut package = me.package().unwrap().clone(); package.workspace = None; - if let Some(StringOrBool::String(path)) = &package.build { + // Validates if build script file exists. If not, warn and ignore. + if let Some(TomlPackageBuild::SingleScript(path)) = &package.build { let path = Path::new(path).to_path_buf(); let included = packaged_files.map(|i| i.contains(&path)).unwrap_or(true); let build = if included { @@ -2894,13 +2895,13 @@ fn prepare_toml_for_publish( .into_string() .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; let path = normalize_path_string_sep(path); - StringOrBool::String(path) + TomlPackageBuild::SingleScript(path) } else { ws.gctx().shell().warn(format!( "ignoring `package.build` as `{}` is not included in the published package", path.display() ))?; - StringOrBool::Bool(false) + TomlPackageBuild::Auto(false) }; package.build = Some(build); } diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index e2f61d551..995e0b03d 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -17,8 +17,8 @@ use std::path::{Path, PathBuf}; use anyhow::Context as _; use cargo_util::paths; use cargo_util_schemas::manifest::{ - PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, - TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget, + PathValue, StringOrVec, TomlBenchTarget, TomlBinTarget, TomlExampleTarget, TomlLibTarget, + TomlManifest, TomlPackageBuild, TomlTarget, TomlTestTarget, }; use crate::core::compiler::rustdoc::RustdocScrapeExamples; @@ -1076,7 +1076,10 @@ Cargo doesn't know which to use because multiple target files found at `{}` and /// Returns the path to the build script if one exists for this crate. #[tracing::instrument(skip_all)] -pub fn normalize_build(build: Option<&StringOrBool>, package_root: &Path) -> Option { +pub fn normalize_build( + build: Option<&TomlPackageBuild>, + package_root: &Path, +) -> Option { const BUILD_RS: &str = "build.rs"; match build { None => { @@ -1084,21 +1087,23 @@ pub fn normalize_build(build: Option<&StringOrBool>, package_root: &Path) -> Opt // a build script. let build_rs = package_root.join(BUILD_RS); if build_rs.is_file() { - Some(StringOrBool::String(BUILD_RS.to_owned())) + Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())) } else { - Some(StringOrBool::Bool(false)) + Some(TomlPackageBuild::Auto(false)) } } // Explicitly no build script. - Some(StringOrBool::Bool(false)) => build.cloned(), - Some(StringOrBool::String(build_file)) => { + Some(TomlPackageBuild::Auto(false)) => build.cloned(), + Some(TomlPackageBuild::SingleScript(build_file)) => { let build_file = paths::normalize_path(Path::new(build_file)); let build = build_file.into_os_string().into_string().expect( "`build_file` started as a String and `normalize_path` shouldn't have changed that", ); - Some(StringOrBool::String(build)) + Some(TomlPackageBuild::SingleScript(build)) + } + Some(TomlPackageBuild::Auto(true)) => { + Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())) } - Some(StringOrBool::Bool(true)) => Some(StringOrBool::String(BUILD_RS.to_owned())), } } From 126b3a18c103029d0af4fb0497946f4bf95d2936 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Thu, 12 Jun 2025 04:08:02 +0530 Subject: [PATCH 4/9] Refactor `normalized_build` to accept slice instead of string --- crates/cargo-util-schemas/src/manifest/mod.rs | 4 ++-- src/cargo/util/toml/targets.rs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index aa0248893..9a9f0f35a 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -254,12 +254,12 @@ impl TomlPackage { self.authors.as_ref().map(|v| v.normalized()).transpose() } - pub fn normalized_build(&self) -> Result, UnresolvedError> { + pub fn normalized_build(&self) -> Result, UnresolvedError> { let build = self.build.as_ref().ok_or(UnresolvedError)?; match build { TomlPackageBuild::Auto(false) => Ok(None), TomlPackageBuild::Auto(true) => Err(UnresolvedError), - TomlPackageBuild::SingleScript(value) => Ok(Some(value)), + TomlPackageBuild::SingleScript(value) => Ok(Some(std::slice::from_ref(value))), } } diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 995e0b03d..4ddd9654f 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -105,7 +105,8 @@ pub(super) fn to_targets( if metabuild.is_some() { anyhow::bail!("cannot specify both `metabuild` and `build`"); } - let custom_build = Path::new(custom_build); + assert_eq!(custom_build.len(), 1); + let custom_build = Path::new(&custom_build[0]); let name = format!( "build-script-{}", custom_build From db8dbaf3edf25e9037977c84678d079abad395cb Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Sat, 14 Jun 2025 01:51:38 +0530 Subject: [PATCH 5/9] Add initial tests for multiple build scripts (not implemented yet) --- tests/testsuite/build_scripts_multiple.rs | 271 ++++++++++++++++++++++ tests/testsuite/main.rs | 1 + 2 files changed, 272 insertions(+) create mode 100644 tests/testsuite/build_scripts_multiple.rs diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs new file mode 100644 index 000000000..853e2aebe --- /dev/null +++ b/tests/testsuite/build_scripts_multiple.rs @@ -0,0 +1,271 @@ +//! Tests for multiple build scripts feature. + +use cargo_test_support::git; +use cargo_test_support::prelude::*; +use cargo_test_support::str; +use cargo_test_support::{project, Project}; + +#[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] invalid type: sequence, expected a boolean or string + --> Cargo.toml:6:25 + | +6 | build = ["build1.rs", "build2.rs"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +"#]]) + .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(101) + .with_stderr_data(str![[r#" +[ERROR] invalid type: sequence, expected a boolean or string + --> Cargo.toml:8:25 + | +8 | build = ["build1.rs", "build2.rs"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +"#]]) + .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(101) + .with_stderr_data(str![[r#" +[ERROR] invalid type: sequence, expected a boolean or string + --> Cargo.toml:8:25 + | +8 | build = ["build1.rs", "build2.rs"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +"#]]) + .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(101) + .with_stderr_data(str![[r#" +[ERROR] invalid type: sequence, expected a boolean or string + --> Cargo.toml:13:25 + | +13 | build = ["build1.rs", "build2.rs"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +"#]]) + .run(); +} + +#[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(101) + .with_stderr_data(str![[r#" +[UPDATING] git repository `[ROOTURL]/dep` +[ERROR] invalid type: sequence, expected a boolean or string + --> ../home/.cargo/git/checkouts/dep-[HASH]/[..]/Cargo.toml:13:25 + | +13 | build = ["build1.rs", "build2.rs"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +[ERROR] failed to sync + +Caused by: + failed to load lockfile for [ROOT]/foo + +Caused by: + failed to get `dep` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` + +Caused by: + failed to load source for dependency `dep` + +Caused by: + Unable to update [ROOTURL]/dep + +"#]]) + .run(); +} + +#[cargo_test] +fn rerun_untracks_other_files() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2024" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "build.rs", + r#" +fn main() { + foo(); + bar(); +} +fn foo() { + let _path = "assets/foo.txt"; +} +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("build").run(); + + // Editing foo.txt won't recompile, leading to unnoticed changes + + p.change_file("assets/foo.txt", "foo updated"); + p.cargo("build -v") + .with_stderr_data(str![[r#" +[FRESH] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s + +"#]]) + .run(); + + // Editing bar.txt will recompile + + p.change_file("assets/bar.txt", "bar updated"); + p.cargo("build -v") + .with_stderr_data(str![[r#" +[DIRTY] foo v0.1.0 ([ROOT]/foo): the file `assets/bar.txt` has changed ([TIME_DIFF_AFTER_LAST_BUILD]) +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[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(); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 038953dc4..d841cd385 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -16,6 +16,7 @@ mod build_plan; mod build_script; mod build_script_env; mod build_script_extra_link_arg; +mod build_scripts_multiple; mod cache_lock; mod cache_messages; mod cargo; From a9d5cb8abe0b70526adfb3960b160918626d5ad9 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Fri, 20 Jun 2025 00:10:38 +0530 Subject: [PATCH 6/9] Add `MultipleScript` (error on stable, error about not implemented on nightly) --- .../cargo-util-schemas/manifest.schema.json | 7 +++ crates/cargo-util-schemas/src/manifest/mod.rs | 5 ++ src/cargo/util/toml/mod.rs | 7 ++- src/cargo/util/toml/targets.rs | 19 +++--- tests/testsuite/bad_config.rs | 2 +- tests/testsuite/build_scripts_multiple.rs | 58 +++++++++---------- 6 files changed, 58 insertions(+), 40 deletions(-) diff --git a/crates/cargo-util-schemas/manifest.schema.json b/crates/cargo-util-schemas/manifest.schema.json index 318abcca0..c584ad255 100644 --- a/crates/cargo-util-schemas/manifest.schema.json +++ b/crates/cargo-util-schemas/manifest.schema.json @@ -549,6 +549,13 @@ { "description": "Path of Build Script if there's just one script.", "type": "string" + }, + { + "description": "Vector of paths if multiple build script are to be used.", + "type": "array", + "items": { + "type": "string" + } } ] }, diff --git a/crates/cargo-util-schemas/src/manifest/mod.rs b/crates/cargo-util-schemas/src/manifest/mod.rs index 9a9f0f35a..194a5b295 100644 --- a/crates/cargo-util-schemas/src/manifest/mod.rs +++ b/crates/cargo-util-schemas/src/manifest/mod.rs @@ -260,6 +260,7 @@ impl TomlPackage { TomlPackageBuild::Auto(false) => Ok(None), TomlPackageBuild::Auto(true) => Err(UnresolvedError), TomlPackageBuild::SingleScript(value) => Ok(Some(std::slice::from_ref(value))), + TomlPackageBuild::MultipleScript(scripts) => Ok(Some(scripts)), } } @@ -1712,6 +1713,9 @@ pub enum TomlPackageBuild { /// Path of Build Script if there's just one script. SingleScript(String), + + /// Vector of paths if multiple build script are to be used. + MultipleScript(Vec), } impl<'de> Deserialize<'de> for TomlPackageBuild { @@ -1722,6 +1726,7 @@ impl<'de> Deserialize<'de> for TomlPackageBuild { UntaggedEnumVisitor::new() .bool(|b| Ok(TomlPackageBuild::Auto(b))) .string(|s| Ok(TomlPackageBuild::SingleScript(s.to_owned()))) + .seq(|value| value.deserialize().map(TomlPackageBuild::MultipleScript)) .deserialize(deserializer) } } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index efff0b50c..1913a53dc 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -344,6 +344,7 @@ fn normalize_toml( is_embedded, gctx, &inherit, + features, )?; let package_name = &normalized_package .normalized_name() @@ -607,6 +608,7 @@ fn normalize_package_toml<'a>( is_embedded: bool, gctx: &GlobalContext, inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>, + features: &Features, ) -> CargoResult> { let package_root = manifest_file.parent().unwrap(); @@ -672,7 +674,10 @@ fn normalize_package_toml<'a>( let build = if is_embedded { Some(TomlPackageBuild::Auto(false)) } else { - targets::normalize_build(original_package.build.as_ref(), package_root) + if let Some(TomlPackageBuild::MultipleScript(_)) = original_package.build { + features.require(Feature::multiple_build_scripts())?; + } + targets::normalize_build(original_package.build.as_ref(), package_root)? }; let metabuild = original_package.metabuild.clone(); let default_target = original_package.default_target.clone(); diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index 4ddd9654f..a9ac3bdc8 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -105,7 +105,9 @@ pub(super) fn to_targets( if metabuild.is_some() { anyhow::bail!("cannot specify both `metabuild` and `build`"); } - assert_eq!(custom_build.len(), 1); + if custom_build.len() > 1 { + anyhow::bail!("multiple build scripts feature is not implemented yet! ") + } let custom_build = Path::new(&custom_build[0]); let name = format!( "build-script-{}", @@ -1080,7 +1082,7 @@ Cargo doesn't know which to use because multiple target files found at `{}` and pub fn normalize_build( build: Option<&TomlPackageBuild>, package_root: &Path, -) -> Option { +) -> CargoResult> { const BUILD_RS: &str = "build.rs"; match build { None => { @@ -1088,22 +1090,25 @@ pub fn normalize_build( // a build script. let build_rs = package_root.join(BUILD_RS); if build_rs.is_file() { - Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())) + Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned()))) } else { - Some(TomlPackageBuild::Auto(false)) + Ok(Some(TomlPackageBuild::Auto(false))) } } // Explicitly no build script. - Some(TomlPackageBuild::Auto(false)) => build.cloned(), + Some(TomlPackageBuild::Auto(false)) => Ok(build.cloned()), Some(TomlPackageBuild::SingleScript(build_file)) => { let build_file = paths::normalize_path(Path::new(build_file)); let build = build_file.into_os_string().into_string().expect( "`build_file` started as a String and `normalize_path` shouldn't have changed that", ); - Some(TomlPackageBuild::SingleScript(build)) + Ok(Some(TomlPackageBuild::SingleScript(build))) } Some(TomlPackageBuild::Auto(true)) => { - Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())) + Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned()))) + } + Some(TomlPackageBuild::MultipleScript(_scripts)) => { + anyhow::bail!("multiple build scripts feature is not implemented yet!"); } } } diff --git a/tests/testsuite/bad_config.rs b/tests/testsuite/bad_config.rs index 8e7316dd2..baced0c95 100644 --- a/tests/testsuite/bad_config.rs +++ b/tests/testsuite/bad_config.rs @@ -2733,7 +2733,7 @@ fn bad_opt_level() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[ERROR] invalid type: integer `3`, expected a boolean or string +[ERROR] invalid type: integer `3`, expected a boolean, string or array --> Cargo.toml:7:25 | 7 | build = 3 diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs index 853e2aebe..764b9ce67 100644 --- a/tests/testsuite/build_scripts_multiple.rs +++ b/tests/testsuite/build_scripts_multiple.rs @@ -26,12 +26,14 @@ fn build_without_feature_enabled_aborts_with_error() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(101) .with_stderr_data(str![[r#" -[ERROR] invalid type: sequence, expected a boolean or string - --> Cargo.toml:6:25 - | -6 | build = ["build1.rs", "build2.rs"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +[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(); @@ -64,12 +66,10 @@ fn empty_multiple_build_script_project() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(101) .with_stderr_data(str![[r#" -[ERROR] invalid type: sequence, expected a boolean or string - --> Cargo.toml:8:25 - | -8 | build = ["build1.rs", "build2.rs"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + multiple build scripts feature is not implemented yet! "#]]) .run(); @@ -82,12 +82,10 @@ fn multiple_build_scripts_metadata() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(101) .with_stderr_data(str![[r#" -[ERROR] invalid type: sequence, expected a boolean or string - --> Cargo.toml:8:25 - | -8 | build = ["build1.rs", "build2.rs"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + multiple build scripts feature is not implemented yet! "#]]) .run(); @@ -123,12 +121,10 @@ fn verify_package_multiple_build_scripts() { .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) .with_status(101) .with_stderr_data(str![[r#" -[ERROR] invalid type: sequence, expected a boolean or string - --> Cargo.toml:13:25 - | -13 | build = ["build1.rs", "build2.rs"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | +[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` + +Caused by: + multiple build scripts feature is not implemented yet! "#]]) .run(); @@ -187,12 +183,6 @@ fn verify_vendor_multiple_build_scripts() { .with_status(101) .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/dep` -[ERROR] invalid type: sequence, expected a boolean or string - --> ../home/.cargo/git/checkouts/dep-[HASH]/[..]/Cargo.toml:13:25 - | -13 | build = ["build1.rs", "build2.rs"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | [ERROR] failed to sync Caused by: @@ -207,6 +197,12 @@ Caused by: Caused by: Unable to update [ROOTURL]/dep +Caused by: + failed to parse manifest at `[ROOT]/home/.cargo/git/checkouts/dep-[HASH]/[..]/Cargo.toml` + +Caused by: + multiple build scripts feature is not implemented yet! + "#]]) .run(); } @@ -258,7 +254,7 @@ fn bar() { // Editing bar.txt will recompile p.change_file("assets/bar.txt", "bar updated"); - p.cargo("build -v") + p.cargo("build -v") .with_stderr_data(str![[r#" [DIRTY] foo v0.1.0 ([ROOT]/foo): the file `assets/bar.txt` has changed ([TIME_DIFF_AFTER_LAST_BUILD]) [COMPILING] foo v0.1.0 ([ROOT]/foo) From 68429eb381845935bd7a793699dd8c6cfed32e70 Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Thu, 19 Jun 2025 03:32:31 +0530 Subject: [PATCH 7/9] Attach all build scripts to the target --- src/cargo/util/toml/targets.rs | 33 ++- tests/testsuite/build_scripts_multiple.rs | 280 +++++++++++++++++++--- 2 files changed, 258 insertions(+), 55 deletions(-) diff --git a/src/cargo/util/toml/targets.rs b/src/cargo/util/toml/targets.rs index a9ac3bdc8..e809188ef 100644 --- a/src/cargo/util/toml/targets.rs +++ b/src/cargo/util/toml/targets.rs @@ -105,22 +105,21 @@ pub(super) fn to_targets( if metabuild.is_some() { anyhow::bail!("cannot specify both `metabuild` and `build`"); } - if custom_build.len() > 1 { - anyhow::bail!("multiple build scripts feature is not implemented yet! ") + for script in custom_build { + let script_path = Path::new(script); + let name = format!( + "build-script-{}", + script_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("") + ); + targets.push(Target::custom_build_target( + &name, + package_root.join(script_path), + edition, + )); } - let custom_build = Path::new(&custom_build[0]); - let name = format!( - "build-script-{}", - custom_build - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or("") - ); - targets.push(Target::custom_build_target( - &name, - package_root.join(custom_build), - edition, - )); } if let Some(metabuild) = metabuild { // Verify names match available build deps. @@ -1107,9 +1106,7 @@ pub fn normalize_build( Some(TomlPackageBuild::Auto(true)) => { Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned()))) } - Some(TomlPackageBuild::MultipleScript(_scripts)) => { - anyhow::bail!("multiple build scripts feature is not implemented yet!"); - } + Some(TomlPackageBuild::MultipleScript(_scripts)) => Ok(build.cloned()), } } diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs index 764b9ce67..6e92cefee 100644 --- a/tests/testsuite/build_scripts_multiple.rs +++ b/tests/testsuite/build_scripts_multiple.rs @@ -1,9 +1,12 @@ //! Tests for multiple build scripts feature. +use cargo_test_support::compare::assert_e2e; use cargo_test_support::git; use cargo_test_support::prelude::*; +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() { @@ -64,12 +67,10 @@ fn empty_multiple_build_script_project() { let p = basic_empty_project(); p.cargo("check") .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) - .with_status(101) + .with_status(0) .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - multiple build scripts feature is not implemented yet! +[COMPILING] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); @@ -80,14 +81,107 @@ 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(101) - .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - multiple build scripts feature is not implemented yet! - -"#]]) + .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", + "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(); } @@ -119,15 +213,91 @@ fn verify_package_multiple_build_scripts() { p.cargo("package") .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) - .with_status(101) + .with_status(0) .with_stderr_data(str![[r#" -[ERROR] failed to parse manifest at `[ROOT]/foo/Cargo.toml` - -Caused by: - multiple build scripts feature is not implemented yet! +[PACKAGING] foo v0.1.0 ([ROOT]/foo) +[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", + "build2.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] @@ -180,31 +350,67 @@ fn verify_vendor_multiple_build_scripts() { p.cargo("vendor --respect-source-config") .masquerade_as_nightly_cargo(&["multiple-build-scripts"]) - .with_status(101) + .with_status(0) .with_stderr_data(str![[r#" [UPDATING] git repository `[ROOTURL]/dep` -[ERROR] failed to sync +[LOCKING] 1 package to latest [..] compatible version + Vendoring dep v0.1.0 ([ROOTURL]/dep#[..]) ([ROOT]/home/.cargo/git/checkouts/dep-[HASH]/[..]) to vendor/dep +To use vendored sources, add this to your .cargo/config.toml for this project: -Caused by: - failed to load lockfile for [ROOT]/foo - -Caused by: - failed to get `dep` as a dependency of package `foo v0.1.0 ([ROOT]/foo)` - -Caused by: - failed to load source for dependency `dep` - -Caused by: - Unable to update [ROOTURL]/dep - -Caused by: - failed to parse manifest at `[ROOT]/home/.cargo/git/checkouts/dep-[HASH]/[..]/Cargo.toml` - -Caused by: - multiple build scripts feature is not implemented yet! "#]]) .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", + "build2.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] From 21c2f1615d21edff3f242a554f9659beb87fd71a Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Fri, 20 Jun 2025 01:34:41 +0530 Subject: [PATCH 8/9] Add multi-build script support to `cargo publish` --- src/cargo/util/toml/mod.rs | 45 +++++++++++++---------- tests/testsuite/build_scripts_multiple.rs | 6 +-- tests/testsuite/package.rs | 8 ++-- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 1913a53dc..b93ec97fd 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -2890,25 +2890,32 @@ fn prepare_toml_for_publish( let mut package = me.package().unwrap().clone(); package.workspace = None; - // Validates if build script file exists. If not, warn and ignore. - if let Some(TomlPackageBuild::SingleScript(path)) = &package.build { - let path = Path::new(path).to_path_buf(); - let included = packaged_files.map(|i| i.contains(&path)).unwrap_or(true); - let build = if included { - let path = path - .into_os_string() - .into_string() - .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; - let path = normalize_path_string_sep(path); - TomlPackageBuild::SingleScript(path) - } else { - ws.gctx().shell().warn(format!( - "ignoring `package.build` as `{}` is not included in the published package", - path.display() - ))?; - TomlPackageBuild::Auto(false) - }; - package.build = Some(build); + // Validates if build script file is included in package. If not, warn and ignore. + if let Some(custom_build_scripts) = package.normalized_build().expect("previously normalized") { + let mut included_scripts = Vec::new(); + for script in custom_build_scripts { + let path = Path::new(script).to_path_buf(); + let included = packaged_files.map(|i| i.contains(&path)).unwrap_or(true); + if included { + let path = path + .into_os_string() + .into_string() + .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; + let path = normalize_path_string_sep(path); + included_scripts.push(path); + } else { + ws.gctx().shell().warn(format!( + "ignoring `package.build` entry `{}` as it is not included in the published package", + path.display() + ))?; + } + } + + package.build = Some(match included_scripts.len() { + 0 => TomlPackageBuild::Auto(false), + 1 => TomlPackageBuild::SingleScript(included_scripts[0].clone()), + _ => TomlPackageBuild::MultipleScript(included_scripts), + }); } let current_resolver = package .resolver diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs index 6e92cefee..fe116d85c 100644 --- a/tests/testsuite/build_scripts_multiple.rs +++ b/tests/testsuite/build_scripts_multiple.rs @@ -216,6 +216,7 @@ fn verify_package_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) @@ -256,10 +257,7 @@ edition = "2024" name = "foo" version = "0.1.0" authors = [] -build = [ - "build1.rs", - "build2.rs", -] +build = "build1.rs" include = [ "src/main.rs", "build1.rs", diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs index 65fd4b771..4d70f5bd7 100644 --- a/tests/testsuite/package.rs +++ b/tests/testsuite/package.rs @@ -3856,7 +3856,7 @@ fn normalize_case() { [WARNING] manifest has no documentation, homepage or repository. See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. [PACKAGING] foo v0.0.1 ([ROOT]/foo) -[WARNING] ignoring `package.build` as `build.rs` is not included in the published package +[WARNING] ignoring `package.build` entry `build.rs` as it is not included in the published package [WARNING] ignoring binary `foo` as `src/main.rs` is not included in the published package [WARNING] ignoring example `ExampleFoo` as `examples/ExampleFoo.rs` is not included in the published package [WARNING] ignoring test `ExplicitPath` as `tests/ExplicitPath.rs` is not included in the published package @@ -3885,7 +3885,7 @@ src/lib.rs [WARNING] manifest has no documentation, homepage or repository. See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. [PACKAGING] foo v0.0.1 ([ROOT]/foo) -[WARNING] ignoring `package.build` as `build.rs` is not included in the published package +[WARNING] ignoring `package.build` entry `build.rs` as it is not included in the published package [WARNING] ignoring binary `foo` as `src/main.rs` is not included in the published package [WARNING] ignoring example `ExampleFoo` as `examples/ExampleFoo.rs` is not included in the published package [WARNING] ignoring test `ExplicitPath` as `tests/ExplicitPath.rs` is not included in the published package @@ -4486,7 +4486,7 @@ fn discovery_inferred_build_rs_excluded() { .with_stdout_data("") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) -[WARNING] ignoring `package.build` as `build.rs` is not included in the published package +[WARNING] ignoring `package.build` entry `build.rs` as it is not included in the published package [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) @@ -4654,7 +4654,7 @@ fn discovery_explicit_build_rs_excluded() { .with_stdout_data("") .with_stderr_data(str![[r#" [PACKAGING] foo v0.0.1 ([ROOT]/foo) -[WARNING] ignoring `package.build` as `build.rs` is not included in the published package +[WARNING] ignoring `package.build` entry `build.rs` as it is not included in the published package [PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed) [VERIFYING] foo v0.0.1 ([ROOT]/foo) [COMPILING] foo v0.0.1 ([ROOT]/foo/target/package/foo-0.0.1) From 5c7f68fe5da5c7190c2209e197e7255f44accd4f Mon Sep 17 00:00:00 2001 From: Naman Garg Date: Fri, 20 Jun 2025 03:17:46 +0530 Subject: [PATCH 9/9] Add multi-build script support to `cargo vendor` --- src/cargo/ops/vendor.rs | 44 +++++++++++++---------- tests/testsuite/build_scripts_multiple.rs | 6 ++-- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/cargo/ops/vendor.rs b/src/cargo/ops/vendor.rs index fde6cee40..a0067e79c 100644 --- a/src/cargo/ops/vendor.rs +++ b/src/cargo/ops/vendor.rs @@ -514,25 +514,31 @@ fn prepare_toml_for_vendor( .package .as_mut() .expect("venedored manifests must have packages"); - // Validates if build script file exists. If not, warn and ignore. - if let Some(TomlPackageBuild::SingleScript(path)) = &package.build { - let path = paths::normalize_path(Path::new(path)); - let included = packaged_files.contains(&path); - let build = if included { - let path = path - .into_os_string() - .into_string() - .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; - let path = crate::util::toml::normalize_path_string_sep(path); - TomlPackageBuild::SingleScript(path) - } else { - gctx.shell().warn(format!( - "ignoring `package.build` as `{}` is not included in the published package", - path.display() - ))?; - TomlPackageBuild::Auto(false) - }; - package.build = Some(build); + // Validates if build script file is included in package. If not, warn and ignore. + if let Some(custom_build_scripts) = package.normalized_build().expect("previously normalized") { + let mut included_scripts = Vec::new(); + for script in custom_build_scripts { + let path = paths::normalize_path(Path::new(script)); + let included = packaged_files.contains(&path); + if included { + let path = path + .into_os_string() + .into_string() + .map_err(|_err| anyhow::format_err!("non-UTF8 `package.build`"))?; + let path = crate::util::toml::normalize_path_string_sep(path); + included_scripts.push(path); + } else { + gctx.shell().warn(format!( + "ignoring `package.build` entry `{}` as it is not included in the published package", + path.display() + ))?; + } + } + package.build = Some(match included_scripts.len() { + 0 => TomlPackageBuild::Auto(false), + 1 => TomlPackageBuild::SingleScript(included_scripts[0].clone()), + _ => TomlPackageBuild::MultipleScript(included_scripts), + }); } let lib = if let Some(target) = &me.lib { diff --git a/tests/testsuite/build_scripts_multiple.rs b/tests/testsuite/build_scripts_multiple.rs index fe116d85c..4f81b8f42 100644 --- a/tests/testsuite/build_scripts_multiple.rs +++ b/tests/testsuite/build_scripts_multiple.rs @@ -353,6 +353,7 @@ fn verify_vendor_multiple_build_scripts() { [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: @@ -381,10 +382,7 @@ edition = "2024" name = "dep" version = "0.1.0" authors = [] -build = [ - "build1.rs", - "build2.rs", -] +build = "build1.rs" include = [ "src/main.rs", "build1.rs",