use crate::api::{Feature, FileType, OptimizationOptions, OptimizeLevel, Pass, ShrinkLevel};
use crate::profiles::Profile;
use crate::run::OptimizationError;
use std::ffi::{OsStr, OsString};
use std::iter::Iterator;
use std::num::ParseIntError;
use std::path::PathBuf;
use std::result::Result;
use std::str::FromStr;
use strum::IntoEnumIterator;
use thiserror::Error;
pub use crate::fake_command::Command;
pub fn run_from_command_args(command: Command) -> Result<(), Error> {
let parsed = parse_command_args(command)?;
parsed.opts.run_with_sourcemaps(
parsed.input_file,
parsed.input_sourcemap,
parsed.output_file,
parsed.output_sourcemap,
parsed.sourcemap_url,
)?;
Ok(())
}
#[derive(Error, Debug)]
pub enum Error {
#[error("An input file is required")]
InputFileRequired,
#[error("The `-o` option to `wasm-opt` is required")]
OutputFileRequired,
#[error("The `wasm-opt` argument list ended while expecting another argument")]
UnexpectedEndOfArgs,
#[error("Argument must be unicode: {arg:?}")]
NeedUnicode { arg: OsString },
#[error("Argument must be a number: {arg:?}")]
NeedNumber {
arg: OsString,
#[source]
source: ParseIntError,
},
#[error("Unsupported `wasm-opt` command-line arguments: {args:?}")]
Unsupported { args: Vec<OsString> },
#[error("Error while optimization wasm modules")]
ExecutionError(
#[from]
#[source]
OptimizationError,
),
}
struct ParsedCliArgs {
opts: OptimizationOptions,
input_file: PathBuf,
input_sourcemap: Option<PathBuf>,
output_file: PathBuf,
output_sourcemap: Option<PathBuf>,
sourcemap_url: Option<String>,
}
#[rustfmt::skip]
fn parse_command_args(command: Command) -> Result<ParsedCliArgs, Error> {
let mut opts = OptimizationOptions::new_opt_level_0();
let mut args = command.get_args();
let mut input_file: Option<PathBuf> = None;
let mut input_sourcemap: Option<PathBuf> = None;
let mut output_file: Option<PathBuf> = None;
let mut output_sourcemap: Option<PathBuf> = None;
let mut sourcemap_url: Option<String> = None;
let mut unsupported: Vec<OsString> = vec![];
while let Some(arg) = args.next() {
let arg = if let Some(arg) = arg.to_str() {
arg
} else {
parse_infile_path(arg, &mut input_file, &mut unsupported);
continue;
};
match arg {
"--output" | "-o" => {
parse_path_into(&mut args, &mut output_file, &mut unsupported)?;
}
"--emit-text" | "-S" => {
opts.writer_file_type(FileType::Wat);
}
"--converge" | "-c" => {
opts.set_converge();
}
"--input-source-map" | "-ism" => {
parse_path_into(&mut args, &mut input_sourcemap, &mut unsupported)?;
}
"--output-source-map" | "-osm" => {
parse_path_into(&mut args, &mut output_sourcemap, &mut unsupported)?;
}
"--output-source-map-url" | "-osu" => {
sourcemap_url = Some(parse_unicode(&mut args)?);
}
"-O" => {
Profile::optimize_for_size().apply_to_opts(&mut opts);
}
"-O0" => {
Profile::opt_level_0().apply_to_opts(&mut opts);
}
"-O1" => {
Profile::opt_level_1().apply_to_opts(&mut opts);
}
"-O2" => {
Profile::opt_level_2().apply_to_opts(&mut opts);
}
"-O3" => {
Profile::opt_level_3().apply_to_opts(&mut opts);
}
"-O4" => {
Profile::opt_level_4().apply_to_opts(&mut opts);
}
"-Os" => {
Profile::optimize_for_size().apply_to_opts(&mut opts);
}
"-Oz" => {
Profile::optimize_for_size_aggressively().apply_to_opts(&mut opts);
}
"--optimize-level" | "-ol" => {
match parse_unicode(&mut args)?.as_str() {
"0" => { opts.optimize_level(OptimizeLevel::Level0); },
"1" => { opts.optimize_level(OptimizeLevel::Level1); },
"2" => { opts.optimize_level(OptimizeLevel::Level2); },
"3" => { opts.optimize_level(OptimizeLevel::Level3); },
"4" => { opts.optimize_level(OptimizeLevel::Level4); },
arg => {
unsupported.push(OsString::from(arg));
}
}
}
"--shrink-level" | "-s" => {
match parse_unicode(&mut args)?.as_str() {
"0" => { opts.shrink_level(ShrinkLevel::Level0); },
"1" => { opts.shrink_level(ShrinkLevel::Level1); },
"2" => { opts.shrink_level(ShrinkLevel::Level2); },
arg => {
unsupported.push(OsString::from(arg));
}
}
}
"--debuginfo" | "-g" => {
opts.debug_info(true);
}
"--always-inline-max-function-size" | "-aimfs" => {
opts.always_inline_max_size(parse_u32(&mut args)?);
}
"--flexible-inline-max-function-size" | "-fimfs" => {
opts.flexible_inline_max_size(parse_u32(&mut args)?);
}
"--one-caller-inline-max-function-size" | "-ocifms" => {
opts.one_caller_inline_max_size(parse_u32(&mut args)?);
}
"--inline-functions-with-loops" | "-ifwl" => {
opts.allow_functions_with_loops(true);
}
"--partial-inlining-ifs" | "-pii" => {
opts.partial_inlining_ifs(parse_u32(&mut args)?);
}
"--traps-never-happen" | "-tnh" => {
opts.traps_never_happen(true);
}
"--low-memory-unused" | "-lmu" => {
opts.low_memory_unused(true);
}
"--fast-math" | "-ffm" => {
opts.fast_math(true);
}
"--zero-filled-memory" | "-uim" => {
opts.zero_filled_memory(true);
}
"--mvp-features" | "-mvp" => {
opts.mvp_features_only();
}
"--all-features" | "-all" => {
opts.all_features();
}
"--quiet" | "-q" => {
}
"--no-validation" | "-n" => {
opts.validate(false);
}
"--pass-arg" | "-pa" => {
let args = parse_unicode(&mut args)?;
if args.contains("@") {
let args: Vec<&str> = args.split("@").collect();
opts.set_pass_arg(args[0], args[1]);
} else {
opts.set_pass_arg(&args, "1");
}
}
_ => {
if arg.starts_with("--enable-") {
let feature = &arg[9..];
if let Ok(feature) = Feature::from_str(feature) {
opts.enable_feature(feature);
} else {
unsupported.push(OsString::from(arg));
}
} else if arg.starts_with("--disable-") {
let feature = &arg[10..];
if let Ok(feature) = Feature::from_str(feature) {
opts.disable_feature(feature);
} else {
unsupported.push(OsString::from(arg));
}
} else {
let mut is_pass = false;
for pass in Pass::iter() {
if is_pass_argument(arg, &pass) {
opts.add_pass(pass);
is_pass = true;
}
}
if !is_pass {
if arg.starts_with("-") && arg.len() > 1 {
unsupported.push(OsString::from(arg));
} else {
parse_infile_path(OsStr::new(arg), &mut input_file, &mut unsupported);
}
}
}
}
}
}
let input_file = if let Some(input_file) = input_file {
input_file
} else {
return Err(Error::InputFileRequired);
};
let output_file = if let Some(output_file) = output_file {
output_file
} else {
return Err(Error::OutputFileRequired);
};
if unsupported.len() > 0 {
return Err(Error::Unsupported {
args: unsupported,
});
}
Ok(ParsedCliArgs {
opts,
input_file,
input_sourcemap,
output_file,
output_sourcemap,
sourcemap_url,
})
}
fn is_pass_argument(arg: &str, pass: &Pass) -> bool {
let pass_name = pass.name();
arg.starts_with("--") && arg.contains(pass_name) && arg.len() == 2 + pass_name.len()
}
fn parse_infile_path(
arg: &OsStr,
maybe_input_file: &mut Option<PathBuf>,
unsupported: &mut Vec<OsString>,
) {
parse_path_into(&mut Some(arg).into_iter(), maybe_input_file, unsupported).expect("impossible")
}
fn parse_path_into<'item>(
args: &mut impl Iterator<Item = &'item OsStr>,
maybe_path: &mut Option<PathBuf>,
unsupported: &mut Vec<OsString>,
) -> Result<(), Error> {
if let Some(arg) = args.next() {
if maybe_path.is_none() {
*maybe_path = Some(PathBuf::from(arg));
} else {
unsupported.push(OsString::from(arg));
}
Ok(())
} else {
Err(Error::UnexpectedEndOfArgs)
}
}
fn parse_unicode<'item>(args: &mut impl Iterator<Item = &'item OsStr>) -> Result<String, Error> {
if let Some(arg) = args.next() {
if let Some(arg) = arg.to_str() {
Ok(arg.to_owned())
} else {
Err(Error::NeedUnicode {
arg: arg.to_owned(),
})
}
} else {
Err(Error::UnexpectedEndOfArgs)
}
}
fn parse_u32<'item>(args: &mut impl Iterator<Item = &'item OsStr>) -> Result<u32, Error> {
let arg = parse_unicode(args)?;
let number: u32 = arg.parse().map_err(|e| Error::NeedNumber {
arg: OsString::from(arg),
source: e,
})?;
Ok(number)
}