Skip to content

Commit

Permalink
Add Rust specific build info to metadata (use-ink#680)
Browse files Browse the repository at this point in the history
* Write (slightly-hardcoded) build info to metadata

* Add `channel-date` formatted rustc version to metadata

* Remove `features` from `BuildInfo`

Since we hardcode these into `cargo-contract` we shouldn't need to track
these manually

* Add `wasm-opt` version to metadata

* Move `BuildInfo` construction to `build` closure

* Add `build_info` field to tests

* Improve conversion between `BuildInfo` and `serde_json::Map`

* Add `version` getter to `WasmOptHandler`

* Move code which indicates current toolchain to `utils`

* Handle case where `cargo-contract` version can't be parsed

* Use a stable `rustc` version

* Add `keep_debug_symbols` to metadata

* Add `CHANGELOG` entry

* Remove `version` from `WasmOptSettings`

Since we're using the `wasm-opt` library from crates.io
we'll assume that every matching version of `cargo-contract` contains the
same version of `wasm-opt`.

This doesn't hold if a user installs without the `--locked` flag though,
so we may want to add this back in the future to warn users if there are
mismatching `wasm-opt` versions.

* Add target triple to `build_info`

* Move `CARGO_PKG_VERSION` into a const

* Appease Clippy

* Rename field to more accurate `rust_toolchain`
  • Loading branch information
HCastano authored Oct 20, 2022
1 parent 9c1959f commit f268549
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Add Rust specific build info to metadata - [#680](https://github.com/paritytech/cargo-contract/pull/680)

### Changed
- Removed requirement to install binaryen. The `wasm-opt` tool is now compiled into `cargo-contract`.

Expand Down
37 changes: 33 additions & 4 deletions crates/cargo-contract/src/cmd/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ use std::{
/// This is the maximum number of pages available for a contract to allocate.
const MAX_MEMORY_PAGES: u32 = 16;

/// Version of the currently executing `cargo-contract` binary.
const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Arguments to use when executing `build` or `check` commands.
#[derive(Default)]
pub(crate) struct ExecuteArgs {
Expand Down Expand Up @@ -284,6 +287,7 @@ fn exec_cargo_for_wasm_target(
let cargo_build = |manifest_path: &ManifestPath| {
let target_dir = &crate_metadata.target_directory;
let target_dir = format!("--target-dir={}", target_dir.to_string_lossy());

let mut args = vec![
"--target=wasm32-unknown-unknown",
"-Zbuild-std",
Expand Down Expand Up @@ -576,6 +580,8 @@ pub fn assert_debug_mode_supported(ink_version: &Version) -> anyhow::Result<()>
///
/// It does so by invoking `cargo build` and then post processing the final binary.
pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
use crate::cmd::metadata::BuildInfo;

let ExecuteArgs {
manifest_path,
verbosity,
Expand All @@ -596,7 +602,9 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
assert_debug_mode_supported(&crate_metadata.ink_version)?;
}

let build = || -> Result<OptimizationResult> {
let build = || -> Result<(OptimizationResult, BuildInfo)> {
use crate::cmd::metadata::WasmOptSettings;

if skip_linting {
maybe_println!(
verbosity,
Expand Down Expand Up @@ -650,7 +658,26 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
&crate_metadata.contract_artifact_name,
)?;

Ok(optimization_result)
let cargo_contract_version = if let Ok(version) = Version::parse(VERSION) {
version
} else {
anyhow::bail!(
"Unable to parse version number for the currently running \
`cargo-contract` binary."
);
};

let build_info = BuildInfo {
rust_toolchain: crate::util::rust_toolchain()?,
cargo_contract_version,
build_mode,
wasm_opt_settings: WasmOptSettings {
optimization_passes,
keep_debug_symbols,
},
};

Ok((optimization_result, build_info))
};

let (opt_result, metadata_result) = match build_artifact {
Expand Down Expand Up @@ -689,11 +716,11 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
(None, None)
}
BuildArtifacts::CodeOnly => {
let optimization_result = build()?;
let (optimization_result, _build_info) = build()?;
(Some(optimization_result), None)
}
BuildArtifacts::All => {
let optimization_result = build()?;
let (optimization_result, build_info) = build()?;

let metadata_result = super::metadata::execute(
&crate_metadata,
Expand All @@ -702,7 +729,9 @@ pub(crate) fn execute(args: ExecuteArgs) -> Result<BuildResult> {
verbosity,
build_artifact.steps(),
&unstable_flags,
build_info,
)?;

(Some(optimization_result), Some(metadata_result))
}
};
Expand Down
48 changes: 46 additions & 2 deletions crates/cargo-contract/src/cmd/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use crate::{
ManifestPath,
Workspace,
},
BuildMode,
Network,
OptimizationPasses,
UnstableFlags,
Verbosity,
};
Expand Down Expand Up @@ -73,6 +75,40 @@ struct ExtendedMetadataResult {
user: Option<User>,
}

/// Information about the settings used to build a particular ink! contract.
///
/// Note that this should be an optional part of the metadata since it may not necessarily
/// translate to other languages (e.g ask!, Solidity).
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct BuildInfo {
/// The Rust toolchain used to build the contract.
pub rust_toolchain: String,
/// The version of `cargo-contract` used to build the contract.
pub cargo_contract_version: Version,
/// The type of build that was used when building the contract.
pub build_mode: BuildMode,
/// Information about the `wasm-opt` optimization settings.
pub wasm_opt_settings: WasmOptSettings,
}

impl TryFrom<BuildInfo> for serde_json::Map<String, serde_json::Value> {
type Error = serde_json::Error;

fn try_from(build_info: BuildInfo) -> Result<Self, Self::Error> {
let tmp = serde_json::to_string(&build_info)?;
serde_json::from_str(&tmp)
}
}

/// Settings used when optimizing the Wasm binary using Binaryen's `wasm-opt`.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct WasmOptSettings {
/// The level of optimization used during the `wasm-opt` run.
pub optimization_passes: OptimizationPasses,
/// Whether or not the Wasm name section should be kept.
pub keep_debug_symbols: bool,
}

/// Generates a file with metadata describing the ABI of the smart contract.
///
/// It does so by generating and invoking a temporary workspace member.
Expand All @@ -83,6 +119,7 @@ pub(crate) fn execute(
verbosity: Verbosity,
total_steps: usize,
unstable_options: &UnstableFlags,
build_info: BuildInfo,
) -> Result<MetadataResult> {
let target_directory = crate_metadata.target_directory.clone();
let out_path_metadata = target_directory.join(METADATA_FILE);
Expand All @@ -95,7 +132,7 @@ pub(crate) fn execute(
source,
contract,
user,
} = extended_metadata(crate_metadata, final_contract_wasm)?;
} = extended_metadata(crate_metadata, final_contract_wasm, build_info)?;

let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
let mut current_progress = 5;
Expand Down Expand Up @@ -171,6 +208,7 @@ pub(crate) fn execute(
fn extended_metadata(
crate_metadata: &CrateMetadata,
final_contract_wasm: &Path,
build_info: BuildInfo,
) -> Result<ExtendedMetadataResult> {
let contract_package = &crate_metadata.root_package;
let ink_version = &crate_metadata.ink_version;
Expand All @@ -193,7 +231,13 @@ fn extended_metadata(
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)
Source::new(
Some(SourceWasm::new(wasm)),
hash,
lang,
compiler,
Some(build_info.try_into()?),
)
};

// Required contract fields
Expand Down
4 changes: 2 additions & 2 deletions crates/cargo-contract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl FromStr for HexData {
}
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum OptimizationPasses {
Zero,
One,
Expand Down Expand Up @@ -298,7 +298,7 @@ impl Default for BuildArtifacts {
}

/// The mode to build the contract in.
#[derive(Eq, PartialEq, Copy, Clone, Debug, serde::Serialize)]
#[derive(Eq, PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum BuildMode {
/// Functionality to output debug messages is build into the contract.
Debug,
Expand Down
8 changes: 8 additions & 0 deletions crates/cargo-contract/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ fn assert_channel() -> Result<()> {
}
}

// Returns the current Rust toolchain formatted by `<channel>-<target-triple>`.
pub(crate) fn rust_toolchain() -> Result<String> {
let meta = rustc_version::version_meta()?;
let toolchain = format!("{:?}-{}", meta.channel, meta.host,).to_lowercase();

Ok(toolchain)
}

/// Invokes `cargo` with the subcommand `command` and the supplied `args`.
///
/// In case `working_dir` is set, the command will be invoked with that folder
Expand Down
59 changes: 54 additions & 5 deletions crates/metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
//! let language = SourceLanguage::new(Language::Ink, Version::new(2, 1, 0));
//! let compiler = SourceCompiler::new(Compiler::RustC, Version::parse("1.46.0-nightly").unwrap());
//! let wasm = SourceWasm::new(vec![0u8]);
//! let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler);
//! // Optional information about how the contract was build
//! let build_info: Map<String, Value> = Map::new();
//! let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler, Some(build_info));
//! let contract = Contract::builder()
//! .name("incrementer".to_string())
//! .version(Version::new(2, 1, 0))
Expand Down Expand Up @@ -137,6 +139,11 @@ pub struct Source {
/// with the metadata.
#[serde(skip_serializing_if = "Option::is_none")]
pub wasm: Option<SourceWasm>,
/// Extra information about the environment in which the contract was built.
///
/// Useful for producing deterministic builds.
#[serde(skip_serializing_if = "Option::is_none")]
pub build_info: Option<Map<String, Value>>,
}

impl Source {
Expand All @@ -146,12 +153,14 @@ impl Source {
hash: CodeHash,
language: SourceLanguage,
compiler: SourceCompiler,
build_info: Option<Map<String, Value>>,
) -> Self {
Source {
hash,
language,
compiler,
wasm,
build_info,
}
}
}
Expand Down Expand Up @@ -646,7 +655,25 @@ mod tests {
Version::parse("1.46.0-nightly").unwrap(),
);
let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]);
let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler);
let build_info = json! {
{
"example_compiler_version": 42,
"example_settings": [],
"example_name": "increment"
}
}
.as_object()
.unwrap()
.clone();

let source = Source::new(
Some(wasm),
CodeHash([0u8; 32]),
language,
compiler,
Some(build_info),
);

let contract = Contract::builder()
.name("incrementer")
.version(Version::new(2, 1, 0))
Expand Down Expand Up @@ -690,7 +717,12 @@ mod tests {
"hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"language": "ink! 2.1.0",
"compiler": "rustc 1.46.0-nightly",
"wasm": "0x000102"
"wasm": "0x000102",
"build_info": {
"example_compiler_version": 42,
"example_settings": [],
"example_name": "increment"
}
},
"contract": {
"name": "incrementer",
Expand Down Expand Up @@ -729,7 +761,7 @@ mod tests {
Compiler::RustC,
Version::parse("1.46.0-nightly").unwrap(),
);
let source = Source::new(None, CodeHash([0u8; 32]), language, compiler);
let source = Source::new(None, CodeHash([0u8; 32]), language, compiler, None);
let contract = Contract::builder()
.name("incrementer")
.version(Version::new(2, 1, 0))
Expand Down Expand Up @@ -782,7 +814,24 @@ mod tests {
Version::parse("1.46.0-nightly").unwrap(),
);
let wasm = SourceWasm::new(vec![0u8, 1u8, 2u8]);
let source = Source::new(Some(wasm), CodeHash([0u8; 32]), language, compiler);
let build_info = json! {
{
"example_compiler_version": 42,
"example_settings": [],
"example_name": "increment",
}
}
.as_object()
.unwrap()
.clone();

let source = Source::new(
Some(wasm),
CodeHash([0u8; 32]),
language,
compiler,
Some(build_info),
);
let contract = Contract::builder()
.name("incrementer")
.version(Version::new(2, 1, 0))
Expand Down

0 comments on commit f268549

Please sign in to comment.