Skip to content

Commit

Permalink
Metabuild (RFC 2196)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehuss committed Jul 17, 2018
1 parent 8d211fb commit a225f5f
Show file tree
Hide file tree
Showing 11 changed files with 851 additions and 2 deletions.
36 changes: 36 additions & 0 deletions src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoRes
let build_plan = bcx.build_config.build_plan;
let invocation_name = unit.buildkey();

if let Some(deps) = unit.pkg.manifest().metabuild() {
prepare_metabuild(cx, build_script_unit, deps)?;
}

// Building the command to execute
let to_exec = script_output.join(unit.target.name());

Expand Down Expand Up @@ -536,6 +540,38 @@ impl BuildOutput {
}
}

fn prepare_metabuild<'a, 'cfg>(
cx: &Context<'a, 'cfg>,
unit: &Unit<'a>,
deps: &[String],
) -> CargoResult<()> {
let mut output = Vec::new();
let available_deps = cx.dep_targets(unit);
// Filter out optional dependencies, and look up the actual lib name.
let meta_deps: Vec<_> = deps
.iter()
.filter_map(|name| {
available_deps
.iter()
.find(|u| u.pkg.name().as_str() == name.as_str())
.map(|dep| dep.target.crate_name())
})
.collect();
for dep in &meta_deps {
output.push(format!("extern crate {};\n", dep));
}
output.push("fn main() {\n".to_string());
for dep in &meta_deps {
output.push(format!(" {}::metabuild();\n", dep));
}
output.push("}\n".to_string());
let output = output.join("");
let path = unit.target.src_path();
fs::create_dir_all(path.parent().unwrap())?;
paths::write_if_changed(path, &output)?;
Ok(())
}

