Newer
Older
// 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 <http://www.gnu.org/licenses/>.
Andrew Jones
committed
use crate::{
maybe_println,
util,
validate_wasm,
workspace::{
Manifest,
ManifestPath,
Profile,
Workspace,
},
BuildArtifacts,
BuildMode,
BuildResult,
Network,
OptimizationPasses,
OptimizationResult,
OutputType,
UnstableFlags,
UnstableOptions,
Verbosity,
VerbosityFlags,
};
use anyhow::{
Context,
Result,
Andrew Jones
committed
};
use parity_wasm::elements::{
External,
Internal,
MemoryType,
Module,
Section,
};
use std::{
convert::TryFrom,
ffi::OsStr,
fs::metadata,
path::{
Path,
PathBuf,
},
process::Command,
str,
};
/// This is the maximum number of pages available for a contract to allocate.
const MAX_MEMORY_PAGES: u32 = 16;
/// Arguments to use when executing `build` or `check` commands.
#[derive(Default)]
pub(crate) struct ExecuteArgs {
/// The location of the Cargo manifest (`Cargo.toml`) file to use.
pub(crate) manifest_path: ManifestPath,
verbosity: Verbosity,
build_mode: BuildMode,
build_artifact: BuildArtifacts,
unstable_flags: UnstableFlags,
optimization_passes: OptimizationPasses,
keep_debug_symbols: bool,
output_type: OutputType,
}
/// 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, clap::Args)]
#[clap(name = "build")]
/// Path to the `Cargo.toml` of the contract to build
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
/// By default the contract is compiled with debug functionality
/// included. This enables the contract to output debug messages,
/// but increases the contract size and the amount of gas used.
///
/// A production contract should always be build in `release` mode!
/// Then no debug functionality is compiled into the contract.
/// Skips linting checks during the build process
#[clap(long = "--skip-linting")]
skip_linting: bool,
/// 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.
///
/// - `check-only`: No artifacts produced: runs the `cargo check` command for the Wasm target,
/// only checks for compilation errors.
#[clap(long = "generate", arg_enum, default_value = "all")]
build_artifact: BuildArtifacts,
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 `z`
///
/// - 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.
optimization_passes: Option<OptimizationPasses>,
/// Do not remove symbols (Wasm name section) when optimizing.
///
/// This is useful if one wants to analyze or debug the optimized binary.
/// Export the build output in JSON format.
#[clap(long, conflicts_with = "verbose")]
}
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 mut 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,
}
}
};
let build_mode = match self.build_release {
true => BuildMode::Release,
false => BuildMode::Debug,
};
let network = match self.build_offline {
true => Network::Offline,
false => Network::Online,
};
let output_type = match self.output_json {
true => OutputType::Json,
false => OutputType::HumanReadable,
};
// We want to ensure that the only thing in `STDOUT` is our JSON formatted string.
if matches!(output_type, OutputType::Json) {
verbosity = Verbosity::Quiet;
}
let args = ExecuteArgs {
manifest_path,
build_artifact: self.build_artifact,
keep_debug_symbols: self.keep_debug_symbols,
skip_linting: self.skip_linting,
output_type,
};
execute(args)
#[derive(Debug, clap::Args)]
#[clap(name = "check")]
/// Path to the `Cargo.toml` of the contract to build
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
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)?;
let args = ExecuteArgs {
manifest_path,
build_mode: BuildMode::Debug,
build_artifact: BuildArtifacts::CheckOnly,
optimization_passes: OptimizationPasses::Zero,
keep_debug_symbols: false,
output_type: OutputType::default(),
};
execute(args)
/// 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.
///
/// The `[workspace]` will be added if it is missing to ignore `workspace` from parent `Cargo.toml`.
///
/// 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()?;
let cargo_build = |manifest_path: &ManifestPath| {
let target_dir = &crate_metadata.target_directory;
let target_dir = format!("--target-dir={}", target_dir.to_string_lossy());
let mut args = vec![
"--target=wasm32-unknown-unknown",
"-Zbuild-std",
"--no-default-features",
"--release",
if network == Network::Offline {
args.push("--offline");
}
if build_mode == BuildMode::Debug {
args.push("--features=ink_env/ink-debug");
} else {
args.push("-Zbuild-std-features=panic_immediate_abort");
let env = vec![(
"RUSTFLAGS",
Some("-C link-arg=-zstack-size=65536 -C link-arg=--import-memory -Clinker-plugin-lto"),
)];
util::invoke_cargo(command, &args, manifest_path.directory(), verbosity, env)?;
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())?
.with_workspace()?;
Ok(())
})?
Andrew Jones
committed
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
Ok(())
}
/// Executes `cargo dylint` with the ink! linting driver that is built during
/// the `build.rs`.
///
/// We create a temporary folder, extract the linting driver there and run
/// `cargo dylint` with it.
fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Result<()> {
check_dylint_requirements(crate_metadata.manifest_path.directory())?;
let tmp_dir = tempfile::Builder::new()
.prefix("cargo-contract-dylint_")
.tempdir()?;
log::debug!("Using temp workspace at '{}'", tmp_dir.path().display());
let driver = include_bytes!(concat!(env!("OUT_DIR"), "/ink-dylint-driver.zip"));
crate::util::unzip(driver, tmp_dir.path().to_path_buf(), None)?;
let manifest_path = crate_metadata.manifest_path.cargo_arg()?;
let args = vec!["--lib", "ink_linting", &manifest_path];
let tmp_dir_path = tmp_dir.path().as_os_str().to_string_lossy();
let env = vec![
("DYLINT_LIBRARY_PATH", Some(tmp_dir_path.as_ref())),
// For tests we need to set the `DYLINT_DRIVER_PATH` to a tmp folder,
// otherwise tests running in parallel will try to write to the same
// file at the same time which will result in a `Text file busy` error.
#[cfg(test)]
("DYLINT_DRIVER_PATH", Some(tmp_dir_path.as_ref())),
// We need to remove the `CARGO_TARGET_DIR` environment variable in
// case `cargo dylint` is invoked.
//
// This is because the ink! dylint driver crate found in `dylint` uses a
// fixed Rust toolchain via the `ink_linting/rust-toolchain` file. By
// removing this env variable we avoid issues with different Rust toolchains
// interfering with each other.
("CARGO_TARGET_DIR", None),
// There are generally problems with having a custom `rustc` wrapper, while
// executing `dylint` (which has a custom linker). Especially for `sccache`
// there is this bug: https://github.com/mozilla/sccache/issues/1000.
// Until we have a justification for leaving the wrapper we should unset it.
("RUSTC_WRAPPER", None),
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
];
let working_dir = crate_metadata
.manifest_path
.directory()
.unwrap_or_else(|| Path::new("."))
.canonicalize()?;
let verbosity = if verbosity == Verbosity::Verbose {
// `dylint` is verbose by default, it doesn't have a `--verbose` argument,
Verbosity::Default
} else {
verbosity
};
util::invoke_cargo("dylint", &args, Some(working_dir), verbosity, env)?;
Ok(())
}
/// Checks if all requirements for `dylint` are installed.
///
/// We require only an installed version of `cargo-dylint` here and don't
/// check for an installed version of `dylint-link`. This is because
/// `dylint-link` is only required for the `dylint` driver build process
/// in `build.rs`.
///
/// This function takes a `_working_dir` which is only used for unit tests.
fn check_dylint_requirements(_working_dir: Option<&Path>) -> Result<()> {
let execute_cmd = |cmd: &mut Command| {
// when testing this function we set the `PATH` to the `working_dir`
// so that we can have mocked binaries in there which are executed
// instead of the real ones.
#[cfg(test)]
{
let default_dir = PathBuf::from(".");
let working_dir = _working_dir.unwrap_or(default_dir.as_path());
let path_env = std::env::var("PATH").unwrap();
let path_env = format!("{}:{}", working_dir.to_string_lossy(), path_env);
cmd.env("PATH", path_env);
}
cmd.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|err| {
log::debug!("Error spawning `{:?}`", cmd);
err
})?
.wait()
.map(|res| res.success())
.map_err(|err| {
log::debug!("Error waiting for `{:?}`: {:?}", cmd, err);
err
})
};
// when testing this function we should never fall back to a `cargo` specified
// in the env variable, as this would mess with the mocked binaries.
#[cfg(not(test))]
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
#[cfg(test)]
let cargo = "cargo";
if !execute_cmd(Command::new(cargo).arg("dylint").arg("--version"))? {
anyhow::bail!("cargo-dylint was not found!\n\
Make sure it is installed and the binary is in your PATH environment.\n\n\
You can install it by executing `cargo install cargo-dylint`."
.to_string()
.bright_yellow());
}
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| {
section.entries_mut().iter_mut().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.
/// The name section is already stripped by `wasm-opt`.
fn strip_custom_sections(module: &mut Module) {
module.sections_mut().retain(|section| {
match section {
Section::Reloc(_) => false,
Section::Custom(custom) if custom.name() != "name" => false,
_ => true,
}
})
}
/// A contract should export nothing but the "call" and "deploy" functions.
///
/// Any elements not referenced by these exports become orphaned and are removed by `wasm-opt`.
fn strip_exports(module: &mut Module) {
if let Some(section) = module.export_section_mut() {
section.entries_mut().retain(|entry| {
matches!(entry.internal(), Internal::Function(_))
&& (entry.field() == "call" || entry.field() == "deploy")
})
}
}
/// Load and parse a Wasm file from disk.
fn load_module<P: AsRef<Path>>(path: P) -> Result<Module> {
let path = path.as_ref();
parity_wasm::deserialize_file(path).context(format!(
"Loading of wasm module at '{}' failed",
path.display(),
))
/// Performs required post-processing steps on the Wasm artifact.
fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
// Deserialize Wasm module from a file.
let mut module = load_module(&crate_metadata.original_wasm)
.context("Loading of original wasm 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(())
}
/// 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.contract_artifact_name
));
Michael Müller
committed
let _ = do_optimization(
crate_metadata.dest_wasm.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 `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(
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\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 mut command = Command::new(wasm_opt_path);
command
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
.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")
"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.
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,
);
}
Michael Müller
committed
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.
///
/// 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, vec![])
.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(())
}
/// Checks whether the supplied `ink_version` already contains the debug feature.
///
/// This feature was introduced in `3.0.0-rc4` with `ink_env/ink-debug`.
pub fn assert_debug_mode_supported(ink_version: &Version) -> anyhow::Result<()> {
log::info!("Contract version: {:?}", ink_version);
let minimum_version = Version::parse("3.0.0-rc4").expect("parsing version failed");
if ink_version < &minimum_version {
anyhow::bail!(
"Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
);
}
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.
pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
let ExecuteArgs {
manifest_path,
verbosity,
build_mode,
build_artifact,
unstable_flags,
optimization_passes,
keep_debug_symbols,
output_type,
} = args;
let crate_metadata = CrateMetadata::collect(&manifest_path)?;
assert_compatible_ink_dependencies(&manifest_path, verbosity)?;
if build_mode == BuildMode::Debug {
assert_debug_mode_supported(&crate_metadata.ink_version)?;
}
let build = || -> Result<OptimizationResult> {
if skip_linting {
maybe_println!(
verbosity,
" {} {}",
format!("[1/{}]", build_artifact.steps()).bold(),
"Skip ink! linting rules".bright_yellow().bold()
);
} else {
maybe_println!(
verbosity,
" {} {}",
format!("[1/{}]", build_artifact.steps()).bold(),
"Checking ink! linting rules".bright_green().bold()
);
exec_cargo_dylint(&crate_metadata, verbosity)?;
}
maybe_println!(
verbosity,
" {} {}",
format!("[2/{}]", build_artifact.steps()).bold(),
"Building cargo project".bright_green().bold()
);
exec_cargo_for_wasm_target(
&crate_metadata,
"build",
build_mode,
format!("[3/{}]", build_artifact.steps()).bold(),
"Post processing wasm file".bright_green().bold()
);
post_process_wasm(&crate_metadata)?;
Michael Müller
committed
maybe_println!(
verbosity,
" {} {}",
format!("[4/{}]", build_artifact.steps()).bold(),
"Optimizing wasm file".bright_green().bold()
);
let optimization_result =
optimize_wasm(&crate_metadata, optimization_passes, keep_debug_symbols)?;
Ok(optimization_result)
};
let (opt_result, metadata_result) = match build_artifact {
BuildArtifacts::CheckOnly => {
if skip_linting {
maybe_println!(
verbosity,
" {} {}",
format!("[1/{}]", build_artifact.steps()).bold(),
"Skip ink! linting rules".bright_yellow().bold()
);
} else {
maybe_println!(
verbosity,
" {} {}",
format!("[1/{}]", build_artifact.steps()).bold(),
"Checking ink! linting rules".bright_green().bold()
);
exec_cargo_dylint(&crate_metadata, verbosity)?;
}
maybe_println!(
verbosity,
" {} {}",
format!("[2/{}]", build_artifact.steps()).bold(),
"Executing `cargo check`".bright_green().bold()
);
exec_cargo_for_wasm_target(
&crate_metadata,
"check",
BuildMode::Release,
(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,
#[cfg(feature = "test-ci-only")]
mod tests_ci_only {
assert_compatible_ink_dependencies,
assert_debug_mode_supported,
check_wasm_opt_version_compatibility,
};
cmd::{
build::load_module,
BuildCommand,
},
util::tests::{
with_new_contract_project,
with_tmp_dir,
},
workspace::Manifest,
BuildArtifacts,
BuildMode,
ManifestPath,
OptimizationPasses,
OutputType,
UnstableOptions,
Verbosity,
VerbosityFlags,
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
path::{
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: &Path,
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");
}
fn has_debug_symbols<P: AsRef<Path>>(p: P) -> bool {
load_module(p)
.unwrap()
.custom_sections()
.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) {