use crate::{
crate_metadata::CrateMetadata,
maybe_println,
util,
validate_wasm,
wasm_opt::WasmOptHandler,
workspace::{
Manifest,
ManifestPath,
Profile,
Workspace,
},
BuildArtifacts,
BuildMode,
BuildResult,
Network,
OptimizationPasses,
OptimizationResult,
OutputType,
UnstableFlags,
UnstableOptions,
Verbosity,
VerbosityFlags,
};
use anyhow::{
Context,
Result,
};
use colored::Colorize;
use parity_wasm::elements::{
External,
Internal,
MemoryType,
Module,
Section,
};
use semver::Version;
use std::{
convert::TryFrom,
path::{
Path,
PathBuf,
},
process::Command,
str,
};
const MAX_MEMORY_PAGES: u32 = 16;
#[derive(Default)]
pub(crate) struct ExecuteArgs {
pub(crate) manifest_path: ManifestPath,
verbosity: Verbosity,
build_mode: BuildMode,
network: Network,
build_artifact: BuildArtifacts,
unstable_flags: UnstableFlags,
optimization_passes: OptimizationPasses,
keep_debug_symbols: bool,
skip_linting: bool,
output_type: OutputType,
}
#[derive(Debug, clap::Args)]
#[clap(name = "build")]
pub struct BuildCommand {
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[clap(long = "--release")]
build_release: bool,
#[clap(long = "--offline")]
build_offline: bool,
#[clap(long = "--skip-linting")]
skip_linting: bool,
#[clap(long = "generate", arg_enum, default_value = "all")]
build_artifact: BuildArtifacts,
#[clap(flatten)]
verbosity: VerbosityFlags,
#[clap(flatten)]
unstable_options: UnstableOptions,
#[clap(long)]
optimization_passes: Option<OptimizationPasses>,
#[clap(long)]
keep_debug_symbols: bool,
#[clap(long, conflicts_with = "verbose")]
output_json: bool,
}
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)?;
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() {
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,
};
if matches!(output_type, OutputType::Json) {
verbosity = Verbosity::Quiet;
}
let args = ExecuteArgs {
manifest_path,
verbosity,
build_mode,
network,
build_artifact: self.build_artifact,
unstable_flags,
optimization_passes,
keep_debug_symbols: self.keep_debug_symbols,
skip_linting: self.skip_linting,
output_type,
};
execute(args)
}
}
#[derive(Debug, clap::Args)]
#[clap(name = "check")]
pub struct CheckCommand {
#[clap(long, parse(from_os_str))]
manifest_path: Option<PathBuf>,
#[clap(flatten)]
verbosity: VerbosityFlags,
#[clap(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)?;
let args = ExecuteArgs {
manifest_path,
verbosity,
build_mode: BuildMode::Debug,
network: Network::default(),
build_artifact: BuildArtifacts::CheckOnly,
unstable_flags,
optimization_passes: OptimizationPasses::Zero,
keep_debug_symbols: false,
skip_linting: false,
output_type: OutputType::default(),
};
execute(args)
}
}
fn exec_cargo_for_wasm_target(
crate_metadata: &CrateMetadata,
command: &str,
build_mode: BuildMode,
network: Network,
verbosity: Verbosity,
unstable_flags: &UnstableFlags,
) -> Result<()> {
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",
&target_dir,
];
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)?;
Ok(())
};
if unstable_flags.original_manifest {
maybe_println!(
verbosity,
"{} {}",
"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(())
})?
.using_temp(cargo_build)?;
}
Ok(())
}
fn exec_cargo_dylint(crate_metadata: &CrateMetadata, verbosity: Verbosity) -> Result<()> {
check_dylint_requirements(crate_metadata.manifest_path.directory())?;
let verbosity = if verbosity == Verbosity::Verbose {
Verbosity::Default
} else {
verbosity
};
let target_dir = &crate_metadata.target_directory.to_string_lossy();
let args = vec!["--lib=ink_linting", "--quiet"];
let env = vec![
("CARGO_TARGET_DIR", Some(target_dir.as_ref())),
("RUSTC_WRAPPER", None),
];
Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
.with_root_package_manifest(|manifest| {
manifest.with_dylint()?;
Ok(())
})?
.using_temp(|manifest_path| {
util::invoke_cargo("dylint", &args, manifest_path.directory(), verbosity, env)
.map(|_| ())
})?;
Ok(())
}
fn check_dylint_requirements(_working_dir: Option<&Path>) -> Result<()> {
let execute_cmd = |cmd: &mut Command| {
#[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);
}
let mut child = if let Ok(child) = cmd
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.spawn()
{
child
} else {
tracing::debug!("Error spawning `{:?}`", cmd);
return false
};
child.wait().map(|ret| ret.success()).unwrap_or_else(|err| {
tracing::debug!("Error waiting for `{:?}`: {:?}", cmd, err);
false
})
};
#[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());
}
if !execute_cmd(Command::new("dylint-link").arg("--version")) {
anyhow::bail!("dylint-link 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 dylint-link`."
.to_string()
.bright_yellow());
}
Ok(())
}
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() {
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(())
}
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,
}
})
}
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")
})
}
}
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(),
))
}
fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
let mut module = load_module(&crate_metadata.original_wasm)
.context("Loading of original wasm failed")?;
strip_exports(&mut module);
ensure_maximum_memory_pages(&mut module, MAX_MEMORY_PAGES)?;
strip_custom_sections(&mut module);
validate_wasm::validate_import_section(&module)?;
debug_assert!(
!module.clone().into_bytes().unwrap().is_empty(),
"resulting wasm size of post processing must be > 0"
);
parity_wasm::serialize_to_file(&crate_metadata.dest_wasm, module)?;
Ok(())
}
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![])
.with_context(|| {
format!(
"Mismatching versions of `{}` were found!\n\
Please ensure that your contract and your ink! dependencies use a compatible \
version of this package.",
dependency
)
})?;
}
Ok(())
}
pub fn assert_debug_mode_supported(ink_version: &Version) -> anyhow::Result<()> {
tracing::debug!("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(())
}
pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
let ExecuteArgs {
manifest_path,
verbosity,
build_mode,
network,
build_artifact,
unstable_flags,
optimization_passes,
keep_debug_symbols,
skip_linting,
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,
network,
verbosity,
&unstable_flags,
)?;
maybe_println!(
verbosity,
" {} {}",
format!("[3/{}]", build_artifact.steps()).bold(),
"Post processing wasm file".bright_green().bold()
);
post_process_wasm(&crate_metadata)?;
maybe_println!(
verbosity,
" {} {}",
format!("[4/{}]", build_artifact.steps()).bold(),
"Optimizing wasm file".bright_green().bold()
);
let handler = WasmOptHandler::new(optimization_passes, keep_debug_symbols)?;
let optimization_result = handler.optimize(
&crate_metadata.dest_wasm,
&crate_metadata.contract_artifact_name,
)?;
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,
network,
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(),
network,
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_mode,
build_artifact,
verbosity,
output_type,
})
}
#[cfg(feature = "test-ci-only")]
#[cfg(test)]
mod tests_ci_only {
use super::{
assert_compatible_ink_dependencies,
assert_debug_mode_supported,
};
use crate::{
cmd::{
build::load_module,
BuildCommand,
},
util::tests::{
create_executable,
with_new_contract_project,
},
workspace::Manifest,
BuildArtifacts,
BuildMode,
ManifestPath,
OptimizationPasses,
OutputType,
UnstableOptions,
Verbosity,
VerbosityFlags,
};
use semver::Version;
use std::{
ffi::OsStr,
path::Path,
};
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")
}
#[test]
fn build_code_only() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_mode: BuildMode::Release,
build_artifact: BuildArtifacts::CodeOnly,
..Default::default()
};
let res = super::execute(args).expect("build failed");
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);
assert!(optimized_size < 3.0);
assert!(!has_debug_symbols(&res.dest_wasm.unwrap()));
Ok(())
})
}
#[test]
fn check_must_not_output_contract_artifacts_in_project_dir() {
with_new_contract_project(|manifest_path| {
let project_dir = manifest_path.directory().expect("directory must exist");
let args = crate::cmd::build::ExecuteArgs {
manifest_path: manifest_path.clone(),
build_artifact: BuildArtifacts::CheckOnly,
..Default::default()
};
super::execute(args).expect("build failed");
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_new_contract_project(|manifest_path| {
write_optimization_passes_into_manifest(
manifest_path.as_ref(),
OptimizationPasses::Three,
);
let cmd = BuildCommand {
manifest_path: Some(manifest_path.into()),
build_artifact: BuildArtifacts::All,
build_release: false,
build_offline: false,
verbosity: VerbosityFlags::default(),
unstable_options: UnstableOptions::default(),
optimization_passes: Some(OptimizationPasses::Zero),
keep_debug_symbols: false,
skip_linting: false,
output_json: false,
};
let res = cmd.exec().expect("build failed");
let optimization = res
.optimization_result
.expect("no optimization result available");
let size_diff = optimization.original_size - optimization.optimized_size;
assert!(
0.0 < size_diff && size_diff < 10.0,
"The optimized size savings are larger than allowed or negative: {}",
size_diff,
);
Ok(())
})
}
#[test]
fn optimization_passes_from_profile_must_be_used() {
with_new_contract_project(|manifest_path| {
write_optimization_passes_into_manifest(
manifest_path.as_ref(),
OptimizationPasses::Three,
);
let cmd = BuildCommand {
manifest_path: Some(manifest_path.into()),
build_artifact: BuildArtifacts::All,
build_release: false,
build_offline: false,
verbosity: VerbosityFlags::default(),
unstable_options: UnstableOptions::default(),
optimization_passes: None,
keep_debug_symbols: false,
skip_linting: false,
output_json: false,
};
let res = cmd.exec().expect("build failed");
let optimization = res
.optimization_result
.expect("no optimization result available");
let size_diff = optimization.original_size - optimization.optimized_size;
assert!(
size_diff > (optimization.original_size / 2.0),
"The optimized size savings are too small: {}",
size_diff,
);
Ok(())
})
}
#[test]
fn project_template_dependencies_must_be_ink_compatible() {
with_new_contract_project(|manifest_path| {
let res =
assert_compatible_ink_dependencies(&manifest_path, Verbosity::Default);
assert!(res.is_ok());
Ok(())
})
}
#[test]
fn detect_mismatching_parity_scale_codec_dependencies() {
with_new_contract_project(|manifest_path| {
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");
let res =
assert_compatible_ink_dependencies(&manifest_path, Verbosity::Default);
assert!(res.is_err());
Ok(())
})
}
#[test]
fn contract_lib_name_different_from_package_name_must_build() {
with_new_contract_project(|manifest_path| {
let mut manifest =
Manifest::new(manifest_path.clone()).expect("manifest creation failed");
let _ = manifest
.set_lib_name("some_lib_name")
.expect("setting lib name failed");
let _ = manifest
.set_package_name("some_package_name")
.expect("setting pacakge name failed");
manifest
.write(&manifest_path)
.expect("writing manifest failed");
let cmd = BuildCommand {
manifest_path: Some(manifest_path.into()),
build_artifact: BuildArtifacts::All,
build_release: false,
build_offline: false,
verbosity: VerbosityFlags::default(),
unstable_options: UnstableOptions::default(),
optimization_passes: None,
keep_debug_symbols: false,
skip_linting: false,
output_json: false,
};
let res = cmd.exec().expect("build failed");
assert_eq!(
res.dest_wasm
.expect("`dest_wasm` does not exist")
.file_name(),
Some(OsStr::new("some_lib_name.wasm"))
);
Ok(())
})
}
#[test]
pub fn debug_mode_must_be_compatible() {
assert_debug_mode_supported(
&Version::parse("3.0.0-rc4").expect("parsing must work"),
)
.expect("debug mode must be compatible");
assert_debug_mode_supported(
&Version::parse("4.0.0-rc1").expect("parsing must work"),
)
.expect("debug mode must be compatible");
assert_debug_mode_supported(&Version::parse("5.0.0").expect("parsing must work"))
.expect("debug mode must be compatible");
}
#[test]
pub fn debug_mode_must_be_incompatible() {
let res = assert_debug_mode_supported(
&Version::parse("3.0.0-rc3").expect("parsing must work"),
)
.expect_err("assertion must fail");
assert_eq!(
res.to_string(),
"Building the contract in debug mode requires an ink! version newer than `3.0.0-rc3`!"
);
}
#[test]
fn building_template_in_debug_mode_must_work() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_mode: BuildMode::Debug,
..Default::default()
};
let res = super::execute(args);
assert!(res.is_ok(), "building template in debug mode failed!");
Ok(())
})
}
#[test]
fn building_template_in_release_mode_must_work() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_mode: BuildMode::Release,
..Default::default()
};
let res = super::execute(args);
assert!(res.is_ok(), "building template in release mode failed!");
Ok(())
})
}
#[test]
fn building_contract_with_source_file_in_subfolder_must_work() {
with_new_contract_project(|manifest_path| {
let path = manifest_path.directory().expect("dir must exist");
let old_lib_path = path.join(Path::new("lib.rs"));
let new_lib_path = path.join(Path::new("srcfoo")).join(Path::new("lib.rs"));
let new_dir_path = path.join(Path::new("srcfoo"));
std::fs::create_dir_all(new_dir_path).expect("creating dir must work");
std::fs::rename(old_lib_path, new_lib_path).expect("moving file must work");
let mut manifest = Manifest::new(manifest_path.clone())
.expect("creating manifest must work");
manifest
.set_lib_path("srcfoo/lib.rs")
.expect("setting lib path must work");
manifest.write(&manifest_path).expect("writing must work");
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_artifact: BuildArtifacts::CheckOnly,
..Default::default()
};
let res = super::execute(args);
assert!(res.is_ok(), "building contract failed!");
Ok(())
})
}
#[test]
fn keep_debug_symbols_in_debug_mode() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_mode: BuildMode::Debug,
build_artifact: BuildArtifacts::CodeOnly,
keep_debug_symbols: true,
..Default::default()
};
let res = super::execute(args).expect("build failed");
assert!(has_debug_symbols(&res.dest_wasm.unwrap()));
Ok(())
})
}
#[test]
fn keep_debug_symbols_in_release_mode() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_mode: BuildMode::Release,
build_artifact: BuildArtifacts::CodeOnly,
keep_debug_symbols: true,
..Default::default()
};
let res = super::execute(args).expect("build failed");
assert!(has_debug_symbols(&res.dest_wasm.unwrap()));
Ok(())
})
}
#[test]
fn build_with_json_output_works() {
with_new_contract_project(|manifest_path| {
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
output_type: OutputType::Json,
..Default::default()
};
let res = super::execute(args).expect("build failed");
assert!(res.serialize_json().is_ok());
Ok(())
})
}
#[test]
#[ignore]
fn dylint_must_find_issue() {
with_new_contract_project(|manifest_path| {
let contract = r#"
#![cfg_attr(not(feature = "std"), no_std)]
use ink_lang as ink;
#[ink::contract]
mod fail_mapping_01 {
use ink_storage::{traits::SpreadAllocate, Mapping};
#[ink(storage)]
#[derive(SpreadAllocate)]
pub struct MyContract {
balances: Mapping<AccountId, Balance>,
}
impl MyContract {
#[ink(constructor)]
pub fn new() -> Self {
Self {
balances: Default::default(),
}
}
/// Returns the total token supply.
#[ink(message)]
pub fn get(&self) {
// ...
}
}
}"#;
let project_dir = manifest_path.directory().expect("directory must exist");
let lib = project_dir.join("lib.rs");
std::fs::write(&lib, contract)?;
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
build_artifact: BuildArtifacts::CheckOnly,
..Default::default()
};
let res = super::execute(args);
match res {
Err(err) => {
eprintln!("err: {:?}", err);
assert!(err.to_string().contains(
"help: add an `initialize_contract` function in this constructor"
));
}
_ => panic!("build succeeded, but must fail!"),
};
Ok(())
})
}
#[cfg(unix)]
#[test]
fn missing_cargo_dylint_installation_must_be_detected() {
with_new_contract_project(|manifest_path| {
let manifest_dir = manifest_path.directory().unwrap();
create_executable(&manifest_dir.join("dylint-link"), "#!/bin/sh\nexit 0");
create_executable(&manifest_dir.join("cargo"), "#!/bin/sh\nexit 1");
let args = crate::cmd::build::ExecuteArgs {
manifest_path,
..Default::default()
};
let res = super::execute(args).map(|_| ()).unwrap_err();
assert!(format!("{:?}", res).contains("cargo-dylint was not found!"));
Ok(())
})
}
}