Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce wasm binary size with cargo-xbuild & removing rlib crate-type #33

Merged
merged 99 commits into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
2cda649
WIP: building wasm with xargo
ascjones Feb 5, 2020
efa20d3
Fix compilation errors
ascjones Feb 6, 2020
16d34e0
Fmt
ascjones Feb 6, 2020
9502f62
Run commands with `rustup run nightly`
ascjones Feb 6, 2020
e61035b
Pass separate link-args in rustflags for xargo compat
ascjones Feb 6, 2020
7ecfc46
Warn user if 'rlib' crate type
ascjones Feb 6, 2020
fcc7157
Colourise printed error
ascjones Feb 6, 2020
cd1658b
Ignore Xargo.toml in template
ascjones Feb 6, 2020
e85fe45
Refactor Xargo file generation, only remove if generated.
ascjones Feb 6, 2020
416f210
WIP: Add rlib crate-type when generating metadata
ascjones Feb 6, 2020
dbb93fe
Add rlib when building metadata, remove when building wasm
ascjones Feb 7, 2020
d8bbc91
Fmt
ascjones Feb 7, 2020
9998a1f
Make error bright red
ascjones Feb 7, 2020
9a0b0dc
Fix generating without modified Cargo.toml
ascjones Feb 7, 2020
8ddb26d
Don't need to check nightly installed, the command will fail
ascjones Feb 7, 2020
3c34994
Only load toml when ready to modify: allow for multi usage
ascjones Feb 7, 2020
82c64b3
Fmt
ascjones Feb 7, 2020
3568343
Show error context
ascjones Feb 10, 2020
2c70524
Debug crate metadata
ascjones Feb 10, 2020
4a092ea
Disable rlib by default for template
ascjones Feb 10, 2020
0067ce7
Use correct working dir, not workspace root
ascjones Feb 10, 2020
00e7fa6
Use `cargo-xbuild` as lib
ascjones Feb 12, 2020
3230d89
Check for nightly channel
ascjones Feb 12, 2020
17e0680
Check for correct xbuild configuration
ascjones Feb 13, 2020
1f48c07
Add xbuild config to template
ascjones Feb 13, 2020
9c3c4e1
Fix xbuild config check and use latest xbuild version
ascjones Feb 13, 2020
4ff2971
Fmt
ascjones Feb 13, 2020
436077d
Restore tempfile dev dependency
ascjones Feb 13, 2020
fe27fee
Move xbuild config to the end of the file
ascjones Feb 13, 2020
4ace9b3
Enable rlib by default in template
ascjones Feb 13, 2020
9b49dbe
Don't need nightly for generating the metadata
ascjones Feb 13, 2020
b4ab11e
Actually do need nightly, and just run plain cargo
ascjones Feb 13, 2020
f38c6e3
Not verbose: need to pass that flag through properly
ascjones Feb 13, 2020
8c00f0d
Fmt
ascjones Feb 13, 2020
3aa17bf
Fix tests
ascjones Feb 13, 2020
2c53aeb
Error when xbuild config not present, and update README
ascjones Feb 13, 2020
9d056ac
Fix tests
ascjones Feb 13, 2020
90f1485
Remove references to xargo and update readmes
ascjones Feb 13, 2020
be57190
Fmt
ascjones Feb 13, 2020
4e42146
Add error context to cargo invocation
ascjones Feb 13, 2020
3d44090
Fix tests compilation
ascjones Feb 13, 2020
7f5f356
Fmt
ascjones Feb 13, 2020
98d2101
Nightly toolchain for CI
ascjones Feb 14, 2020
1919afa
Add docs for nightly toolchain requirement
ascjones Feb 14, 2020
dab7d85
Link to nightly docs
ascjones Feb 14, 2020
8973138
Disable backtrace on CI
ascjones Feb 14, 2020
3a67388
Make tests pass
ascjones Feb 14, 2020
4b689ff
Install rust-src
ascjones Feb 14, 2020
631ca91
Disable backtrace to make tests pass
ascjones Feb 14, 2020
c36bb60
Move args closer to invocation
ascjones Feb 14, 2020
d2e0693
Create temporary Cargo.toml
ascjones Feb 17, 2020
67ee4c7
Rework temp manifest api
ascjones Feb 17, 2020
fb75207
Target dir is already absolute
ascjones Feb 17, 2020
2acc79d
temp dir prefix
ascjones Feb 18, 2020
afe99ab
xbuild config with sysroot path and explicit args
ascjones Feb 18, 2020
4bc56fc
Use custom xbuild branch
ascjones Feb 18, 2020
407a548
Remove check for xbuild config
ascjones Feb 18, 2020
dc0351f
Rewrite relatives paths when using temp file
ascjones Feb 18, 2020
2fd59a6
Fix dependency path rewrite
ascjones Feb 18, 2020
afa76ab
Update cargo-xbuild
ascjones Feb 19, 2020
90e7564
workspaces: parse workspace member manifests
ascjones Feb 19, 2020
f039b79
Merge branch 'master' into aj-xargo
ascjones Feb 20, 2020
bc4060e
WIP workspaces
ascjones Feb 20, 2020
cd1b69c
Implement temp workspace copy
ascjones Feb 20, 2020
9df4691
Fmt
ascjones Feb 20, 2020
2da47dc
Rewrite bin relative path
ascjones Feb 21, 2020
52b82cb
Handle package rename for contracts
ascjones Feb 21, 2020
9aff20d
Fmt
ascjones Feb 21, 2020
3480057
Pass rustflags by setting env var
ascjones Feb 21, 2020
963cd34
Fmt
ascjones Feb 21, 2020
6f5f759
Use abs path for lib default
ascjones Feb 21, 2020
67e30a0
Add 1 decimal place to file size
ascjones Feb 24, 2020
b85ad22
Make generate-metadata work, introduces ManifestPath
ascjones Feb 24, 2020
7103d60
Fmt
ascjones Feb 24, 2020
e02c5c9
cargo update
ascjones Feb 24, 2020
81db473
Rename manifest to workspace
ascjones Feb 24, 2020
a3af596
Fix test compilation and fmt
ascjones Feb 24, 2020
0310ce9
Fix link
ascjones Feb 24, 2020
7cf71c7
Add prerequisites section to readme
ascjones Feb 24, 2020
c5eba00
Remove rust-src component (added to image)
ascjones Feb 24, 2020
e8b227d
Fix deploy build
ascjones Feb 24, 2020
4b82756
Use builder like method for amending root manifest
ascjones Feb 24, 2020
ca3a8e9
List installed components
ascjones Feb 24, 2020
bde09c7
Show active-toolchain and whether rust-src installed
ascjones Feb 24, 2020
f4fff3a
Install nightly rust-src (temporary)
ascjones Feb 24, 2020
5aae77b
Fix metadata test
ascjones Feb 24, 2020
a3b0e6d
Fmt
ascjones Feb 24, 2020
8a2cfeb
Remove manual install of rust-src and diagnostics
ascjones Feb 24, 2020
fb6722a
More doc comments
ascjones Feb 25, 2020
5bba2f1
Add verbosity flags
ascjones Feb 25, 2020
c73dab7
Add verbosity flags to metadata command
ascjones Feb 25, 2020
6579826
Fix working dir for generate-metadata
ascjones Feb 25, 2020
848197a
Add verbosity to tests
ascjones Feb 25, 2020
6c7a8cd
Add verbosity to tests
ascjones Feb 25, 2020
24d085c
Make url optional and cargo update
ascjones Feb 25, 2020
7897f8f
Remove bk file from gitignore
ascjones Feb 25, 2020
4db24be
Bump version
ascjones Feb 25, 2020
574ee00
Fix comment and formatting
ascjones Feb 26, 2020
ab40adb
Add CHANGELOG.md
ascjones Feb 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ variables:
CARGO_TARGET_DIR: "/ci-cache/${CI_PROJECT_NAME}/targets/${CI_COMMIT_REF_NAME}/${CI_JOB_NAME}"
CI_SERVER_NAME: "GitLab CI"
REGISTRY: registry.parity.io/parity/infrastructure/scripts
RUSTUP_TOOLCHAIN: nightly
RUST_LIB_BACKTRACE: 0

