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,94 +380,74 @@ fn do_optimization( ...@@ -384,94 +380,74 @@ 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( let build = || -> Result<OptimizationResult> {
&crate_metadata, maybe_println!(
verbosity, verbosity,
optimize_contract, " {} {}",
build_artifact, format!("[1/{}]", build_artifact.steps()).bold(),
unstable_flags, "Building cargo project".bright_green().bold()
optimization_passes, );
)?; exec_cargo_for_wasm_target(&crate_metadata, "build", verbosity, &unstable_flags)?;
let res = BuildResult {
dest_wasm: maybe_dest_wasm, maybe_println!(
dest_metadata: None,
dest_bundle: None,
target_directory: crate_metadata.target_directory,
optimization_result: maybe_optimization_result,
build_artifact,
verbosity, 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( maybe_println!(
&manifest_path, verbosity,
verbosity, " {} {}",
build_artifact, format!("[3/{}]", build_artifact.steps()).bold(),
unstable_flags, "Optimizing wasm file".bright_green().bold()
optimization_passes, );
)?; let optimization_result = optimize_wasm(&crate_metadata, optimization_passes)?;
Ok(res)
}
/// Executes build of the smart-contract which produces a Wasm binary that is ready for deploying. Ok(optimization_result)
/// };
/// It does so by invoking `cargo build` and then post processing the final binary.
/// let (opt_result, metadata_result) = match build_artifact {
/// # Note BuildArtifacts::CheckOnly => {
/// exec_cargo_for_wasm_target(&crate_metadata, "check", verbosity, &unstable_flags)?;
/// Uses the supplied `CrateMetadata`. If an instance is not available use [`execute_build`] (None, None)
/// }
/// Returns a tuple of `(maybe_optimized_wasm_path, maybe_optimization_result)`. BuildArtifacts::CodeOnly => {
pub(crate) fn execute_with_crate_metadata( let optimization_result = build()?;
crate_metadata: &CrateMetadata, (Some(optimization_result), None)
verbosity: Verbosity, }
optimize_contract: bool, BuildArtifacts::All => {
build_artifact: BuildArtifacts, let optimization_result = build()?;
unstable_flags: UnstableFlags,
optimization_passes: OptimizationPasses, let metadata_result = super::metadata::execute(
) -> Result<(Option<PathBuf>, Option<OptimizationResult>)> { &crate_metadata,
maybe_println!( optimization_result.dest_wasm.as_path(),
verbosity, verbosity,
" {} {}", build_artifact.steps(),
format!("[1/{}]", build_artifact.steps()).bold(), &unstable_flags,
"Building cargo project".bright_green().bold() )?;
); (Some(optimization_result), Some(metadata_result))
build_cargo_project(&crate_metadata, build_artifact, verbosity, unstable_flags)?; }
if build_artifact == BuildArtifacts::CheckOnly { };
return Ok((None, None)); let dest_wasm = opt_result.as_ref().map(|r| r.dest_wasm.clone());
} Ok(BuildResult {
maybe_println!( dest_wasm,
verbosity, metadata_result,
" {} {}", target_directory: crate_metadata.target_directory,
format!("[2/{}]", build_artifact.steps()).bold(), optimization_result: opt_result,
"Post processing wasm file".bright_green().bold() build_artifact,
);
post_process_wasm(&crate_metadata)?;
if !optimize_contract {
return Ok((None, None));
}
maybe_println!(
verbosity, 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")] #[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,225 +29,186 @@ use contract_metadata::{ ...@@ -29,225 +29,186 @@ 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> { ///
util::assert_channel()?; /// It does so by generating and invoking a temporary workspace member.
pub(crate) fn execute(
let target_directory = self.crate_metadata.target_directory.clone(); crate_metadata: &CrateMetadata,
let out_path_metadata = target_directory.join(METADATA_FILE); final_contract_wasm: &Path,
verbosity: Verbosity,
let fname_bundle = format!("{}.contract", self.crate_metadata.package_name); total_steps: usize,
let out_path_bundle = target_directory.join(fname_bundle); unstable_options: &UnstableFlags,
) -> Result<MetadataResult> {
// build the extended contract project metadata util::assert_channel()?;
let ExtendedMetadataResult {
dest_wasm, let target_directory = crate_metadata.target_directory.clone();
source, let out_path_metadata = target_directory.join(METADATA_FILE);
contract,
user, let fname_bundle = format!("{}.contract", crate_metadata.package_name);
optimization_result, let out_path_bundle = target_directory.join(fname_bundle);
} = self.extended_metadata()?;
// build the extended contract project metadata
let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> { let ExtendedMetadataResult {
let mut current_progress = 4; source,
maybe_println!( contract,
self.verbosity, user,
" {} {}", } = extended_metadata(crate_metadata, final_contract_wasm)?;
format!("[{}/{}]", current_progress, self.build_artifact.steps()).bold(),
"Generating metadata".bright_green().bold() let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
); let mut current_progress = 4;
let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy()); maybe_println!(
let stdout = util::invoke_cargo( verbosity,
"run", " {} {}",
&[ format!("[{}/{}]", current_progress, total_steps).bold(),
"--package", "Generating metadata".bright_green().bold()
"metadata-gen", );
&manifest_path.cargo_arg(), let target_dir_arg = format!("--target-dir={}", target_directory.to_string_lossy());
&target_dir_arg, let stdout = util::invoke_cargo(
"--release", "run",
], &[
self.crate_metadata.manifest_path.directory(), "--package",
self.verbosity, "metadata-gen",
)?; &manifest_path.cargo_arg(),
&target_dir_arg,
"--release",
],
crate_metadata.manifest_path.directory(),
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(); metadata.remove_source_wasm_attribute();
metadata.remove_source_wasm_attribute(); let contents = serde_json::to_string_pretty(&metadata)?;
let contents = serde_json::to_string_pretty(&metadata)?; fs::write(&out_path_metadata, contents)?;
fs::write(&out_path_metadata, contents)?; current_progress += 1;
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)?;
}
Ok(()) maybe_println!(
}; verbosity,
" {} {}",
if self.unstable_options.original_manifest { format!("[{}/{}]", current_progress, total_steps).bold(),
generate_metadata(&self.crate_metadata.manifest_path)?; "Generating bundle".bright_green().bold()
} else { );
Workspace::new( let contents = serde_json::to_string(&metadata)?;
&self.crate_metadata.cargo_meta, fs::write(&out_path_bundle, contents)?;
&self.crate_metadata.root_package.id,
)? 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| { .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 {
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 Ok(MetadataResult {
fn extended_metadata(&self) -> Result<ExtendedMetadataResult> { dest_metadata: out_path_metadata,
let contract_package = &self.crate_metadata.root_package; dest_bundle: out_path_bundle,
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);
}
if let Some(documentation) = documentation { /// Generate the extended contract project metadata
builder.documentation(documentation); fn extended_metadata(
} crate_metadata: &CrateMetadata,
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 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 { // Required contract fields
builder.repository(repository); let mut builder = Contract::builder();
}