use crate::bindgen;
use crate::build;
use crate::cache;
use crate::command::utils::{create_pkg_dir, get_crate_path};
use crate::emoji;
use crate::install::{self, InstallMode, Tool};
use crate::license;
use crate::lockfile::Lockfile;
use crate::manifest;
use crate::readme;
use crate::wasm_opt;
use crate::PBAR;
use anyhow::{anyhow, bail, Error, Result};
use binary_install::Cache;
use clap::Args;
use log::info;
use path_clean::PathClean;
use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Instant;
#[allow(missing_docs)]
pub struct Build {
pub crate_path: PathBuf,
pub crate_data: manifest::CrateData,
pub scope: Option<String>,
pub disable_dts: bool,
pub weak_refs: bool,
pub reference_types: bool,
pub target: Target,
pub no_pack: bool,
pub no_opt: bool,
pub profile: BuildProfile,
pub mode: InstallMode,
pub out_dir: PathBuf,
pub out_name: Option<String>,
pub bindgen: Option<install::Status>,
pub cache: Cache,
pub extra_options: Vec<String>,
}
#[derive(Clone, Copy, Debug)]
pub enum Target {
Bundler,
Web,
Nodejs,
NoModules,
Deno,
}
impl Default for Target {
fn default() -> Target {
Target::Bundler
}
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Target::Bundler => "bundler",
Target::Web => "web",
Target::Nodejs => "nodejs",
Target::NoModules => "no-modules",
Target::Deno => "deno",
};
write!(f, "{}", s)
}
}
impl FromStr for Target {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"bundler" | "browser" => Ok(Target::Bundler),
"web" => Ok(Target::Web),
"nodejs" => Ok(Target::Nodejs),
"no-modules" => Ok(Target::NoModules),
"deno" => Ok(Target::Deno),
_ => bail!("Unknown target: {}", s),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum BuildProfile {
Dev,
Release,
Profiling,
}
#[derive(Debug, Args)]
#[command(allow_hyphen_values = true, trailing_var_arg = true)]
pub struct BuildOptions {
#[clap()]
pub path: Option<PathBuf>,
#[clap(long = "scope", short = 's')]
pub scope: Option<String>,
#[clap(long = "mode", short = 'm', default_value = "normal")]
pub mode: InstallMode,
#[clap(long = "no-typescript")]
pub disable_dts: bool,
#[clap(long = "weak-refs")]
pub weak_refs: bool,
#[clap(long = "reference-types")]
pub reference_types: bool,
#[clap(long = "target", short = 't', default_value = "bundler")]
pub target: Target,
#[clap(long = "debug")]
pub debug: bool,
#[clap(long = "dev")]
pub dev: bool,
#[clap(long = "release")]
pub release: bool,
#[clap(long = "profiling")]
pub profiling: bool,
#[clap(long = "out-dir", short = 'd', default_value = "pkg")]
pub out_dir: String,
#[clap(long = "out-name")]
pub out_name: Option<String>,
#[clap(long = "no-pack", alias = "no-package")]
pub no_pack: bool,
#[clap(long = "no-opt", alias = "no-optimization")]
pub no_opt: bool,
pub extra_options: Vec<String>,
}
impl Default for BuildOptions {
fn default() -> Self {
Self {
path: None,
scope: None,
mode: InstallMode::default(),
disable_dts: false,
weak_refs: false,
reference_types: false,
target: Target::default(),
debug: false,
dev: false,
no_pack: false,
no_opt: false,
release: false,
profiling: false,
out_dir: String::new(),
out_name: None,
extra_options: Vec::new(),
}
}
}
type BuildStep = fn(&mut Build) -> Result<()>;
impl Build {
pub fn try_from_opts(mut build_opts: BuildOptions) -> Result<Self> {
if let Some(path) = &build_opts.path {
if path.to_string_lossy().starts_with("--") {
let path = build_opts.path.take().unwrap();
build_opts
.extra_options
.insert(0, path.to_string_lossy().into_owned());
}
}
let crate_path = get_crate_path(build_opts.path)?;
let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
let out_dir = crate_path.join(PathBuf::from(build_opts.out_dir)).clean();
let dev = build_opts.dev || build_opts.debug;
let profile = match (dev, build_opts.release, build_opts.profiling) {
(false, false, false) | (false, true, false) => BuildProfile::Release,
(true, false, false) => BuildProfile::Dev,
(false, false, true) => BuildProfile::Profiling,
_ => bail!("Can only supply one of the --dev, --release, or --profiling flags"),
};
Ok(Build {
crate_path,
crate_data,
scope: build_opts.scope,
disable_dts: build_opts.disable_dts,
weak_refs: build_opts.weak_refs,
reference_types: build_opts.reference_types,
target: build_opts.target,
no_pack: build_opts.no_pack,
no_opt: build_opts.no_opt,
profile,
mode: build_opts.mode,
out_dir,
out_name: build_opts.out_name,
bindgen: None,
cache: cache::get_wasm_pack_cache()?,
extra_options: build_opts.extra_options,
})
}
pub fn set_cache(&mut self, cache: Cache) {
self.cache = cache;
}
pub fn run(&mut self) -> Result<()> {
let process_steps = Build::get_process_steps(self.mode, self.no_pack, self.no_opt);
let started = Instant::now();
for (_, process_step) in process_steps {
process_step(self)?;
}
let duration = crate::command::utils::elapsed(started.elapsed());
info!("Done in {}.", &duration);
info!(
"Your wasm pkg is ready to publish at {}.",
self.out_dir.display()
);
PBAR.info(&format!("{} Done in {}", emoji::SPARKLE, &duration));
PBAR.info(&format!(
"{} Your wasm pkg is ready to publish at {}.",
emoji::PACKAGE,
self.out_dir.display()
));
Ok(())
}
fn get_process_steps(
mode: InstallMode,
no_pack: bool,
no_opt: bool,
) -> Vec<(&'static str, BuildStep)> {
macro_rules! steps {
($($name:ident),+) => {
{
let mut steps: Vec<(&'static str, BuildStep)> = Vec::new();
$(steps.push((stringify!($name), Build::$name));)*
steps
}
};
($($name:ident,)*) => (steps![$($name),*])
}
let mut steps = Vec::new();
match &mode {
InstallMode::Force => {}
_ => {
steps.extend(steps![
step_check_rustc_version,
step_check_crate_config,
step_check_for_wasm_target,
]);
}
}
steps.extend(steps![
step_build_wasm,
step_create_dir,
step_install_wasm_bindgen,
step_run_wasm_bindgen,
]);
if !no_opt {
steps.extend(steps![step_run_wasm_opt]);
}
if !no_pack {
steps.extend(steps![
step_create_json,
step_copy_readme,
step_copy_license,
]);
}
steps
}
fn step_check_rustc_version(&mut self) -> Result<()> {
info!("Checking rustc version...");
let version = build::check_rustc_version()?;
let msg = format!("rustc version is {}.", version);
info!("{}", &msg);
Ok(())
}
fn step_check_crate_config(&mut self) -> Result<()> {
info!("Checking crate configuration...");
self.crate_data.check_crate_config()?;
info!("Crate is correctly configured.");
Ok(())
}
fn step_check_for_wasm_target(&mut self) -> Result<()> {
info!("Checking for wasm-target...");
build::wasm_target::check_for_wasm32_target()?;
info!("Checking for wasm-target was successful.");
Ok(())
}
fn step_build_wasm(&mut self) -> Result<()> {
info!("Building wasm...");
build::cargo_build_wasm(&self.crate_path, self.profile, &self.extra_options)?;
info!(
"wasm built at {:#?}.",
&self
.crate_path
.join("target")
.join("wasm32-unknown-unknown")
.join("release")
);
Ok(())
}
fn step_create_dir(&mut self) -> Result<()> {
info!("Creating a pkg directory...");
create_pkg_dir(&self.out_dir)?;
info!("Created a pkg directory at {:#?}.", &self.crate_path);
Ok(())
}
fn step_create_json(&mut self) -> Result<()> {
self.crate_data.write_package_json(
&self.out_dir,
&self.scope,
self.disable_dts,
self.target,
)?;
info!(
"Wrote a package.json at {:#?}.",
&self.out_dir.join("package.json")
);
Ok(())
}
fn step_copy_readme(&mut self) -> Result<()> {
info!("Copying readme from crate...");
readme::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
info!("Copied readme from crate to {:#?}.", &self.out_dir);
Ok(())
}
fn step_copy_license(&mut self) -> Result<()> {
info!("Copying license from crate...");
license::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
info!("Copied license from crate to {:#?}.", &self.out_dir);
Ok(())
}
fn step_install_wasm_bindgen(&mut self) -> Result<()> {
info!("Identifying wasm-bindgen dependency...");
let lockfile = Lockfile::new(&self.crate_data)?;
let bindgen_version = lockfile.require_wasm_bindgen()?;
info!("Installing wasm-bindgen-cli...");
let bindgen = install::download_prebuilt_or_cargo_install(
Tool::WasmBindgen,
&self.cache,
bindgen_version,
self.mode.install_permitted(),
)?;
self.bindgen = Some(bindgen);
info!("Installing wasm-bindgen-cli was successful.");
Ok(())
}
fn step_run_wasm_bindgen(&mut self) -> Result<()> {
info!("Building the wasm bindings...");
bindgen::wasm_bindgen_build(
&self.crate_data,
self.bindgen.as_ref().unwrap(),
&self.out_dir,
&self.out_name,
self.disable_dts,
self.weak_refs,
self.reference_types,
self.target,
self.profile,
&self.extra_options,
)?;
info!("wasm bindings were built at {:#?}.", &self.out_dir);
Ok(())
}
fn step_run_wasm_opt(&mut self) -> Result<()> {
let mut args = match self
.crate_data
.configured_profile(self.profile)
.wasm_opt_args()
{
Some(args) => args,
None => return Ok(()),
};
if self.reference_types {
args.push("--enable-reference-types".into());
}
info!("executing wasm-opt with {:?}", args);
wasm_opt::run(
&self.cache,
&self.out_dir,
&args,
self.mode.install_permitted(),
).map_err(|e| {
anyhow!(
"{}\nTo disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.", e
)
})
}
}