diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b5ad2c21..8c1918161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- Removed requirement to install binaryen. The `wasm-opt` tool is now compiled into `cargo-contract`. + ## [2.0.0-alpha.4] - 2022-10-03 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 701e16443..2f0c236ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -501,6 +501,7 @@ dependencies = [ "url", "wabt", "walkdir", + "wasm-opt", "which", "zip", ] @@ -532,6 +533,9 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -597,6 +601,16 @@ dependencies = [ "cc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colored" version = "2.0.0" @@ -783,6 +797,50 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cxx" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7df2292959b7e22a5cb39d37b7e72b2c748b12f956cc409b529fddcdc8857b" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0806e5c64f74bd64b94d857b1c28cc3d493579a65f5f31e7d3451706d4025405" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2069b1573efd6e5901004e8fdca2e28bc6f47f86dc24da81182851e71cf3208" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d980827d1ec28ea6e0db545fceaa611eb8e43f70eff8c1c33cc2c96ffa0f0476" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.14.1" @@ -1645,6 +1703,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + [[package]] name = "joinery" version = "2.1.0" @@ -1817,6 +1884,15 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -2729,6 +2805,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "sct" version = "0.7.0" @@ -3310,6 +3392,25 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "substrate-bip39" version = "0.4.4" @@ -3467,18 +3568,18 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" dependencies = [ "proc-macro2", "quote", @@ -3774,6 +3875,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.3" @@ -3953,6 +4060,45 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +[[package]] +name = "wasm-opt" +version = "0.110.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02484958a1edbd8e71485c5c10e89cbcf58b3a234aafc2fe78dee79cbfbbaae5" +dependencies = [ + "anyhow", + "libc", + "strum", + "strum_macros", + "tempfile", + "thiserror", + "wasm-opt-cxx-sys", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-cxx-sys" +version = "0.110.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c150ac22758b63137b0ccbcddcc45cfaa2bb1b4d7ebc7d50f6bb76d4d52a5bf1" +dependencies = [ + "anyhow", + "cxx", + "cxx-build", + "wasm-opt-sys", +] + +[[package]] +name = "wasm-opt-sys" +version = "0.110.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc8110e347152693834f357f67b1505ab85ec9546b9546d41a6299bab06d4f9a" +dependencies = [ + "anyhow", + "cc", + "regex", +] + [[package]] name = "wasmi" version = "0.9.1" diff --git a/README.md b/README.md index 875c896dd..789db18e7 100644 --- a/README.md +++ b/README.md @@ -34,25 +34,18 @@ More relevant links: ## Installation -* Step 1: `rustup component add rust-src`. - -* Step 2: Install `binaryen` in a version >= 99: +In addition to Rust, installation requires a C++ compiler that supports C++17. +Modern releases of gcc and clang, as well as Visual Studio 2019+ should work. - * [Debian/Ubuntu](https://tracker.debian.org/pkg/binaryen): `apt-get install binaryen` - * [Homebrew](https://formulae.brew.sh/formula/binaryen): `brew install binaryen` - * [Arch Linux](https://archlinux.org/packages/community/x86_64/binaryen/): `pacman -S binaryen` - * Windows: [binary releases are available](https://github.com/WebAssembly/binaryen/releases) - - There's only an old version in your distributions package manager? Just use a - [binary release](https://github.com/WebAssembly/binaryen/releases). +* Step 1: `rustup component add rust-src`. -* Step 3: Install `dylint` +* Step 2: Install `dylint` * (MacOS) `brew install openssl` * `cargo install cargo-dylint dylint-link`. -* Step 4: `cargo install --force --locked cargo-contract`. +* Step 3: `cargo install --force --locked cargo-contract`. -You can always update the `cargo-contract` binary to the latest version by running the Step 4. +You can always update the `cargo-contract` binary to the latest version by running the Step 3. ### Installation using Docker Image diff --git a/crates/cargo-contract/Cargo.toml b/crates/cargo-contract/Cargo.toml index ae6ebf313..c98affe3d 100644 --- a/crates/cargo-contract/Cargo.toml +++ b/crates/cargo-contract/Cargo.toml @@ -40,7 +40,7 @@ serde_json = "1.0.85" tempfile = "3.3.0" url = { version = "2.3.1", features = ["serde"] } impl-serde = "0.4.0" -regex = "1.6.0" +wasm-opt = "0.110.0" # dependencies for extrinsics (deploying and calling a contract) async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } diff --git a/crates/cargo-contract/src/main.rs b/crates/cargo-contract/src/main.rs index 1cc06b674..901f4506b 100644 --- a/crates/cargo-contract/src/main.rs +++ b/crates/cargo-contract/src/main.rs @@ -50,6 +50,7 @@ use std::{ str::FromStr, }; +use ::wasm_opt::OptimizationOptions; use anyhow::{ anyhow, Error, @@ -71,6 +72,12 @@ use assert_cmd as _; #[cfg(test)] use predicates as _; +#[cfg(test)] +use regex as _; + +// Only used on windows. +use which as _; + #[derive(Debug, Parser)] #[clap(bin_name = "cargo")] #[clap(version = env!("CARGO_CONTRACT_CLI_IMPL_VERSION"))] @@ -158,6 +165,22 @@ impl From for OptimizationPasses { } } +impl From for OptimizationOptions { + fn from(passes: OptimizationPasses) -> OptimizationOptions { + match passes { + OptimizationPasses::Zero => OptimizationOptions::new_opt_level_0(), + OptimizationPasses::One => OptimizationOptions::new_opt_level_1(), + OptimizationPasses::Two => OptimizationOptions::new_opt_level_2(), + OptimizationPasses::Three => OptimizationOptions::new_opt_level_3(), + OptimizationPasses::Four => OptimizationOptions::new_opt_level_4(), + OptimizationPasses::S => OptimizationOptions::new_optimize_for_size(), + OptimizationPasses::Z => { + OptimizationOptions::new_optimize_for_size_aggressively() + } + } + } +} + #[derive(Default, Clone, Debug, Args)] pub struct VerbosityFlags { /// No output printed to stdout diff --git a/crates/cargo-contract/src/wasm_opt.rs b/crates/cargo-contract/src/wasm_opt.rs index 6ea5a6c97..0037f22d5 100644 --- a/crates/cargo-contract/src/wasm_opt.rs +++ b/crates/cargo-contract/src/wasm_opt.rs @@ -20,40 +20,19 @@ use crate::{ }; use anyhow::Result; -use colored::Colorize; -use regex::Regex; +use wasm_opt::OptimizationOptions; use std::{ fs::metadata, - path::{ - Path, - PathBuf, - }, - process::Command, - str, + path::PathBuf, }; -const WASM_OPT_INSTALLATION_SUGGESTION: &str = - "wasm-opt not found! Make sure the binary is in your PATH environment.\n\n\ - We use this tool to optimize the size of your contract's Wasm binary.\n\n\ - wasm-opt is part of the binaryen package. You can find detailed\n\ - installation instructions on https://github.com/WebAssembly/binaryen#tools.\n\n\ - There are ready-to-install packages for many platforms:\n\ - * Debian/Ubuntu: apt-get install binaryen\n\ - * Homebrew: brew install binaryen\n\ - * Arch Linux: pacman -S binaryen\n\ - * Windows: binary releases at https://github.com/WebAssembly/binaryen/releases"; - /// A helpful struct for interacting with Binaryen's `wasm-opt` tool. pub struct WasmOptHandler { - /// The path to the `wasm-opt` binary. - wasm_opt_path: PathBuf, /// The optimization level that should be used when optimizing the Wasm binary. optimization_level: OptimizationPasses, /// Whether or not to keep debugging information in the final Wasm binary. keep_debug_symbols: bool, - /// The version number of the `wasm-opt` binary being executed. - _version: u32, } impl WasmOptHandler { @@ -65,23 +44,9 @@ impl WasmOptHandler { optimization_level: OptimizationPasses, keep_debug_symbols: bool, ) -> Result { - let which = which::which("wasm-opt"); - if which.is_err() { - anyhow::bail!(WASM_OPT_INSTALLATION_SUGGESTION.to_string().bright_yellow()); - } - - let wasm_opt_path = - which.expect("we just checked if `which` returned an err; qed"); - tracing::debug!("Path to wasm-opt executable: {}", wasm_opt_path.display()); - - let version = - Self::check_wasm_opt_version_compatibility(wasm_opt_path.as_path())?; - Ok(Self { - wasm_opt_path, optimization_level, keep_debug_symbols, - _version: version, }) } @@ -103,41 +68,13 @@ impl WasmOptHandler { self.optimization_level ); - let mut command = Command::new(self.wasm_opt_path.as_path()); - command - .arg(dest_wasm.as_os_str()) - .arg(format!("-O{}", self.optimization_level)) - .arg("-o") - .arg(dest_optimized.as_os_str()) + OptimizationOptions::from(self.optimization_level) // the memory in our module is imported, `wasm-opt` needs to be told that // the memory is initialized to zeroes, otherwise it won't run the // memory-packing pre-pass. - .arg("--zero-filled-memory"); - - if self.keep_debug_symbols { - command.arg("-g"); - } - - tracing::debug!("Invoking wasm-opt with {:?}", command); - - let output = command.output().map_err(|err| { - anyhow::anyhow!( - "Executing {} failed with {:?}", - self.wasm_opt_path.display(), - err - ) - })?; - - if !output.status.success() { - let err = str::from_utf8(&output.stderr) - .expect("Cannot convert stderr output of wasm-opt to string") - .trim(); - anyhow::bail!( - "The wasm-opt optimization failed.\n\n\ - The error which wasm-opt returned was: \n{}", - err - ); - } + .zero_filled_memory(true) + .debug_info(self.keep_debug_symbols) + .run(&dest_wasm, &dest_optimized)?; if !dest_optimized.exists() { return Err(anyhow::anyhow!( @@ -157,195 +94,4 @@ impl WasmOptHandler { optimized_size, }) } - - /// Checks if the `wasm-opt` binary under `wasm_opt_path` returns a version - /// compatible with `cargo-contract`. - /// - /// Currently this must be a version >= 99. - fn check_wasm_opt_version_compatibility(wasm_opt_path: &Path) -> Result { - let mut cmd_res = Command::new(wasm_opt_path).arg("--version").output(); - - // The following condition is a workaround for a spurious CI failure: - // ``` - // Executing `"/tmp/cargo-contract.test.GGnC0p/wasm-opt-mocked" --version` failed with - // Os { code: 26, kind: ExecutableFileBusy, message: "Text file busy" } - // ``` - if cmd_res.is_err() && format!("{:?}", cmd_res).contains("ExecutableFileBusy") { - std::thread::sleep(std::time::Duration::from_secs(1)); - cmd_res = Command::new(wasm_opt_path).arg("--version").output(); - } - - let res = cmd_res.map_err(|err| { - anyhow::anyhow!( - "Executing `{:?} --version` failed with {:?}", - wasm_opt_path.display(), - err - ) - })?; - if !res.status.success() { - let err = str::from_utf8(&res.stderr) - .expect("Cannot convert stderr output of wasm-opt to string") - .trim(); - anyhow::bail!( - "Getting version information from wasm-opt failed.\n\ - The error which wasm-opt returned was: \n{}", - err - ); - } - - // ```sh - // $ wasm-opt --version - // wasm-opt version 99 (version_99-79-gc12cc3f50) - // ``` - let github_note = "\n\n\ - If you tried installing from your system package manager the best\n\ - way forward is to download a recent binary release directly:\n\n\ - https://github.com/WebAssembly/binaryen/releases\n\n\ - Make sure that the `wasm-opt` file from that release is in your `PATH`."; - let version_stdout = str::from_utf8(&res.stdout) - .expect("Cannot convert stdout output of wasm-opt to string") - .trim(); - let re = Regex::new(r"wasm-opt version (\d+)").expect("invalid regex"); - let captures = re.captures(version_stdout).ok_or_else(|| { - anyhow::anyhow!( - "Unable to extract version information from '{}'.\n\ - Your wasm-opt version is most probably too old. Make sure you use a version >= 99.{}", - version_stdout, - github_note, - ) - })?; - let version_number: u32 = captures - .get(1) // first capture group is at index 1 - .ok_or_else(|| { - anyhow::anyhow!( - "Unable to extract version number from '{:?}'", - version_stdout - ) - })? - .as_str() - .parse() - .map_err(|err| { - anyhow::anyhow!( - "Parsing version number failed with '{:?}' for '{:?}'", - err, - version_stdout - ) - })?; - - tracing::debug!( - "The wasm-opt version output is '{}', which was parsed to '{}'", - version_stdout, - version_number - ); - if version_number < 99 { - anyhow::bail!( - "Your wasm-opt version is {}, but we require a version >= 99.{}", - version_number, - github_note, - ); - } - - Ok(version_number) - } -} - -#[cfg(feature = "test-ci-only")] -#[cfg(all(test, unix))] -mod tests_ci_only { - use super::*; - - use crate::util::tests::{ - create_executable, - with_tmp_dir, - MockGuard, - }; - - /// Creates an executable `wasm-opt-mocked` file which outputs - /// "wasm-opt version `version`". - /// - /// Returns the path to this file. - /// - /// Currently works only on `unix`. - fn mock_wasm_opt_version(tmp_dir: &Path, version: &str) -> MockGuard { - let path = tmp_dir.join("wasm-opt-mocked"); - let content = format!("#!/bin/sh\necho \"wasm-opt version {}\"", version); - create_executable(&path, &content) - } - - #[test] - fn incompatible_wasm_opt_version_must_be_detected_if_built_from_repo() { - with_tmp_dir(|path| { - // given - let path = mock_wasm_opt_version(path, "98 (version_13-79-gc12cc3f50)"); - - // when - let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); - - // then - assert!(res.is_err()); - assert!( - format!("{:?}", res).starts_with( - "Err(Your wasm-opt version is 98, but we require a version >= 99." - ), - "Expected a different output, found {:?}", - res - ); - - Ok(()) - }) - } - - #[test] - fn compatible_wasm_opt_version_must_be_detected_if_built_from_repo() { - with_tmp_dir(|path| { - // given - let path = mock_wasm_opt_version(path, "99 (version_99-79-gc12cc3f50"); - - // when - let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); - - // then - assert!(res.is_ok()); - - Ok(()) - }) - } - - #[test] - fn incompatible_wasm_opt_version_must_be_detected_if_installed_as_package() { - with_tmp_dir(|path| { - // given - let path = mock_wasm_opt_version(path, "98"); - - // when - let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); - - // then - assert!(res.is_err()); - - // this println is here to debug a spuriously failing CI at the following assert. - eprintln!("error: {:?}", res); - assert!(format!("{:?}", res).starts_with( - "Err(Your wasm-opt version is 98, but we require a version >= 99." - )); - - Ok(()) - }) - } - - #[test] - fn compatible_wasm_opt_version_must_be_detected_if_installed_as_package() { - with_tmp_dir(|path| { - // given - let path = mock_wasm_opt_version(path, "99"); - - // when - let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); - - // then - assert!(res.is_ok()); - - Ok(()) - }) - } }