1use crate::bindgen;
2use crate::build;
3use crate::install::{self, InstallMode, Tool};
4use crate::js_bin::WasmJsWriter;
5use crate::lockfile::Lockfile;
6use crate::manifest;
7use crate::utils::*;
8use crate::wasm_opt;
9use crate::PBAR;
10
11use anyhow::{anyhow, bail, Result};
12use binary_install::Cache;
13use clap::Args;
14use log::info;
15use path_clean::PathClean;
16use std::fs;
17use std::fs::File;
18use std::io::BufWriter;
19use std::io::Write;
20use std::path::Path;
21use std::path::PathBuf;
22use std::time::Instant;
23
24pub struct Build {
26 pub crate_path: PathBuf,
27 pub crate_data: manifest::CrateData,
28 pub weak_refs: bool,
29 pub reference_types: bool,
30 pub no_opt: bool,
31 pub profile: BuildProfile,
32 pub mode: InstallMode,
33 pub out_dir: PathBuf,
34 pub out_name: Option<String>,
35 pub bindgen: Option<install::Status>,
36 pub cache: Cache,
37 pub extra_options: Vec<String>,
38}
39
40#[derive(Clone, Debug)]
43pub enum BuildProfile {
44 Dev,
46 Release,
48 Profiling,
50 Custom(String),
52}
53
54#[derive(Debug, Args)]
56#[command(allow_hyphen_values = true, trailing_var_arg = true)]
57pub struct BuildOptions {
58 #[clap()]
60 pub path: Option<PathBuf>,
61
62 #[clap(long = "mode", short = 'm', default_value = "normal")]
63 pub mode: InstallMode,
65
66 #[clap(long = "weak-refs")]
67 pub weak_refs: bool,
69
70 #[clap(long = "reference-types")]
71 pub reference_types: bool,
73
74 #[clap(long = "debug")]
75 pub debug: bool,
77
78 #[clap(long = "dev")]
79 pub dev: bool,
82
83 #[clap(long = "release")]
84 pub release: bool,
86
87 #[clap(long = "profiling")]
88 pub profiling: bool,
90
91 #[clap(long = "profile")]
92 pub profile: Option<String>,
94
95 #[clap(long = "out-dir", short = 'd', default_value = "dist")]
96 pub out_dir: String,
98
99 #[clap(long = "out-name")]
100 pub out_name: Option<String>,
102
103 #[clap(long = "no-opt", alias = "no-optimization")]
104 pub no_opt: bool,
106
107 pub extra_options: Vec<String>,
109}
110
111impl Default for BuildOptions {
112 fn default() -> Self {
113 Self {
114 path: None,
115 mode: InstallMode::default(),
116 weak_refs: false,
117 reference_types: false,
118 debug: false,
119 dev: false,
120 no_opt: false,
121 release: false,
122 profiling: false,
123 profile: None,
124 out_dir: String::new(),
125 out_name: None,
126 extra_options: Vec::new(),
127 }
128 }
129}
130
131impl Build {
132 pub fn try_from_opts(args: &crate::Cli, build_opts: &BuildOptions) -> Result<Self> {
134 let mut extra_options: Vec<String> = build_opts.extra_options.clone();
135 let mut path_arg = build_opts.path.clone();
136 if let Some(path) = &build_opts.path {
137 if path.to_string_lossy().starts_with("--") {
138 let path = path_arg.take().unwrap();
139 extra_options.insert(0, path.to_string_lossy().into_owned());
140 }
141 }
142 let crate_path = get_crate_path(path_arg)?;
143 let crate_data = manifest::CrateData::new(&crate_path, build_opts.out_name.clone())?;
144 let out_dir = crate_path.join(PathBuf::from(&build_opts.out_dir)).clean();
145
146 let dev = build_opts.dev || build_opts.debug;
147 let profile = match (
148 dev,
149 build_opts.release,
150 build_opts.profiling,
151 &build_opts.profile,
152 ) {
153 (false, false, false, None) | (false, true, false, None) => BuildProfile::Release,
154 (true, false, false, None) => BuildProfile::Dev,
155 (false, false, true, None) => BuildProfile::Profiling,
156 (false, false, false, Some(profile)) => BuildProfile::Custom(profile.clone()),
157 _ => bail!("Can only supply one of the --dev, --release, --profiling, or --profile 'name' flags"),
160 };
161
162 Ok(Build {
163 crate_path,
164 crate_data,
165 weak_refs: build_opts.weak_refs,
166 reference_types: build_opts.reference_types,
167 no_opt: build_opts.no_opt,
168 profile,
169 mode: build_opts.mode,
170 out_dir,
171 out_name: build_opts.out_name.clone(),
172 bindgen: None,
173 cache: get_install_cache(&args.install_cache)?,
174 extra_options: extra_options,
175 })
176 }
177
178 pub fn set_cache(&mut self, cache: Cache) {
180 self.cache = cache;
181 }
182
183 pub fn run(&mut self) -> Result<()> {
185 let started = Instant::now();
186
187 if self.mode != InstallMode::Force {
188 self.step_check_rustc_version()?;
189 self.step_check_crate_config()?;
190 self.step_check_for_wasm_target()?;
191 }
192
193 self.step_build_wasm()?;
194 self.step_create_dir()?;
195 self.step_install_wasm_bindgen()?;
196 let temp_dir = self.step_run_wasm_bindgen()?;
197
198 if !self.no_opt {
199 self.step_run_wasm_opt()?;
200 }
201 self.step_transform_wasm(&temp_dir)?;
202
203 let duration = elapsed(started.elapsed());
204 info!("Done in {}.", &duration);
205 info!("Javascript files created in {}.", self.out_dir.display());
206
207 PBAR.info(&format!("Done in {}", &duration));
208
209 PBAR.info(&format!(
210 "Javascript files created in {}.",
211 self.out_dir.display()
212 ));
213 Ok(())
214 }
215
216 fn step_check_rustc_version(&mut self) -> Result<()> {
217 info!("Checking rustc version...");
218 let version = build::check_rustc_version()?;
219 let msg = format!("rustc version is {}.", version);
220 info!("{}", &msg);
221 Ok(())
222 }
223
224 fn step_check_crate_config(&mut self) -> Result<()> {
225 info!("Checking crate configuration...");
226 self.crate_data.check_crate_config()?;
227 info!("Crate is correctly configured.");
228 Ok(())
229 }
230
231 fn step_check_for_wasm_target(&mut self) -> Result<()> {
232 info!("Checking for wasm-target...");
233 build::wasm_target::check_for_wasm32_target()?;
234 info!("Checking for wasm-target was successful.");
235 Ok(())
236 }
237
238 fn step_build_wasm(&mut self) -> Result<()> {
239 info!("Building wasm...");
240 build::cargo_build_wasm(&self.crate_path, self.profile.clone(), &self.extra_options)?;
241
242 info!(
243 "wasm built at {:#?}.",
244 &self
245 .crate_path
246 .join("target")
247 .join("wasm32-unknown-unknown")
248 .join("release")
249 );
250 Ok(())
251 }
252
253 fn step_create_dir(&mut self) -> Result<()> {
254 info!("Creating a dist directory...");
255 create_output_dir(&self.out_dir)?;
256 info!("Created a dist directory at {:#?}.", &self.crate_path);
257 Ok(())
258 }
259
260 fn step_install_wasm_bindgen(&mut self) -> Result<()> {
261 info!("Identifying wasm-bindgen dependency...");
262 let lockfile = Lockfile::new(&self.crate_data)?;
263 let bindgen_version = lockfile.require_wasm_bindgen()?;
264 info!("Installing wasm-bindgen-cli...");
265 let bindgen = install::download_prebuilt_or_cargo_install(
266 Tool::WasmBindgen,
267 &self.cache,
268 bindgen_version,
269 self.mode.install_permitted(),
270 )?;
271 self.bindgen = Some(bindgen);
272 info!("Installing wasm-bindgen-cli was successful.");
273 Ok(())
274 }
275
276 fn step_run_wasm_bindgen(&mut self) -> Result<PathBuf> {
277 info!("Building the wasm bindings...");
278 let temp_dir = bindgen::wasm_bindgen_build(
279 &self.crate_data,
280 self.bindgen.as_ref().unwrap(),
281 &self.out_name,
282 self.weak_refs,
283 self.reference_types,
284 self.profile.clone(),
285 &self.extra_options,
286 )?;
287 info!("wasm bindings were built at {:#?}.", &temp_dir);
288 Ok(temp_dir)
289 }
290
291 fn step_run_wasm_opt(&mut self) -> Result<()> {
292 let mut args = match self
293 .crate_data
294 .configured_profile(self.profile.clone())
295 .wasm_opt_args()
296 {
297 Some(args) => args,
298 None => return Ok(()),
299 };
300 if self.reference_types {
301 args.push("--enable-reference-types".into());
302 }
303 info!("executing wasm-opt with {:?}", args);
304 wasm_opt::run(
305 &self.cache,
306 &self.out_dir,
307 &args,
308 self.mode.install_permitted(),
309 ).map_err(|e| {
310 anyhow!(
311 "{}\nTo disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.", e
312 )
313 })
314 }
315
316 fn step_transform_wasm(&mut self, temp_dir: &Path) -> Result<()> {
317 let name_prefix = self.crate_data.name_prefix();
318 let wasm_filename = format!("{}_bg.wasm", name_prefix);
319 let imports_filename = format!("{}_bg.js", name_prefix);
320 let types_filename = format!("{}.d.ts", name_prefix);
321 let module_filename = format!("{}.js", name_prefix);
322 let imports_module = format!("./{}", imports_filename);
323 {
325 let mut outfile = File::create(self.out_dir.join(module_filename))?;
326 {
327 let mut outbw = BufWriter::new(&mut outfile);
328 let mut wasm_writer = WasmJsWriter::new(&mut outbw, &imports_module);
329 let input_path = temp_dir.join(wasm_filename);
330 read_and_compress(&mut wasm_writer, &input_path)?;
331 wasm_writer.flush()?;
332 }
333 outfile.sync_all()?;
334 }
335 fs::copy(
336 temp_dir.join(&imports_filename),
337 self.out_dir.join(&imports_filename),
338 )?;
339 {
341 let types_text = fs::read(temp_dir.join(&types_filename))?;
342 let mut outfile = File::create(self.out_dir.join(&types_filename))?;
343 {
344 let mut outbw = BufWriter::new(&mut outfile);
345 outbw.write_all(
346 "/* tslint:disable */\n/* eslint-disable */\ndeclare namespace WasmDecls {\n"
347 .to_os_bytes()
348 .as_ref(),
349 )?;
350 outbw.write_all(&types_text)?;
351 outbw.write_all(
352 "\n}\nexport type WasmExports = typeof WasmDecls;\nexport function getWasm(): Promise<WasmExports>;\n"
353 .to_os_bytes()
354 .as_ref(),
355 )?;
356 outbw.flush()?;
357 }
358 outfile.sync_all()?;
359 }
360
361 for file in self.out_dir.read_dir()? {
362 let file = file?;
363 let path = file.path();
364 let extension = path.extension().and_then(|s| s.to_str());
365 if extension == Some("wasm") {}
366 }
367
368 Ok(())
369 }
370}