diff --git a/src/cmd/build.rs b/src/cmd/build.rs index 32542d39f4501b4b0ac4fca3cbf5343b3557d649..60efcd5bee0923220be86ed4752c58b1d1a56321 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -81,7 +81,6 @@ impl BuildCommand { execute( &manifest_path, verbosity, - true, self.build_artifact, unstable_flags, optimization_passes, @@ -111,7 +110,6 @@ impl CheckCommand { execute( &manifest_path, verbosity, - false, BuildArtifacts::CheckOnly, unstable_flags, optimization_passes, @@ -119,7 +117,8 @@ impl CheckCommand { } } -/// Builds the project in the specified directory, defaults to the current directory. +/// 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) @@ -134,11 +133,11 @@ impl CheckCommand { /// user-defined settings will be preserved. /// /// To disable this and use the original `Cargo.toml` as is then pass the `-Z original_manifest` flag. -fn build_cargo_project( +fn exec_cargo_for_wasm_target( crate_metadata: &CrateMetadata, - build_artifact: BuildArtifacts, + command: &str, verbosity: Verbosity, - unstable_flags: UnstableFlags, + unstable_flags: &UnstableFlags, ) -> Result<()> { util::assert_channel()?; @@ -159,11 +158,7 @@ fn build_cargo_project( "--release", &format!("--target-dir={}", target_dir.to_string_lossy()), ]; - if build_artifact == BuildArtifacts::CheckOnly { - util::invoke_cargo("check", &args, manifest_path.directory(), verbosity)?; - } else { - util::invoke_cargo("build", &args, manifest_path.directory(), verbosity)?; - } + util::invoke_cargo(command, &args, manifest_path.directory(), verbosity)?; Ok(()) }; @@ -293,6 +288,7 @@ fn optimize_wasm( // 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, }) @@ -384,94 +380,74 @@ fn do_optimization( /// 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. -fn execute( +pub(crate) fn execute( manifest_path: &ManifestPath, verbosity: Verbosity, - optimize_contract: bool, build_artifact: BuildArtifacts, unstable_flags: UnstableFlags, optimization_passes: OptimizationPasses, ) -> Result { - if build_artifact == BuildArtifacts::CodeOnly || build_artifact == BuildArtifacts::CheckOnly { - let crate_metadata = CrateMetadata::collect(manifest_path)?; - let (maybe_dest_wasm, maybe_optimization_result) = execute_with_crate_metadata( - &crate_metadata, + let crate_metadata = CrateMetadata::collect(manifest_path)?; + + let build = || -> Result { + maybe_println!( verbosity, - optimize_contract, - build_artifact, - unstable_flags, - optimization_passes, - )?; - let res = BuildResult { - dest_wasm: maybe_dest_wasm, - dest_metadata: None, - dest_bundle: None, - target_directory: crate_metadata.target_directory, - optimization_result: maybe_optimization_result, - build_artifact, + " {} {}", + 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!( verbosity, - }; - return Ok(res); - } + " {} {}", + format!("[2/{}]", build_artifact.steps()).bold(), + "Post processing wasm file".bright_green().bold() + ); + post_process_wasm(&crate_metadata)?; - let res = super::metadata::execute( - &manifest_path, - verbosity, - build_artifact, - unstable_flags, - optimization_passes, - )?; - Ok(res) -} + maybe_println!( + verbosity, + " {} {}", + format!("[3/{}]", build_artifact.steps()).bold(), + "Optimizing wasm file".bright_green().bold() + ); + let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?; -/// 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. -/// -/// # Note -/// -/// Uses the supplied `CrateMetadata`. If an instance is not available use [`execute_build`] -/// -/// Returns a tuple of `(maybe_optimized_wasm_path, maybe_optimization_result)`. -pub(crate) fn execute_with_crate_metadata( - crate_metadata: &CrateMetadata, - verbosity: Verbosity, - optimize_contract: bool, - build_artifact: BuildArtifacts, - unstable_flags: UnstableFlags, - optimization_passes: OptimizationPasses, -) -> Result<(Option, Option)> { - maybe_println!( - verbosity, - " {} {}", - format!("[1/{}]", build_artifact.steps()).bold(), - "Building cargo project".bright_green().bold() - ); - build_cargo_project(&crate_metadata, build_artifact, verbosity, unstable_flags)?; - if build_artifact == BuildArtifacts::CheckOnly { - return Ok((None, None)); - } - maybe_println!( - verbosity, - " {} {}", - format!("[2/{}]", build_artifact.steps()).bold(), - "Post processing wasm file".bright_green().bold() - ); - post_process_wasm(&crate_metadata)?; - if !optimize_contract { - return Ok((None, None)); - } - maybe_println!( + 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, verbosity, - " {} {}", - format!("[3/{}]", build_artifact.steps()).bold(), - "Optimizing wasm file".bright_green().bold() - ); - let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?; - Ok(( - Some(crate_metadata.dest_wasm.clone()), - Some(optimization_result), - )) + }) } #[cfg(feature = "test-ci-only")] @@ -483,7 +459,7 @@ mod tests_ci_only { }; #[test] - fn build_template() { + fn build_code_only() { with_tmp_dir(|path| { cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); let manifest_path = @@ -491,8 +467,7 @@ mod tests_ci_only { let res = super::execute( &manifest_path, Verbosity::Default, - true, - BuildArtifacts::All, + BuildArtifacts::CodeOnly, UnstableFlags::default(), OptimizationPasses::default(), ) @@ -506,6 +481,11 @@ mod tests_ci_only { // 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); @@ -528,7 +508,6 @@ mod tests_ci_only { super::execute( &manifest_path, Verbosity::Default, - true, BuildArtifacts::CheckOnly, UnstableFlags::default(), OptimizationPasses::default(), diff --git a/src/cmd/metadata.rs b/src/cmd/metadata.rs index a1c2ea1a412666914d19e6084b5f08ba813d3d5b..4030e4460dba1f3e1e34745582787d3dabb11791 100644 --- a/src/cmd/metadata.rs +++ b/src/cmd/metadata.rs @@ -18,7 +18,7 @@ use crate::{ crate_metadata::CrateMetadata, maybe_println, util, workspace::{ManifestPath, Workspace}, - BuildArtifacts, BuildResult, OptimizationPasses, OptimizationResult, UnstableFlags, Verbosity, + UnstableFlags, Verbosity, }; use anyhow::Result; @@ -29,225 +29,186 @@ use contract_metadata::{ SourceLanguage, SourceWasm, User, }; use semver::Version; -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use url::Url; const METADATA_FILE: &str = "metadata.json"; -/// Executes the metadata generation process -struct GenerateMetadataCommand { - crate_metadata: CrateMetadata, - verbosity: Verbosity, - build_artifact: BuildArtifacts, - unstable_options: UnstableFlags, - optimization_passes: OptimizationPasses, +/// Metadata generation result. +pub struct MetadataResult { + /// Path to the resulting metadata file. + pub dest_metadata: PathBuf, + /// Path to the bundled file. + pub dest_bundle: PathBuf, } /// Result of generating the extended contract project metadata struct ExtendedMetadataResult { - dest_wasm: Option, source: Source, contract: Contract, user: Option, - optimization_result: Option, } -impl GenerateMetadataCommand { - pub fn exec(&self) -> Result { - util::assert_channel()?; - - let target_directory = self.crate_metadata.target_directory.clone(); - let out_path_metadata = target_directory.join(METADATA_FILE); - - let fname_bundle = format!("{}.contract", self.crate_metadata.package_name); - let out_path_bundle = target_directory.join(fname_bundle); - - // build the extended contract project metadata - let ExtendedMetadataResult { - dest_wasm, - source, - contract, - user, - optimization_result, - } = self.extended_metadata()?; - - let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { - let mut current_progress = 4; - maybe_println!( - self.verbosity, - " {} {}", - format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(), - "Generating metadata".bright_green().bold() - ); - let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy()); - let stdout = util::invoke_cargo( - "run", - &[ - "--package", - "metadata-gen", - &manifest_path.cargo_arg(), - &target_dir_arg, - "--release", - ], - self.crate_metadata.manifest_path.directory(), - self.verbosity, - )?; +/// Generates a file with metadata describing the ABI of the smart-contract. +/// +/// It does so by generating and invoking a temporary workspace member. +pub(crate) fn execute( + crate_metadata: &CrateMetadata, + final_contract_wasm: &Path, + verbosity: Verbosity, + total_steps: usize, + unstable_options: &UnstableFlags, +) -> Result { + util::assert_channel()?; + + let target_directory = crate_metadata.target_directory.clone(); + let out_path_metadata = target_directory.join(METADATA_FILE); + + let fname_bundle = format!("{}.contract", crate_metadata.package_name); + let out_path_bundle = target_directory.join(fname_bundle); + + // build the extended contract project metadata + let ExtendedMetadataResult { + source, + contract, + user, + } = extended_metadata(crate_metadata, final_contract_wasm)?; + + let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { + let mut current_progress = 4; + maybe_println!( + verbosity, + " {} {}", + format!("[{}/{}]", current_progress, total_steps).bold(), + "Generating metadata".bright_green().bold() + ); + let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy()); + let stdout = util::invoke_cargo( + "run", + &[ + "--package", + "metadata-gen", + &manifest_path.cargo_arg(), + &target_dir_arg, + "--release", + ], + crate_metadata.manifest_path.directory(), + verbosity, + )?; - let ink_meta: serde_json::Map = - serde_json::from_slice(&stdout)?; - let metadata = ContractMetadata::new(source, contract, user, ink_meta); - { - let mut metadata = metadata.clone(); - metadata.remove_source_wasm_attribute(); - let contents = serde_json::to_string_pretty(&metadata)?; - fs::write(&out_path_metadata, contents)?; - current_progress += 1; - } - - if self.build_artifact == BuildArtifacts::All { - maybe_println!( - self.verbosity, - " {} {}", - format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(), - "Generating bundle".bright_green().bold() - ); - let contents = serde_json::to_string(&metadata)?; - fs::write(&out_path_bundle, contents)?; - } + let ink_meta: serde_json::Map = serde_json::from_slice(&stdout)?; + let metadata = ContractMetadata::new(source, contract, user, ink_meta); + { + let mut metadata = metadata.clone(); + metadata.remove_source_wasm_attribute(); + let contents = serde_json::to_string_pretty(&metadata)?; + fs::write(&out_path_metadata, contents)?; + current_progress += 1; + } - Ok(()) - }; - - if self.unstable_options.original_manifest { - generate_metadata(&self.crate_metadata.manifest_path)?; - } else { - Workspace::new( - &self.crate_metadata.cargo_meta, - &self.crate_metadata.root_package.id, - )? + maybe_println!( + verbosity, + " {} {}", + format!("[{}/{}]", current_progress, total_steps).bold(), + "Generating bundle".bright_green().bold() + ); + let contents = serde_json::to_string(&metadata)?; + fs::write(&out_path_bundle, contents)?; + + Ok(()) + }; + + if unstable_options.original_manifest { + generate_metadata(&crate_metadata.manifest_path)?; + } else { + Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)? .with_root_package_manifest(|manifest| { manifest .with_added_crate_type("rlib")? .with_profile_release_lto(false)?; Ok(()) })? - .with_metadata_gen_package(self.crate_metadata.manifest_path.absolute_directory()?)? + .with_metadata_gen_package(crate_metadata.manifest_path.absolute_directory()?)? .using_temp(generate_metadata)?; - } - - let dest_bundle = if self.build_artifact == BuildArtifacts::All { - Some(out_path_bundle) - } else { - None - }; - Ok(BuildResult { - dest_metadata: Some(out_path_metadata), - dest_wasm, - dest_bundle, - optimization_result, - target_directory, - build_artifact: self.build_artifact, - verbosity: self.verbosity, - }) } - /// Generate the extended contract project metadata - fn extended_metadata(&self) -> Result { - let contract_package = &self.crate_metadata.root_package; - let ink_version = &self.crate_metadata.ink_version; - let rust_version = Version::parse(&rustc_version::version()?.to_string())?; - let contract_name = contract_package.name.clone(); - let contract_version = Version::parse(&contract_package.version.to_string())?; - let contract_authors = contract_package.authors.clone(); - // optional - let description = contract_package.description.clone(); - let documentation = self.crate_metadata.documentation.clone(); - let repository = contract_package - .repository - .as_ref() - .map(|repo| Url::parse(&repo)) - .transpose()?; - let homepage = self.crate_metadata.homepage.clone(); - let license = contract_package.license.clone(); - let (dest_wasm, hash, optimization_result) = self.wasm_hash()?; - let source = { - let lang = SourceLanguage::new(Language::Ink, ink_version.clone()); - let compiler = SourceCompiler::new(Compiler::RustC, rust_version); - let maybe_wasm = if self.build_artifact == BuildArtifacts::All { - let wasm = fs::read(&self.crate_metadata.dest_wasm)?; - // The Wasm which we read must have the same hash as `source.hash` - debug_assert!({ - let expected = blake2_hash(wasm.as_slice()); - expected == hash - }); - Some(SourceWasm::new(wasm)) - } else { - None - }; - Source::new(maybe_wasm, hash, lang, compiler) - }; - - // Required contract fields - let mut builder = Contract::builder(); - builder - .name(contract_name) - .version(contract_version) - .authors(contract_authors); - - if let Some(description) = description { - builder.description(description); - } + Ok(MetadataResult { + dest_metadata: out_path_metadata, + dest_bundle: out_path_bundle, + }) +} - if let Some(documentation) = documentation { - builder.documentation(documentation); - } +/// Generate the extended contract project metadata +fn extended_metadata( + crate_metadata: &CrateMetadata, + final_contract_wasm: &Path, +) -> Result { + let contract_package = &crate_metadata.root_package; + let ink_version = &crate_metadata.ink_version; + let rust_version = Version::parse(&rustc_version::version()?.to_string())?; + let contract_name = contract_package.name.clone(); + let contract_version = Version::parse(&contract_package.version.to_string())?; + let contract_authors = contract_package.authors.clone(); + // optional + let description = contract_package.description.clone(); + let documentation = crate_metadata.documentation.clone(); + let repository = contract_package + .repository + .as_ref() + .map(|repo| Url::parse(&repo)) + .transpose()?; + let homepage = crate_metadata.homepage.clone(); + let license = contract_package.license.clone(); + let source = { + let lang = SourceLanguage::new(Language::Ink, ink_version.clone()); + let compiler = SourceCompiler::new(Compiler::RustC, rust_version); + let wasm = fs::read(final_contract_wasm)?; + let hash = blake2_hash(wasm.as_slice()); + Source::new(Some(SourceWasm::new(wasm)), hash, lang, compiler) + }; - if let Some(repository) = repository { - builder.repository(repository); - } + // Required contract fields + let mut builder = Contract::builder(); + builder + .name(contract_name) + .version(contract_version) + .authors(contract_authors); - if let Some(homepage) = homepage { - builder.homepage(homepage); - } + if let Some(description) = description { + builder.description(description); + } - if let Some(license) = license { - builder.license(license); - } + if let Some(documentation) = documentation { + builder.documentation(documentation); + } - let contract = builder - .build() - .map_err(|err| anyhow::anyhow!("Invalid contract metadata builder state: {}", err))?; + if let Some(repository) = repository { + builder.repository(repository); + } - // user defined metadata - let user = self.crate_metadata.user.clone().map(User::new); + if let Some(homepage) = homepage { + builder.homepage(homepage); + } - Ok(ExtendedMetadataResult { - dest_wasm: Some(dest_wasm), - source, - contract, - user, - optimization_result: Some(optimization_result), - }) + if let Some(license) = license { + builder.license(license); } - /// Compile the contract and then hash the resulting Wasm. - /// - /// Return a tuple of `(dest_wasm, hash, optimization_result)`. - fn wasm_hash(&self) -> Result<(PathBuf, CodeHash, OptimizationResult)> { - let (maybe_dest_wasm, maybe_optimization_res) = super::build::execute_with_crate_metadata( - &self.crate_metadata, - self.verbosity, - true, // for the hash we always use the optimized version of the contract - self.build_artifact, - self.unstable_options.clone(), - self.optimization_passes, - )?; + let contract = builder + .build() + .map_err(|err| anyhow::anyhow!("Invalid contract metadata builder state: {}", err))?; - let wasm = fs::read(&self.crate_metadata.dest_wasm)?; - let dest_wasm = maybe_dest_wasm.expect("dest wasm must exist"); - let optimization_res = maybe_optimization_res.expect("optimization result must exist"); - Ok((dest_wasm, blake2_hash(wasm.as_slice()), optimization_res)) - } + // user defined metadata + let user = crate_metadata.user.clone().map(User::new); + + Ok(ExtendedMetadataResult { + source, + contract, + user, + }) } /// Returns the blake2 hash of the submitted slice. @@ -259,28 +220,6 @@ fn blake2_hash(code: &[u8]) -> CodeHash { CodeHash(output) } -/// Generates a file with metadata describing the ABI of the smart-contract. -/// -/// It does so by generating and invoking a temporary workspace member. -pub(crate) fn execute( - manifest_path: &ManifestPath, - verbosity: Verbosity, - build_artifact: BuildArtifacts, - unstable_options: UnstableFlags, - optimization_passes: OptimizationPasses, -) -> Result { - let crate_metadata = CrateMetadata::collect(manifest_path)?; - let res = GenerateMetadataCommand { - crate_metadata, - verbosity, - build_artifact, - unstable_options, - optimization_passes, - } - .exec()?; - Ok(res) -} - #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { @@ -381,15 +320,24 @@ mod tests { test_manifest.write()?; let crate_metadata = CrateMetadata::collect(&test_manifest.manifest_path)?; - let dest_bundle = cmd::metadata::execute( + + // usually this file will be produced by a previous build step + let final_contract_wasm_path = &crate_metadata.dest_wasm; + fs::create_dir_all(final_contract_wasm_path.parent().unwrap()).unwrap(); + fs::write(final_contract_wasm_path, "TEST FINAL WASM BLOB").unwrap(); + + let build_result = cmd::build::execute( &test_manifest.manifest_path, Verbosity::Default, BuildArtifacts::All, UnstableFlags::default(), OptimizationPasses::default(), - )? - .dest_bundle - .expect("bundle file not found"); + )?; + let dest_bundle = build_result + .metadata_result + .expect("Metadata should be generated") + .dest_bundle; + let metadata_json: Map = serde_json::from_slice(&fs::read(&dest_bundle)?)?; diff --git a/src/main.rs b/src/main.rs index 26369b448771c50424029657109f411e7721bf4b..b34c10b064884c167e0693d107f96714a18ffed4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,7 @@ mod workspace; use self::workspace::ManifestPath; -use crate::cmd::{BuildCommand, CheckCommand}; +use crate::cmd::{metadata::MetadataResult, BuildCommand, CheckCommand}; #[cfg(feature = "extrinsics")] use sp_core::{crypto::Pair, sr25519, H256}; @@ -156,6 +156,7 @@ impl TryFrom<&OptimizationFlags> for OptimizationPasses { impl OptimizationPasses { /// Returns the string representation of `OptimizationPasses` + #[cfg(not(feature = "binaryen-as-dependency"))] pub(crate) fn to_str(&self) -> &str { match self { OptimizationPasses::Zero => "0", @@ -306,12 +307,10 @@ impl std::str::FromStr for BuildArtifacts { /// Result of the metadata generation process. pub struct BuildResult { - /// Path to the resulting metadata file. - pub dest_metadata: Option, /// Path to the resulting Wasm file. pub dest_wasm: Option, - /// Path to the bundled file. - pub dest_bundle: Option, + /// Result of the metadata generation. + pub metadata_result: Option, /// Path to the directory where output files are written to. pub target_directory: PathBuf, /// If existent the result of the optimization. @@ -324,6 +323,8 @@ pub struct BuildResult { /// Result of the optimization process. pub struct OptimizationResult { + /// The path of the optimized wasm file. + pub dest_wasm: PathBuf, /// The original Wasm size. pub original_size: f64, /// The Wasm size after optimizations have been applied. @@ -362,10 +363,10 @@ impl BuildResult { size_diff, self.target_directory.display().to_string().bold(), ); - if let Some(dest_bundle) = self.dest_bundle.as_ref() { + if let Some(metadata_result) = self.metadata_result.as_ref() { let bundle = format!( " - {} (code + metadata)\n", - util::base_name(&dest_bundle).bold() + util::base_name(&metadata_result.dest_bundle).bold() ); out.push_str(&bundle); } @@ -376,10 +377,10 @@ impl BuildResult { ); out.push_str(&wasm); } - if let Some(dest_metadata) = self.dest_metadata.as_ref() { + if let Some(metadata_result) = self.metadata_result.as_ref() { let metadata = format!( " - {} (the contract's metadata)", - util::base_name(&dest_metadata).bold() + util::base_name(&metadata_result.dest_metadata).bold() ); out.push_str(&metadata); }