From ed282dbe59eb8fc718554e70bb24d66ba56285f5 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Thu, 4 Aug 2022 13:08:44 -0400 Subject: [PATCH 01/12] Add `WasmOptHandler` Just a basic copy-pasta of what's in the existing optimization code --- src/cmd/build.rs | 111 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 72cf7dc89..a2cd35bbe 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -550,13 +550,22 @@ fn optimize_wasm( "{}-opt.wasm", crate_metadata.contract_artifact_name )); - do_optimization( + + let mut handler = WasmOptHandler::new()?; + handler.set_optimization_level(optimization_passes); + handler.set_debug_symbols(keep_debug_symbols); + handler.optimize( crate_metadata.dest_wasm.as_os_str(), dest_optimized.as_os_str(), - optimization_passes, - keep_debug_symbols, )?; + // do_optimization( + // crate_metadata.dest_wasm.as_os_str(), + // dest_optimized.as_os_str(), + // optimization_passes, + // keep_debug_symbols, + // )?; + if !dest_optimized.exists() { return Err(anyhow::anyhow!( "Optimization failed, optimized wasm output file `{}` not found.", @@ -564,6 +573,7 @@ fn optimize_wasm( )) } + // TODO: Maybe get this from the handler instead of reading it from a file? let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0; let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; @@ -929,6 +939,101 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { }) } +#[derive(Default)] +struct WasmOptHandler { + wasm_opt_path: PathBuf, + optimization_level: OptimizationPasses, + keep_debug_symbols: bool, +} + +impl WasmOptHandler { + fn new() -> Result { + let which = which::which("wasm-opt"); + if which.is_err() { + anyhow::bail!( + "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" + .to_string() + .bright_yellow() + ); + } + + let wasm_opt_path = + which.expect("we just checked if `which` returned an err; qed"); + log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); + + // TODO: Should use our new `version` functions do to this + check_wasm_opt_version_compatibility(wasm_opt_path.as_path())?; + + Ok(Self { + wasm_opt_path, + optimization_level: Default::default(), + keep_debug_symbols: Default::default(), + }) + } + + // Should take a path to the binary we want to optimize + fn optimize(&self, dest_wasm: &OsStr, dest_optimized: &OsStr) -> Result<()> { + log::info!( + "Optimization level passed to wasm-opt: {}", + self.optimization_level + ); + let mut command = Command::new(self.wasm_opt_path.as_path()); + command + .arg(dest_wasm) + .arg(format!("-O{}", self.optimization_level)) + .arg("-o") + .arg(dest_optimized) + // 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"); + } + log::info!("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 + ); + } + + Ok(()) + } + + fn version(&self) -> u32 { + todo!() + } + + fn set_optimization_level(&mut self, optimization_level: OptimizationPasses) { + self.optimization_level = optimization_level; + } + + fn set_debug_symbols(&mut self, keep: bool) { + self.keep_debug_symbols = keep; + } +} + #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests_ci_only { From 68014210d579b818dc50e9fb3bb1d0eef61ef76a Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 5 Aug 2022 12:48:18 -0400 Subject: [PATCH 02/12] Move code from `optimize_wasm` into a handler method --- src/cmd/build.rs | 94 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 29 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index a2cd35bbe..26df438ef 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -551,14 +551,6 @@ fn optimize_wasm( crate_metadata.contract_artifact_name )); - let mut handler = WasmOptHandler::new()?; - handler.set_optimization_level(optimization_passes); - handler.set_debug_symbols(keep_debug_symbols); - handler.optimize( - crate_metadata.dest_wasm.as_os_str(), - dest_optimized.as_os_str(), - )?; - // do_optimization( // crate_metadata.dest_wasm.as_os_str(), // dest_optimized.as_os_str(), @@ -566,6 +558,9 @@ fn optimize_wasm( // keep_debug_symbols, // )?; + // NANDO: This should probably be part of the WasmOptHandler as well + // Maybe optimize can return an `OptimizationResult` + if !dest_optimized.exists() { return Err(anyhow::anyhow!( "Optimization failed, optimized wasm output file `{}` not found.", @@ -749,6 +744,7 @@ fn check_wasm_opt_version_compatibility(wasm_opt_path: &Path) -> Result<()> { github_note, ); } + Ok(()) } @@ -866,8 +862,18 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { format!("[4/{}]", build_artifact.steps()).bold(), "Optimizing wasm file".bright_green().bold() ); - let optimization_result = - optimize_wasm(&crate_metadata, optimization_passes, keep_debug_symbols)?; + + // let optimization_result = + // optimize_wasm(&crate_metadata, optimization_passes, keep_debug_symbols)?; + + let mut handler = WasmOptHandler::new()?; + handler.set_optimization_level(optimization_passes); + handler.set_debug_symbols(keep_debug_symbols); + let optimization_result = handler.optimize( + &crate_metadata.dest_wasm, + &mut crate_metadata.dest_wasm.clone(), + &crate_metadata.contract_artifact_name, + )?; Ok(optimization_result) }; @@ -939,11 +945,13 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { }) } -#[derive(Default)] struct WasmOptHandler { wasm_opt_path: PathBuf, optimization_level: OptimizationPasses, keep_debug_symbols: bool, + // Take these from optimize_wasm + // output_path: PathBuf, + // artifact_name: String, } impl WasmOptHandler { @@ -951,18 +959,18 @@ impl WasmOptHandler { let which = which::which("wasm-opt"); if which.is_err() { anyhow::bail!( - "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" - .to_string() - .bright_yellow() - ); + "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" + .to_string() + .bright_yellow() + ); } let wasm_opt_path = @@ -979,26 +987,38 @@ impl WasmOptHandler { }) } - // Should take a path to the binary we want to optimize - fn optimize(&self, dest_wasm: &OsStr, dest_optimized: &OsStr) -> Result<()> { + fn optimize( + &self, + dest_wasm: &PathBuf, + dest_optimized: &mut PathBuf, + contract_artifact_name: &String, + ) -> Result { + // We'll create a temporary file for our optimized Wasm binary. Note that we'll later + // overwrite this with the original path of the Wasm binary. + dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); + log::info!( "Optimization level passed to wasm-opt: {}", self.optimization_level ); + let mut command = Command::new(self.wasm_opt_path.as_path()); command - .arg(dest_wasm) + .arg(dest_wasm.as_os_str()) .arg(format!("-O{}", self.optimization_level)) .arg("-o") - .arg(dest_optimized) + .arg(dest_optimized.as_os_str()) // 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"); } + log::info!("Invoking wasm-opt with {:?}", command); + let output = command.output().map_err(|err| { anyhow::anyhow!( "Executing {} failed with {:?}", @@ -1013,12 +1033,28 @@ impl WasmOptHandler { .trim(); anyhow::bail!( "The wasm-opt optimization failed.\n\n\ - The error which wasm-opt returned was: \n{}", + The error which wasm-opt returned was: \n{}", err ); } - Ok(()) + if !dest_optimized.exists() { + return Err(anyhow::anyhow!( + "Optimization failed, optimized wasm output file `{}` not found.", + dest_optimized.display() + )) + } + + let original_size = metadata(&dest_wasm)?.len() as f64 / 1000.0; + let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; + + // Overwrite existing destination wasm file with the optimised version + std::fs::rename(&dest_optimized, &dest_wasm)?; + Ok(OptimizationResult { + dest_wasm: dest_wasm.clone(), + original_size, + optimized_size, + }) } fn version(&self) -> u32 { From 04fce2f263fc9a5d30332d0698fcffa66af0651d Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 5 Aug 2022 12:50:46 -0400 Subject: [PATCH 03/12] Pull suggestion string into const --- src/cmd/build.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 26df438ef..813095efd 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -945,6 +945,17 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { }) } +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"; + struct WasmOptHandler { wasm_opt_path: PathBuf, optimization_level: OptimizationPasses, @@ -958,19 +969,7 @@ impl WasmOptHandler { fn new() -> Result { let which = which::which("wasm-opt"); if which.is_err() { - anyhow::bail!( - "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" - .to_string() - .bright_yellow() - ); + anyhow::bail!(WASM_OPT_INSTALLATION_SUGGESTION.to_string().bright_yellow()); } let wasm_opt_path = From bbec6570e8083736e7f256cf42b75694f09b92d1 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Fri, 5 Aug 2022 12:57:58 -0400 Subject: [PATCH 04/12] Remove old Wasm optimization functions --- src/cmd/build.rs | 127 ++--------------------------------------------- 1 file changed, 5 insertions(+), 122 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 813095efd..d7b5bd3e3 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -536,128 +536,6 @@ fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> { Ok(()) } -/// Attempts to perform optional Wasm optimization using `binaryen`. -/// -/// The intention is to reduce the size of bloated Wasm binaries as a result of missing -/// optimizations (or bugs?) between Rust and Wasm. -fn optimize_wasm( - crate_metadata: &CrateMetadata, - optimization_passes: OptimizationPasses, - keep_debug_symbols: bool, -) -> Result { - let mut dest_optimized = crate_metadata.dest_wasm.clone(); - dest_optimized.set_file_name(format!( - "{}-opt.wasm", - crate_metadata.contract_artifact_name - )); - - // do_optimization( - // crate_metadata.dest_wasm.as_os_str(), - // dest_optimized.as_os_str(), - // optimization_passes, - // keep_debug_symbols, - // )?; - - // NANDO: This should probably be part of the WasmOptHandler as well - // Maybe optimize can return an `OptimizationResult` - - if !dest_optimized.exists() { - return Err(anyhow::anyhow!( - "Optimization failed, optimized wasm output file `{}` not found.", - dest_optimized.display() - )) - } - - // TODO: Maybe get this from the handler instead of reading it from a file? - let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0; - let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; - - // overwrite existing destination wasm file with the optimised version - std::fs::rename(&dest_optimized, &crate_metadata.dest_wasm)?; - Ok(OptimizationResult { - dest_wasm: crate_metadata.dest_wasm.clone(), - original_size, - optimized_size, - }) -} - -/// 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 is written to `dest_optimized`. -fn do_optimization( - dest_wasm: &OsStr, - dest_optimized: &OsStr, - optimization_level: OptimizationPasses, - keep_debug_symbols: bool, -) -> Result<()> { - // check `wasm-opt` is installed - let which = which::which("wasm-opt"); - if which.is_err() { - anyhow::bail!( - "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" - .to_string() - .bright_yellow() - ); - } - let wasm_opt_path = which - .as_ref() - .expect("we just checked if `which` returned an err; qed") - .as_path(); - log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); - - check_wasm_opt_version_compatibility(wasm_opt_path)?; - - log::info!( - "Optimization level passed to wasm-opt: {}", - optimization_level - ); - let mut command = Command::new(wasm_opt_path); - command - .arg(dest_wasm) - .arg(format!("-O{}", optimization_level)) - .arg("-o") - .arg(dest_optimized) - // 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 keep_debug_symbols { - command.arg("-g"); - } - log::info!("Invoking wasm-opt with {:?}", command); - let output = command.output().map_err(|err| { - anyhow::anyhow!( - "Executing {} failed with {:?}", - 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 - ); - } - Ok(()) -} - /// Checks if the `wasm-opt` binary under `wasm_opt_path` returns a version /// compatible with `cargo-contract`. /// @@ -958,6 +836,8 @@ const WASM_OPT_INSTALLATION_SUGGESTION: &str = struct WasmOptHandler { wasm_opt_path: PathBuf, + /// The supplied `optimization_level` denotes the number of optimization passes, + /// resulting in potentially a lot of time spent optimizing. optimization_level: OptimizationPasses, keep_debug_symbols: bool, // Take these from optimize_wasm @@ -986,6 +866,9 @@ impl WasmOptHandler { }) } + /// Attempts to perform optional Wasm optimization using `binaryen`'s `wasm-opt` tool. + /// + /// If successful, the optimized Wasm is written to `dest_optimized`. fn optimize( &self, dest_wasm: &PathBuf, From 3bc82fb0deca1e9da84e2f3931953aff0f2c4b39 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Tue, 9 Aug 2022 17:12:05 -0400 Subject: [PATCH 05/12] Remove `dest_optimized` argument from `optimize` method --- src/cmd/build.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index d7b5bd3e3..3c63c8f49 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -749,10 +749,11 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { handler.set_debug_symbols(keep_debug_symbols); let optimization_result = handler.optimize( &crate_metadata.dest_wasm, - &mut crate_metadata.dest_wasm.clone(), &crate_metadata.contract_artifact_name, )?; + // We'll have the `BuildInfo` built here + Ok(optimization_result) }; @@ -872,11 +873,11 @@ impl WasmOptHandler { fn optimize( &self, dest_wasm: &PathBuf, - dest_optimized: &mut PathBuf, contract_artifact_name: &String, ) -> Result { // We'll create a temporary file for our optimized Wasm binary. Note that we'll later // overwrite this with the original path of the Wasm binary. + let mut dest_optimized = dest_wasm.clone(); dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); log::info!( From 5b1f6473ece1c0527872a7a3d6ba0300b4437967 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 10 Aug 2022 16:28:16 -0400 Subject: [PATCH 06/12] Move `check_wasm_opt_version_compatibility` into WasmOptHandler impl I think it makes sense to have this be associated with the handler instead of just floating around on its own. --- src/cmd/build.rs | 218 ++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 115 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 3c63c8f49..5a3f16682 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -53,7 +53,6 @@ use regex::Regex; use semver::Version; use std::{ convert::TryFrom, - ffi::OsStr, fs::metadata, path::{ Path, @@ -536,96 +535,6 @@ fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> { Ok(()) } -/// 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 - ) - })?; - - log::info!( - "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(()) -} - /// Asserts that the contract's dependencies are compatible to the ones used in ink!. /// /// This function utilizes `cargo tree`, which takes semver into consideration. @@ -744,13 +653,12 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { // let optimization_result = // optimize_wasm(&crate_metadata, optimization_passes, keep_debug_symbols)?; - let mut handler = WasmOptHandler::new()?; - handler.set_optimization_level(optimization_passes); - handler.set_debug_symbols(keep_debug_symbols); + let handler = WasmOptHandler::new(optimization_passes, keep_debug_symbols)?; let optimization_result = handler.optimize( &crate_metadata.dest_wasm, &crate_metadata.contract_artifact_name, )?; + let _v = handler.version; // We'll have the `BuildInfo` built here @@ -841,13 +749,14 @@ struct WasmOptHandler { /// resulting in potentially a lot of time spent optimizing. optimization_level: OptimizationPasses, keep_debug_symbols: bool, - // Take these from optimize_wasm - // output_path: PathBuf, - // artifact_name: String, + version: u32, } impl WasmOptHandler { - fn new() -> Result { + fn new( + 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()); @@ -857,13 +766,14 @@ impl WasmOptHandler { which.expect("we just checked if `which` returned an err; qed"); log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); - // TODO: Should use our new `version` functions do to this - check_wasm_opt_version_compatibility(wasm_opt_path.as_path())?; + let version = + Self::check_wasm_opt_version_compatibility(wasm_opt_path.as_path())?; Ok(Self { wasm_opt_path, - optimization_level: Default::default(), - keep_debug_symbols: Default::default(), + optimization_level, + keep_debug_symbols, + version, }) } @@ -940,16 +850,94 @@ impl WasmOptHandler { }) } - fn version(&self) -> u32 { - todo!() - } + /// 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(); + } - fn set_optimization_level(&mut self, optimization_level: OptimizationPasses) { - self.optimization_level = optimization_level; - } + 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 + ) + })?; + + log::info!( + "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, + ); + } - fn set_debug_symbols(&mut self, keep: bool) { - self.keep_debug_symbols = keep; + Ok(version_number) } } @@ -959,7 +947,7 @@ mod tests_ci_only { use super::{ assert_compatible_ink_dependencies, assert_debug_mode_supported, - check_wasm_opt_version_compatibility, + WasmOptHandler, }; use crate::{ cmd::{ @@ -1246,7 +1234,7 @@ mod tests_ci_only { let path = mock_wasm_opt_version(path, "98 (version_13-79-gc12cc3f50)"); // when - let res = check_wasm_opt_version_compatibility(&path); + let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); // then assert!(res.is_err()); @@ -1270,7 +1258,7 @@ mod tests_ci_only { let path = mock_wasm_opt_version(path, "99 (version_99-79-gc12cc3f50"); // when - let res = check_wasm_opt_version_compatibility(&path); + let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); // then assert!(res.is_ok()); @@ -1287,7 +1275,7 @@ mod tests_ci_only { let path = mock_wasm_opt_version(path, "98"); // when - let res = check_wasm_opt_version_compatibility(&path); + let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); // then assert!(res.is_err()); @@ -1310,7 +1298,7 @@ mod tests_ci_only { let path = mock_wasm_opt_version(path, "99"); // when - let res = check_wasm_opt_version_compatibility(&path); + let res = WasmOptHandler::check_wasm_opt_version_compatibility(&path); // then assert!(res.is_ok()); From 9179531aefe9760c00b89892d0014b41d7a7dcee Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Wed, 10 Aug 2022 16:39:52 -0400 Subject: [PATCH 07/12] Move the handler code further up the module, before all the functions --- src/cmd/build.rs | 431 ++++++++++++++++++++++++----------------------- 1 file changed, 216 insertions(+), 215 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 5a3f16682..667eb2b07 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -249,6 +249,222 @@ impl CheckCommand { } } +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. +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 { + /// Generate a new instance of the handler. + /// + /// Fails if the `wasm-opt` binary is not installed on the system, or if an outdated `wasm-opt` + /// binary is used (currently a version >= 99 is required). + fn new( + 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"); + log::info!("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, + }) + } + + /// Attempts to perform optional Wasm optimization using Binaryen's `wasm-opt` tool. + /// + /// If successful, the optimized Wasm binary is written to `dest_wasm`. + fn optimize( + &self, + dest_wasm: &PathBuf, + contract_artifact_name: &String, + ) -> Result { + // We'll create a temporary file for our optimized Wasm binary. Note that we'll later + // overwrite this with the original path of the Wasm binary. + let mut dest_optimized = dest_wasm.clone(); + dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); + + log::info!( + "Optimization level passed to wasm-opt: {}", + 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()) + // 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"); + } + + log::info!("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 + ); + } + + if !dest_optimized.exists() { + return Err(anyhow::anyhow!( + "Optimization failed, optimized wasm output file `{}` not found.", + dest_optimized.display() + )) + } + + let original_size = metadata(&dest_wasm)?.len() as f64 / 1000.0; + let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; + + // Overwrite existing destination wasm file with the optimised version + std::fs::rename(&dest_optimized, &dest_wasm)?; + Ok(OptimizationResult { + dest_wasm: dest_wasm.clone(), + original_size, + 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 + ) + })?; + + log::info!( + "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) + } +} + /// Executes the supplied cargo command on the project in the specified directory, defaults to the /// current directory. /// @@ -650,17 +866,11 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { "Optimizing wasm file".bright_green().bold() ); - // let optimization_result = - // optimize_wasm(&crate_metadata, optimization_passes, keep_debug_symbols)?; - let handler = WasmOptHandler::new(optimization_passes, keep_debug_symbols)?; let optimization_result = handler.optimize( &crate_metadata.dest_wasm, &crate_metadata.contract_artifact_name, )?; - let _v = handler.version; - - // We'll have the `BuildInfo` built here Ok(optimization_result) }; @@ -732,215 +942,6 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result { }) } -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"; - -struct WasmOptHandler { - wasm_opt_path: PathBuf, - /// The supplied `optimization_level` denotes the number of optimization passes, - /// resulting in potentially a lot of time spent optimizing. - optimization_level: OptimizationPasses, - keep_debug_symbols: bool, - version: u32, -} - -impl WasmOptHandler { - fn new( - 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"); - log::info!("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, - }) - } - - /// Attempts to perform optional Wasm optimization using `binaryen`'s `wasm-opt` tool. - /// - /// If successful, the optimized Wasm is written to `dest_optimized`. - fn optimize( - &self, - dest_wasm: &PathBuf, - contract_artifact_name: &String, - ) -> Result { - // We'll create a temporary file for our optimized Wasm binary. Note that we'll later - // overwrite this with the original path of the Wasm binary. - let mut dest_optimized = dest_wasm.clone(); - dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); - - log::info!( - "Optimization level passed to wasm-opt: {}", - 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()) - // 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"); - } - - log::info!("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 - ); - } - - if !dest_optimized.exists() { - return Err(anyhow::anyhow!( - "Optimization failed, optimized wasm output file `{}` not found.", - dest_optimized.display() - )) - } - - let original_size = metadata(&dest_wasm)?.len() as f64 / 1000.0; - let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; - - // Overwrite existing destination wasm file with the optimised version - std::fs::rename(&dest_optimized, &dest_wasm)?; - Ok(OptimizationResult { - dest_wasm: dest_wasm.clone(), - original_size, - 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 - ) - })?; - - log::info!( - "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(test)] mod tests_ci_only { From a82385aaf2e260a6adacb6acaafe14e454e9b1a3 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 15 Aug 2022 12:05:53 -0400 Subject: [PATCH 08/12] Decrease indentation of error string --- src/cmd/build.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 667eb2b07..7795e2969 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -250,15 +250,15 @@ impl CheckCommand { } 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"; + "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. struct WasmOptHandler { From 9e9b3988265b1c0b16d18ae7189772c5ed4be0e1 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 15 Aug 2022 12:26:20 -0400 Subject: [PATCH 09/12] Move `WasmOptHandler` into its own module --- src/cmd/build.rs | 325 +---------------------------------------- src/main.rs | 1 + src/wasm_opt.rs | 373 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 377 insertions(+), 322 deletions(-) create mode 100644 src/wasm_opt.rs diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 7795e2969..821ad6d53 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -19,6 +19,7 @@ use crate::{ maybe_println, util, validate_wasm, + wasm_opt::WasmOptHandler, workspace::{ Manifest, ManifestPath, @@ -49,11 +50,9 @@ use parity_wasm::elements::{ Module, Section, }; -use regex::Regex; use semver::Version; use std::{ convert::TryFrom, - fs::metadata, path::{ Path, PathBuf, @@ -249,222 +248,6 @@ impl CheckCommand { } } -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. -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 { - /// Generate a new instance of the handler. - /// - /// Fails if the `wasm-opt` binary is not installed on the system, or if an outdated `wasm-opt` - /// binary is used (currently a version >= 99 is required). - fn new( - 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"); - log::info!("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, - }) - } - - /// Attempts to perform optional Wasm optimization using Binaryen's `wasm-opt` tool. - /// - /// If successful, the optimized Wasm binary is written to `dest_wasm`. - fn optimize( - &self, - dest_wasm: &PathBuf, - contract_artifact_name: &String, - ) -> Result { - // We'll create a temporary file for our optimized Wasm binary. Note that we'll later - // overwrite this with the original path of the Wasm binary. - let mut dest_optimized = dest_wasm.clone(); - dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); - - log::info!( - "Optimization level passed to wasm-opt: {}", - 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()) - // 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"); - } - - log::info!("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 - ); - } - - if !dest_optimized.exists() { - return Err(anyhow::anyhow!( - "Optimization failed, optimized wasm output file `{}` not found.", - dest_optimized.display() - )) - } - - let original_size = metadata(&dest_wasm)?.len() as f64 / 1000.0; - let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; - - // Overwrite existing destination wasm file with the optimised version - std::fs::rename(&dest_optimized, &dest_wasm)?; - Ok(OptimizationResult { - dest_wasm: dest_wasm.clone(), - original_size, - 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 - ) - })?; - - log::info!( - "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) - } -} - /// Executes the supplied cargo command on the project in the specified directory, defaults to the /// current directory. /// @@ -948,17 +731,13 @@ mod tests_ci_only { use super::{ assert_compatible_ink_dependencies, assert_debug_mode_supported, - WasmOptHandler, }; use crate::{ cmd::{ build::load_module, BuildCommand, }, - util::tests::{ - with_new_contract_project, - with_tmp_dir, - }, + util::tests::with_new_contract_project, workspace::Manifest, BuildArtifacts, BuildMode, @@ -975,10 +754,7 @@ mod tests_ci_only { use std::{ ffi::OsStr, io::Write, - path::{ - Path, - PathBuf, - }, + path::Path, }; /// Modifies the `Cargo.toml` under the supplied `cargo_toml_path` by @@ -1020,20 +796,6 @@ mod tests_ci_only { .expect("setting permissions failed"); } - /// Creates an executable `wasm-opt-mocked` file which outputs - /// "wasm-opt version `version`". - /// - /// Returns the path to this file. - /// - /// Currently works only on `unix`. - #[cfg(unix)] - fn mock_wasm_opt_version(tmp_dir: &Path, version: &str) -> PathBuf { - let path = tmp_dir.join("wasm-opt-mocked"); - let content = format!("#!/bin/sh\necho \"wasm-opt version {}\"", version); - create_executable(&path, &content); - path - } - #[test] fn build_code_only() { with_new_contract_project(|manifest_path| { @@ -1227,87 +989,6 @@ mod tests_ci_only { }) } - #[cfg(unix)] - #[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(()) - }) - } - - #[cfg(unix)] - #[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(()) - }) - } - - #[cfg(unix)] - #[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(()) - }) - } - - #[cfg(unix)] - #[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(()) - }) - } - #[test] fn contract_lib_name_different_from_package_name_must_build() { with_new_contract_project(|manifest_path| { diff --git a/src/main.rs b/src/main.rs index 6819209dc..f246b8b0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ mod cmd; mod crate_metadata; mod util; mod validate_wasm; +mod wasm_opt; mod workspace; use self::{ diff --git a/src/wasm_opt.rs b/src/wasm_opt.rs new file mode 100644 index 000000000..416aa64bd --- /dev/null +++ b/src/wasm_opt.rs @@ -0,0 +1,373 @@ +// Copyright 2018-2022 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// cargo-contract is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// cargo-contract is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with cargo-contract. If not, see . + +use crate::{ + OptimizationPasses, + OptimizationResult, +}; + +use anyhow::Result; +use colored::Colorize; +use regex::Regex; + +use std::{ + fs::metadata, + path::{ + Path, + PathBuf, + }, + process::Command, + str, +}; + +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 { + /// Generate a new instance of the handler. + /// + /// Fails if the `wasm-opt` binary is not installed on the system, or if an outdated `wasm-opt` + /// binary is used (currently a version >= 99 is required). + pub fn new( + 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"); + log::info!("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, + }) + } + + /// Attempts to perform optional Wasm optimization using Binaryen's `wasm-opt` tool. + /// + /// If successful, the optimized Wasm binary is written to `dest_wasm`. + pub fn optimize( + &self, + dest_wasm: &PathBuf, + contract_artifact_name: &String, + ) -> Result { + // We'll create a temporary file for our optimized Wasm binary. Note that we'll later + // overwrite this with the original path of the Wasm binary. + let mut dest_optimized = dest_wasm.clone(); + dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); + + log::info!( + "Optimization level passed to wasm-opt: {}", + 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()) + // 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"); + } + + log::info!("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 + ); + } + + if !dest_optimized.exists() { + return Err(anyhow::anyhow!( + "Optimization failed, optimized wasm output file `{}` not found.", + dest_optimized.display() + )) + } + + let original_size = metadata(&dest_wasm)?.len() as f64 / 1000.0; + let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0; + + // Overwrite existing destination wasm file with the optimised version + std::fs::rename(&dest_optimized, &dest_wasm)?; + Ok(OptimizationResult { + dest_wasm: dest_wasm.clone(), + original_size, + 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 + ) + })?; + + log::info!( + "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(test)] +mod tests_ci_only { + use super::*; + + use crate::util::tests::with_tmp_dir; + + use std::io::Write; + #[cfg(unix)] + use std::os::unix::fs::PermissionsExt; + + /// Creates an executable `wasm-opt-mocked` file which outputs + /// "wasm-opt version `version`". + /// + /// Returns the path to this file. + /// + /// Currently works only on `unix`. + #[cfg(unix)] + fn mock_wasm_opt_version(tmp_dir: &Path, version: &str) -> PathBuf { + let path = tmp_dir.join("wasm-opt-mocked"); + let content = format!("#!/bin/sh\necho \"wasm-opt version {}\"", version); + create_executable(&path, &content); + path + } + + /// TODO: Dup from build.rs + /// + /// Creates an executable file at `path` with the content `content`. + /// + /// Currently works only on `unix`. + #[cfg(unix)] + fn create_executable(path: &Path, content: &str) { + { + let mut file = std::fs::File::create(&path).unwrap(); + file.write_all(content.as_bytes()) + .expect("writing of executable failed"); + } + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) + .expect("setting permissions failed"); + } + + #[cfg(unix)] + #[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(()) + }) + } + + #[cfg(unix)] + #[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(()) + }) + } + + #[cfg(unix)] + #[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(()) + }) + } + + #[cfg(unix)] + #[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(()) + }) + } +} From 099cf0186f7ad4a22a1537a4180f19be93292d68 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 15 Aug 2022 12:31:03 -0400 Subject: [PATCH 10/12] Move `create_executable` test helper to `utils` module --- src/cmd/build.rs | 22 ++++------------------ src/util.rs | 17 +++++++++++++++++ src/wasm_opt.rs | 25 ++++--------------------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 821ad6d53..8ac31cb4a 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -737,7 +737,10 @@ mod tests_ci_only { build::load_module, BuildCommand, }, - util::tests::with_new_contract_project, + util::tests::{ + create_executable, + with_new_contract_project, + }, workspace::Manifest, BuildArtifacts, BuildMode, @@ -749,11 +752,8 @@ mod tests_ci_only { VerbosityFlags, }; use semver::Version; - #[cfg(unix)] - use std::os::unix::fs::PermissionsExt; use std::{ ffi::OsStr, - io::Write, path::Path, }; @@ -782,20 +782,6 @@ mod tests_ci_only { .any(|e| e.name() == "name") } - /// Creates an executable file at `path` with the content `content`. - /// - /// Currently works only on `unix`. - #[cfg(unix)] - fn create_executable(path: &Path, content: &str) { - { - let mut file = std::fs::File::create(&path).unwrap(); - file.write_all(content.as_bytes()) - .expect("writing of executable failed"); - } - std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) - .expect("setting permissions failed"); - } - #[test] fn build_code_only() { with_new_contract_project(|manifest_path| { diff --git a/src/util.rs b/src/util.rs index 10652b523..1b788518b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -234,6 +234,23 @@ pub mod tests { f(manifest_path) }) } + + /// Creates an executable file at `path` with the content `content`. + /// + /// Currently works only on `unix`. + #[cfg(unix)] + pub fn create_executable(path: &Path, content: &str) { + use std::io::Write; + #[cfg(unix)] + use std::os::unix::fs::PermissionsExt; + { + let mut file = std::fs::File::create(&path).unwrap(); + file.write_all(content.as_bytes()) + .expect("writing of executable failed"); + } + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) + .expect("setting permissions failed"); + } } // Unzips the file at `template` to `out_dir`. diff --git a/src/wasm_opt.rs b/src/wasm_opt.rs index 416aa64bd..1bf6ef2a6 100644 --- a/src/wasm_opt.rs +++ b/src/wasm_opt.rs @@ -254,11 +254,10 @@ impl WasmOptHandler { mod tests_ci_only { use super::*; - use crate::util::tests::with_tmp_dir; - - use std::io::Write; - #[cfg(unix)] - use std::os::unix::fs::PermissionsExt; + use crate::util::tests::{ + create_executable, + with_tmp_dir, + }; /// Creates an executable `wasm-opt-mocked` file which outputs /// "wasm-opt version `version`". @@ -274,22 +273,6 @@ mod tests_ci_only { path } - /// TODO: Dup from build.rs - /// - /// Creates an executable file at `path` with the content `content`. - /// - /// Currently works only on `unix`. - #[cfg(unix)] - fn create_executable(path: &Path, content: &str) { - { - let mut file = std::fs::File::create(&path).unwrap(); - file.write_all(content.as_bytes()) - .expect("writing of executable failed"); - } - std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777)) - .expect("setting permissions failed"); - } - #[cfg(unix)] #[test] fn incompatible_wasm_opt_version_must_be_detected_if_built_from_repo() { From 7642b4ea2bcf156ba3dd2c49e63240f32feb8813 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Mon, 15 Aug 2022 12:37:49 -0400 Subject: [PATCH 11/12] Switch from `env_logger` to `tracing` Related PR: https://github.com/paritytech/cargo-contract/pull/689 --- src/wasm_opt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wasm_opt.rs b/src/wasm_opt.rs index 1bf6ef2a6..236280ca8 100644 --- a/src/wasm_opt.rs +++ b/src/wasm_opt.rs @@ -72,7 +72,7 @@ impl WasmOptHandler { let wasm_opt_path = which.expect("we just checked if `which` returned an err; qed"); - log::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); + tracing::info!("Path to wasm-opt executable: {}", wasm_opt_path.display()); let version = Self::check_wasm_opt_version_compatibility(wasm_opt_path.as_path())?; @@ -98,7 +98,7 @@ impl WasmOptHandler { let mut dest_optimized = dest_wasm.clone(); dest_optimized.set_file_name(format!("{}-opt.wasm", contract_artifact_name)); - log::info!( + tracing::info!( "Optimization level passed to wasm-opt: {}", self.optimization_level ); @@ -118,7 +118,7 @@ impl WasmOptHandler { command.arg("-g"); } - log::info!("Invoking wasm-opt with {:?}", command); + tracing::info!("Invoking wasm-opt with {:?}", command); let output = command.output().map_err(|err| { anyhow::anyhow!( @@ -232,7 +232,7 @@ impl WasmOptHandler { ) })?; - log::info!( + tracing::info!( "The wasm-opt version output is '{}', which was parsed to '{}'", version_stdout, version_number From 86beededa88c5d08e0f501ce537751785c6fe63e Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Tue, 16 Aug 2022 13:01:22 -0400 Subject: [PATCH 12/12] Indent some code --- src/wasm_opt.rs | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/wasm_opt.rs b/src/wasm_opt.rs index 236280ca8..70655e22d 100644 --- a/src/wasm_opt.rs +++ b/src/wasm_opt.rs @@ -105,14 +105,14 @@ impl WasmOptHandler { 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()) - // 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"); + .arg(dest_wasm.as_os_str()) + .arg(format!("-O{}", self.optimization_level)) + .arg("-o") + .arg(dest_optimized.as_os_str()) + // 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"); @@ -215,22 +215,22 @@ impl WasmOptHandler { ) })?; 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 - ) - })?; + .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::info!( "The wasm-opt version output is '{}', which was parsed to '{}'",