impl BuildDeps {
pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
BuildDeps {
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 @@ -189,6 +189,9 @@ features! {

// "default-run" manifest option,
[unstable] default_run: bool,

// Declarative build scripts.
[unstable] metabuild: bool,
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/cargo/core/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct Manifest {
edition: Edition,
im_a_teapot: Option<bool>,
default_run: Option<String>,
metabuild: Option<Vec<String>>,
}

/// When parsing `Cargo.toml`, some warnings should silenced
Expand Down Expand Up @@ -300,6 +301,7 @@ impl Manifest {
im_a_teapot: Option<bool>,
default_run: Option<String>,
original: Rc<TomlManifest>,
metabuild: Option<Vec<String>>,
) -> Manifest {
Manifest {
summary,
Expand All @@ -321,6 +323,7 @@ impl Manifest {
im_a_teapot,
default_run,
publish_lockfile,
metabuild,
}
}

Expand Down Expand Up @@ -348,6 +351,9 @@ impl Manifest {
pub fn targets(&self) -> &[Target] {
&self.targets
}
pub fn targets_mut(&mut self) -> &mut[Target] {
&mut self.targets
}
pub fn version(&self) -> &Version {
self.package_id().version()
}
Expand Down Expand Up @@ -443,6 +449,10 @@ impl Manifest {
pub fn default_run(&self) -> Option<&str> {
self.default_run.as_ref().map(|s| &s[..])
}

pub fn metabuild(&self) -> Option<&Vec<String>> {
self.metabuild.as_ref()
}
}

impl VirtualManifest {
Expand Down Expand Up @@ -746,6 +756,11 @@ impl Target {
self.doc = doc;
self
}
pub fn set_src_path(&mut self, src_path: PathBuf) -> &mut Target {
assert!(src_path.is_absolute());
self.src_path = NonHashedPathBuf { path: src_path };
self
}
}

impl fmt::Display for Target {
Expand Down
3 changes: 3 additions & 0 deletions src/cargo/core/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ impl Package {
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn manifest_mut(&mut self) -> &mut Manifest {
&mut self.manifest
}
/// Get the path to the manifest
pub fn manifest_path(&self) -> &Path {
&self.manifest_path
Expand Down
32 changes: 32 additions & 0 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![allow(deprecated)] // for SipHasher

use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::BTreeMap;
use std::hash::{Hash, Hasher, SipHasher};
use std::path::{Path, PathBuf};
use std::slice;

Expand Down Expand Up @@ -150,6 +153,7 @@ impl<'cfg> Workspace<'cfg> {
};
ws.root_manifest = ws.find_root(manifest_path)?;
ws.find_members()?;
ws.fixup()?;
ws.validate()?;
Ok(ws)
}
Expand Down Expand Up @@ -513,6 +517,11 @@ impl<'cfg> Workspace<'cfg> {
Ok(())
}

fn fixup(&mut self) -> CargoResult<()> {
let target_dir = self.target_dir();
self.packages.fixup(target_dir)
}

/// Validates a workspace, ensuring that a number of invariants are upheld:
///
/// 1. A workspace only has one root.
Expand Down Expand Up @@ -773,6 +782,29 @@ impl<'cfg> Packages<'cfg> {
}
}
}

fn fixup(&mut self, target_dir: Filesystem) -> CargoResult<()> {
for maybe_pkg in self.packages.values_mut() {
if let MaybePackage::Package(pkg) = maybe_pkg {
let mut hasher = SipHasher::new_with_keys(0, 0);
pkg.hash(&mut hasher);
let hash = hasher.finish();
let name = pkg.name();
for target in pkg.manifest_mut().targets_mut().iter_mut() {
// TODO: Don't rely on magic name?
if target.is_custom_build()
&& target.src_path().file_name().unwrap() == "metabuild.rs"
{
let path = target_dir
.join(".metabuild")
.join(format!("metabuild-{}-{:016x}.rs", name, hash));
target.set_src_path(path.into_path_unlocked());
}
}
}
}
Ok(())
}
}

impl<'a, 'cfg> Members<'a, 'cfg> {
Expand Down
21 changes: 21 additions & 0 deletions src/cargo/util/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> {
Ok(())
}

pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> CargoResult<()> {
(|| -> CargoResult<()> {
let contents = contents.as_ref();
let mut f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)?;
let mut orig = Vec::new();
f.read_to_end(&mut orig)?;
if orig != contents {
f.set_len(0)?;
f.seek(io::SeekFrom::Start(0))?;
f.write_all(contents)?;
}
Ok(())
})()
.chain_err(|| format!("failed to write `{}`", path.as_ref().display()))?;
Ok(())
}

pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> {
(|| -> CargoResult<()> {
let mut f = OpenOptions::new()
Expand Down
45 changes: 45 additions & 0 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,44 @@ impl TomlProfile {
}
}

#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
pub struct StringOrVec(Vec<String>);

impl<'de> de::Deserialize<'de> for StringOrVec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct Visitor;

impl<'de> de::Visitor<'de> for Visitor {
type Value = StringOrVec;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("string or list of strings")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(StringOrVec(vec![s.to_string()]))
}

fn visit_seq<V>(self, v: V) -> Result<Self::Value, V::Error>
where
V: de::SeqAccess<'de>,
{
let seq = de::value::SeqAccessDeserializer::new(v);
Vec::deserialize(seq).map(StringOrVec)

}
}

deserializer.deserialize_any(Visitor)
}
}

#[derive(Clone, Debug, Serialize, Eq, PartialEq)]
#[serde(untagged)]
pub enum StringOrBool {
Expand Down Expand Up @@ -571,6 +609,7 @@ pub struct TomlProject {
version: semver::Version,
authors: Option<Vec<String>>,
build: Option<StringOrBool>,
metabuild: Option<StringOrVec>,
links: Option<String>,
exclude: Option<Vec<String>>,
include: Option<Vec<String>>,
Expand Down Expand Up @@ -775,6 +814,10 @@ impl TomlManifest {
Edition::Edition2015
};

if project.metabuild.is_some() {
features.require(Feature::metabuild())?;
}

// If we have no lib at all, use the inferred lib if available
// If we have a lib with a path, we're done
// If we have a lib with no path, use the inferred lib or_else package name
Expand All @@ -784,6 +827,7 @@ impl TomlManifest {
package_root,
edition,
&project.build,
&project.metabuild,
&mut warnings,
&mut errors,
)?;
Expand Down Expand Up @@ -974,6 +1018,7 @@ impl TomlManifest {
project.im_a_teapot,
project.default_run.clone(),
Rc::clone(me),
project.metabuild.clone().map(|sov| sov.0),
);
if project.license_file.is_some() && project.license.is_some() {
manifest.warnings_mut().add_warning(
Expand Down
26 changes: 24 additions & 2 deletions src/cargo/util/toml/targets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ use std::collections::HashSet;

use core::{compiler, Edition, Target};
use util::errors::CargoResult;
use super::{LibKind, PathValue, StringOrBool, TomlBenchTarget, TomlBinTarget, TomlExampleTarget,
TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget};
use super::{LibKind, PathValue, StringOrBool, StringOrVec, TomlBenchTarget, TomlBinTarget,
TomlExampleTarget, TomlLibTarget, TomlManifest, TomlTarget, TomlTestTarget};

pub fn targets(
manifest: &TomlManifest,
package_name: &str,
package_root: &Path,
edition: Edition,
custom_build: &Option<StringOrBool>,
metabuild: &Option<StringOrVec>,
warnings: &mut Vec<String>,
errors: &mut Vec<String>,
) -> CargoResult<Vec<Target>> {
Expand Down Expand Up @@ -91,6 +92,9 @@ pub fn targets(

// processing the custom build script
if let Some(custom_build) = manifest.maybe_custom_build(custom_build, package_root) {
if metabuild.is_some() {
bail!("cannot specify both `metabuild` and `build`");
}
let name = format!(
"build-script-{}",
custom_build
Expand All @@ -103,6 +107,24 @@ pub fn targets(
package_root.join(custom_build),
));
}
if let Some(metabuild) = metabuild {
// Verify names match available build deps.
let bdeps = manifest.build_dependencies.as_ref();
for name in &metabuild.0 {
if !bdeps.map_or(false, |bd| bd.contains_key(name)) {
bail!(
"metabuild package `{}` must be specified in `build-dependencies`",
name
);
}
}

targets.push(Target::custom_build_target(
&format!("metabuild-{}", package.name),
// TODO: This "magic" name is kinda weird.
package_root.join("metabuild.rs"),
));
}

Ok(targets)
}
Expand Down
33 changes: 33 additions & 0 deletions src/doc/src/reference/unstable.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,36 @@ both `src/bin/a.rs` and `src/bin/b.rs`:
[project]
default-run = "a"
```

### Metabuild
* Tracking Issue: [rust-lang/rust#49803](https://github.com/rust-lang/rust/issues/49803)
* RFC: [#2196](https://github.com/rust-lang/rfcs/blob/master/text/2196-metabuild.md)

Metabuild is a feature to have declarative build scripts. Instead of writing
a `build.rs` script, you specify a list of build dependencies in the
`metabuild` key in `Cargo.toml`. A build script is automatically generated
that runs each build dependency in order. Metabuild packages can then read
metadata from `Cargo.toml` to specify their behavior.

Include `cargo-features` at the top of `Cargo.toml`, a `metadata` key in the
`package`, list the dependencies in `build-dependencies`, and add any metadata
that the metabuild packages require. Example:

```toml
cargo-features = ["metabuild"]

[package]
name = "mypackage"
version = "0.0.1"
metabuild = ["foo", "bar"]

[build-dependencies]
foo = "1.0"
bar = "1.0"

[package.metadata.foo]
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.
1 change: 1 addition & 0 deletions tests/testsuite/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ mod jobserver;
mod local_registry;
mod lockfile_compat;
mod login;
mod metabuild;
mod metadata;
mod net_config;
mod new;
Expand Down
Loading

0 comments on commit a225f5f

Please sign in to comment.