2017-08-09 13:45:35 +03:00
|
|
|
//! Experimental build tool for cargo
|
2017-08-08 16:13:15 +03:00
|
|
|
|
|
|
|
extern crate glob;
|
2017-08-09 13:45:35 +03:00
|
|
|
extern crate wasm_utils;
|
|
|
|
extern crate clap;
|
2017-08-09 14:30:37 +03:00
|
|
|
extern crate parity_wasm;
|
2017-08-08 16:13:15 +03:00
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
mod source;
|
|
|
|
|
2017-08-09 13:51:47 +03:00
|
|
|
use std::{fs, io};
|
2017-10-25 20:36:05 +03:00
|
|
|
use std::io::Write;
|
2017-08-08 16:13:15 +03:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
2017-08-09 13:45:35 +03:00
|
|
|
use clap::{App, Arg};
|
2017-10-25 20:36:05 +03:00
|
|
|
use parity_wasm::elements;
|
2017-08-09 13:45:35 +03:00
|
|
|
|
2018-02-05 18:20:34 +03:00
|
|
|
use wasm_utils::{CREATE_SYMBOL, CALL_SYMBOL, ununderscore_funcs, externalize_mem, shrink_unknown_stack};
|
2017-10-26 15:49:55 +03:00
|
|
|
|
2017-08-08 16:13:15 +03:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
Io(io::Error),
|
|
|
|
NoSuitableFile(String),
|
|
|
|
TooManyFiles(String),
|
|
|
|
NoEnvVar,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<io::Error> for Error {
|
|
|
|
fn from(err: io::Error) -> Self {
|
|
|
|
Error::Io(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
pub fn wasm_path(input: &source::SourceInput) -> String {
|
|
|
|
let mut path = PathBuf::from(input.target_dir());
|
2017-12-27 12:12:09 +03:00
|
|
|
path.push(format!("{}.wasm", input.final_name()));
|
2017-08-09 14:30:37 +03:00
|
|
|
path.to_string_lossy().to_string()
|
|
|
|
}
|
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
pub fn process_output(input: &source::SourceInput) -> Result<(), Error> {
|
|
|
|
let mut cargo_path = PathBuf::from(input.target_dir());
|
|
|
|
let wasm_name = input.bin_name().to_string().replace("-", "_");
|
2017-12-27 12:12:09 +03:00
|
|
|
cargo_path.push(
|
|
|
|
match input.target() {
|
2017-12-27 17:50:31 +03:00
|
|
|
source::SourceTarget::Emscripten => source::EMSCRIPTEN_TRIPLET,
|
|
|
|
source::SourceTarget::Unknown => source::UNKNOWN_TRIPLET,
|
2017-12-27 12:12:09 +03:00
|
|
|
}
|
|
|
|
);
|
2017-11-03 13:52:51 +03:00
|
|
|
cargo_path.push("release");
|
|
|
|
cargo_path.push(format!("{}.wasm", wasm_name));
|
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
let mut target_path = PathBuf::from(input.target_dir());
|
2017-12-27 12:12:09 +03:00
|
|
|
target_path.push(format!("{}.wasm", input.final_name()));
|
2017-11-03 13:52:51 +03:00
|
|
|
fs::copy(cargo_path, target_path)?;
|
2017-08-08 16:13:15 +03:00
|
|
|
|
|
|
|
Ok(())
|
2017-08-09 13:45:35 +03:00
|
|
|
}
|
|
|
|
|
2017-10-25 20:36:05 +03:00
|
|
|
fn has_ctor(module: &elements::Module) -> bool {
|
|
|
|
if let Some(ref section) = module.export_section() {
|
2017-10-27 18:32:33 +03:00
|
|
|
section.entries().iter().any(|e| CREATE_SYMBOL == e.field())
|
2017-10-25 20:36:05 +03:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-09 13:45:35 +03:00
|
|
|
fn main() {
|
|
|
|
wasm_utils::init_log();
|
|
|
|
|
2017-11-03 13:52:51 +03:00
|
|
|
let matches = App::new("wasm-build")
|
2017-08-09 13:51:47 +03:00
|
|
|
.arg(Arg::with_name("target")
|
2017-08-09 13:45:35 +03:00
|
|
|
.index(1)
|
|
|
|
.required(true)
|
2017-08-09 13:51:47 +03:00
|
|
|
.help("Cargo target directory"))
|
|
|
|
.arg(Arg::with_name("wasm")
|
2017-08-09 13:45:35 +03:00
|
|
|
.index(2)
|
|
|
|
.required(true)
|
2017-08-09 13:51:47 +03:00
|
|
|
.help("Wasm binary name"))
|
2017-08-15 16:44:25 +03:00
|
|
|
.arg(Arg::with_name("skip_optimization")
|
|
|
|
.help("Skip symbol optimization step producing final wasm")
|
|
|
|
.long("skip-optimization"))
|
2017-09-25 20:14:46 +03:00
|
|
|
.arg(Arg::with_name("runtime_type")
|
|
|
|
.help("Injects RUNTIME_TYPE global export")
|
|
|
|
.takes_value(true)
|
|
|
|
.long("runtime-type"))
|
|
|
|
.arg(Arg::with_name("runtime_version")
|
|
|
|
.help("Injects RUNTIME_VERSION global export")
|
|
|
|
.takes_value(true)
|
|
|
|
.long("runtime-version"))
|
2017-12-26 14:03:08 +03:00
|
|
|
.arg(Arg::with_name("source_target")
|
2017-12-27 17:03:51 +03:00
|
|
|
.help("Cargo target type kind ('wasm32-unknown-unknown' or 'wasm32-unknown-emscripten'")
|
2017-12-26 14:03:08 +03:00
|
|
|
.takes_value(true)
|
|
|
|
.long("target"))
|
2017-12-27 12:12:09 +03:00
|
|
|
.arg(Arg::with_name("final_name")
|
|
|
|
.help("Final wasm binary name")
|
|
|
|
.takes_value(true)
|
|
|
|
.long("final"))
|
2017-12-27 12:35:31 +03:00
|
|
|
.arg(Arg::with_name("save_raw")
|
|
|
|
.help("Save intermediate raw bytecode to path")
|
|
|
|
.takes_value(true)
|
|
|
|
.long("save-raw"))
|
2018-02-05 18:20:34 +03:00
|
|
|
.arg(Arg::with_name("shrink_stack")
|
|
|
|
.help("Shrinks the new stack size for wasm32-unknown-unknown")
|
|
|
|
.takes_value(true)
|
|
|
|
.long("shrink-stack"))
|
2017-08-09 13:45:35 +03:00
|
|
|
.get_matches();
|
|
|
|
|
2017-08-09 13:51:47 +03:00
|
|
|
let target_dir = matches.value_of("target").expect("is required; qed");
|
|
|
|
let wasm_binary = matches.value_of("wasm").expect("is required; qed");
|
2017-12-26 14:03:08 +03:00
|
|
|
let mut source_input = source::SourceInput::new(target_dir, wasm_binary);
|
|
|
|
|
2017-12-27 17:50:31 +03:00
|
|
|
let source_target_val = matches.value_of("source_target").unwrap_or_else(|| source::EMSCRIPTEN_TRIPLET);
|
|
|
|
if source_target_val == source::UNKNOWN_TRIPLET {
|
2017-12-26 14:03:08 +03:00
|
|
|
source_input = source_input.unknown()
|
2017-12-27 17:50:31 +03:00
|
|
|
} else if source_target_val == source::EMSCRIPTEN_TRIPLET {
|
2017-12-26 14:03:08 +03:00
|
|
|
source_input = source_input.emscripten()
|
|
|
|
} else {
|
2017-12-27 17:50:31 +03:00
|
|
|
println!("--target can be: '{}' or '{}'", source::EMSCRIPTEN_TRIPLET, source::UNKNOWN_TRIPLET);
|
2017-12-26 14:03:08 +03:00
|
|
|
::std::process::exit(1);
|
|
|
|
}
|
2017-08-09 13:45:35 +03:00
|
|
|
|
2017-12-27 12:12:09 +03:00
|
|
|
if let Some(final_name) = matches.value_of("final_name") {
|
|
|
|
source_input = source_input.with_final(final_name);
|
|
|
|
}
|
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
process_output(&source_input).expect("Failed to process cargo target directory");
|
2017-08-09 14:30:37 +03:00
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
let path = wasm_path(&source_input);
|
2017-08-09 18:38:46 +03:00
|
|
|
|
2017-08-15 16:44:25 +03:00
|
|
|
let mut module = parity_wasm::deserialize_file(&path).unwrap();
|
2017-08-09 14:30:37 +03:00
|
|
|
|
2018-01-30 15:21:56 +03:00
|
|
|
if let source::SourceTarget::Emscripten = source_input.target() {
|
2018-01-30 15:21:17 +03:00
|
|
|
module = ununderscore_funcs(module);
|
2017-12-27 12:12:09 +03:00
|
|
|
}
|
|
|
|
|
2018-02-01 19:42:27 +03:00
|
|
|
if let source::SourceTarget::Unknown = source_input.target() {
|
2018-02-05 18:26:59 +03:00
|
|
|
// 49152 is 48kb!
|
2018-02-05 18:20:34 +03:00
|
|
|
let stack_size: u32 = matches.value_of("shrink_stack").unwrap_or_else(|| "49152").parse().expect("New stack size is not valid u32");
|
|
|
|
assert!(stack_size <= 1024*1024);
|
|
|
|
let (new_module, new_stack_top) = shrink_unknown_stack(module, 1024 * 1024 - stack_size);
|
|
|
|
module = new_module;
|
|
|
|
let mut stack_top_page = new_stack_top / 65536;
|
|
|
|
if new_stack_top % 65536 > 0 { stack_top_page += 1 };
|
|
|
|
|
2018-02-05 19:43:36 +03:00
|
|
|
module = externalize_mem(module, Some(stack_top_page), 16);
|
2018-02-01 19:42:27 +03:00
|
|
|
}
|
|
|
|
|
2017-09-25 20:14:46 +03:00
|
|
|
if let Some(runtime_type) = matches.value_of("runtime_type") {
|
|
|
|
let runtime_type: &[u8] = runtime_type.as_bytes();
|
|
|
|
if runtime_type.len() != 4 {
|
|
|
|
panic!("--runtime-type should be equal to 4 bytes");
|
|
|
|
}
|
|
|
|
let runtime_version: u32 = matches.value_of("runtime_version").unwrap_or("1").parse()
|
|
|
|
.expect("--runtime-version should be a positive integer");
|
|
|
|
module = wasm_utils::inject_runtime_type(module, &runtime_type, runtime_version);
|
|
|
|
}
|
|
|
|
|
2017-10-25 20:36:05 +03:00
|
|
|
let mut ctor_module = module.clone();
|
|
|
|
|
|
|
|
if !matches.is_present("skip_optimization") {
|
2018-01-30 15:21:17 +03:00
|
|
|
wasm_utils::optimize(
|
|
|
|
&mut module,
|
2018-02-04 22:12:34 +03:00
|
|
|
vec![CALL_SYMBOL]
|
2018-01-30 15:21:17 +03:00
|
|
|
).expect("Optimizer to finish without errors");
|
2017-10-25 20:36:05 +03:00
|
|
|
}
|
|
|
|
|
2017-12-27 12:35:31 +03:00
|
|
|
if let Some(save_raw_path) = matches.value_of("save_raw") {
|
|
|
|
parity_wasm::serialize_to_file(save_raw_path, module.clone())
|
|
|
|
.expect("Failed to write intermediate module");
|
|
|
|
}
|
|
|
|
|
2017-10-25 20:36:05 +03:00
|
|
|
let raw_module = parity_wasm::serialize(module).expect("Failed to serialize module");
|
|
|
|
|
2017-10-26 15:49:55 +03:00
|
|
|
// If module has an exported function with name=CREATE_SYMBOL
|
|
|
|
// build will pack the module (raw_module) into this funciton and export as CALL_SYMBOL.
|
|
|
|
// Otherwise it will just save an optimised raw_module
|
2017-10-26 19:24:40 +03:00
|
|
|
if has_ctor(&ctor_module) {
|
2017-10-25 20:36:05 +03:00
|
|
|
if !matches.is_present("skip_optimization") {
|
2017-11-09 17:36:13 +03:00
|
|
|
wasm_utils::optimize(&mut ctor_module, vec![CREATE_SYMBOL]).expect("Optimizer to finish without errors");
|
2017-10-25 20:36:05 +03:00
|
|
|
}
|
2017-11-22 01:40:58 +03:00
|
|
|
let ctor_module = wasm_utils::pack_instance(raw_module, ctor_module).expect("Packing failed");
|
2017-10-26 19:24:40 +03:00
|
|
|
parity_wasm::serialize_to_file(&path, ctor_module).expect("Failed to serialize to file");
|
2017-10-26 15:49:55 +03:00
|
|
|
} else {
|
|
|
|
let mut file = fs::File::create(&path).expect("Failed to create file");
|
|
|
|
file.write_all(&raw_module).expect("Failed to write module to file");
|
2017-10-25 20:36:05 +03:00
|
|
|
}
|
2017-09-25 20:14:46 +03:00
|
|
|
}
|
2017-11-03 13:52:51 +03:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
extern crate tempdir;
|
|
|
|
|
|
|
|
use self::tempdir::TempDir;
|
|
|
|
use std::fs;
|
|
|
|
|
|
|
|
use super::process_output;
|
2017-12-26 13:38:07 +03:00
|
|
|
use super::source::SourceInput;
|
2017-11-03 13:52:51 +03:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn processes_cargo_output() {
|
2017-11-09 17:36:13 +03:00
|
|
|
let tmp_dir = TempDir::new("target").expect("temp dir failed");
|
2017-11-03 13:52:51 +03:00
|
|
|
|
|
|
|
let target_path = tmp_dir.path().join("wasm32-unknown-emscripten").join("release");
|
|
|
|
fs::create_dir_all(target_path.clone()).expect("create dir failed");
|
|
|
|
|
|
|
|
{
|
|
|
|
use std::io::Write;
|
|
|
|
|
|
|
|
let wasm_path = target_path.join("example_wasm.wasm");
|
|
|
|
let mut f = fs::File::create(wasm_path).expect("create fail failed");
|
|
|
|
f.write(b"\0asm").expect("write file failed");
|
|
|
|
}
|
|
|
|
|
2017-12-26 13:38:07 +03:00
|
|
|
let path = tmp_dir.path().to_string_lossy();
|
|
|
|
let input = SourceInput::new(&path, "example-wasm");
|
|
|
|
|
|
|
|
process_output(&input).expect("process output failed");
|
2017-11-03 13:52:51 +03:00
|
|
|
|
|
|
|
assert!(
|
|
|
|
fs::metadata(tmp_dir.path().join("example-wasm.wasm")).expect("metadata failed").is_file()
|
|
|
|
)
|
|
|
|
}
|
2017-11-09 17:36:13 +03:00
|
|
|
}
|