diff --git a/Cargo.toml b/Cargo.toml index fed702dd390eec9d689c3d003cc0e75cfb9a9c02..28282bbc6d923c34282037b35397df5ab52ec9f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ serde = { version = "1.0.123", default-features = false, features = ["derive"] } serde_json = "1.0.61" tempfile = "3.2.0" url = { version = "2.2.0", features = ["serde"] } -binaryen = "0.12.0" +binaryen = { version = "0.12.0", optional = true } # dependencies for optional extrinsics feature async-std = { version = "1.9.0", optional = true } @@ -61,6 +61,7 @@ wabt = "0.10.0" [features] default = [] +binaryen-as-dependency = ["binaryen"] # Enable this for (experimental) commands to deploy, instantiate and call contracts. # diff --git a/README.md b/README.md index d765607ca404607f8c72973d0b64fe9f4df3a37e..600d4be4d1e450ada2777afa6c228928e0d6b016 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,19 @@ A CLI tool for helping setting up and managing WebAssembly smart contracts writt ## Installation -- **Prerequisites** +`rust-src` is a prerequisite: `rustup component add rust-src`. - - **rust-src**: `rustup component add rust-src` - - A C++14 compiler and python >= 3.5 is required for building the - [binaryen](https://github.com/WebAssembly/binaryen) dependency. - `binaryen` is built automatically during the `cargo-contract` build process. +We optimize the resulting contract Wasm using `binaryen`. You have two options for installing it: -- **Install latest version from [crates.io](https://crates.io/crates/cargo-contract)** - - `cargo install cargo-contract` + - _The preferred way:_ + Install [`binaryen`](https://github.com/WebAssembly/binaryen#tools). Many package managers + have it available nowadays ‒ e.g. it's a package for [Debian/Ubuntu](https://tracker.debian.org/pkg/binaryen), + [Homebrew](https://formulae.brew.sh/formula/binaryen) and [Arch Linux](https://archlinux.org/packages/community/x86_64/binaryen/). + After you've installed the package execute `cargo install --force cargo-contract`. + + - _Build `binaryen` as a dependency when installing `cargo-contract`:_ + A C++14 compiler and python >= 3.5 is required. + Execute `cargo install --force --features wasm-opt-unavailable cargo-contract`. ## Usage diff --git a/build.rs b/build.rs index 3dd691036ccaf119c39d65aae862928d83459ce4..232f09765497c2d04e227360f9f7404c7548064e 100644 --- a/build.rs +++ b/build.rs @@ -20,7 +20,7 @@ use std::{ fs::File, io::{prelude::*, Write}, iter::Iterator, - path::PathBuf, + path::{Path, PathBuf}, }; use anyhow::Result; @@ -64,7 +64,7 @@ fn main() { ); } -fn zip_dir(src_dir: &PathBuf, dst_file: &PathBuf, method: CompressionMethod) -> Result<()> { +fn zip_dir(src_dir: &Path, dst_file: &Path, method: CompressionMethod) -> Result<()> { if !src_dir.exists() { anyhow::bail!("src_dir '{}' does not exist", src_dir.display()); } diff --git a/src/cmd/build.rs b/src/cmd/build.rs index adf88e90beb793fc1abfd3fe1512dd8d906a39bf..4104a37210eb9ffd21b088603509a625932b4fa2 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -18,9 +18,12 @@ use std::{ convert::TryFrom, fs::{metadata, File}, io::{Read, Write}, - path::PathBuf, + path::{Path, PathBuf}, }; +#[cfg(not(feature = "binaryen-as-dependency"))] +use std::{io, process::Command}; + use crate::{ crate_metadata::CrateMetadata, maybe_println, util, @@ -261,23 +264,11 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result { let mut optimized = crate_metadata.dest_wasm.clone(); optimized.set_file_name(format!("{}-opt.wasm", crate_metadata.package_name)); - let codegen_config = binaryen::CodegenConfig { - // execute -O3 optimization passes (spends potentially a lot of time optimizing) - optimization_level: 3, - // the default - shrink_level: 1, - // the default - debug_info: false, - }; - let mut dest_wasm_file = File::open(crate_metadata.dest_wasm.as_os_str())?; let mut dest_wasm_file_content = Vec::new(); dest_wasm_file.read_to_end(&mut dest_wasm_file_content)?; - let mut module = binaryen::Module::read(&dest_wasm_file_content) - .map_err(|_| anyhow::anyhow!("binaryen failed to read file content"))?; - module.optimize(&codegen_config); - let optimized_wasm = module.write(); + let optimized_wasm = do_optimization(crate_metadata, &optimized, &dest_wasm_file_content, 3)?; let mut optimized_wasm_file = File::create(optimized.as_os_str())?; optimized_wasm_file.write_all(&optimized_wasm)?; @@ -293,6 +284,75 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result { }) } +/// Optimizes the Wasm supplied as `wasm` using the `binaryen-rs` dependency. +/// +/// The supplied `optimization_level` denotes the number of optimization passes, +/// resulting in potentially a lot of time spent optimizing. +/// +/// If successful, the optimized Wasm is returned as a `Vec`. +#[cfg(feature = "binaryen-as-dependency")] +fn do_optimization( + _: &CrateMetadata, + _: &Path, + wasm: &[u8], + optimization_level: u32, +) -> Result> { + let codegen_config = binaryen::CodegenConfig { + // number of optimization passes (spends potentially a lot of time optimizing) + optimization_level, + // the default + shrink_level: 1, + // the default + debug_info: false, + }; + let mut module = binaryen::Module::read(&wasm) + .map_err(|_| anyhow::anyhow!("binaryen failed to read file content"))?; + module.optimize(&codegen_config); + Ok(module.write()) +} + +/// Optimizes the Wasm supplied as `crate_metadata.dest_wasm` using +/// the `wasm-opt` binary. +/// +/// The supplied `optimization_level` denotes the number of optimization passes, +/// resulting in potentially a lot of time spent optimizing. +/// +/// If successful, the optimized Wasm file is created under `optimized` +/// and returned as a `Vec`. +#[cfg(not(feature = "binaryen-as-dependency"))] +fn do_optimization( + crate_metadata: &CrateMetadata, + optimized_dest: &Path, + _: &[u8], + optimization_level: u32, +) -> Result> { + // check `wasm-opt` is installed + if which::which("wasm-opt").is_err() { + anyhow::bail!( + "{}", + "wasm-opt is not installed. Install this tool on your system in order to \n\ + reduce the size of your contract's Wasm binary. \n\ + See https://github.com/WebAssembly/binaryen#tools" + .bright_yellow() + ); + } + + let output = Command::new("wasm-opt") + .arg(crate_metadata.dest_wasm.as_os_str()) + .arg(format!("-O{}", optimization_level)) + .arg("-o") + .arg(optimized_dest.as_os_str()) + .output()?; + + if !output.status.success() { + // Dump the output streams produced by `wasm-opt` into the stdout/stderr. + io::stdout().write_all(&output.stdout)?; + io::stderr().write_all(&output.stderr)?; + anyhow::bail!("wasm-opt optimization failed"); + } + Ok(output.stdout) +} + /// Executes build of the smart-contract which produces a wasm binary that is ready for deploying. /// /// It does so by invoking `cargo build` and then post processing the final binary.