.collect-artifacts: &collect-artifacts
artifacts:
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Version 0.4.0 (2020-02-26)

- Minimize contract wasm binary size:
- Uses [`cargo-xbuild`](https://github.com/rust-osdev/cargo-xbuild) to build custom sysroot crates without panic string
bloat.
- Automatically removes the `rlib` crate type from `Cargo.toml`, removing redundant metadata.
- Removes requirement for linker args specified in `.cargo/config`.
- Added `--verbose` and `--quiet` flags for `build` and `generate-metadata` commands.
842 changes: 455 additions & 387 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cargo-contract"
version = "0.3.0"
version = "0.4.0"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
edition = "2018"
Expand All @@ -27,14 +27,19 @@ cargo_metadata = "0.9.1"
codec = { package = "parity-scale-codec", version = "1.1" }
which = "3.1.0"
colored = "1.9"
toml = "0.5.4"
cargo-xbuild = "0.5.26"
rustc_version = "0.2.3"
serde_json = "1.0"
tempfile = "3.1.0"

# dependencies for optional extrinsics feature
async-std = { version = "1.5.0", optional = true }
sp-core = { git = "https://github.com/paritytech/substrate/", package = "sp-core", optional = true }
subxt = { git = "https://github.com/paritytech/substrate-subxt/", rev = "b7565ff435b9499ca1ff4dad519009e94a13111c", package = "substrate-subxt", optional = true }
futures = { version = "0.3.2", optional = true }
url = { version = "2.1.1", optional = true }
hex = { version = "0.4.0", optional = true }
url = { version = "2.1.1", optional = true }

[build-dependencies]
anyhow = "1.0.26"
Expand All @@ -43,10 +48,9 @@ walkdir = "2.3.1"

[dev-dependencies]
assert_matches = "1.3.0"
tempfile = "3.1.0"
wabt = "0.9.2"

[features]
default = []
extrinsics = ["sp-core", "subxt", "async-std", "futures", "url", "hex"]
extrinsics = ["sp-core", "subxt", "async-std", "futures", "hex", "url"]
test-ci-only = []
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ A small CLI tool for helping setting up and managing WebAssembly smart contracts

## Installation

### Prerequisites

- **rust-src**: `rustup component add rust-src`
- **wasm-opt**: https://github.com/WebAssembly/binaryen#tools

`cargo install --git https://github.com/paritytech/cargo-contract cargo-contract --force`

Use the --force to ensure you are updated to the most recent cargo-contract version.
Expand Down Expand Up @@ -35,6 +40,13 @@ SUBCOMMANDS:
help Prints this message or the help of the given subcommand(s)
```

## `build` requires the `nightly` toolchain

`cargo contract build` must be run using the `nightly` toolchain. If you have
[`rustup`](https://github.com/rust-lang/rustup) installed, the simplest way to do so is `cargo +nightly contract build`.
To avoid having to add `+nightly` you can also create a `rust-toolchain` file in your local directory containing
`nightly`. Read more about how to [specify the rustup toolchain](https://github.com/rust-lang/rustup#override-precedence).

## Features

The `deploy` and `instantiate` subcommands are **disabled by default**, since they are not fully stable yet and increase the build time.
Expand Down
128 changes: 93 additions & 35 deletions src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,52 @@
use std::{
fs::metadata,
io::{self, Write},
path::PathBuf,
path::{Path, PathBuf},
process::Command,
};

use crate::{
util,
workspace::{ManifestPath, Workspace},
Verbosity,
};
use anyhow::{Context, Result};
use cargo_metadata::MetadataCommand;
use cargo_metadata::Package;
use colored::Colorize;
use parity_wasm::elements::{External, MemoryType, Module, Section};

/// This is the maximum number of pages available for a contract to allocate.
const MAX_MEMORY_PAGES: u32 = 16;

/// Relevant metadata obtained from Cargo.toml.
#[derive(Debug)]
pub struct CrateMetadata {
manifest_path: ManifestPath,
cargo_meta: cargo_metadata::Metadata,
package_name: String,
root_package: Package,
original_wasm: PathBuf,
pub dest_wasm: PathBuf,
}

/// Parses the contract manifest and returns relevant metadata.
pub fn collect_crate_metadata(working_dir: Option<&PathBuf>) -> Result<CrateMetadata> {
let mut cmd = MetadataCommand::new();
if let Some(dir) = working_dir {
cmd.current_dir(dir);
impl CrateMetadata {
pub fn target_dir(&self) -> &Path {
self.cargo_meta.target_directory.as_path()
}
let metadata = cmd.exec()?;
}

let root_package_id = metadata
.resolve
.and_then(|resolve| resolve.root)
.context("Cannot infer the root project id")?;
/// Parses the contract manifest and returns relevant metadata.
pub fn collect_crate_metadata(manifest_path: &ManifestPath) -> Result<CrateMetadata> {
let (metadata, root_package_id) = crate::util::get_cargo_metadata(manifest_path)?;

// Find the root package by id in the list of packages. It is logical error if the root
// package is not found in the list.
let root_package = metadata
.packages
.iter()
.find(|package| package.id == root_package_id)
.expect("The package is not found in the `cargo metadata` output");
.expect("The package is not found in the `cargo metadata` output")
.clone();

// Normalize the package name.
let package_name = root_package.name.replace("-", "_");
Expand All @@ -72,27 +79,69 @@ pub fn collect_crate_metadata(working_dir: Option<&PathBuf>) -> Result<CrateMeta
dest_wasm.push(package_name.clone());
dest_wasm.set_extension("wasm");

Ok(CrateMetadata {
let crate_metadata = CrateMetadata {
manifest_path: manifest_path.clone(),
cargo_meta: metadata,
root_package: root_package.clone(),
package_name,
original_wasm,
dest_wasm,
})
};
Ok(crate_metadata)
}

/// Invokes `cargo build` in the specified directory, defaults to the current directory.
/// Builds the project in the specified directory, defaults to the current directory.
///
/// Currently it assumes that user wants to use `+nightly`.
fn build_cargo_project(working_dir: Option<&PathBuf>) -> Result<()> {
super::exec_cargo(
"build",
&[
/// Uses [`cargo-xbuild`](https://github.com/rust-osdev/cargo-xbuild) for maximum optimization of
/// the resulting Wasm binary.
fn build_cargo_project(crate_metadata: &CrateMetadata, verbosity: Option<Verbosity>) -> Result<()> {
util::assert_channel()?;

// set RUSTFLAGS, read from environment var by cargo-xbuild
std::env::set_var(
"RUSTFLAGS",
"-C link-arg=-z -C link-arg=stack-size=65536 -C link-arg=--import-memory",
);
ascjones marked this conversation as resolved.
Show resolved Hide resolved

let verbosity = verbosity.map(|v| match v {
Verbosity::Verbose => xargo_lib::Verbosity::Verbose,
Verbosity::Quiet => xargo_lib::Verbosity::Quiet,
});
ascjones marked this conversation as resolved.
Show resolved Hide resolved

let xbuild = |manifest_path: &ManifestPath| {
ascjones marked this conversation as resolved.
Show resolved Hide resolved
let manifest_path = Some(manifest_path);
let target = Some("wasm32-unknown-unknown");
let target_dir = crate_metadata.target_dir();
let other_args = [
"--no-default-features",
"--release",
"--target=wasm32-unknown-unknown",
"--verbose",
],
working_dir,
)
&format!("--target-dir={}", target_dir.to_string_lossy()),
];
let args = xargo_lib::Args::new(target, manifest_path, verbosity, &other_args)
.map_err(|e| anyhow::anyhow!("{}", e))
.context("Creating xargo args")?;

let config = xargo_lib::Config {
sysroot_path: target_dir.join("sysroot"),
memcpy: false,
panic_immediate_abort: true,
};

let exit_status = xargo_lib::build(args, "build", Some(config))
.map_err(|e| anyhow::anyhow!("{}", e))
.context("Building with xbuild")?;
log::debug!("xargo exit status: {:?}", exit_status);
Ok(())
};

Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
.with_root_package_manifest(|manifest| {
manifest.with_removed_crate_type("rlib")?;
Ok(())
})?
.using_temp(xbuild)?;

Ok(())
}

/// Ensures the wasm memory import of a given module has the maximum number of pages.
Expand Down Expand Up @@ -145,7 +194,11 @@ fn strip_custom_sections(module: &mut Module) {
/// Performs required post-processing steps on the wasm artifact.
fn post_process_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
// Deserialize wasm module from a file.
let mut module = parity_wasm::deserialize_file(&crate_metadata.original_wasm)?;
let mut module =
parity_wasm::deserialize_file(&crate_metadata.original_wasm).context(format!(
"Loading original wasm file '{}'",
crate_metadata.original_wasm.display()
))?;

// Perform optimization.
//
Expand Down Expand Up @@ -198,10 +251,10 @@ fn optimize_wasm(crate_metadata: &CrateMetadata) -> Result<()> {
anyhow::bail!("wasm-opt optimization failed");
}

let original_size = metadata(&crate_metadata.dest_wasm)?.len() / 1000;
let optimized_size = metadata(&optimized)?.len() / 1000;
let original_size = metadata(&crate_metadata.dest_wasm)?.len() as f64 / 1000.0;
let optimized_size = metadata(&optimized)?.len() as f64 / 1000.0;
println!(
" Original wasm size: {}K, Optimized: {}K",
" Original wasm size: {:.1}K, Optimized: {:.1}K",
original_size, optimized_size
);

Expand All @@ -213,19 +266,22 @@ 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 build by cargo and then post processing the final binary.
pub(crate) fn execute_build(working_dir: Option<&PathBuf>) -> Result<String> {
pub(crate) fn execute_build(
manifest_path: ManifestPath,
verbosity: Option<Verbosity>,
) -> Result<String> {
println!(
" {} {}",
"[1/4]".bold(),
"Collecting crate metadata".bright_green().bold()
);
let crate_metadata = collect_crate_metadata(working_dir)?;
let crate_metadata = collect_crate_metadata(&manifest_path)?;
println!(
" {} {}",
"[2/4]".bold(),
"Building cargo project".bright_green().bold()
);
build_cargo_project(working_dir)?;
build_cargo_project(&crate_metadata, verbosity)?;
println!(
" {} {}",
"[3/4]".bold(),
Expand All @@ -248,13 +304,15 @@ pub(crate) fn execute_build(working_dir: Option<&PathBuf>) -> Result<String> {
#[cfg(feature = "test-ci-only")]
#[cfg(test)]
mod tests {
use crate::cmd::{execute_new, tests::with_tmp_dir};
use crate::{cmd::execute_new, util::tests::with_tmp_dir, workspace::ManifestPath};

#[test]
fn build_template() {
with_tmp_dir(|path| {
execute_new("new_project", Some(path)).expect("new project creation failed");
super::execute_build(Some(&path.join("new_project"))).expect("build failed");
let manifest_path =
ManifestPath::new(&path.join("new_project").join("Cargo.toml")).unwrap();
super::execute_build(manifest_path, None).expect("build failed");
});
}
}
7 changes: 2 additions & 5 deletions src/cmd/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{cmd::build, ExtrinsicOpts};
fn load_contract_code(path: Option<&PathBuf>) -> Result<Vec<u8>> {
let contract_wasm_path = match path {
Some(path) => path.clone(),
None => build::collect_crate_metadata(path)?.dest_wasm,
None => build::collect_crate_metadata(&Default::default())?.dest_wasm,
};
log::info!("Contract code path: {}", contract_wasm_path.display());
let mut data = Vec::new();
Expand Down Expand Up @@ -61,10 +61,7 @@ pub(crate) fn execute_deploy(
mod tests {
use std::{fs, io::Write};

use crate::{
cmd::{deploy::execute_deploy, tests::with_tmp_dir},
ExtrinsicOpts,
};
use crate::{cmd::deploy::execute_deploy, util::tests::with_tmp_dir, ExtrinsicOpts};
use assert_matches::assert_matches;

const CONTRACT: &str = r#"
Expand Down
5 changes: 1 addition & 4 deletions src/cmd/instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,7 @@ pub(crate) fn execute_instantiate(
mod tests {
use std::{fs, io::Write};

use crate::{
cmd::{deploy::execute_deploy, tests::with_tmp_dir},
ExtrinsicOpts, HexData,
};
use crate::{cmd::deploy::execute_deploy, util::tests::with_tmp_dir, ExtrinsicOpts, HexData};
use assert_matches::assert_matches;

const CONTRACT: &str = r#"
Expand Down
Loading