diff --git a/src/cmd/build.rs b/src/cmd/build.rs index d2e9f7f056a04624e4fe70b65af8726254aea2ab..0435cfe2532f97f44248ae030adc4c7f067ece76 100644 --- a/src/cmd/build.rs +++ b/src/cmd/build.rs @@ -15,6 +15,7 @@ // along with cargo-contract. If not, see . use std::{ + convert::TryFrom, fs::{metadata, File}, io::{Read, Write}, path::PathBuf, @@ -24,15 +25,89 @@ use crate::{ crate_metadata::CrateMetadata, util, workspace::{ManifestPath, Profile, Workspace}, - GenerateArtifacts, GenerationResult, OptimizationResult, UnstableFlags, Verbosity, + BuildArtifacts, BuildResult, UnstableFlags, UnstableOptions, VerbosityFlags, }; +use crate::{OptimizationResult, Verbosity}; use anyhow::{Context, Result}; use colored::Colorize; use parity_wasm::elements::{External, MemoryType, Module, Section}; +use structopt::StructOpt; /// 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, + /// Which build artifacts to generate. + /// + /// - `all`: Generate the Wasm, the metadata and a bundled `.contract` file. + /// + /// - `code-only`: Only the Wasm is created, generation of metadata and a bundled + /// `.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, +} + +impl BuildCommand { + pub fn exec(&self) -> Result { + let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let unstable_flags: UnstableFlags = + TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?; + let verbosity: Option = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; + execute( + &manifest_path, + verbosity, + true, + 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, + #[structopt(flatten)] + verbosity: VerbosityFlags, + #[structopt(flatten)] + unstable_options: UnstableOptions, +} + +impl CheckCommand { + pub fn exec(&self) -> Result { + let manifest_path = ManifestPath::try_from(self.manifest_path.as_ref())?; + let unstable_flags: UnstableFlags = + TryFrom::<&UnstableOptions>::try_from(&self.unstable_options)?; + let verbosity: Option = TryFrom::<&VerbosityFlags>::try_from(&self.verbosity)?; + execute( + &manifest_path, + verbosity, + false, + BuildArtifacts::CheckOnly, + unstable_flags, + ) + } +} + /// Builds 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) @@ -51,7 +126,7 @@ const MAX_MEMORY_PAGES: u32 = 16; fn build_cargo_project( crate_metadata: &CrateMetadata, verbosity: Option, - unstable_options: UnstableFlags, + unstable_flags: UnstableFlags, ) -> Result<()> { util::assert_channel()?; @@ -80,7 +155,7 @@ fn build_cargo_project( Ok(()) }; - if unstable_options.original_manifest { + if unstable_flags.original_manifest { println!( "{} {}", "warning:".yellow().bold(), @@ -218,39 +293,34 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result { /// 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 -/// -/// Collects the contract crate's metadata using the supplied manifest (`Cargo.toml`) path. Use -/// [`execute_build_with_metadata`] if an instance is already available. -pub(crate) fn execute( +fn execute( manifest_path: &ManifestPath, verbosity: Option, optimize_contract: bool, - build_artifact: GenerateArtifacts, - unstable_options: UnstableFlags, -) -> Result { + build_artifact: BuildArtifacts, + unstable_flags: UnstableFlags, +) -> Result { let crate_metadata = CrateMetadata::collect(manifest_path)?; - if build_artifact == GenerateArtifacts::CodeOnly { + if build_artifact == BuildArtifacts::CodeOnly || build_artifact == BuildArtifacts::CheckOnly { let (maybe_dest_wasm, maybe_optimization_result) = execute_with_crate_metadata( &crate_metadata, verbosity, optimize_contract, build_artifact, - unstable_options, + unstable_flags, )?; - let res = GenerationResult { + let res = BuildResult { dest_wasm: maybe_dest_wasm, dest_metadata: None, dest_bundle: None, target_directory: crate_metadata.cargo_meta.target_directory, optimization_result: maybe_optimization_result, + build_artifact, }; return Ok(res); } - let res = - super::metadata::execute(&manifest_path, verbosity, build_artifact, unstable_options)?; + let res = super::metadata::execute(&manifest_path, verbosity, build_artifact, unstable_flags)?; Ok(res) } @@ -267,15 +337,15 @@ pub(crate) fn execute_with_crate_metadata( crate_metadata: &CrateMetadata, verbosity: Option, optimize_contract: bool, - build_artifact: GenerateArtifacts, - unstable_options: UnstableFlags, + build_artifact: BuildArtifacts, + unstable_flags: UnstableFlags, ) -> Result<(Option, Option)> { println!( " {} {}", format!("[1/{}]", build_artifact.steps()).bold(), "Building cargo project".bright_green().bold() ); - build_cargo_project(&crate_metadata, verbosity, unstable_options)?; + build_cargo_project(&crate_metadata, verbosity, unstable_flags)?; println!( " {} {}", format!("[2/{}]", build_artifact.steps()).bold(), @@ -300,7 +370,7 @@ pub(crate) fn execute_with_crate_metadata( #[cfg(feature = "test-ci-only")] #[cfg(test)] mod tests { - use crate::{cmd, util::tests::with_tmp_dir, GenerateArtifacts, ManifestPath, UnstableFlags}; + use crate::{cmd, util::tests::with_tmp_dir, BuildArtifacts, ManifestPath, UnstableFlags}; #[test] fn build_template() { @@ -312,7 +382,7 @@ mod tests { &manifest_path, None, true, - GenerateArtifacts::All, + BuildArtifacts::All, UnstableFlags::default(), ) .expect("build failed"); diff --git a/src/cmd/metadata.rs b/src/cmd/metadata.rs index 32b8677a56f48103f80b82d585113f08de79b9b8..d93565adf6e3d2997ec7dd91da81c01af4f3c34b 100644 --- a/src/cmd/metadata.rs +++ b/src/cmd/metadata.rs @@ -18,7 +18,7 @@ use crate::{ crate_metadata::CrateMetadata, util, workspace::{ManifestPath, Workspace}, - GenerateArtifacts, GenerationResult, OptimizationResult, UnstableFlags, Verbosity, + BuildArtifacts, BuildResult, OptimizationResult, UnstableFlags, Verbosity, }; use anyhow::Result; @@ -38,7 +38,7 @@ const METADATA_FILE: &str = "metadata.json"; struct GenerateMetadataCommand { crate_metadata: CrateMetadata, verbosity: Option, - build_artifact: GenerateArtifacts, + build_artifact: BuildArtifacts, unstable_options: UnstableFlags, } @@ -52,7 +52,7 @@ struct ExtendedMetadataResult { } impl GenerateMetadataCommand { - pub fn exec(&self) -> Result { + pub fn exec(&self) -> Result { util::assert_channel()?; let cargo_meta = &self.crate_metadata.cargo_meta; @@ -104,7 +104,7 @@ impl GenerateMetadataCommand { current_progress += 1; } - if self.build_artifact == GenerateArtifacts::All { + if self.build_artifact == BuildArtifacts::All { println!( " {} {}", format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(), @@ -131,17 +131,18 @@ impl GenerateMetadataCommand { .using_temp(generate_metadata)?; } - let dest_bundle = if self.build_artifact == GenerateArtifacts::All { + let dest_bundle = if self.build_artifact == BuildArtifacts::All { Some(out_path_bundle) } else { None }; - Ok(GenerationResult { + Ok(BuildResult { dest_metadata: Some(out_path_metadata), dest_wasm, dest_bundle, optimization_result, target_directory, + build_artifact: self.build_artifact, }) } @@ -167,7 +168,7 @@ impl GenerateMetadataCommand { 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 == GenerateArtifacts::All { + 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!({ @@ -258,9 +259,9 @@ fn blake2_hash(code: &[u8]) -> CodeHash { pub(crate) fn execute( manifest_path: &ManifestPath, verbosity: Option, - build_artifact: GenerateArtifacts, + build_artifact: BuildArtifacts, unstable_options: UnstableFlags, -) -> Result { +) -> Result { let crate_metadata = CrateMetadata::collect(manifest_path)?; let res = GenerateMetadataCommand { crate_metadata, @@ -277,7 +278,7 @@ pub(crate) fn execute( mod tests { use crate::cmd::metadata::blake2_hash; use crate::{ - cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, GenerateArtifacts, + cmd, crate_metadata::CrateMetadata, util::tests::with_tmp_dir, BuildArtifacts, ManifestPath, UnstableFlags, }; use contract_metadata::*; @@ -375,7 +376,7 @@ mod tests { let dest_bundle = cmd::metadata::execute( &test_manifest.manifest_path, None, - GenerateArtifacts::All, + BuildArtifacts::All, UnstableFlags::default(), )? .dest_bundle diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 6a7191d6d10eb59dedc4eceed4b61a2124a7fc29..01483a48deafd8aaa7fa7b3e5cc6ddad41869c1b 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -22,5 +22,6 @@ mod instantiate; pub mod metadata; pub mod new; +pub(crate) use self::build::{BuildCommand, CheckCommand}; #[cfg(feature = "extrinsics")] pub(crate) use self::{deploy::execute_deploy, instantiate::execute_instantiate}; diff --git a/src/main.rs b/src/main.rs index c5f6823187e190f7732245fea1ead538c473b8e5..6fed88ddee63af6b147eb35e10e17137e5c547a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,12 +21,11 @@ mod workspace; use self::workspace::ManifestPath; +use crate::cmd::{BuildCommand, CheckCommand}; + #[cfg(feature = "extrinsics")] use sp_core::{crypto::Pair, sr25519, H256}; -use std::{ - convert::{TryFrom, TryInto}, - path::PathBuf, -}; +use std::{convert::TryFrom, path::PathBuf}; #[cfg(feature = "extrinsics")] use subxt::PairSigner; @@ -93,8 +92,8 @@ impl ExtrinsicOpts { } } -#[derive(Debug, StructOpt)] -struct VerbosityFlags { +#[derive(Clone, Debug, StructOpt)] +pub struct VerbosityFlags { #[structopt(long)] quiet: bool, #[structopt(long)] @@ -120,7 +119,7 @@ impl TryFrom<&VerbosityFlags> for Option { } } -#[derive(Debug, StructOpt)] +#[derive(Clone, Debug, StructOpt)] struct UnstableOptions { /// Use the original manifest (Cargo.toml), do not modify for build optimizations #[structopt(long = "unstable-options", short = "Z", number_of_values = 1)] @@ -154,39 +153,77 @@ impl TryFrom<&UnstableOptions> for UnstableFlags { /// Describes which artifacts to generate #[derive(Copy, Clone, Eq, PartialEq, Debug, StructOpt)] #[structopt(name = "build-artifacts")] -pub enum GenerateArtifacts { +pub enum BuildArtifacts { /// Generate the Wasm, the metadata and a bundled `.contract` file #[structopt(name = "all")] All, /// Only the Wasm is created, generation of metadata and a bundled `.contract` file is skipped #[structopt(name = "code-only")] CodeOnly, + CheckOnly, } -impl GenerateArtifacts { +impl BuildArtifacts { /// Returns the number of steps required to complete a build artifact. /// Used as output on the cli. pub fn steps(&self) -> usize { match self { - GenerateArtifacts::All => 5, - GenerateArtifacts::CodeOnly => 3, + BuildArtifacts::All => 5, + BuildArtifacts::CodeOnly => 3, + BuildArtifacts::CheckOnly => 2, + } + } +} + +impl std::str::FromStr for BuildArtifacts { + type Err = String; + fn from_str(artifact: &str) -> Result { + match artifact { + "all" => Ok(BuildArtifacts::All), + "code-only" => Ok(BuildArtifacts::CodeOnly), + _ => Err("Could not parse build artifact".to_string()), } } +} - pub fn display(&self, result: &GenerationResult) -> String { - let optimization = GenerationResult::display_optimization(result); +/// 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, + /// Path to the directory where output files are written to. + pub target_directory: PathBuf, + /// If existent the result of the optimization. + pub optimization_result: Option, + /// Which build artifacts were generated. + pub build_artifact: BuildArtifacts, +} + +/// Result of the optimization process. +pub struct OptimizationResult { + /// The original Wasm size. + pub original_size: f64, + /// The Wasm size after optimizations have been applied. + pub optimized_size: f64, +} + +impl BuildResult { + pub fn display(&self) -> String { + let optimization = self.display_optimization(); let size_diff = format!( "\nOriginal wasm size: {}, Optimized: {}\n\n", format!("{:.1}K", optimization.0).bold(), format!("{:.1}K", optimization.1).bold(), ); - if self == &GenerateArtifacts::CodeOnly { + if self.build_artifact == BuildArtifacts::CodeOnly { let out = format!( "{}Your contract's code is ready. You can find it here:\n{}", size_diff, - result - .dest_wasm + self.dest_wasm .as_ref() .expect("wasm path must exist") .display() @@ -199,80 +236,37 @@ impl GenerateArtifacts { let mut out = format!( "{}Your contract artifacts are ready. You can find them in:\n{}\n\n", size_diff, - result.target_directory.display().to_string().bold(), + self.target_directory.display().to_string().bold(), ); - if let Some(dest_bundle) = result.dest_bundle.as_ref() { + if let Some(dest_bundle) = self.dest_bundle.as_ref() { let bundle = format!( " - {} (code + metadata)\n", - GenerationResult::display(&dest_bundle).bold() + util::base_name(&dest_bundle).bold() ); out.push_str(&bundle); } - if let Some(dest_wasm) = result.dest_wasm.as_ref() { + if let Some(dest_wasm) = self.dest_wasm.as_ref() { let wasm = format!( " - {} (the contract's code)\n", - GenerationResult::display(&dest_wasm).bold() + util::base_name(&dest_wasm).bold() ); out.push_str(&wasm); } - if let Some(dest_metadata) = result.dest_metadata.as_ref() { + if let Some(dest_metadata) = self.dest_metadata.as_ref() { let metadata = format!( " - {} (the contract's metadata)", - GenerationResult::display(&dest_metadata).bold() + util::base_name(&dest_metadata).bold() ); out.push_str(&metadata); } out } -} - -impl std::str::FromStr for GenerateArtifacts { - type Err = String; - fn from_str(artifact: &str) -> Result { - match artifact { - "all" => Ok(GenerateArtifacts::All), - "code-only" => Ok(GenerateArtifacts::CodeOnly), - _ => Err("Could not parse build artifact".to_string()), - } - } -} - -/// Result of the metadata generation process. -pub struct GenerationResult { - /// 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, - /// Path to the directory where output files are written to. - pub target_directory: PathBuf, - /// If existent the result of the optimization. - pub optimization_result: Option, -} - -/// Result of the optimization process. -pub struct OptimizationResult { - /// The original Wasm size. - pub original_size: f64, - /// The Wasm size after optimizations have been applied. - pub optimized_size: f64, -} - -impl GenerationResult { - /// Returns the base name of the path. - pub fn display(path: &PathBuf) -> &str { - path.file_name() - .expect("file name must exist") - .to_str() - .expect("must be valid utf-8") - } /// Returns a tuple of `(original_size, optimized_size)`. /// /// Panics if no optimization result is available. - pub fn display_optimization(res: &GenerationResult) -> (f64, f64) { - let optimization = res + fn display_optimization(&self) -> (f64, f64) { + let optimization = self .optimization_result .as_ref() .expect("optimization result must exist"); @@ -293,42 +287,13 @@ enum Command { }, /// Compiles the contract, generates metadata, bundles both together in a `.contract` file #[structopt(name = "build")] - Build { - /// Path to the Cargo.toml of the contract to build - #[structopt(long, parse(from_os_str))] - manifest_path: Option, - /// Which build artifacts to generate. - /// - /// - `all`: Generate the Wasm, the metadata and a bundled `.contract` file. - /// - /// - `code-only`: Only the Wasm is created, generation of metadata and a bundled - /// `.contract` file is skipped. - #[structopt( - long = "generate", - default_value = "all", - value_name = "all | code-only", - verbatim_doc_comment - )] - build_artifact: GenerateArtifacts, - #[structopt(flatten)] - verbosity: VerbosityFlags, - #[structopt(flatten)] - unstable_options: UnstableOptions, - }, + Build(BuildCommand), /// Command has been deprecated, use `cargo contract build` instead #[structopt(name = "generate-metadata")] GenerateMetadata {}, /// Check that the code builds as Wasm; does not output any build artifact to the top level `target/` directory #[structopt(name = "check")] - Check { - /// Path to the Cargo.toml of the contract to build - #[structopt(long, parse(from_os_str))] - manifest_path: Option, - #[structopt(flatten)] - verbosity: VerbosityFlags, - #[structopt(flatten)] - unstable_options: UnstableOptions, - }, + Check(CheckCommand), /// Test the smart contract off-chain #[structopt(name = "test")] Test {}, @@ -394,37 +359,16 @@ fn main() { fn exec(cmd: Command) -> Result { match &cmd { Command::New { name, target_dir } => cmd::new::execute(name, target_dir.as_ref()), - Command::Build { - manifest_path, - verbosity, - build_artifact, - unstable_options, - } => { - let manifest_path = ManifestPath::try_from(manifest_path.as_ref())?; - let result = cmd::build::execute( - &manifest_path, - verbosity.try_into()?, - true, - *build_artifact, - unstable_options.try_into()?, - )?; - - Ok(build_artifact.display(&result)) + Command::Build(build) => { + let result = build.exec()?; + Ok(result.display()) } - Command::Check { - manifest_path, - verbosity, - unstable_options, - } => { - let manifest_path = ManifestPath::try_from(manifest_path.as_ref())?; - let res = cmd::build::execute( - &manifest_path, - verbosity.try_into()?, - false, - GenerateArtifacts::CodeOnly, - unstable_options.try_into()?, - )?; - assert!(res.dest_wasm.is_none(), "no dest_wasm should exist"); + Command::Check(check) => { + let res = check.exec()?; + assert!( + res.dest_wasm.is_none(), + "no dest_wasm must be on the generation result" + ); Ok("\nYour contract's code was built successfully.".to_string()) } Command::GenerateMetadata {} => Err(anyhow::anyhow!( diff --git a/src/util.rs b/src/util.rs index 84b2b89488d57e4cfbb68a4e4500e7c4b2b01004..f8deaa40a2f5510dbb1ba4d1bbc3b33b93b863b7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -17,6 +17,7 @@ use crate::Verbosity; use anyhow::{Context, Result}; use rustc_version::Channel; +use std::path::PathBuf; use std::{ffi::OsStr, path::Path, process::Command}; /// Check whether the current rust channel is valid: `nightly` is recommended. @@ -84,6 +85,14 @@ where } } +/// Returns the base name of the path. +pub(crate) fn base_name(path: &PathBuf) -> &str { + path.file_name() + .expect("file name must exist") + .to_str() + .expect("must be valid utf-8") +} + #[cfg(test)] pub mod tests { use std::path::Path;