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 target_triple: String,
45 wasm_path: Option<String>,
46}
47
48#[derive(Clone, Copy, Debug)]
51pub enum Target {
52 Bundler,
55 Web,
58 Nodejs,
61 NoModules,
65 Deno,
68}
69
70impl Default for Target {
71 fn default() -> Target {
72 Target::Bundler
73 }
74}
75
76impl fmt::Display for Target {
77 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78 let s = match self {
79 Target::Bundler => "bundler",
80 Target::Web => "web",
81 Target::Nodejs => "nodejs",
82 Target::NoModules => "no-modules",
83 Target::Deno => "deno",
84 };
85 write!(f, "{}", s)
86 }
87}
88
89impl FromStr for Target {
90 type Err = Error;
91 fn from_str(s: &str) -> Result<Self> {
92 match s {
93 "bundler" | "browser" => Ok(Target::Bundler),
94 "web" => Ok(Target::Web),
95 "nodejs" => Ok(Target::Nodejs),
96 "no-modules" => Ok(Target::NoModules),
97 "deno" => Ok(Target::Deno),
98 _ => bail!("Unknown target: {}", s),
99 }
100 }
101}
102
103#[derive(Clone, Debug)]
106pub enum BuildProfile {
107 Dev,
109 Release,
111 Profiling,
113 Custom(String),
115}
116
117#[derive(Debug, Args)]
119#[command(allow_hyphen_values = true, trailing_var_arg = true)]
120pub struct BuildOptions {
121 #[clap()]
123 pub path: Option<PathBuf>,
124
125 #[clap(long = "scope", short = 's')]
127 pub scope: Option<String>,
128
129 #[clap(long = "mode", short = 'm', default_value = "normal")]
130 pub mode: InstallMode,
132
133 #[clap(long = "no-typescript")]
134 pub disable_dts: bool,
137
138 #[clap(long = "weak-refs")]
139 pub weak_refs: bool,
141
142 #[clap(long = "reference-types")]
143 pub reference_types: bool,
145
146 #[clap(long = "target", short = 't', default_value = "bundler")]
147 pub target: Target,
149
150 #[clap(long = "debug")]
151 pub debug: bool,
153
154 #[clap(long = "dev")]
155 pub dev: bool,
158
159 #[clap(long = "release")]
160 pub release: bool,
162
163 #[clap(long = "profiling")]
164 pub profiling: bool,
166
167 #[clap(long = "profile")]
168 pub profile: Option<String>,
170
171 #[clap(long = "out-dir", short = 'd', default_value = "pkg")]
172 pub out_dir: String,
174
175 #[clap(long = "out-name")]
176 pub out_name: Option<String>,
178
179 #[clap(long = "no-pack", alias = "no-package")]
180 pub no_pack: bool,
182
183 #[clap(long = "no-opt", alias = "no-optimization")]
184 pub no_opt: bool,
186
187 pub extra_options: Vec<String>,
189}
190
191impl Default for BuildOptions {
192 fn default() -> Self {
193 Self {
194 path: None,
195 scope: None,
196 mode: InstallMode::default(),
197 disable_dts: false,
198 weak_refs: false,
199 reference_types: false,
200 target: Target::default(),
201 debug: false,
202 dev: false,
203 no_pack: false,
204 no_opt: false,
205 release: false,
206 profiling: false,
207 profile: None,
208 out_dir: String::new(),
209 out_name: None,
210 extra_options: Vec::new(),
211 }
212 }
213}
214
215type BuildStep = fn(&mut Build) -> Result<()>;
216
217impl Build {
218 pub fn try_from_opts(mut build_opts: BuildOptions) -> Result<Self> {
220 if let Some(path) = &build_opts.path {
221 if path.to_string_lossy().starts_with("--") {
222 let path = build_opts.path.take().unwrap();
223 build_opts
224 .extra_options
225 .insert(0, path.to_string_lossy().into_owned());
226 }
227 }
228 let crate_path = get_crate_path(build_opts.path)?;
229 let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
230 let out_dir = crate_path.join(PathBuf::from(build_opts.out_dir)).clean();
231
232 let dev = build_opts.dev || build_opts.debug;
233 let profile = match (
234 dev,
235 build_opts.release,
236 build_opts.profiling,
237 build_opts.profile,
238 ) {
239 (false, false, false, None) | (false, true, false, None) => BuildProfile::Release,
240 (true, false, false, None) => BuildProfile::Dev,
241 (false, false, true, None) => BuildProfile::Profiling,
242 (false, false, false, Some(profile)) => BuildProfile::Custom(profile),
243 _ => bail!("Can only supply one of the --dev, --release, --profiling, or --profile 'name' flags"),
246 };
247
248 let extra_options = build_opts.extra_options;
249
250 let target_triple = {
251 let mut extra_options_iter = extra_options.iter();
252 if extra_options_iter
253 .by_ref()
254 .any(|option| option == "--target")
255 {
256 extra_options_iter.next().map(|s| s.as_str())
257 } else {
258 None
259 }
260 .unwrap_or("wasm32-unknown-unknown")
261 };
262
263 Ok(Build {
264 crate_path,
265 crate_data,
266 scope: build_opts.scope,
267 disable_dts: build_opts.disable_dts,
268 weak_refs: build_opts.weak_refs,
269 reference_types: build_opts.reference_types,
270 target: build_opts.target,
271 no_pack: build_opts.no_pack,
272 no_opt: build_opts.no_opt,
273 profile,
274 mode: build_opts.mode,
275 out_dir,
276 out_name: build_opts.out_name,
277 bindgen: None,
278 cache: cache::get_wasm_pack_cache()?,
279 target_triple: target_triple.to_owned(),
280 extra_options,
281 wasm_path: None,
282 })
283 }
284
285 pub fn set_cache(&mut self, cache: Cache) {
287 self.cache = cache;
288 }
289
290 pub fn run(&mut self) -> Result<()> {
292 let process_steps = Build::get_process_steps(self.mode, self.no_pack, self.no_opt);
293
294 let started = Instant::now();
295
296 for (_, process_step) in process_steps {
297 process_step(self)?;
298 }
299
300 let duration = crate::command::utils::elapsed(started.elapsed());
301 info!("Done in {}.", &duration);
302 info!(
303 "Your wasm pkg is ready to publish at {}.",
304 self.out_dir.display()
305 );
306
307 PBAR.info(&format!("{} Done in {}", emoji::SPARKLE, &duration));
308
309 PBAR.info(&format!(
310 "{} Your wasm pkg is ready to publish at {}.",
311 emoji::PACKAGE,
312 self.out_dir.display()
313 ));
314 Ok(())
315 }
316
317 fn get_process_steps(
318 mode: InstallMode,
319 no_pack: bool,
320 no_opt: bool,
321 ) -> Vec<(&'static str, BuildStep)> {
322 macro_rules! steps {
323 ($($name:ident),+) => {
324 {
325 let mut steps: Vec<(&'static str, BuildStep)> = Vec::new();
326 $(steps.push((stringify!($name), Build::$name));)*
327 steps
328 }
329 };
330 ($($name:ident,)*) => (steps![$($name),*])
331 }
332 let mut steps = Vec::new();
333 match &mode {
334 InstallMode::Force => {}
335 _ => {
336 steps.extend(steps![
337 step_check_rustc_version,
338 step_check_crate_config,
339 step_check_for_wasm_target,
340 ]);
341 }
342 }
343
344 steps.extend(steps![
345 step_build_wasm,
346 step_create_dir,
347 step_install_wasm_bindgen,
348 step_run_wasm_bindgen,
349 ]);
350
351 if !no_opt {
352 steps.extend(steps![step_run_wasm_opt]);
353 }
354
355 if !no_pack {
356 steps.extend(steps![
357 step_create_json,
358 step_copy_readme,
359 step_copy_license,
360 ]);
361 }
362
363 steps
364 }
365
366 fn step_check_rustc_version(&mut self) -> Result<()> {
367 info!("Checking rustc version...");
368 let version = build::check_rustc_version()?;
369 let msg = format!("rustc version is {}.", version);
370 info!("{}", &msg);
371 Ok(())
372 }
373
374 fn step_check_crate_config(&mut self) -> Result<()> {
375 info!("Checking crate configuration...");
376 self.crate_data.check_crate_config()?;
377 info!("Crate is correctly configured.");
378 Ok(())
379 }
380
381 fn step_check_for_wasm_target(&mut self) -> Result<()> {
382 info!("Checking for wasm-target...");
383 build::wasm_target::check_for_wasm_target(&self.target_triple)?;
384 info!("Checking for wasm-target was successful.");
385 Ok(())
386 }
387
388 fn step_build_wasm(&mut self) -> Result<()> {
389 info!("Building wasm...");
390 let wasm_path =
391 build::cargo_build_wasm(&self.crate_path, self.profile.clone(), &self.extra_options)?;
392 info!("wasm built at {wasm_path:#?}.");
393 self.wasm_path = Some(wasm_path);
394 Ok(())
395 }
396
397 fn step_create_dir(&mut self) -> Result<()> {
398 info!("Creating a pkg directory...");
399 create_pkg_dir(&self.out_dir)?;
400 info!("Created a pkg directory at {:#?}.", &self.crate_path);
401 Ok(())
402 }
403
404 fn step_create_json(&mut self) -> Result<()> {
405 self.crate_data.write_package_json(
406 &self.out_dir,
407 &self.scope,
408 self.disable_dts,
409 self.target,
410 )?;
411 info!(
412 "Wrote a package.json at {:#?}.",
413 &self.out_dir.join("package.json")
414 );
415 Ok(())
416 }
417
418 fn step_copy_readme(&mut self) -> Result<()> {
419 info!("Copying readme from crate...");
420 readme::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
421 info!("Copied readme from crate to {:#?}.", &self.out_dir);
422 Ok(())
423 }
424
425 fn step_copy_license(&mut self) -> Result<()> {
426 info!("Copying license from crate...");
427 license::copy_from_crate(&self.crate_data, &self.crate_path, &self.out_dir)?;
428 info!("Copied license from crate to {:#?}.", &self.out_dir);
429 Ok(())
430 }
431
432 fn step_install_wasm_bindgen(&mut self) -> Result<()> {
433 info!("Identifying wasm-bindgen dependency...");
434 let lockfile = Lockfile::new(&self.crate_data)?;
435 let bindgen_version = lockfile.require_wasm_bindgen()?;
436 info!("Installing wasm-bindgen-cli...");
437 let bindgen = install::download_prebuilt_or_cargo_install(
438 Tool::WasmBindgen,
439 &self.cache,
440 bindgen_version,
441 self.mode.install_permitted(),
442 )?;
443 self.bindgen = Some(bindgen);
444 info!("Installing wasm-bindgen-cli was successful.");
445 Ok(())
446 }
447
448 fn step_run_wasm_bindgen(&mut self) -> Result<()> {
449 info!("Building the wasm bindings...");
450 bindgen::wasm_bindgen_build(
451 self.wasm_path.as_ref().unwrap(),
452 &self.crate_data,
453 self.bindgen.as_ref().unwrap(),
454 &self.out_dir,
455 &self.out_name,
456 self.disable_dts,
457 self.weak_refs,
458 self.reference_types,
459 self.target,
460 self.profile.clone(),
461 )?;
462 info!("wasm bindings were built at {:#?}.", &self.out_dir);
463 Ok(())
464 }
465
466 fn step_run_wasm_opt(&mut self) -> Result<()> {
467 let mut args = match self
468 .crate_data
469 .configured_profile(self.profile.clone())
470 .wasm_opt_args()
471 {
472 Some(args) => args,
473 None => return Ok(()),
474 };
475 if self.reference_types {
476 args.push("--enable-reference-types".into());
477 }
478 info!("executing wasm-opt with {:?}", args);
479 wasm_opt::run(
480 &self.cache,
481 &self.out_dir,
482 &args,
483 self.mode.install_permitted(),
484 ).map_err(|e| {
485 anyhow!(
486 "{}\nTo disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.", e
487 )
488 })
489 }
490}