Skip to content

Commit

Permalink
Do not postprocess or generate metadata if contract unchanged (#964)
Browse files Browse the repository at this point in the history
* Don't optimize or build metadata if no change

* Tracing if no change. Fmt

* Check all contract artifacts already exist

* Add path and modified to fingerprint

* Don't print extra new lines when no changes

* Fmt

* Add newline back

* rename

* tests: remove original wasm to force optimization and metadata rebuild

* Fmt

* Add test

* Print message when no changes

* Clippy
  • Loading branch information
ascjones authored Feb 13, 2023
1 parent fded85c commit a57a5f8
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 59 deletions.
30 changes: 22 additions & 8 deletions crates/build/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ pub enum BuildArtifacts {
impl BuildArtifacts {
/// Returns the number of steps required to complete a build artifact.
/// Used as output on the cli.
pub fn steps(&self) -> BuildSteps {
pub fn steps(&self) -> usize {
match self {
BuildArtifacts::All => BuildSteps::new(5),
BuildArtifacts::CodeOnly => BuildSteps::new(4),
BuildArtifacts::CheckOnly => BuildSteps::new(1),
BuildArtifacts::All => 5,
BuildArtifacts::CodeOnly => 4,
BuildArtifacts::CheckOnly => 1,
}
}
}
Expand All @@ -134,25 +134,38 @@ impl Default for BuildArtifacts {
#[derive(Debug, Clone, Copy)]
pub struct BuildSteps {
pub current_step: usize,
pub total_steps: usize,
pub total_steps: Option<usize>,
}

impl BuildSteps {
pub fn new(total_steps: usize) -> Self {
pub fn new() -> Self {
Self {
current_step: 1,
total_steps,
total_steps: None,
}
}

pub fn increment_current(&mut self) {
self.current_step += 1;
}

pub fn set_total_steps(&mut self, steps: usize) {
self.total_steps = Some(steps)
}
}

impl Default for BuildSteps {
fn default() -> Self {
Self::new()
}
}

impl fmt::Display for BuildSteps {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}/{}]", self.current_step, self.total_steps)
let total_steps = self
.total_steps
.map_or("*".to_string(), |steps| steps.to_string());
write!(f, "[{}/{}]", self.current_step, total_steps)
}
}

Expand Down Expand Up @@ -181,6 +194,7 @@ impl fmt::Display for BuildMode {
}

/// The type of output to display at the end of a build.
#[derive(Clone, Debug)]
pub enum OutputType {
/// Output build results in a human readable format.
HumanReadable,
Expand Down
164 changes: 115 additions & 49 deletions crates/build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ use parity_wasm::elements::{
};
use semver::Version;
use std::{
fs,
path::{
Path,
PathBuf,
Expand All @@ -94,7 +95,7 @@ const MAX_MEMORY_PAGES: u32 = 16;
const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Arguments to use when executing `build` or `check` commands.
#[derive(Default)]
#[derive(Default, Clone)]
pub struct ExecuteArgs {
/// The location of the Cargo manifest (`Cargo.toml`) file to use.
pub manifest_path: ManifestPath,
Expand Down Expand Up @@ -135,16 +136,26 @@ pub struct BuildResult {

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(),
);
debug_assert!(
optimization.1 > 0.0,
"optimized file size must be greater 0"
);
if self.optimization_result.is_none() && self.metadata_result.is_none() {
return "\nNo changes in contract detected: Wasm and metadata artifacts unchanged."
.to_string()
}

let (opt_size_diff, newlines) =
if let Some(ref opt_result) = self.optimization_result {
let size_diff = format!(
"\nOriginal wasm size: {}, Optimized: {}\n\n",
format!("{:.1}K", opt_result.original_size).bold(),
format!("{:.1}K", opt_result.optimized_size).bold(),
);
debug_assert!(
opt_result.optimized_size > 0.0,
"optimized file size must be greater 0"
);
(size_diff, "\n\n")
} else {
("\n".to_string(), "")
};

let build_mode = format!(
"The contract was built in {} mode.\n\n",
Expand All @@ -154,7 +165,7 @@ impl BuildResult {
if self.build_artifact == BuildArtifacts::CodeOnly {
let out = format!(
"{}{}Your contract's code is ready. You can find it here:\n{}",
size_diff,
opt_size_diff,
build_mode,
self.dest_wasm
.as_ref()
Expand All @@ -167,10 +178,11 @@ impl BuildResult {
};

let mut out = format!(
"{}{}Your contract artifacts are ready. You can find them in:\n{}\n\n",
size_diff,
"{}{}Your contract artifacts are ready. You can find them in:\n{}{}",
opt_size_diff,
build_mode,
self.target_directory.display().to_string().bold(),
newlines,
);
if let Some(metadata_result) = self.metadata_result.as_ref() {
let bundle = format!(
Expand All @@ -196,17 +208,6 @@ impl BuildResult {
out
}

/// Returns a tuple of `(original_size, optimized_size)`.
///
/// Panics if no optimization result is available.
fn display_optimization(&self) -> (f64, f64) {
let optimization = self
.optimization_result
.as_ref()
.expect("optimization result must exist");
(optimization.original_size, optimization.optimized_size)
}

/// Display the build results in a pretty formatted JSON string.
pub fn serialize_json(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(self)?)
Expand Down Expand Up @@ -593,10 +594,10 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
assert_debug_mode_supported(&crate_metadata.ink_version)?;
}

let maybe_lint = || -> Result<BuildSteps> {
let maybe_lint = |steps: &mut BuildSteps| -> Result<()> {
let total_steps = build_artifact.steps();
if lint {
let mut steps = build_artifact.steps();
steps.total_steps += 1;
steps.set_total_steps(total_steps + 1);
maybe_println!(
verbosity,
" {} {}",
Expand All @@ -605,14 +606,16 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
);
steps.increment_current();
exec_cargo_dylint(&crate_metadata, verbosity)?;
Ok(steps)
Ok(())
} else {
Ok(build_artifact.steps())
steps.set_total_steps(total_steps);
Ok(())
}
};

let build = || -> Result<(OptimizationResult, BuildInfo, BuildSteps)> {
let mut build_steps = maybe_lint()?;
let build = || -> Result<(Option<(OptimizationResult, BuildInfo)>, BuildSteps)> {
let mut build_steps = BuildSteps::new();
let pre_fingerprint = Fingerprint::try_from_path(&crate_metadata.original_wasm)?;

maybe_println!(
verbosity,
Expand All @@ -631,6 +634,30 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
&unstable_flags,
)?;

let post_fingerprint = Fingerprint::try_from_path(&crate_metadata.original_wasm)?
.ok_or_else(|| {
anyhow::anyhow!(
"Expected '{}' to be generated by build",
crate_metadata.original_wasm.display()
)
})?;

if pre_fingerprint == Some(post_fingerprint)
&& crate_metadata.dest_wasm.exists()
&& crate_metadata.metadata_path().exists()
&& crate_metadata.contract_bundle_path().exists()
{
tracing::info!(
"No changes in the original wasm at {}, fingerprint {:?}. \
Skipping Wasm optimization and metadata generation.",
crate_metadata.original_wasm.display(),
pre_fingerprint
);
return Ok((None, build_steps))
}

maybe_lint(&mut build_steps)?;

maybe_println!(
verbosity,
" {} {}",
Expand Down Expand Up @@ -673,12 +700,13 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
},
};

Ok((optimization_result, build_info, build_steps))
Ok((Some((optimization_result, build_info)), build_steps))
};

let (opt_result, metadata_result) = match build_artifact {
BuildArtifacts::CheckOnly => {
let build_steps = maybe_lint()?;
let mut build_steps = BuildSteps::new();
maybe_lint(&mut build_steps)?;

maybe_println!(
verbosity,
Expand All @@ -698,23 +726,28 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
(None, None)
}
BuildArtifacts::CodeOnly => {
let (optimization_result, _build_info, _) = build()?;
(Some(optimization_result), None)
let (build_result, _) = build()?;
let opt_result = build_result.map(|(opt_result, _)| opt_result);
(opt_result, None)
}
BuildArtifacts::All => {
let (optimization_result, build_info, build_steps) = build()?;

let metadata_result = crate::metadata::execute(
&crate_metadata,
optimization_result.dest_wasm.as_path(),
network,
verbosity,
build_steps,
&unstable_flags,
build_info,
)?;

(Some(optimization_result), Some(metadata_result))
let (build_result, build_steps) = build()?;

match build_result {
Some((opt_result, build_info)) => {
let metadata_result = metadata::execute(
&crate_metadata,
opt_result.dest_wasm.as_path(),
network,
verbosity,
build_steps,
&unstable_flags,
build_info,
)?;
(Some(opt_result), Some(metadata_result))
}
None => (None, None),
}
}
};
let dest_wasm = opt_result.as_ref().map(|r| r.dest_wasm.clone());
Expand All @@ -731,8 +764,41 @@ pub fn execute(args: ExecuteArgs) -> Result<BuildResult> {
})
}

/// Unique fingerprint for a file to detect whether it has changed.
#[derive(Debug, Eq, PartialEq)]
struct Fingerprint {
path: PathBuf,
hash: [u8; 32],
modified: std::time::SystemTime,
}

impl Fingerprint {
pub fn try_from_path<P>(path: P) -> Result<Option<Fingerprint>>
where
P: AsRef<Path>,
{
if path.as_ref().exists() {
let modified = fs::metadata(&path)?.modified()?;
let bytes = fs::read(&path)?;
let hash = blake2_hash(&bytes);
Ok(Some(Self {
path: path.as_ref().to_path_buf(),
hash,
modified,
}))
} else {
Ok(None)
}
}
}

/// Returns the blake2 hash of the code slice.
pub fn code_hash(code: &[u8]) -> [u8; 32] {
blake2_hash(code)
}

/// Returns the blake2 hash of the given bytes.
fn blake2_hash(code: &[u8]) -> [u8; 32] {
use blake2::digest::{
consts::U32,
Digest as _,
Expand Down
Loading

0 comments on commit a57a5f8

Please sign in to comment.