use clap::{crate_authors, crate_version, Parser};
#[cfg(feature = "check-endpoints")]
use ic_wasm::check_endpoints::check_endpoints;
use ic_wasm::utils::make_validator_with_features;
use std::path::PathBuf;
#[derive(Parser)]
#[clap(
version = crate_version!(),
author = crate_authors!(),
)]
struct Opts {
input: PathBuf,
#[clap(short, long)]
output: Option<PathBuf>,
#[clap(subcommand)]
subcommand: SubCommand,
}
#[derive(Parser)]
enum SubCommand {
Metadata {
name: Option<String>,
#[clap(short, long, requires("name"))]
data: Option<String>,
#[clap(short, long, requires("name"), conflicts_with("data"))]
file: Option<PathBuf>,
#[clap(short, long, value_parser = ["public", "private"], default_value = "private")]
visibility: String,
#[clap(short, long)]
keep_name_section: bool,
},
Resource {
#[clap(short, long)]
remove_cycles_transfer: bool,
#[clap(short, long, conflicts_with_all = &["remove_cycles_transfer", "playground_backend_redirect"])]
filter_cycles_transfer: bool,
#[clap(short('m'), long)]
limit_heap_memory_page: Option<u32>,
#[clap(short, long)]
limit_stable_memory_page: Option<u32>,
#[clap(short, long)]
playground_backend_redirect: Option<candid::Principal>,
},
Info {
#[clap(short, long)]
#[cfg(feature = "serde")]
json: bool,
},
Shrink {
#[clap(short, long)]
keep_name_section: bool,
},
#[cfg(feature = "wasm-opt")]
Optimize {
#[clap()]
level: ic_wasm::optimize::OptLevel,
#[clap(long("inline-functions-with-loops"))]
inline_functions_with_loops: bool,
#[clap(long("always-inline-max-function-size"))]
always_inline_max_function_size: Option<u32>,
#[clap(short, long)]
keep_name_section: bool,
},
Instrument {
#[clap(short, long)]
trace_only: Option<Vec<String>>,
#[clap(short, long)]
start_page: Option<i32>,
#[clap(short, long, requires("start_page"))]
page_limit: Option<i32>,
},
#[cfg(feature = "check-endpoints")]
CheckEndpoints {
#[clap(long)]
candid: Option<PathBuf>,
#[arg(long)]
hidden: Option<PathBuf>,
},
}
fn main() -> anyhow::Result<()> {
let opts: Opts = Opts::parse();
let keep_name_section = match opts.subcommand {
SubCommand::Shrink { keep_name_section } => keep_name_section,
#[cfg(feature = "wasm-opt")]
SubCommand::Optimize {
keep_name_section, ..
} => keep_name_section,
SubCommand::Metadata {
keep_name_section, ..
} => keep_name_section,
_ => false,
};
let mut m = ic_wasm::utils::parse_wasm_file(opts.input, keep_name_section)?;
match &opts.subcommand {
#[cfg(feature = "serde")]
SubCommand::Info { json } => {
let wasm_info = ic_wasm::info::WasmInfo::from(&m);
if *json {
let json = serde_json::to_string_pretty(&wasm_info)
.expect("Failed to express the Wasm information as JSON.");
println!("{json}");
} else {
print!("{wasm_info}");
}
}
#[cfg(not(feature = "serde"))]
SubCommand::Info {} => {
let wasm_info = ic_wasm::info::WasmInfo::from(&m);
print!("{wasm_info}");
}
SubCommand::Shrink { .. } => ic_wasm::shrink::shrink(&mut m),
#[cfg(feature = "wasm-opt")]
SubCommand::Optimize {
level,
inline_functions_with_loops,
always_inline_max_function_size,
..
} => ic_wasm::optimize::optimize(
&mut m,
level,
*inline_functions_with_loops,
always_inline_max_function_size,
keep_name_section,
)?,
SubCommand::Instrument {
trace_only,
start_page,
page_limit,
} => {
use ic_wasm::instrumentation::{instrument, Config};
let config = Config {
trace_only_funcs: trace_only.clone().unwrap_or(vec![]),
start_address: start_page.map(|page| i64::from(page) * 65536),
page_limit: *page_limit,
};
instrument(&mut m, config).map_err(|e| anyhow::anyhow!("{e}"))?;
}
SubCommand::Resource {
remove_cycles_transfer,
filter_cycles_transfer,
limit_heap_memory_page,
limit_stable_memory_page,
playground_backend_redirect,
} => {
use ic_wasm::limit_resource::{limit_resource, Config};
let config = Config {
remove_cycles_add: *remove_cycles_transfer,
filter_cycles_add: *filter_cycles_transfer,
limit_heap_memory_page: *limit_heap_memory_page,
limit_stable_memory_page: *limit_stable_memory_page,
playground_canister_id: *playground_backend_redirect,
};
limit_resource(&mut m, &config)
}
SubCommand::Metadata {
name,
data,
file,
visibility,
keep_name_section: _,
} => {
use ic_wasm::metadata::*;
if let Some(name) = name {
let visibility = match visibility.as_str() {
"public" => Kind::Public,
"private" => Kind::Private,
_ => unreachable!(),
};
let data = match (data, file) {
(Some(data), None) => data.as_bytes().to_vec(),
(None, Some(path)) => std::fs::read(path)?,
(None, None) => {
let data = get_metadata(&m, name);
if let Some(data) = data {
println!("{}", String::from_utf8_lossy(&data));
} else {
println!("Cannot find metadata {name}");
}
return Ok(());
}
(_, _) => unreachable!(),
};
add_metadata(&mut m, visibility, name, data)
} else {
let names = list_metadata(&m);
for name in names.iter() {
println!("{name}");
}
return Ok(());
}
}
#[cfg(feature = "check-endpoints")]
SubCommand::CheckEndpoints { candid, hidden } => {
return check_endpoints(&m, candid.as_deref(), hidden.as_deref());
}
};
let module_bytes = m.emit_wasm();
let mut validator = make_validator_with_features();
if let Err(e) = validator.validate_all(&module_bytes) {
println!("WARNING: The output of ic-wasm failed to validate. Please report this via github issue or on https://forum.dfinity.org/");
eprintln!("{e}");
}
if let Some(output) = opts.output {
std::fs::write(output, module_bytes).expect("failed to write wasm module");
}
Ok(())
}