Skip to content

[WIP] feat: Multiple build scripts #15630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
25 changes: 22 additions & 3 deletions crates/cargo-util-schemas/manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
"build": {
"anyOf": [
{
"$ref": "#/$defs/StringOrBool"
"$ref": "#/$defs/TomlPackageBuild"
},
{
"type": "null"
Expand Down Expand Up @@ -540,13 +540,22 @@
}
]
},
"StringOrBool": {
"TomlPackageBuild": {
"anyOf": [
{
"description": "If build scripts are disabled or enabled.\n If true, `build.rs` in the root folder will be the build script.",
"type": "boolean"
},
{
"description": "Path of Build Script if there's just one script.",
"type": "string"
},
{
"type": "boolean"
"description": "Vector of paths if multiple build script are to be used.",
"type": "array",
"items": {
"type": "string"
}
}
]
},
Expand Down Expand Up @@ -596,6 +605,16 @@
}
]
},
"StringOrBool": {
"anyOf": [
{
"type": "string"
},
{
"type": "boolean"
}
]
},
"TomlValue": true,
"TomlTarget": {
"type": "object",
Expand Down
43 changes: 36 additions & 7 deletions crates/cargo-util-schemas/src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ pub struct TomlPackage {
pub name: Option<PackageName>,
pub version: Option<InheritableSemverVersion>,
pub authors: Option<InheritableVecString>,
pub build: Option<StringOrBool>,
pub build: Option<TomlPackageBuild>,
pub metabuild: Option<StringOrVec>,
pub default_target: Option<String>,
pub forced_target: Option<String>,
Expand Down Expand Up @@ -254,12 +254,13 @@ impl TomlPackage {
self.authors.as_ref().map(|v| v.normalized()).transpose()
}

pub fn normalized_build(&self) -> Result<Option<&String>, UnresolvedError> {
let readme = self.build.as_ref().ok_or(UnresolvedError)?;
match readme {
StringOrBool::Bool(false) => Ok(None),
StringOrBool::Bool(true) => Err(UnresolvedError),
StringOrBool::String(value) => Ok(Some(value)),
pub fn normalized_build(&self) -> Result<Option<&[String]>, 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(std::slice::from_ref(value))),
TomlPackageBuild::MultipleScript(scripts) => Ok(Some(scripts)),
}
}

Expand Down Expand Up @@ -1700,6 +1701,34 @@ 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),

/// Vector of paths if multiple build script are to be used.
MultipleScript(Vec<String>),
}

impl<'de> Deserialize<'de> for TomlPackageBuild {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
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)
}
}

#[derive(PartialEq, Clone, Debug, Serialize)]
#[serde(untagged)]
#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))]
Expand Down
3 changes: 3 additions & 0 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 5 additions & 3 deletions src/cargo/ops/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down
18 changes: 12 additions & 6 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -344,6 +344,7 @@ fn normalize_toml(
is_embedded,
gctx,
&inherit,
features,
)?;
let package_name = &normalized_package
.normalized_name()
Expand Down Expand Up @@ -607,6 +608,7 @@ fn normalize_package_toml<'a>(
is_embedded: bool,
gctx: &GlobalContext,
inherit: &dyn Fn() -> CargoResult<&'a InheritableFields>,
features: &Features,
) -> CargoResult<Box<manifest::TomlPackage>> {
let package_root = manifest_file.parent().unwrap();

Expand Down Expand Up @@ -670,9 +672,12 @@ 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)
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();
Expand Down Expand Up @@ -2885,7 +2890,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 {
Expand All @@ -2894,13 +2900,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);
}
Expand Down
31 changes: 21 additions & 10 deletions src/cargo/util/toml/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -105,7 +105,10 @@ pub(super) fn to_targets(
if metabuild.is_some() {
anyhow::bail!("cannot specify both `metabuild` and `build`");
}
let custom_build = Path::new(custom_build);
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-{}",
custom_build
Expand Down Expand Up @@ -1076,29 +1079,37 @@ 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<StringOrBool> {
pub fn normalize_build(
build: Option<&TomlPackageBuild>,
package_root: &Path,
) -> CargoResult<Option<TomlPackageBuild>> {
const BUILD_RS: &str = "build.rs";
match build {
None => {
// If there is a `build.rs` file next to the `Cargo.toml`, assume it is
// a build script.
let build_rs = package_root.join(BUILD_RS);
if build_rs.is_file() {
Some(StringOrBool::String(BUILD_RS.to_owned()))
Ok(Some(TomlPackageBuild::SingleScript(BUILD_RS.to_owned())))
} else {
Some(StringOrBool::Bool(false))
Ok(Some(TomlPackageBuild::Auto(false)))
}
}
// Explicitly no build script.
Some(StringOrBool::Bool(false)) => build.cloned(),
Some(StringOrBool::String(build_file)) => {
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(StringOrBool::String(build))
Ok(Some(TomlPackageBuild::SingleScript(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!");
Copy link
Contributor

Choose a reason for hiding this comment

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

I forgot to call this out as well if we decided to do prepare_toml_for_vendor and prepare_toml_for_publish in this commit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess it would be better to deal with them in different commit, because it is not dependant? Or maybe we can do this in this one?

}
Some(StringOrBool::Bool(true)) => Some(StringOrBool::String(BUILD_RS.to_owned())),
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -331,6 +332,23 @@ 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
* 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)

Expand Down
2 changes: 1 addition & 1 deletion tests/testsuite/bad_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down