Skip to content

Commit a225f5f

Browse files
committed
Metabuild (RFC 2196)
1 parent 8d211fb commit a225f5f

File tree

11 files changed

+851
-2
lines changed

11 files changed

+851
-2
lines changed

Diff for: src/cargo/core/compiler/custom_build.rs

+36
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
134134
let build_plan = bcx.build_config.build_plan;
135135
let invocation_name = unit.buildkey();
136136

137+
if let Some(deps) = unit.pkg.manifest().metabuild() {
138+
prepare_metabuild(cx, build_script_unit, deps)?;
139+
}
140+
137141
// Building the command to execute
138142
let to_exec = script_output.join(unit.target.name());
139143

@@ -536,6 +540,38 @@ impl BuildOutput {
536540
}
537541
}
538542

543+
fn prepare_metabuild<'a, 'cfg>(
544+
cx: &Context<'a, 'cfg>,
545+
unit: &Unit<'a>,
546+
deps: &[String],
547+
) -> CargoResult<()> {
548+
let mut output = Vec::new();
549+
let available_deps = cx.dep_targets(unit);
550+
// Filter out optional dependencies, and look up the actual lib name.
551+
let meta_deps: Vec<_> = deps
552+
.iter()
553+
.filter_map(|name| {
554+
available_deps
555+
.iter()
556+
.find(|u| u.pkg.name().as_str() == name.as_str())
557+
.map(|dep| dep.target.crate_name())
558+
})
559+
.collect();
560+
for dep in &meta_deps {
561+
output.push(format!("extern crate {};\n", dep));
562+
}
563+
output.push("fn main() {\n".to_string());
564+
for dep in &meta_deps {
565+
output.push(format!(" {}::metabuild();\n", dep));
566+
}
567+
output.push("}\n".to_string());
568+
let output = output.join("");
569+
let path = unit.target.src_path();
570+
fs::create_dir_all(path.parent().unwrap())?;
571+
paths::write_if_changed(path, &output)?;
572+
Ok(())
573+
}
574+
539575
impl BuildDeps {
540576
pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
541577
BuildDeps {

Diff for: src/cargo/core/features.rs

+3
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ features! {
189189

190190
// "default-run" manifest option,
191191
[unstable] default_run: bool,
192+
193+
// Declarative build scripts.
194+
[unstable] metabuild: bool,
192195
}
193196
}
194197

Diff for: src/cargo/core/manifest.rs

+15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub struct Manifest {
4444
edition: Edition,
4545
im_a_teapot: Option<bool>,
4646
default_run: Option<String>,
47+
metabuild: Option<Vec<String>>,
4748
}
4849

4950
/// When parsing `Cargo.toml`, some warnings should silenced
@@ -300,6 +301,7 @@ impl Manifest {
300301
im_a_teapot: Option<bool>,
301302
default_run: Option<String>,
302303
original: Rc<TomlManifest>,
304+
metabuild: Option<Vec<String>>,
303305
) -> Manifest {
304306
Manifest {
305307
summary,
@@ -321,6 +323,7 @@ impl Manifest {
321323
im_a_teapot,
322324
default_run,
323325
publish_lockfile,
326+
metabuild,
324327
}
325328
}
326329

@@ -348,6 +351,9 @@ impl Manifest {
348351
pub fn targets(&self) -> &[Target] {
349352
&self.targets
350353
}
354+
pub fn targets_mut(&mut self) -> &mut[Target] {
355+
&mut self.targets
356+
}
351357
pub fn version(&self) -> &Version {
352358
self.package_id().version()
353359
}
@@ -443,6 +449,10 @@ impl Manifest {
443449
pub fn default_run(&self) -> Option<&str> {
444450
self.default_run.as_ref().map(|s| &s[..])
445451
}
452+
453+
pub fn metabuild(&self) -> Option<&Vec<String>> {
454+
self.metabuild.as_ref()
455+
}
446456
}
447457

448458
impl VirtualManifest {
@@ -746,6 +756,11 @@ impl Target {
746756
self.doc = doc;
747757
self
748758
}
759+
pub fn set_src_path(&mut self, src_path: PathBuf) -> &mut Target {
760+
assert!(src_path.is_absolute());
761+
self.src_path = NonHashedPathBuf { path: src_path };
762+
self
763+
}
749764
}
750765

751766
impl fmt::Display for Target {

Diff for: src/cargo/core/package.rs

+3
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ impl Package {
105105
pub fn manifest(&self) -> &Manifest {
106106
&self.manifest
107107
}
108+
pub fn manifest_mut(&mut self) -> &mut Manifest {
109+
&mut self.manifest
110+
}
108111
/// Get the path to the manifest
109112
pub fn manifest_path(&self) -> &Path {
110113
&self.manifest_path

Diff for: src/cargo/core/workspace.rs

+32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
#![allow(deprecated)] // for SipHasher
2+
13
use std::cell::RefCell;
24
use std::collections::hash_map::{Entry, HashMap};
35
use std::collections::BTreeMap;
6+
use std::hash::{Hash, Hasher, SipHasher};
47
use std::path::{Path, PathBuf};
58
use std::slice;
69

@@ -150,6 +153,7 @@ impl<'cfg> Workspace<'cfg> {
150153
};
151154
ws.root_manifest = ws.find_root(manifest_path)?;
152155
ws.find_members()?;
156+
ws.fixup()?;
153157
ws.validate()?;
154158
Ok(ws)
155159
}
@@ -513,6 +517,11 @@ impl<'cfg> Workspace<'cfg> {
513517
Ok(())
514518
}
515519

520+
fn fixup(&mut self) -> CargoResult<()> {
521+
let target_dir = self.target_dir();
522+
self.packages.fixup(target_dir)
523+
}
524+
516525
/// Validates a workspace, ensuring that a number of invariants are upheld:
517526
///
518527
/// 1. A workspace only has one root.
@@ -773,6 +782,29 @@ impl<'cfg> Packages<'cfg> {
773782
}
774783
}
775784
}
785+
786+
fn fixup(&mut self, target_dir: Filesystem) -> CargoResult<()> {
787+
for maybe_pkg in self.packages.values_mut() {
788+
if let MaybePackage::Package(pkg) = maybe_pkg {
789+
let mut hasher = SipHasher::new_with_keys(0, 0);
790+
pkg.hash(&mut hasher);
791+
let hash = hasher.finish();
792+
let name = pkg.name();
793+
for target in pkg.manifest_mut().targets_mut().iter_mut() {
794+
// TODO: Don't rely on magic name?
795+
if target.is_custom_build()
796+
&& target.src_path().file_name().unwrap() == "metabuild.rs"
797+
{
798+
let path = target_dir
799+
.join(".metabuild")
800+
.join(format!("metabuild-{}-{:016x}.rs", name, hash));
801+
target.set_src_path(path.into_path_unlocked());
802+
}
803+
}
804+
}
805+
}
806+
Ok(())
807+
}
776808
}
777809

778810
impl<'a, 'cfg> Members<'a, 'cfg> {

Diff for: src/cargo/util/paths.rs

+21
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,27 @@ pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> {
142142
Ok(())
143143
}
144144

145+
pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> CargoResult<()> {
146+
(|| -> CargoResult<()> {
147+
let contents = contents.as_ref();
148+
let mut f = OpenOptions::new()
149+
.read(true)
150+
.write(true)
151+
.create(true)
152+
.open(&path)?;
153+
let mut orig = Vec::new();
154+
f.read_to_end(&mut orig)?;
155+
if orig != contents {
156+
f.set_len(0)?;
157+
f.seek(io::SeekFrom::Start(0))?;
158+
f.write_all(contents)?;
159+
}
160+
Ok(())
161+
})()
162+
.chain_err(|| format!("failed to write `{}`", path.as_ref().display()))?;
163+
Ok(())
164+
}
165+
145166
pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> {
146167
(|| -> CargoResult<()> {
147168
let mut f = OpenOptions::new()

Diff for: src/cargo/util/toml/mod.rs

+45
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,44 @@ impl TomlProfile {
484484
}
485485
}
486486

487+
#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
488+
pub struct StringOrVec(Vec<String>);
489+
490+
impl<'de> de::Deserialize<'de> for StringOrVec {
491+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
492+
where
493+
D: de::Deserializer<'de>,
494+
{
495+
struct Visitor;
496+
497+
impl<'de> de::Visitor<'de> for Visitor {
498+
type Value = StringOrVec;
499+
500+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
501+
formatter.write_str("string or list of strings")
502+
}
503+
504+
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
505+
where
506+
E: de::Error,
507+
{
508+
Ok(StringOrVec(vec![s.to_string()]))
509+
}
510+
511+
fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
512+
where
513+
V: de::SeqAccess<'de>,
514+
{
515+
let seq = de::value::SeqAccessDeserializer::new(v);
516+
Vec::deserialize(seq).map(StringOrVec)
517+
518+
}
519+
}
520+
521+
deserializer.deserialize_any(Visitor)
522+
}
523+
}
524+
487525
#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
488526
#[serde(untagged)]
489527
pub enum StringOrBool {
@@ -571,6 +609,7 @@ pub struct TomlProject {
571609
version: semver::Version,
572610
authors: Option<Vec<String>>,
573611
build: Option<StringOrBool>,
612+
metabuild: Option<StringOrVec>,
574613
links: Option<String>,
575614
exclude: Option<Vec<String>>,
576615
include: Option<Vec<String>>,
@@ -775,6 +814,10 @@ impl TomlManifest {
775814
Edition::Edition2015
776815
};
777816

817+
if project.metabuild.is_some() {
818+
features.require(Feature::metabuild())?;
819+
}
820+
778821
// If we have no lib at all, use the inferred lib if available
779822
// If we have a lib with a path, we're done
780823
// If we have a lib with no path, use the inferred lib or_else package name
@@ -784,6 +827,7 @@ impl TomlManifest {
784827
package_root,
785828
edition,
786829
&project.build,
830+
&project.metabuild,
787831
&mut warnings,
788832
&mut errors,
789833
)?;
@@ -974,6 +1018,7 @@ impl TomlManifest {
9741018
project.im_a_teapot,
9751019
project.default_run.clone(),
9761020
Rc::clone(me),
1021+
project.metabuild.clone().map(|sov| sov.0),
9771022
);
9781023
if project.license_file.is_some() && project.license.is_some() {
9791024
manifest.warnings_mut().add_warning(

Diff for: src/cargo/util/toml/targets.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ use std::collections::HashSet;
1616

1717
use core::{compiler, Edition, Target};
1818
use util::errors::CargoResult;
19-
use super::{LibKind, PathValue, StringOrBool, TomlBenchTarget, TomlBinTarget, TomlExampleTarget,
20-
TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget};
19+
use super::{LibKind, PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget,
20+
TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget};
2121

2222
pub fn targets(
2323
manifest: &TomlManifest,
2424
package_name: &str,
2525
package_root: &Path,
2626
edition: Edition,
2727
custom_build: &Option<StringOrBool>,
28+
metabuild: &Option<StringOrVec>,
2829
warnings: &mut Vec<String>,
2930
errors: &mut Vec<String>,
3031
) -> CargoResult<Vec<Target>> {
@@ -91,6 +92,9 @@ pub fn targets(
9192

9293
// processing the custom build script
9394
if let Some(custom_build) = manifest.maybe_custom_build(custom_build, package_root) {
95+
if metabuild.is_some() {
96+
bail!("cannot specify both `metabuild` and `build`");
97+
}
9498
let name = format!(
9599
"build-script-{}",
96100
custom_build
@@ -103,6 +107,24 @@ pub fn targets(
103107
package_root.join(custom_build),
104108
));
105109
}
110+
if let Some(metabuild) = metabuild {
111+
// Verify names match available build deps.
112+
let bdeps = manifest.build_dependencies.as_ref();
113+
for name in &metabuild.0 {
114+
if !bdeps.map_or(false, |bd| bd.contains_key(name)) {
115+
bail!(
116+
"metabuild package `{}` must be specified in `build-dependencies`",
117+
name
118+
);
119+
}
120+
}
121+
122+
targets.push(Target::custom_build_target(
123+
&format!("metabuild-{}", package.name),
124+
// TODO: This "magic" name is kinda weird.
125+
package_root.join("metabuild.rs"),
126+
));
127+
}
106128

107129
Ok(targets)
108130
}

Diff for: src/doc/src/reference/unstable.md

+33
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,36 @@ both `src/bin/a.rs` and `src/bin/b.rs`:
320320
[project]
321321
default-run = "a"
322322
```
323+
324+
### Metabuild
325+
* Tracking Issue: [rust-lang/rust#49803](https://github.com/rust-lang/rust/issues/49803)
326+
* RFC: [#2196](https://github.com/rust-lang/rfcs/blob/master/text/2196-metabuild.md)
327+
328+
Metabuild is a feature to have declarative build scripts. Instead of writing
329+
a `build.rs` script, you specify a list of build dependencies in the
330+
`metabuild` key in `Cargo.toml`. A build script is automatically generated
331+
that runs each build dependency in order. Metabuild packages can then read
332+
metadata from `Cargo.toml` to specify their behavior.
333+
334+
Include `cargo-features` at the top of `Cargo.toml`, a `metadata` key in the
335+
`package`, list the dependencies in `build-dependencies`, and add any metadata
336+
that the metabuild packages require. Example:
337+
338+
```toml
339+
cargo-features = ["metabuild"]
340+
341+
[package]
342+
name = "mypackage"
343+
version = "0.0.1"
344+
metabuild = ["foo", "bar"]
345+
346+
[build-dependencies]
347+
foo = "1.0"
348+
bar = "1.0"
349+
350+
[package.metadata.foo]
351+
extra-info = "qwerty"
352+
```
353+
354+
Metabuild packages should have a public function called `metabuild` that
355+
performs the same actions as a regular `build.rs` script would perform.

Diff for: tests/testsuite/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ mod jobserver;
6161
mod local_registry;
6262
mod lockfile_compat;
6363
mod login;
64+
mod metabuild;
6465
mod metadata;
6566
mod net_config;
6667
mod new;

0 commit comments

Comments
 (0)