Unverified Commit cf0671b8 authored by Andrew Jones's avatar Andrew Jones Committed by GitHub
Browse files

Refactor build command (#223)

* Refactoring build, phase 1

* Fmt

* Extract local method for building, always optimize on build

* Add MetadataResult type

* Fmt

* Create dest wasm dir

* Execute build directly from metadata tests

* Modify existing build test to code only, since metadata test now builds all

* Fix errors after merge
parent bddfdf67
Pipeline #129690 passed with stages
in 5 minutes and 59 seconds
...@@ -81,7 +81,6 @@ impl BuildCommand { ...@@ -81,7 +81,6 @@ impl BuildCommand {
execute( execute(
&manifest_path, &manifest_path,
verbosity, verbosity,
true,
self.build_artifact, self.build_artifact,
unstable_flags, unstable_flags,
optimization_passes, optimization_passes,
...@@ -111,7 +110,6 @@ impl CheckCommand { ...@@ -111,7 +110,6 @@ impl CheckCommand {
execute( execute(
&manifest_path, &manifest_path,
verbosity, verbosity,
false,
BuildArtifacts::CheckOnly, BuildArtifacts::CheckOnly,
unstable_flags, unstable_flags,
optimization_passes, optimization_passes,
...@@ -119,7 +117,8 @@ impl CheckCommand { ...@@ -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) /// 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) /// 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 { ...@@ -134,11 +133,11 @@ impl CheckCommand {
/// user-defined settings will be preserved. /// user-defined settings will be preserved.
/// ///
/// To disable this and use the original `Cargo.toml` as is then pass the `-Z original_manifest` flag. /// 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, crate_metadata: &CrateMetadata,
build_artifact: BuildArtifacts, command: &str,
verbosity: Verbosity, verbosity: Verbosity,
unstable_flags: UnstableFlags, unstable_flags: &UnstableFlags,
) -> Result<()> { ) -> Result<()> {
util::assert_channel()?; util::assert_channel()?;
...@@ -159,11 +158,7 @@ fn build_cargo_project( ...@@ -159,11 +158,7 @@ fn build_cargo_project(
"--release", "--release",
&format!("--target-dir={}", target_dir.to_string_lossy()), &format!("--target-dir={}", target_dir.to_string_lossy()),
]; ];
if build_artifact == BuildArtifacts::CheckOnly { util::invoke_cargo(command, &args, manifest_path.directory(), verbosity)?;
util::invoke_cargo("check", &args, manifest_path.directory(), verbosity)?;
} else {
util::invoke_cargo("build", &args, manifest_path.directory(), verbosity)?;
}
Ok(()) Ok(())
}; };
...@@ -293,6 +288,7 @@ fn optimize_wasm( ...@@ -293,6 +288,7 @@ fn optimize_wasm(
// overwrite existing destination wasm file with the optimised version // overwrite existing destination wasm file with the optimised version
std::fs::rename(&dest_optimized, &crate_metadata.dest_wasm)?; std::fs::rename(&dest_optimized, &crate_metadata.dest_wasm)?;
Ok(OptimizationResult { Ok(OptimizationResult {
dest_wasm: crate_metadata.dest_wasm.clone(),
original_size, original_size,
optimized_size, optimized_size,
}) })
...@@ -384,73 +380,24 @@ fn do_optimization( ...@@ -384,73 +380,24 @@ fn do_optimization(
/// Executes build of the smart-contract which produces a wasm binary that is ready for deploying. /// 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. /// It does so by invoking `cargo build` and then post processing the final binary.
fn execute( pub(crate) fn execute(
manifest_path: &ManifestPath, manifest_path: &ManifestPath,
verbosity: Verbosity, verbosity: Verbosity,
optimize_contract: bool,
build_artifact: BuildArtifacts, build_artifact: BuildArtifacts,
unstable_flags: UnstableFlags, unstable_flags: UnstableFlags,
optimization_passes: OptimizationPasses, optimization_passes: OptimizationPasses,
) -> Result<BuildResult> { ) -> Result<BuildResult> {
if build_artifact == BuildArtifacts::CodeOnly || build_artifact == BuildArtifacts::CheckOnly {
let crate_metadata = CrateMetadata::collect(manifest_path)?; let crate_metadata = CrateMetadata::collect(manifest_path)?;
let (maybe_dest_wasm, maybe_optimization_result) = execute_with_crate_metadata(
&crate_metadata,
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,
verbosity,
};
return Ok(res);
}
let res = super::metadata::execute(
&manifest_path,
verbosity,
build_artifact,
unstable_flags,
optimization_passes,
)?;
Ok(res)
}
/// Executes build of the smart-contract which produces a Wasm binary that is ready for deploying. let build = || -> Result<OptimizationResult> {
///
/// 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<PathBuf>, Option<OptimizationResult>)> {
maybe_println!( maybe_println!(
verbosity, verbosity,
" {} {}", " {} {}",
format!("[1/{}]", build_artifact.steps()).bold(), format!("[1/{}]", build_artifact.steps()).bold(),
"Building cargo project".bright_green().bold() "Building cargo project".bright_green().bold()
); );
build_cargo_project(&crate_metadata, build_artifact, verbosity, unstable_flags)?; exec_cargo_for_wasm_target(&crate_metadata, "build", verbosity, &unstable_flags)?;
if build_artifact == BuildArtifacts::CheckOnly {
return Ok((None, None));
}
maybe_println!( maybe_println!(
verbosity, verbosity,
" {} {}", " {} {}",
...@@ -458,9 +405,7 @@ pub(crate) fn execute_with_crate_metadata( ...@@ -458,9 +405,7 @@ pub(crate) fn execute_with_crate_metadata(
"Post processing wasm file".bright_green().bold() "Post processing wasm file".bright_green().bold()
); );
post_process_wasm(&crate_metadata)?; post_process_wasm(&crate_metadata)?;
if !optimize_contract {
return Ok((None, None));
}
maybe_println!( maybe_println!(
verbosity, verbosity,
" {} {}", " {} {}",
...@@ -468,10 +413,41 @@ pub(crate) fn execute_with_crate_metadata( ...@@ -468,10 +413,41 @@ pub(crate) fn execute_with_crate_metadata(
"Optimizing wasm file".bright_green().bold() "Optimizing wasm file".bright_green().bold()
); );
let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?; let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?;
Ok((
Some(crate_metadata.dest_wasm.clone()), Ok(optimization_result)
Some(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,
})
} }
#[cfg(feature = "test-ci-only")] #[cfg(feature = "test-ci-only")]
...@@ -483,7 +459,7 @@ mod tests_ci_only { ...@@ -483,7 +459,7 @@ mod tests_ci_only {
}; };
#[test] #[test]
fn build_template() { fn build_code_only() {
with_tmp_dir(|path| { with_tmp_dir(|path| {
cmd::new::execute("new_project", Some(path)).expect("new project creation failed"); cmd::new::execute("new_project", Some(path)).expect("new project creation failed");
let manifest_path = let manifest_path =
...@@ -491,8 +467,7 @@ mod tests_ci_only { ...@@ -491,8 +467,7 @@ mod tests_ci_only {
let res = super::execute( let res = super::execute(
&manifest_path, &manifest_path,
Verbosity::Default, Verbosity::Default,
true, BuildArtifacts::CodeOnly,
BuildArtifacts::All,
UnstableFlags::default(), UnstableFlags::default(),
OptimizationPasses::default(), OptimizationPasses::default(),
) )
...@@ -506,6 +481,11 @@ mod tests_ci_only { ...@@ -506,6 +481,11 @@ mod tests_ci_only {
// for `/ink` being the root path. // for `/ink` being the root path.
assert!(res.target_directory.ends_with("ink")); 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; let optimized_size = res.optimization_result.unwrap().optimized_size;
assert!(optimized_size > 0.0); assert!(optimized_size > 0.0);
...@@ -528,7 +508,6 @@ mod tests_ci_only { ...@@ -528,7 +508,6 @@ mod tests_ci_only {
super::execute( super::execute(
&manifest_path, &manifest_path,
Verbosity::Default, Verbosity::Default,
true,
BuildArtifacts::CheckOnly, BuildArtifacts::CheckOnly,
UnstableFlags::default(), UnstableFlags::default(),
OptimizationPasses::default(), OptimizationPasses::default(),
......
...@@ -18,7 +18,7 @@ use crate::{ ...@@ -18,7 +18,7 @@ use crate::{
crate_metadata::CrateMetadata, crate_metadata::CrateMetadata,
maybe_println, util, maybe_println, util,
workspace::{ManifestPath, Workspace}, workspace::{ManifestPath, Workspace},
BuildArtifacts, BuildResult, OptimizationPasses, OptimizationResult, UnstableFlags, Verbosity, UnstableFlags, Verbosity,
}; };
use anyhow::Result; use anyhow::Result;
...@@ -29,54 +29,60 @@ use contract_metadata::{ ...@@ -29,54 +29,60 @@ use contract_metadata::{
SourceLanguage, SourceWasm, User, SourceLanguage, SourceWasm, User,
}; };
use semver::Version; use semver::Version;
use std::{fs, path::PathBuf}; use std::{
fs,
path::{Path, PathBuf},
};
use url::Url; use url::Url;
const METADATA_FILE: &str = "metadata.json"; const METADATA_FILE: &str = "metadata.json";
/// Executes the metadata generation process /// Metadata generation result.
struct GenerateMetadataCommand { pub struct MetadataResult {
crate_metadata: CrateMetadata, /// Path to the resulting metadata file.
verbosity: Verbosity, pub dest_metadata: PathBuf,
build_artifact: BuildArtifacts, /// Path to the bundled file.
unstable_options: UnstableFlags, pub dest_bundle: PathBuf,
optimization_passes: OptimizationPasses,
} }
/// Result of generating the extended contract project metadata /// Result of generating the extended contract project metadata
struct ExtendedMetadataResult { struct ExtendedMetadataResult {
dest_wasm: Option<PathBuf>,
source: Source, source: Source,
contract: Contract, contract: Contract,
user: Option<User>, user: Option<User>,
optimization_result: Option<OptimizationResult>,
} }
impl GenerateMetadataCommand { /// Generates a file with metadata describing the ABI of the smart-contract.
pub fn exec(&self) -> Result<BuildResult> { ///
/// 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<MetadataResult> {
util::assert_channel()?; util::assert_channel()?;
let target_directory = self.crate_metadata.target_directory.clone(); let target_directory = crate_metadata.target_directory.clone();
let out_path_metadata = target_directory.join(METADATA_FILE); let out_path_metadata = target_directory.join(METADATA_FILE);
let fname_bundle = format!("{}.contract", self.crate_metadata.package_name); let fname_bundle = format!("{}.contract", crate_metadata.package_name);
let out_path_bundle = target_directory.join(fname_bundle); let out_path_bundle = target_directory.join(fname_bundle);
// build the extended contract project metadata // build the extended contract project metadata
let ExtendedMetadataResult { let ExtendedMetadataResult {
dest_wasm,
source, source,
contract, contract,
user, user,
optimization_result, } = extended_metadata(crate_metadata, final_contract_wasm)?;
} = self.extended_metadata()?;
let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
let mut current_progress = 4; let mut current_progress = 4;
maybe_println!( maybe_println!(
self.verbosity, verbosity,
" {} {}", " {} {}",
format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(), format!("[{}/{}]", current_progress, total_steps).bold(),
"Generating metadata".bright_green().bold() "Generating metadata".bright_green().bold()
); );
let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy()); let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy());
...@@ -89,12 +95,11 @@ impl GenerateMetadataCommand { ...@@ -89,12 +95,11 @@ impl GenerateMetadataCommand {
&target_dir_arg, &target_dir_arg,
"--release", "--release",
], ],
self.crate_metadata.manifest_path.directory(), crate_metadata.manifest_path.directory(),
self.verbosity, verbosity,
)?; )?;
let ink_meta: serde_json::Map<String, serde_json::Value> = let ink_meta: serde_json::Map<String, serde_json::Value> = serde_json::from_slice(&stdout)?;
serde_json::from_slice(&stdout)?;
let metadata = ContractMetadata::new(source, contract, user, ink_meta); let metadata = ContractMetadata::new(source, contract, user, ink_meta);
{ {
let mut metadata = metadata.clone(); let mut metadata = metadata.clone();
...@@ -104,87 +109,65 @@ impl GenerateMetadataCommand { ...@@ -104,87 +109,65 @@ impl GenerateMetadataCommand {
current_progress += 1; current_progress += 1;
} }
if self.build_artifact == BuildArtifacts::All {
maybe_println!( maybe_println!(
self.verbosity, verbosity,
" {} {}", " {} {}",
format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(), format!("[{}/{}]", current_progress, total_steps).bold(),
"Generating bundle".bright_green().bold() "Generating bundle".bright_green().bold()
); );
let contents = serde_json::to_string(&metadata)?; let contents = serde_json::to_string(&metadata)?;
fs::write(&out_path_bundle, contents)?; fs::write(&out_path_bundle, contents)?;
}
Ok(()) Ok(())
}; };
if self.unstable_options.original_manifest { if unstable_options.original_manifest {
generate_metadata(&self.crate_metadata.manifest_path)?; generate_metadata(&crate_metadata.manifest_path)?;
} else { } else {
Workspace::new( Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
&self.crate_metadata.cargo_meta,
&self.crate_metadata.root_package.id,
)?
.with_root_package_manifest(|manifest| { .with_root_package_manifest(|manifest| {
manifest manifest
.with_added_crate_type("rlib")? .with_added_crate_type("rlib")?
.with_profile_release_lto(false)?; .with_profile_release_lto(false)?;
Ok(()) 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)?; .using_temp(generate_metadata)?;
} }
let dest_bundle = if self.build_artifact == BuildArtifacts::All { Ok(MetadataResult {
Some(out_path_bundle) dest_metadata: out_path_metadata,
} else { dest_bundle: out_path_bundle,
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 /// Generate the extended contract project metadata
fn extended_metadata(&self) -> Result<ExtendedMetadataResult> { fn extended_metadata(
let contract_package = &self.crate_metadata.root_package; crate_metadata: &CrateMetadata,
let ink_version = &self.crate_metadata.ink_version; final_contract_wasm: &Path,
) -> Result<ExtendedMetadataResult> {
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 rust_version = Version::parse(&rustc_version::version()?.to_string())?;
let contract_name = contract_package.name.clone(); let contract_name = contract_package.name.clone();
let contract_version = Version::parse(&contract_package.version.to_string())?; let contract_version = Version::parse(&contract_package.version.to_string())?;
let contract_authors = contract_package.authors.clone(); let contract_authors = contract_package.authors.clone();
// optional // optional
let description = contract_package.description.clone(); let description = contract_package.description.clone();
let documentation = self.crate_metadata.documentation.clone(); let documentation = crate_metadata.documentation.clone();
let repository = contract_package let repository = contract_package
.repository .repository
.as_ref() .as_ref()
.map(|repo| Url::parse(&repo)) .map(|repo| Url::parse(&repo))
.transpose()?; .transpose()?;
let homepage = self.crate_metadata.homepage.clone(); let homepage = crate_metadata.homepage.clone();
let license = contract_package.license.clone(); let license = contract_package.license.clone();
let (dest_wasm, hash, optimization_result) = self.wasm_hash()?;
let source = { let source = {
let lang = SourceLanguage::new(Language::Ink, ink_version.clone()); let lang = SourceLanguage::new(Language::Ink, ink_version.clone());
let compiler = SourceCompiler::new(Compiler::RustC, rust_version); let compiler = SourceCompiler::new(Compiler::RustC, rust_version);
let maybe_wasm = if self.build_artifact == BuildArtifacts::All { let wasm = fs::read(final_contract_wasm)?;
let wasm = fs::read(&self.crate_metadata.dest_wasm)?; let hash = blake2_hash(wasm.as_slice());
// The Wasm which we read must have the same hash as `source.hash` Source::new(Some(SourceWasm::new(wasm)), hash, lang, compiler)
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 // Required contract fields
...@@ -219,35 +202,13 @@ impl GenerateMetadataCommand { ...@@ -219,35 +202,13 @@ impl GenerateMetadataCommand {
.map_err(|err| anyhow::anyhow!("Invalid contract metadata builder state: {}", err))?; .map_err(|err| anyhow::anyhow!("Invalid contract metadata builder state: {}", err))?;
// user defined metadata // user defined metadata
let user = self.crate_metadata.user.clone().map(User::new); let user = crate_metadata.user.clone().map(User::new);
Ok(ExtendedMetadataResult { Ok(ExtendedMetadataResult {
dest_wasm: Some(dest_wasm),
source, source,
contract, contract,
user, user,
optimization_result: Some(optimization_result),
}) })
}
/// 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,
)?;