Commit 53fa4d58 authored by Sergei Pepyakin's avatar Sergei Pepyakin Committed by Hero Bird

Implement the build command (#192)

* [cli] Implement the build command

* [cli] Remove all build.sh scripts

* [cli] Restore build-all.sh

* Fix the test.

* Strip custom sections

* Add some testing.

* Strip name and reloc sections as well
parent c3631ce9
......@@ -41,12 +41,17 @@ Use the scripts provided under `scripts` directory in order to run checks on eit
### Examples
For running the example smart contracts found under `examples` you also need to install some further tools:
For building the example smart contracts found under `examples` you will need to have `cargo-contract` installed.
- `wabt` (WebAssembly Binary Toolkit)
- `wasm-utils` (Parity's Wasm Utilities)
```
cargo install cargo-contract
```
Execute the following command in shell while in an example's directory:
For more information how to install these on your system go [here](https://substrate.dev/docs/en/contracts/installing-ink#wasm-utilities).
```
cargo contract build
```
### Testing
......
......@@ -20,7 +20,17 @@ structopt = "0.2.15"
itertools = "0.8"
heck = "0.3"
zip = { version = "0.5", default-features = false }
pwasm-utils = "0.11.0"
parity-wasm = "0.40.2"
cargo_metadata = "0.8.2"
[build-dependencies]
zip = { version = "0.5", default-features = false }
walkdir = "1.0"
[dev-dependencies]
tempfile = "3.1.0"
[features]
default = []
test-ci-only = []
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of ink!.
//
// ink! is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ink! is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
use crate::cmd::{
CommandError as Error,
Result,
};
use cargo_metadata::MetadataCommand;
use parity_wasm::elements::{
External,
MemoryType,
Module,
Section,
};
use std::{
io::{
self,
Write,
},
path::PathBuf,
process::Command,
};
/// 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.
struct CrateMetadata {
original_wasm: PathBuf,
dest_wasm: PathBuf,
}
/// Parses the contract manifest and returns relevant metadata.
fn collect_crate_metadata() -> Result<CrateMetadata> {
let metadata = MetadataCommand::new().exec()?;
let root_package_id = metadata
.resolve
.and_then(|resolve| resolve.root)
.ok_or_else(|| Error::Other("Cannot infer the root project id".to_string()))?;
// 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");
// Normalize the package name.
let package_name = root_package.name.replace("-", "_");
// {target_dir}/wasm32-unknown-unknown/release/{package_name}.wasm
let mut original_wasm = metadata.target_directory.clone();
original_wasm.push("wasm32-unknown-unknown");
original_wasm.push("release");
original_wasm.push(package_name.clone());
original_wasm.set_extension("wasm");
// {target_dir}/{package_name}.wasm
let mut dest_wasm = metadata.target_directory.clone();
dest_wasm.push(package_name);
dest_wasm.set_extension("wasm");
Ok(CrateMetadata {
original_wasm,
dest_wasm,
})
}
/// Invokes `cargo build` in the current directory.
///
/// Currently it assumes that user wants to use `+nightly`.
fn build_cargo_project() -> Result<()> {
// We also assume that the user uses +nightly.
let output = Command::new("cargo")
.args(&[
"+nightly",
"build",
"--no-default-features",
"--release",
"--target=wasm32-unknown-unknown",
"--verbose",
])
.output()?;
if !output.status.success() {
// Dump the output streams produced by cargo into the stdout/stderr.
io::stdout().write_all(&output.stdout)?;
io::stderr().write_all(&output.stderr)?;
return Err(Error::BuildFailed)
}
Ok(())
}
/// Ensures the wasm memory import of a given module has the maximum number of pages.
///
/// Iterates over the import section, finds the memory import entry if any and adjusts the maximum
/// limit.
fn ensure_maximum_memory_pages(
module: &mut Module,
maximum_allowed_pages: u32,
) -> Result<()> {
let mem_ty = module
.import_section_mut()
.and_then(|section| {
section.entries_mut()
.iter_mut()
.find_map(|entry| {
match entry.external_mut() {
External::Memory(ref mut mem_ty) => Some(mem_ty),
_ => None,
}
})
})
.ok_or_else(||
Error::Other(
"Memory import is not found. Is --import-memory specified in the linker args".to_string()
)
)?;
if let Some(requested_maximum) = mem_ty.limits().maximum() {
// The module already has maximum, check if it is within the limit bail out.
if requested_maximum > maximum_allowed_pages {
return Err(
Error::Other(
format!(
"The wasm module requires {} pages. The maximum allowed number of pages is {}",
requested_maximum,
maximum_allowed_pages,
)
)
);
}
} else {
let initial = mem_ty.limits().initial();
*mem_ty = MemoryType::new(initial, Some(MAX_MEMORY_PAGES));
}
Ok(())
}
/// Strips all custom sections.
///
/// Presently all custom sections are not required so they can be stripped safely.
fn strip_custom_sections(module: &mut Module) {
module.sections_mut().retain(|section| match section {
Section::Custom(_) => false,
Section::Name(_) => false,
Section::Reloc(_) => false,
_ => true,
});
}
/// 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)?;
// Perform optimization.
//
// In practice only tree-shaking is performed, i.e transitively removing all symbols that are
// NOT used by the specified entrypoints.
pwasm_utils::optimize(&mut module, ["call", "deploy"].to_vec())?;
ensure_maximum_memory_pages(&mut module, MAX_MEMORY_PAGES)?;
strip_custom_sections(&mut module);
parity_wasm::serialize_to_file(&crate_metadata.dest_wasm, module)?;
Ok(())
}
/// 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() -> Result<String> {
println!(" [1/3] Collecting crate metadata");
let crate_metadata = collect_crate_metadata()?;
println!(" [2/3] Building cargo project");
build_cargo_project()?;
println!(" [3/3] Post processing wasm file");
post_process_wasm(&crate_metadata)?;
Ok(format!(
"Your contract is ready.\nYou can find it here:\n{}",
crate_metadata.dest_wasm.display()
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cmd::execute_new,
AbstractionLayer,
};
use tempfile::TempDir;
use std::env;
fn with_tmp_dir<F: FnOnce()>(f: F) {
let original_cwd = env::current_dir().expect("failed to get current working directory");
let tmp_dir = TempDir::new().expect("temporary directory creation failed");
env::set_current_dir(tmp_dir.path()).expect("setting the current dir to temp failed");
f();
env::set_current_dir(original_cwd).expect("restoring cwd failed");
}
#[cfg(feature="test-ci-only")]
#[test]
fn build_template() {
with_tmp_dir(|| {
execute_new(AbstractionLayer::Lang, "new_project").expect("new project creation failed");
env::set_current_dir("./new_project").expect("cwd to new_project failed");
execute_build().expect("build failed");
});
}
}
......@@ -24,11 +24,17 @@ use zip::result::ZipError;
#[derive(Debug, derive_more::From, derive_more::Display)]
pub enum CommandError {
Io(IoError),
#[display(fmt="Command unimplemented")]
#[display(fmt = "Command unimplemented")]
UnimplementedCommand,
#[display(fmt="Abstraction layer unimplemented")]
#[display(fmt = "Abstraction layer unimplemented")]
UnimplementedAbstractionLayer,
ZipError(ZipError),
BuildFailed,
#[display(fmt = "Error invoking `cargo metadata`")]
CargoMetadata(cargo_metadata::Error),
WasmDeserialization(parity_wasm::elements::Error),
#[display(fmt = "Optimizer failed")]
Optimizer(pwasm_utils::OptimizerError),
Other(String),
}
......
......@@ -14,10 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with ink!. If not, see <http://www.gnu.org/licenses/>.
mod build;
mod error;
mod new;
pub(crate) use self::{
build::execute_build,
error::{
CommandError,
Result,
......
......@@ -150,13 +150,13 @@ mod tests {
let name = "dont_overwrite_existing_files";
let dir = path::Path::new(name);
fs::create_dir_all(dir).unwrap();
fs::File::create(dir.join("build.sh")).unwrap();
fs::File::create(dir.join(".gitignore")).unwrap();
let result = super::initialize_for_lang(name);
// clean up created files
std::fs::remove_dir_all(dir).unwrap();
assert_eq!(
format!("{:?}", result),
r#"Err(Other("New contract file dont_overwrite_existing_files/build.sh already exists"))"#
r#"Err(Other("New contract file dont_overwrite_existing_files/.gitignore already exists"))"#
)
}
}
......@@ -108,19 +108,11 @@ fn main() {
}
fn exec(cmd: Command) -> cmd::Result<String> {
use crate::cmd::{
CommandError,
};
use crate::cmd::CommandError;
match &cmd {
Command::New { layer, name } => cmd::execute_new(*layer, name),
Command::Build {} => {
Err(CommandError::UnimplementedCommand)
}
Command::Test {} => {
Err(CommandError::UnimplementedCommand)
}
Command::Deploy { .. } => {
Err(CommandError::UnimplementedCommand)
}
Command::Build {} => cmd::execute_build(),
Command::Test {} => Err(CommandError::UnimplementedCommand),
Command::Deploy { .. } => Err(CommandError::UnimplementedCommand),
}
}
#!/bin/bash
set -e
PROJNAME={{name}}
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
# Deprecated Note:
# The wasm-build executable that is used to tree-shake the wasm binary
# resulting from the cargo build in the first step expects to find it
# under target/release/wasm32-unknown-unknown/ in the cwd.
PROJNAME=erc20
cargo clean
rm Cargo.lock
CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
#wasm-build target enyzme --target-runtime=substrate --final=adder --save-raw=./target/enzyme-deployed.wasm --target wasm32-unknown-unknown
#!/bin/bash
PROJNAME=accumulator
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --no-default-features --target=wasm32-unknown-unknown --verbose &&
mkdir -p target &&
wasm2wat -o target/$PROJNAME.wat ../target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
PROJNAME=adder
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --no-default-features --target=wasm32-unknown-unknown --verbose &&
mkdir -p target &&
wasm2wat -o target/$PROJNAME.wat ../target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
pushd accumulator && ./build.sh && popd &&
pushd adder && ./build.sh && popd &&
pushd subber && ./build.sh && popd &&
./build.sh
pushd accumulator && cargo contract build && popd &&
pushd adder && cargo contract build && popd &&
pushd subber && cargo contract build && popd &&
cargo contract build
#!/bin/bash
PROJNAME=delegator
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --no-default-features --target=wasm32-unknown-unknown --verbose &&
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
PROJNAME=subber
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --no-default-features --target=wasm32-unknown-unknown --verbose &&
mkdir -p target &&
wasm2wat -o target/$PROJNAME.wat ../target/wasm32-unknown-unknown/release/$PROJNAME.wasm &&
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat &&
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat &&
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
set -e
PROJNAME=erc20
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
PROJNAME=events
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
set -e
PROJNAME=flipper
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
set -e
PROJNAME=incrementer
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
PROJNAME=shared_vec
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-prune --exports call,deploy target/$PROJNAME.wasm target/$PROJNAME-pruned.wasm
#!/bin/bash
# Deprecated Note:
# The wasm-build executable that is used to tree-shake the wasm binary
# resulting from the cargo build in the first step expects to find it
# under target/release/wasm32-unknown-unknown/ in the cwd.
PROJNAME=erc20
#cargo clean
#rm Cargo.lock
CARGO_INCREMENTAL=0 cargo +nightly build --no-default-features --release --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
#wasm-build target enyzme --target-runtime=substrate --final=adder --save-raw=./target/enzyme-deployed.wasm --target wasm32-unknown-unknown
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment