diff --git a/Cargo.lock b/Cargo.lock index f5f93434c..5aa64b123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-wasm", + "pretty_assertions", "pwasm-utils", "rustc_version", "serde_json", @@ -513,6 +514,16 @@ dependencies = [ "subtle 1.0.0", ] +[[package]] +name = "ctor" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "curve25519-dalek" version = "2.0.0" @@ -537,6 +548,12 @@ dependencies = [ "syn", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "digest" version = "0.8.1" @@ -1545,6 +1562,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "pallet-indices" version = "2.0.0-alpha.7" @@ -1756,6 +1782,18 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" +[[package]] +name = "pretty_assertions" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +dependencies = [ + "ansi_term", + "ctor", + "difference", + "output_vt100", +] + [[package]] name = "primitive-types" version = "0.7.2" diff --git a/Cargo.toml b/Cargo.toml index 025704aea..2fd654f50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ walkdir = "2.3.1" [dev-dependencies] assert_matches = "1.3.0" +pretty_assertions = "0.6.1" wabt = "0.9.2" [features] diff --git a/src/cmd/build.rs b/src/cmd/build.rs index ef100e4f6..64984d68a 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -23,7 +23,7 @@ use std::{ use crate::{ util, - workspace::{ManifestPath, Workspace}, + workspace::{ManifestPath, Profile, Workspace}, UnstableFlags, Verbosity, }; use anyhow::{Context, Result}; @@ -100,7 +100,10 @@ pub fn collect_crate_metadata(manifest_path: &ManifestPath) -> Result, @@ -160,7 +163,7 @@ fn build_cargo_project( .with_root_package_manifest(|manifest| { manifest .with_removed_crate_type("rlib")? - .with_profile_release_lto(true)?; + .with_profile_release_defaults(Profile::default_contract_release())?; Ok(()) })? .using_temp(xbuild)?; diff --git a/src/workspace.rs b/src/workspace.rs index 1aa708659..51ad47f3e 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -92,7 +92,7 @@ pub struct Manifest { } impl Manifest { - /// Create new CargoToml for the given manifest path. + /// Create new Manifest for the given manifest path. /// /// The path *must* be to a `Cargo.toml`. pub fn new

(path: P) -> Result @@ -137,6 +137,28 @@ impl Manifest { /// Set `[profile.release]` lto flag pub fn with_profile_release_lto(&mut self, enabled: bool) -> Result<&mut Self> { + let lto = self + .get_profile_release_table_mut()? + .entry("lto") + .or_insert(enabled.into()); + *lto = enabled.into(); + Ok(self) + } + + /// Set preferred defaults for the `[profile.release]` section + /// + /// # Note + /// + /// Existing user defined settings for this section are preserved. Only if a setting is not + /// defined is the preferred default set. + pub fn with_profile_release_defaults(&mut self, defaults: Profile) -> Result<&mut Self> { + let profile_release = self.get_profile_release_table_mut()?; + defaults.merge(profile_release); + Ok(self) + } + + /// Get mutable reference to `[profile.release]` section + fn get_profile_release_table_mut(&mut self) -> Result<&mut value::Table> { let profile = self .toml .entry("profile") @@ -146,13 +168,9 @@ impl Manifest { .ok_or(anyhow::anyhow!("profile should be a table"))? .entry("release") .or_insert(value::Value::Table(Default::default())); - let lto = release + release .as_table_mut() - .ok_or(anyhow::anyhow!("release should be a table"))? - .entry("lto") - .or_insert(enabled.into()); - *lto = enabled.into(); - Ok(self) + .ok_or(anyhow::anyhow!("release should be a table")) } /// Remove a value from the `[lib] crate-types = []` section @@ -411,3 +429,164 @@ impl Workspace { f(root_manifest_path) } } + +/// Subset of cargo profile settings to configure defaults for building contracts +pub struct Profile { + opt_level: OptLevel, + lto: Lto, + // `None` means use rustc default. + codegen_units: Option, + overflow_checks: bool, + panic: PanicStrategy, +} + +impl Profile { + /// The preferred set of defaults for compiling a release build of a contract + pub fn default_contract_release() -> Profile { + Profile { + opt_level: OptLevel::Z, + lto: Lto::Fat, + codegen_units: Some(1), + overflow_checks: true, + panic: PanicStrategy::Abort, + } + } + + /// Set any unset profile settings from the config. + /// + /// Therefore: + /// - If the user has explicitly defined a profile setting, it will not be overwritten. + /// - If a profile setting is not defined, the value from this profile instance will be added + fn merge(&self, profile: &mut value::Table) { + let mut set_value_if_vacant = |key: &'static str, value: value::Value| { + if !profile.contains_key(key) { + profile.insert(key.into(), value); + } + }; + set_value_if_vacant("opt-level", self.opt_level.to_toml_value()); + set_value_if_vacant("lto", self.lto.to_toml_value()); + if let Some(codegen_units) = self.codegen_units { + set_value_if_vacant("codegen-units", codegen_units.into()); + } + set_value_if_vacant("overflow-checks", self.overflow_checks.into()); + set_value_if_vacant("panic", self.panic.to_toml_value()); + } +} + +/// The [`opt-level`](https://doc.rust-lang.org/cargo/reference/profiles.html#opt-level) setting +#[allow(unused)] +#[derive(Clone, Copy)] +pub enum OptLevel { + NoOptimizations, + O1, + O2, + O3, + S, + Z, +} + +impl OptLevel { + fn to_toml_value(&self) -> value::Value { + match self { + OptLevel::NoOptimizations => 0.into(), + OptLevel::O1 => 1.into(), + OptLevel::O2 => 2.into(), + OptLevel::O3 => 3.into(), + OptLevel::S => "s".into(), + OptLevel::Z => "z".into(), + } + } +} + +/// The [`link-time-optimization`](https://doc.rust-lang.org/cargo/reference/profiles.html#lto) setting. +#[derive(Clone, Copy)] +#[allow(unused)] +pub enum Lto { + /// Sets `lto = false` + ThinLocal, + /// Sets `lto = "fat"`, the equivalent of `lto = true` + Fat, + /// Sets `lto = "thin"` + Thin, + /// Sets `lto = "off"` + Off, +} + +impl Lto { + fn to_toml_value(&self) -> value::Value { + match self { + Lto::ThinLocal => value::Value::Boolean(false), + Lto::Fat => value::Value::String("fat".into()), + Lto::Thin => value::Value::String("thin".into()), + Lto::Off => value::Value::String("off".into()), + } + } +} + +/// The `panic` setting. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] +#[allow(unused)] +pub enum PanicStrategy { + Unwind, + Abort, +} + +impl PanicStrategy { + fn to_toml_value(&self) -> value::Value { + match self { + PanicStrategy::Unwind => "unwind".into(), + PanicStrategy::Abort => "abort".into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + + #[test] + fn merge_profile_inserts_preferred_defaults() { + let profile = Profile::default_contract_release(); + + // no `[profile.release]` section specified + let manifest_toml = ""; + let mut expected = toml::value::Table::new(); + expected.insert("opt-level".into(), value::Value::String("z".into())); + expected.insert("lto".into(), value::Value::String("fat".into())); + expected.insert("codegen-units".into(), value::Value::Integer(1)); + expected.insert("overflow-checks".into(), value::Value::Boolean(true)); + expected.insert("panic".into(), value::Value::String("abort".into())); + + let mut manifest_profile = toml::from_str(manifest_toml).unwrap(); + + profile.merge(&mut manifest_profile); + + assert_eq!(expected, manifest_profile) + } + + #[test] + fn merge_profile_preserves_user_defined_settings() { + let profile = Profile::default_contract_release(); + + let manifest_toml = r#" + panic = "unwind" + lto = false + opt-level = 3 + overflow-checks = false + codegen-units = 256 + "#; + let mut expected = toml::value::Table::new(); + expected.insert("opt-level".into(), value::Value::Integer(3)); + expected.insert("lto".into(), value::Value::Boolean(false)); + expected.insert("codegen-units".into(), value::Value::Integer(256)); + expected.insert("overflow-checks".into(), value::Value::Boolean(false)); + expected.insert("panic".into(), value::Value::String("unwind".into())); + + let mut manifest_profile = toml::from_str(manifest_toml).unwrap(); + + profile.merge(&mut manifest_profile); + + assert_eq!(expected, manifest_profile) + } +} diff --git a/template/_Cargo.toml b/template/_Cargo.toml index 125152854..a343b2378 100644 --- a/template/_Cargo.toml +++ b/template/_Cargo.toml @@ -52,12 +52,6 @@ ink-generate-abi = [ ] ink-as-dependency = [] -[profile.release] -panic = "abort" -lto = true -opt-level = "z" -overflow-checks = true - [workspace] members = [ ".ink/abi_gen"