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