1use crate::bindgen;
4use crate::build;
5use crate::cache;
6use crate::command::utils::{create_pkg_dir, get_crate_path};
7use crate::emoji;
8use crate::install::{self, InstallMode, Tool};
9use crate::license;
10use crate::lockfile::Lockfile;
11use crate::manifest;
12use crate::readme;
13use crate::wasm_opt;
14use crate::PBAR;
15use anyhow::{anyhow, bail, Error, Result};
16use binary_install::Cache;
17use clap::Args;
18use log::info;
19use path_clean::PathClean;
20use std::fmt;
21use std::path::PathBuf;
22use std::str::FromStr;
23use std::time::Instant;
24
25#[allow(missing_docs)]
27pub struct Build {
28 pub crate_path: PathBuf,
29 pub crate_data: manifest::CrateData,
30 pub scope: Option<String>,
31 pub disable_dts: bool,
32 pub weak_refs: bool,
33 pub reference_types: bool,
34 pub target: Target,
35 pub no_pack: bool,
36 pub no_opt: bool,
37 pub profile: BuildProfile,
38 pub mode: InstallMode,
39 pub out_dir: PathBuf,
40 pub out_name: Option<String>,
41 pub bindgen: Option<install::Status>,
42 pub cache: Cache,
43 pub extra_options: Vec<String>,
44 pub panic_unwind: bool,
45 target_triple: String,
46 wasm_path: Option<String>,
47}
48
49#[derive(Clone, Copy, Debug)]
52pub enum Target {
53 Bundler,
56 Web,
59 Nodejs,
62 NoModules,
66 Deno,
69}
70
71impl Default for Target {
72 fn default() -> Target {
73 Target::Bundler
74 }
75}
76
77impl fmt::Display for Target {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 let s = match self {
80 Target::Bundler => "bundler",
81 Target::Web => "web",
82 Target::Nodejs => "nodejs",
83 Target::NoModules => "no-modules",
84 Target::Deno => "deno",
85 };
86 write!(f, "{}", s)
87 }
88}
89
90impl FromStr for Target {
91 type Err = Error;
92 fn from_str(s: &str) -> Result<Self> {
93 match s {
94 "bundler" | "browser" => Ok(Target::Bundler),
95 "web" => Ok(Target::Web),
96 "nodejs" => Ok(Target::Nodejs),
97 "no-modules" => Ok(Target::NoModules),
98 "deno" => Ok(Target::Deno),
99 _ => bail!("Unknown target: {}", s),
100 }
101 }
102}
103
104#[derive(Clone, Debug)]
107pub enum BuildProfile {
108 Dev,
110 Release,
112 Profiling,
114 Custom(String),
116}
117
118#[derive(Debug, Args)]
120#[command(allow_hyphen_values = true, trailing_var_arg = true)]
121pub struct BuildOptions {
122 #[clap()]
124 pub path: Option<PathBuf>,
125
126 #[clap(long = "scope", short = 's')]
128 pub scope: Option<String>,
129
130 #[clap(long = "mode", short = 'm', default_value = "normal")]
131 pub mode: InstallMode,
133
134 #[clap(long = "no-typescript")]
135 pub disable_dts: bool,
138
139 #[clap(long = "weak-refs")]
140 pub weak_refs: bool,
142
143 #[clap(long = "reference-types")]
144 pub reference_types: bool,
146
147 #[clap(long = "target", short = 't', default_value = "bundler")]
148 pub target: Target,
150
151 #[clap(long = "debug")]
152 pub debug: bool,
154
155 #[clap(long = "dev")]
156 pub dev: bool,
159
160 #[clap(long = "release")]
161 pub release: bool,
163
164 #[clap(long = "profiling")]
165 pub profiling: bool,
167
168 #[clap(long = "profile")]
169 pub profile: Option<String>,
171
172 #[clap(long = "out-dir", short = 'd', default_value = "pkg")]
173 pub out_dir: String,
175
176 #[clap(long = "out-name")]
177 pub out_name: Option<String>,
179
180 #[clap(long = "no-pack", alias = "no-package")]
181 pub no_pack: bool,
183
184 #[clap(long = "no-opt", alias = "no-optimization")]
185 pub no_opt: bool,
187
188 #[clap(long = "panic-unwind")]
189 pub panic_unwind: bool,
196
197 pub extra_options: Vec<String>,
199}
200
201impl Default for BuildOptions {
202 fn default() -> Self {
203 Self {
204 path: None,
205 scope: None,
206 mode: InstallMode::default(),
207 disable_dts: false,
208 weak_refs: false,
209 reference_types: false,
210 target: Target::default(),
211 debug: false,
212 dev: false,
213 no_pack: false,
214 no_opt: false,
215 release: false,
216 profiling: false,
217 profile: None,
218 out_dir: String::new(),
219 out_name: None,
220 panic_unwind: false,
221 extra_options: Vec::new(),
222 }
223 }
224}
225
226type BuildStep = fn(&mut Build) -> Result<()>;
227
228impl Build {
229 pub fn try_from_opts(mut build_opts: BuildOptions) -> Result<Self> {
231 if let Some(path) = &build_opts.path {
232 if path.to_string_lossy().starts_with("--") {
233 let path = build_opts.path.take().unwrap();
234 build_opts
235 .extra_options
236 .insert(0, path.to_string_lossy().into_owned());
237 }
238 }
239 let crate_path = get_crate_path(build_opts.path)?;
240 let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
241 let out_dir = crate_path.join(PathBuf::from(build_opts.out_dir)).clean();
242
243 let dev = build_opts.dev || build_opts.debug;
244 let profile = match (
245 dev,
246 build_opts.release,
247 build_opts.profiling,
248 build_opts.profile,
249 ) {
250 (false, false, false, None) | (false, true, false, None) => BuildProfile::Release,
251 (true, false, false, None) => BuildProfile::Dev,
252 (false, false, true, None) => BuildProfile::Profiling,
253 (false, false, false, Some(profile)) => BuildProfile::Custom(profile),
254 _ => bail!("Can only supply one of the --dev, --release, --profiling, or --profile 'name' flags"),
257 };
258
259 let extra_options = build_opts.extra_options;
260
261 let target_triple = {
262 let mut extra_options_iter = extra_options.iter();
263 if extra_options_iter
264 .by_ref()
265 .any(|option| option == "--target")
266 {
267 extra_options_iter.next().map(|s| s.as_str())
268 } else {
269 None
270 }
271 .unwrap_or("wasm32-unknown-unknown")
272 };
273
274 Ok(Build {
275 crate_path,
276 crate_data,
277 scope: build_opts.scope,
278 disable_dts: build_opts.disable_dts,
279 weak_refs: build_opts.weak_refs,
280 reference_types: build_opts.reference_types,
281 target: build_opts.target,
282 no_pack: build_opts.no_pack,
283 no_opt: build_opts.no_opt,
284 profile,
285 mode: build_opts.mode,
286 out_dir,
287 out_name: build_opts.out_name,
288 bindgen: None,
289 cache: cache::get_wasm_pack_cache()?,
290 target_triple: target_triple.to_owned(),
291 extra_options,
292 panic_unwind: build_opts.panic_unwind,
293 wasm_path: None,
294 })
295 }
296
297 pub fn set_cache(&mut self, cache: Cache) {
299 self.cache = cache;
300 }
301
302 pub fn run(&mut self) -> Result<()> {
304 let process_steps = Build::get_process_steps(self.mode, self.no_pack, self.no_opt);
305
306 let started = Instant::now();
307
308 for (_, process_step) in process_steps {
309 process_step(self)?;
310 }
311
312 let duration = crate::command::utils::elapsed(started.elapsed());
313 info!("Done in {}.", &duration);
314 info!(
315 "Your wasm pkg is ready to publish at {}.",
316 self.out_dir.display()
317 );
318
319 PBAR.info(&format!("{} Done in {}", emoji::SPARKLE, &duration));
320
321 PBAR.info(&format!(
322 "{} Your wasm pkg is ready to publish at {}.",
323 emoji::PACKAGE,
324 self.out_dir.display()
325 ));
326 Ok(())
327 }
328
329 fn get_process_steps(
330 mode: InstallMode,
331 no_pack: bool,
332 no_opt: bool,
333 ) -> Vec<(&'static str, BuildStep)> {
334 macro_rules! steps {
335 ($($name:ident),+) => {
336 {
337 let mut steps: Vec<(&'static str, BuildStep)> = Vec::new();
338 $(steps.push((stringify!($name), Build::$name));)*
339 steps
340 }
341 };
342 ($($name:ident,)*) => (steps![$($name),*])
343 }
344 let mut steps = Vec::new();
345 match &mode {
346 InstallMode::Force => {}
347 _ => {
348 steps.extend(steps![
349 step_check_rustc_version,
350 step_check_crate_config,
351 step_check_for_wasm_target,
352 ]);
353 }
354 }
355
356 steps.extend(steps![
357 step_build_wasm,
358 step_create_dir,
359 step_install_wasm_bindgen,
360 step_run_wasm_bindgen,
361 ]);
362
363 if !no_opt {
364 steps.extend(steps![step_run_wasm_opt]);
365 }
366
367 if !no_pack {
368 steps.extend(steps![
369 step_create_json,
370 step_copy_readme,
371 step_copy_license,
372 ]);
373 }
374
375 steps
376 }
377
378 fn step_check_rustc_version(&mut self) -> Result<()> {
379 if self.panic_unwind {
382 info!("Skipping rustc version check (using nightly via --panic-unwind).");
383 return Ok(());
384 }
385 info!("Checking rustc version...");
386 let version = build::check_rustc_version()?;
387 let msg = format!("rustc version is {}.", version);
388 info!("{}", &msg);
389 Ok(())
390 }
391
392 fn step_check_crate_config(&mut self) -> Result<()> {
393 info!("Checking crate configuration...");
394 self.crate_data.check_crate_config()?;
395 info!("Crate is correctly configured.");
396 Ok(())
397 }
398
399 fn step_check_for_wasm_target(&mut self) -> Result<()> {
400 if self.panic_unwind {
401 info!("Checking nightly toolchain prerequisites for panic=unwind...");
402 build::wasm_target::check_nightly_prerequisites()?;
403 info!("Nightly prerequisites check was successful.");
404 return Ok(());
405 }
406 info!("Checking for wasm-target...");
407 build::wasm_target::check_for_wasm_target(&self.target_triple)?;
408 info!("Checking for wasm-target was successful.");
409 Ok(())
410 }
411
412 fn step_build_wasm(&mut self) -> Result<()> {
413 info!("Building wasm...");
414 let wasm_path = build::cargo_build_wasm(
415 &self.crate_path,
416 self.profile.clone(),
417 &self.extra_options,
418 &self.target_triple,
419 self.panic_unwind,
420 )?;
421 info!("wasm built at {wasm_path:#?}.");
422 self.wasm_path = Some(wasm_path);
423 Ok(())
424 }
425
426 fn step_create_dir(&mut self) -> Result<()> {
427 info!("Creating a pkg directory...");
428 create_pkg_dir(&self.out_dir)?;
429 info!("Created a pkg directory at {:#?}.", &self.crate_path);
430 Ok(())
431 }
432
433 fn step_create_json(&mut self) -> Result<()> {
434 self.crate_data.write_package_json(
435 &self.out_dir,
436 &self.scope,
437 self.disable_dts,
438 self.target,
439 )?;
440 info!(
441 "Wrote a package.json at {:#?}.",
442 &self.out_dir.join("package.json")
443 );
444 Ok(())
445 }
446
447 fn step_copy_readme(&mut self) -> Result<()> {
448 info!("Copying readme from crate...");
449 readme::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
450 info!("Copied readme from crate to {:#?}.", &self.out_dir);
451 Ok(())
452 }
453
454 fn step_copy_license(&mut self) -> Result<()> {
455 info!("Copying license from crate...");
456 license::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
457 info!("Copied license from crate to {:#?}.", &self.out_dir);
458 Ok(())
459 }
460
461 fn step_install_wasm_bindgen(&mut self) -> Result<()> {
462 info!("Identifying wasm-bindgen dependency...");
463 let lockfile = Lockfile::new(&self.crate_data)?;
464 let bindgen_version = lockfile.require_wasm_bindgen()?;
465 info!("Installing wasm-bindgen-cli...");
466 let bindgen = install::download_prebuilt_or_cargo_install(
467 Tool::WasmBindgen,
468 &self.cache,
469 bindgen_version,
470 self.mode.install_permitted(),
471 )?;
472 self.bindgen = Some(bindgen);
473 info!("Installing wasm-bindgen-cli was successful.");
474 Ok(())
475 }
476
477 fn step_run_wasm_bindgen(&mut self) -> Result<()> {
478 info!("Building the wasm bindings...");
479 bindgen::wasm_bindgen_build(
480 self.wasm_path.as_ref().unwrap(),
481 &self.crate_data,
482 self.bindgen.as_ref().unwrap(),
483 &self.out_dir,
484 &self.out_name,
485 self.disable_dts,
486 self.weak_refs,
487 self.reference_types,
488 self.target,
489 self.profile.clone(),
490 )?;
491 info!("wasm bindings were built at {:#?}.", &self.out_dir);
492 Ok(())
493 }
494
495 fn step_run_wasm_opt(&mut self) -> Result<()> {
496 let mut args = match self
497 .crate_data
498 .configured_profile(self.profile.clone())
499 .wasm_opt_args()
500 {
501 Some(args) => args,
502 None => return Ok(()),
503 };
504 if self.reference_types {
505 args.push("--enable-reference-types".into());
506 }
507 if self.target_triple.starts_with("wasm64") {
508 args.push("--enable-memory64".into());
509 }
510 info!("executing wasm-opt with {:?}", args);
511 wasm_opt::run(
512 &self.cache,
513 &self.out_dir,
514 &args,
515 self.mode.install_permitted(),
516 ).map_err(|e| {
517 anyhow!(
518 "{}\nTo disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.", e
519 )
520 })
521 }
522}