Newer
Older
// Copyright 2018-2021 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 <http://www.gnu.org/licenses/>.
use regex::Regex;
Michael Müller
committed
use std::{convert::TryFrom, ffi::OsStr, fs::metadata, path::PathBuf};
#[cfg(feature = "binaryen-as-dependency")]
Michael Müller
committed
fs::File,
Michael Müller
committed
io::{Read, Write},
#[cfg(any(test, not(feature = "binaryen-as-dependency")))]
use std::{path::Path, process::Command, str};
Andrew Jones
committed
use crate::{
maybe_println, util, validate_wasm,
workspace::{Manifest, ManifestPath, Profile, Workspace},
BuildArtifacts, BuildResult, OptimizationPasses, OptimizationResult, UnstableFlags,
UnstableOptions, Verbosity, VerbosityFlags,
Andrew Jones
committed
};
use anyhow::{Context, Result};
use parity_wasm::elements::{External, MemoryType, Module, Section};
/// This is the maximum number of pages available for a contract to allocate.
const MAX_MEMORY_PAGES: u32 = 16;
/// 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.
#[derive(Debug, StructOpt)]
#[structopt(name = "build")]
pub struct BuildCommand {
/// Path to the Cargo.toml of the contract to build
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
/// Which build artifacts to generate.
///
/// - `all`: Generate the Wasm, the metadata and a bundled `<name>.contract` file.
///
/// - `code-only`: Only the Wasm is created, generation of metadata and a bundled
/// `<name>.contract` file is skipped.
#[structopt(
long = "generate",
default_value = "all",
value_name = "all | code-only",
verbatim_doc_comment
)]
build_artifact: BuildArtifacts,
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
/// Number of optimization passes, passed as an argument to wasm-opt.
///
/// - `0`: execute no optimization passes
///
/// - `1`: execute 1 optimization pass (quick & useful opts, useful for iteration builds)
///
/// - `2`, execute 2 optimization passes (most opts, generally gets most perf)
///
/// - `3`, execute 3 optimization passes (spends potentially a lot of time optimizing)
///
/// - `4`, execute 4 optimization passes (also flatten the IR, which can take a lot more time and memory
/// but is useful on more nested / complex / less-optimized input)
///
/// - `s`, execute default optimization passes, focusing on code size
///
/// - `z`, execute default optimization passes, super-focusing on code size
///
/// - The default value is `3`
///
/// - It is possible to define the number of optimization passes in the
/// `[package.metadata.contract]` of your `Cargo.toml` as e.g. `optimization-passes = "3"`.
/// The CLI argument always takes precedence over the profile value.
#[structopt(long = "optimization-passes")]
optimization_passes: Option<OptimizationPasses>,
}
impl BuildCommand {
pub fn exec(&self) -> Result<BuildResult> {
let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?;
let unstable_flags: UnstableFlags =
TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?;
let verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?;
// The CLI flag `optimization-passes` overwrites optimization passes which are
// potentially defined in the `Cargo.toml` profile.
let optimization_passes = match self.optimization_passes {
Some(opt_passes) => opt_passes,
None => {
let mut manifest = Manifest::new(manifest_path.clone())?;
match manifest.get_profile_optimization_passes() {
// if no setting is found, neither on the cli nor in the profile,
// then we use the default
None => OptimizationPasses::default(),
Some(opt_passes) => opt_passes,
}
}
};
execute(
&manifest_path,
verbosity,
self.build_artifact,
unstable_flags,
)
}
}
#[derive(Debug, StructOpt)]
#[structopt(name = "check")]
pub struct CheckCommand {
/// Path to the Cargo.toml of the contract to build
#[structopt(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[structopt(flatten)]
verbosity: VerbosityFlags,
#[structopt(flatten)]
unstable_options: UnstableOptions,
}
impl CheckCommand {
pub fn exec(&self) -> Result<BuildResult> {
let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?;
let unstable_flags: UnstableFlags =
TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?;
let verbosity: Verbosity = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?;
execute(
&manifest_path,
verbosity,
BuildArtifacts::CheckOnly,
unstable_flags,
OptimizationPasses::Zero,
/// Executes the supplied cargo command on the project in the specified directory, defaults to the
/// current directory.
/// Uses the unstable cargo feature [`build-std`](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std)
/// to build the standard library with [`panic_immediate_abort`](https://github.com/johnthagen/min-sized-rust#remove-panic-string-formatting-with-panic_immediate_abort)
/// which reduces the size of the Wasm binary by not including panic strings and formatting code.
///
/// # Cargo.toml optimizations
///
/// The original Cargo.toml will be amended to remove the `rlib` crate type in order to minimize
/// the final Wasm binary size.
///
/// Preferred default `[profile.release]` settings will be added if they are missing, existing
/// user-defined settings will be preserved.
///
/// To disable this and use the original `Cargo.toml` as is then pass the `-Z original_manifest` flag.
crate_metadata: &CrateMetadata,
) -> Result<()> {
Andrew Jones
committed
util::assert_channel()?;
// set linker args via RUSTFLAGS.
// Currently will override user defined RUSTFLAGS from .cargo/config. See https://github.com/paritytech/cargo-contract/issues/98.
Andrew Jones
committed
std::env::set_var(
"RUSTFLAGS",
"-C link-arg=-z -C link-arg=stack-size=65536 -C link-arg=--import-memory",
);
let cargo_build = |manifest_path: &ManifestPath| {
let target_dir = &crate_metadata.target_directory;
let args = [
"--target=wasm32-unknown-unknown",
"-Zbuild-std",
"-Zbuild-std-features=panic_immediate_abort",
"--no-default-features",
"--release",
&format!("--target-dir={}", target_dir.to_string_lossy()),
];
util::invoke_cargo(command, &args, manifest_path.directory(), verbosity)?;
Andrew Jones
committed
Ok(())
};
if unstable_flags.original_manifest {
"{} {}",
"warning:".yellow().bold(),
"with 'original-manifest' enabled, the contract binary may not be of optimal size."
.bold()
);
cargo_build(&crate_metadata.manifest_path)?;
} else {
Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
.with_root_package_manifest(|manifest| {
manifest
.with_removed_crate_type("rlib")?
.with_profile_release_defaults(Profile::default_contract_release())?;
Ok(())
})?
Andrew Jones
committed
// clear RUSTFLAGS
std::env::remove_var("RUSTFLAGS");
Andrew Jones
committed
Ok(())
}
/// Ensures the wasm memory import of a given module has the maximum number of pages.
///
/// Iterates over the import section, finds the memory import entry if any and adjusts the maximum
/// limit.
fn ensure_maximum_memory_pages(module: &mut Module, maximum_allowed_pages: u32) -> Result<()> {
let mem_ty = module
.import_section_mut()
.and_then(|section| {
.find_map(|entry| match entry.external_mut() {
External::Memory(ref mut mem_ty) => Some(mem_ty),
_ => None,
.context("Memory import is not found. Is --import-memory specified in the linker args")?;
if let Some(requested_maximum) = mem_ty.limits().maximum() {
// The module already has maximum, check if it is within the limit bail out.
if requested_maximum > maximum_allowed_pages {
anyhow::bail!(
"The wasm module requires {} pages. The maximum allowed number of pages is {}",
requested_maximum,
maximum_allowed_pages,
);
}
} else {
let initial = mem_ty.limits().initial();
*mem_ty = MemoryType::new(initial, Some(MAX_MEMORY_PAGES));
}
Ok(())
}
/// Strips all custom sections.
///
/// Presently all custom sections are not required so they can be stripped safely.
fn strip_custom_sections(module: &mut Module) {
module.sections_mut().retain(|section| {
!matches!(
section,
Section::Custom(_) | Section::Name(_) | Section::Reloc(_)
)
});
}
/// Performs required post-processing steps on the wasm artifact.
fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
// Deserialize wasm module from a file.
Andrew Jones
committed
let mut module =
parity_wasm::deserialize_file(&crate_metadata.original_wasm).context(format!(
"Loading original wasm file '{}'",
crate_metadata.original_wasm.display()
))?;
// Perform optimization.
//
// In practice only tree-shaking is performed, i.e transitively removing all symbols that are
// NOT used by the specified entrypoints.
if pwasm_utils::optimize(&mut module, ["call", "deploy"].to_vec()).is_err() {
anyhow::bail!("Optimizer failed");
}
ensure_maximum_memory_pages(&mut module, MAX_MEMORY_PAGES)?;
strip_custom_sections(&mut module);
validate_wasm::validate_import_section(&module)?;
Michael Müller
committed
debug_assert!(
!module.clone().to_bytes().unwrap().is_empty(),
"resulting wasm size of post processing must be > 0"
);
parity_wasm::serialize_to_file(&crate_metadata.dest_wasm, module)?;
Ok(())
}
Michael Müller
committed
/// 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,
) -> Result<OptimizationResult> {
Michael Müller
committed
let mut dest_optimized = crate_metadata.dest_wasm.clone();
dest_optimized.set_file_name(format!("{}-opt.wasm", crate_metadata.package_name));
let _ = do_optimization(
crate_metadata.dest_wasm.as_os_str(),
&dest_optimized.as_os_str(),
Michael Müller
committed
)?;
if !dest_optimized.exists() {
return Err(anyhow::anyhow!(
"Optimization failed, optimized wasm output file `{}` not found.",
dest_optimized.display()
));
}
Andrew Jones
committed
let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0;
Michael Müller
committed
let optimized_size = metadata(&dest_optimized)?.len() as f64 / 1000.0;
// overwrite existing destination wasm file with the optimised version
Michael Müller
committed
std::fs::rename(&dest_optimized, &crate_metadata.dest_wasm)?;
Michael Müller
committed
Ok(OptimizationResult {
dest_wasm: crate_metadata.dest_wasm.clone(),
Michael Müller
committed
original_size,
optimized_size,
})
/// 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.
///
Michael Müller
committed
/// If successful, the optimized wasm is written to `dest_optimized`.
#[cfg(feature = "binaryen-as-dependency")]
fn do_optimization(
Michael Müller
committed
dest_wasm: &OsStr,
dest_optimized: &OsStr,
optimization_level: OptimizationPasses,
Michael Müller
committed
) -> Result<()> {
let mut dest_wasm_file = File::open(dest_wasm)?;
let mut dest_wasm_file_content = Vec::new();
dest_wasm_file.read_to_end(&mut dest_wasm_file_content)?;
let codegen_config = binaryen::CodegenConfig {
// Number of optimization passes (spends potentially a lot of time optimizing)
optimization_level: optimization_level.to_passes(),
shrink_level: optimization_level.to_shrink(),
debug_info: false,
};
log::info!(
"Optimization level passed to `binaryen` dependency: {}",
codegen_config.optimization_level
);
log::info!(
"Shrink level passed to `binaryen` dependency: {}",
codegen_config.shrink_level
);
Michael Müller
committed
let mut module = binaryen::Module::read(&dest_wasm_file_content)
.map_err(|_| anyhow::anyhow!("binaryen failed to read file content"))?;
if optimization_level != OptimizationPasses::Zero {
// binaryen-rs still uses the default optimization passes, even if zero
// is passed. this is the ticket for it: https://github.com/pepyakin/binaryen-rs/issues/56.
// we can remove the if condition here once the issue is fixed.
module.optimize(&codegen_config);
}
Michael Müller
committed
let mut optimized_wasm_file = File::create(dest_optimized)?;
optimized_wasm_file.write_all(&module.write())?;
Ok(())
}
/// 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.
///
Michael Müller
committed
/// If successful, the optimized wasm is written to `dest_optimized`.
#[cfg(not(feature = "binaryen-as-dependency"))]
fn do_optimization(
Michael Müller
committed
dest_wasm: &OsStr,
dest_optimized: &OsStr,
optimization_level: OptimizationPasses,
Michael Müller
committed
) -> 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\
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());
let _ = check_wasm_opt_version_compatibility(wasm_opt_path)?;
log::info!(
"Optimization level passed to wasm-opt: {}",
optimization_level
);
let output = Command::new(wasm_opt_path)
Michael Müller
committed
.arg(dest_wasm)
.arg(format!("-O{}", optimization_level))
Michael Müller
committed
.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")
.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")
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
"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`.
///
/// Currently this must be a version >= 99.
#[cfg(any(test, not(feature = "binaryen-as-dependency")))]
fn check_wasm_opt_version_compatibility(wasm_opt_path: &Path) -> Result<()> {
let cmd = Command::new(wasm_opt_path)
.arg("--version")
.output()
.map_err(|err| {
anyhow::anyhow!(
"Executing `{:?} --version` failed with {:?}",
wasm_opt_path.display(),
err
)
})?;
if !cmd.status.success() {
let err = str::from_utf8(&cmd.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 version_stdout = str::from_utf8(&cmd.stdout)
.expect("Cannot convert stdout output of wasm-opt to string")
.trim();
let re = Regex::new(r"wasm-opt version (\d+)\s+").unwrap();
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
)
})?;
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 is \"{}\"", version_stdout);
if version_number < 99 {
anyhow::bail!(
"Your wasm-opt version is {}, but we require a version >= 99.",
version_number
);
}
Michael Müller
committed
Ok(())
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
/// Asserts that the contract's dependencies are compatible to the ones used in ink!.
///
/// This function utilizes `cargo tree`, which takes semver into consideration.
///
/// Hence this function only returns an `Err` if it is a proper mismatch according
/// to semantic versioning. This means that either:
/// - the major version mismatches, differences in the minor/patch version
/// are not considered incompatible.
/// - or if the version starts with zero (i.e. `0.y.z`) a mismatch in the minor
/// version is already considered incompatible.
fn assert_compatible_ink_dependencies(
manifest_path: &ManifestPath,
verbosity: Verbosity,
) -> Result<()> {
for dependency in ["parity-scale-codec", "scale-info"].iter() {
let args = ["-i", dependency, "--duplicates"];
let _ = util::invoke_cargo("tree", &args, manifest_path.directory(), verbosity).map_err(
|_| {
anyhow::anyhow!(
"Mismatching versions of `{}` were found!\n\
Please ensure that your contract and your ink! dependencies use a compatible \
version of this package.",
dependency
)
},
)?;
}
Ok(())
}
/// 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.
build_artifact: BuildArtifacts,
unstable_flags: UnstableFlags,
optimization_passes: OptimizationPasses,
let crate_metadata = CrateMetadata::collect(&manifest_path)?;
assert_compatible_ink_dependencies(&manifest_path, verbosity)?;
let build = || -> Result<OptimizationResult> {
maybe_println!(
Michael Müller
committed
verbosity,
" {} {}",
format!("[1/{}]", build_artifact.steps()).bold(),
"Building cargo project".bright_green().bold()
);
exec_cargo_for_wasm_target(&crate_metadata, "build", verbosity, &unstable_flags)?;
maybe_println!(
" {} {}",
format!("[2/{}]", build_artifact.steps()).bold(),
"Post processing wasm file".bright_green().bold()
);
post_process_wasm(&crate_metadata)?;
Michael Müller
committed
maybe_println!(
verbosity,
" {} {}",
format!("[3/{}]", build_artifact.steps()).bold(),
"Optimizing wasm file".bright_green().bold()
);
let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?;
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
Ok(optimization_result)
};
let (opt_result, metadata_result) = match build_artifact {
BuildArtifacts::CheckOnly => {
exec_cargo_for_wasm_target(&crate_metadata, "check", verbosity, &unstable_flags)?;
(None, None)
}
BuildArtifacts::CodeOnly => {
let optimization_result = build()?;
(Some(optimization_result), None)
}
BuildArtifacts::All => {
let optimization_result = build()?;
let metadata_result = super::metadata::execute(
&crate_metadata,
optimization_result.dest_wasm.as_path(),
verbosity,
build_artifact.steps(),
&unstable_flags,
)?;
(Some(optimization_result), Some(metadata_result))
}
};
let dest_wasm = opt_result.as_ref().map(|r| r.dest_wasm.clone());
Ok(BuildResult {
dest_wasm,
metadata_result,
target_directory: crate_metadata.target_directory,
optimization_result: opt_result,
build_artifact,
#[cfg(feature = "test-ci-only")]
mod tests_ci_only {
use super::{assert_compatible_ink_dependencies, check_wasm_opt_version_compatibility};
cmd::{self, BuildCommand},
util::tests::with_tmp_dir,
workspace::Manifest,
BuildArtifacts, ManifestPath, OptimizationPasses, UnstableFlags, UnstableOptions,
Verbosity, VerbosityFlags,
use std::{io::Write, os::unix::fs::PermissionsExt, path::PathBuf};
/// Modifies the `Cargo.toml` under the supplied `cargo_toml_path` by
/// setting `optimization-passes` in `[package.metadata.contract]` to `passes`.
fn write_optimization_passes_into_manifest(
cargo_toml_path: &PathBuf,
passes: OptimizationPasses,
) {
let manifest_path =
ManifestPath::new(cargo_toml_path).expect("manifest path creation failed");
let mut manifest = Manifest::new(manifest_path.clone()).expect("manifest creation failed");
manifest
.set_profile_optimization_passes(passes)
.expect("setting `optimization-passes` in profile failed");
manifest
.write(&manifest_path)
.expect("writing manifest failed");
}
with_tmp_dir(|path| {
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
Andrew Jones
committed
let manifest_path =
ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap();
let res = super::execute(
Michael Müller
committed
&manifest_path,
Michael Müller
committed
UnstableFlags::default(),
Michael Müller
committed
)
.expect("build failed");
// our ci has set `CARGO_TARGET_DIR` to cache artifacts.
// this dir does not include `/target/` as a path, hence
// we can't match for e.g. `foo_project/target/ink`.
//
// we also can't match for `/ink` here, since this would match
// for `/ink` being the root path.
assert!(res.target_directory.ends_with("ink"));
assert!(
res.metadata_result.is_none(),
"CodeOnly should not generate the metadata"
);
let optimized_size = res.optimization_result.unwrap().optimized_size;
assert!(optimized_size > 0.0);
// our optimized contract template should always be below 3k.
#[test]
fn check_must_not_output_contract_artifacts_in_project_dir() {
with_tmp_dir(|path| {
// given
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let project_dir = path.join("new_project");
let manifest_path = ManifestPath::new(&project_dir.join("Cargo.toml")).unwrap();
// when
super::execute(
&manifest_path,
BuildArtifacts::CheckOnly,
UnstableFlags::default(),
)
.expect("build failed");
// then
assert!(
!project_dir.join("target/ink/new_project.contract").exists(),
"found contract artifact in project directory!"
);
assert!(
!project_dir.join("target/ink/new_project.wasm").exists(),
"found wasm artifact in project directory!"
);
Ok(())
})
}
#[test]
fn optimization_passes_from_cli_must_take_precedence_over_profile() {
with_tmp_dir(|path| {
// given
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let cargo_toml_path = path.join("new_project").join("Cargo.toml");
write_optimization_passes_into_manifest(&cargo_toml_path, OptimizationPasses::Three);
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
let cmd = BuildCommand {
manifest_path: Some(cargo_toml_path),
build_artifact: BuildArtifacts::All,
verbosity: VerbosityFlags::default(),
unstable_options: UnstableOptions::default(),
// we choose zero optimization passes as the "cli" parameter
optimization_passes: Some(OptimizationPasses::Zero),
};
// when
let res = cmd.exec().expect("build failed");
let optimization = res
.optimization_result
.expect("no optimization result available");
// then
// we have to truncate here to account for a possible small delta
// in the floating point numbers
let optimized_size = optimization.optimized_size.trunc();
let original_size = optimization.original_size.trunc();
assert!(
optimized_size == original_size,
"The optimized size {:?} differs from the original size {:?}",
optimized_size,
original_size
);
Ok(())
})
}
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
#[test]
fn optimization_passes_from_profile_must_be_used() {
with_tmp_dir(|path| {
// given
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let cargo_toml_path = path.join("new_project").join("Cargo.toml");
write_optimization_passes_into_manifest(&cargo_toml_path, OptimizationPasses::Three);
let cmd = BuildCommand {
manifest_path: Some(cargo_toml_path),
build_artifact: BuildArtifacts::All,
verbosity: VerbosityFlags::default(),
unstable_options: UnstableOptions::default(),
// we choose no optimization passes as the "cli" parameter
optimization_passes: None,
};
// when
let res = cmd.exec().expect("build failed");
let optimization = res
.optimization_result
.expect("no optimization result available");
// then
// we have to truncate here to account for a possible small delta
// in the floating point numbers
let optimized_size = optimization.optimized_size.trunc();
let original_size = optimization.original_size.trunc();
assert!(
optimized_size < original_size,
"The optimized size DOES NOT {:?} differ from the original size {:?}",
optimized_size,
original_size
);
Ok(())
})
}
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
#[test]
fn project_template_dependencies_must_be_ink_compatible() {
with_tmp_dir(|path| {
// given
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let cargo_toml_path = path.join("new_project").join("Cargo.toml");
let manifest_path =
ManifestPath::new(&cargo_toml_path).expect("manifest path creation failed");
// when
let res = assert_compatible_ink_dependencies(&manifest_path, Verbosity::Default);
// then
assert!(res.is_ok());
Ok(())
})
}
#[test]
fn detect_mismatching_parity_scale_codec_dependencies() {
with_tmp_dir(|path| {
// given
cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let cargo_toml_path = path.join("new_project").join("Cargo.toml");
let manifest_path =
ManifestPath::new(&cargo_toml_path).expect("manifest path creation failed");
// at the time of writing this test ink! already uses `parity-scale-codec`
// in a version > 2, hence 1 is an incompatible version.
let mut manifest = Manifest::new(manifest_path.clone())?;
manifest
.set_dependency_version("scale", "1.0.0")
.expect("setting `scale` version failed");
manifest
.write(&manifest_path)
.expect("writing manifest failed");
// when
let res = assert_compatible_ink_dependencies(&manifest_path, Verbosity::Default);
// then
assert!(res.is_err());
Ok(())
})
}
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
#[test]
fn incompatible_wasm_opt_version_must_be_detected() {
with_tmp_dir(|path| {
// given
let path = path.join("wasm-opt-mocked");
{
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(
b"#!/bin/sh\necho \"wasm-opt version 98 (version_13-79-gc12cc3f50)\"",
)
.expect("writing wasm-opt-mocked failed");
}
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777))
.expect("setting permissions failed");
// when
let res = check_wasm_opt_version_compatibility(&path);
// then
assert!(res.is_err());
assert_eq!(
format!("{:?}", res),
"Err(Your wasm-opt version is 98, but we require a version >= 99.)"
);
Ok(())
})
}
#[test]
fn compatible_wasm_opt_version_must_be_detected() {
with_tmp_dir(|path| {
// given
let path = path.join("wasm-opt-mocked");
{
let mut file = std::fs::File::create(&path).unwrap();
file.write_all(
b"#!/bin/sh\necho \"wasm-opt version 99 (version_99-79-gc12cc3f50)\"",
)
.expect("writing wasm-opt-mocked failed");
}
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o777))
.expect("setting permissions failed");
// when
let res = check_wasm_opt_version_compatibility(&path);
// then
assert!(res.is_ok());
Ok(())
})
}