1#![warn(missing_docs)]
86
87#[cfg(feature = "prebuilt-wasm-opt")]
88mod prebuilt_wasm_opt;
89
90use anyhow::{anyhow, bail, Context, Result};
91use cargo_metadata::{Metadata, MetadataCommand, Package};
92use downcast_rs::*;
93use fs_extra::dir;
94use notify::RecommendedWatcher;
95use once_cell::sync::OnceCell;
96use std::collections::{HashMap, HashSet};
97use std::fs;
98use std::io::BufReader;
99use std::iter;
100use std::iter::FromIterator;
101use std::path::{Path, PathBuf};
102#[cfg(feature = "dev-server")]
103use std::pin::Pin;
104use std::process::{Child, ChildStdout, Command, Stdio};
105use std::sync::mpsc;
106use std::time;
107use structopt::StructOpt;
108#[cfg(feature = "dev-server")]
109use tide::Server;
110
111pub use wasm_run_proc_macro::*;
112
113#[doc(hidden)]
114pub use structopt;
115
116const DEFAULT_INDEX: &str = r#"<!DOCTYPE html><html><head><meta charset="utf-8"/><script type="module">import init from "/app.js";init(new URL('app_bg.wasm', import.meta.url));</script></head><body></body></html>"#;
117
118static METADATA: OnceCell<Metadata> = OnceCell::new();
119static DEFAULT_BUILD_PATH: OnceCell<PathBuf> = OnceCell::new();
120static FRONTEND_PACKAGE: OnceCell<&Package> = OnceCell::new();
121static BACKEND_PACKAGE: OnceCell<Option<&Package>> = OnceCell::new();
122static HOOKS: OnceCell<Hooks> = OnceCell::new();
123
124#[derive(Debug, PartialEq, Clone, Copy)]
125pub enum BuildProfile {
127 Dev,
129 Release,
131 Profiling,
133}
134
135#[doc(hidden)]
137pub fn wasm_run_init(
138 pkg_name: &str,
139 backend_pkg_name: Option<&str>,
140 default_build_path: Option<Box<dyn FnOnce(&Metadata, &Package) -> PathBuf>>,
141 hooks: Hooks,
142) -> Result<(&'static Metadata, &'static Package)> {
143 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
144
145 let metadata = MetadataCommand::new()
146 .exec()
147 .context("this binary is not meant to be ran outside of its workspace")?;
148
149 METADATA
150 .set(metadata)
151 .expect("the cell is initially empty; qed");
152
153 let metadata = METADATA.get().unwrap();
154
155 let frontend_package = METADATA
156 .get()
157 .unwrap()
158 .packages
159 .iter()
160 .find(|x| x.name == pkg_name)
161 .expect("the frontend package existence has been checked during compile time; qed");
162
163 FRONTEND_PACKAGE
164 .set(frontend_package)
165 .expect("the cell is initially empty; qed");
166
167 let frontend_package = FRONTEND_PACKAGE.get().unwrap();
168
169 if let Some(name) = backend_pkg_name {
170 let backend_package = METADATA
171 .get()
172 .unwrap()
173 .packages
174 .iter()
175 .find(|x| x.name == name)
176 .expect("the backend package existence has been checked during compile time; qed");
177
178 BACKEND_PACKAGE
179 .set(Some(backend_package))
180 .expect("the cell is initially empty; qed");
181 } else {
182 BACKEND_PACKAGE
183 .set(None)
184 .expect("the cell is initially empty; qed");
185 }
186
187 DEFAULT_BUILD_PATH
188 .set(if let Some(default_build_path) = default_build_path {
189 default_build_path(metadata, frontend_package)
190 } else {
191 metadata.workspace_root.join("build")
192 })
193 .expect("the cell is initially empty; qed");
194
195 if HOOKS.set(hooks).is_err() {
196 panic!("the cell is initially empty; qed");
197 }
198
199 Ok((metadata, frontend_package))
200}
201
202#[derive(StructOpt, Debug)]
204pub struct DefaultBuildArgs {
205 #[structopt(long)]
207 pub build_path: Option<PathBuf>,
208
209 #[structopt(long)]
211 pub profiling: bool,
212}
213
214pub trait BuildArgs: Downcast {
216 fn build_path(&self) -> &PathBuf;
218
219 fn default_build_path(&self) -> &PathBuf {
221 DEFAULT_BUILD_PATH
222 .get()
223 .expect("default_build_path has been initialized on startup; qed")
224 }
225
226 fn target_path(&self) -> &PathBuf {
228 &self.metadata().target_directory
229 }
230
231 fn metadata(&self) -> &Metadata {
233 METADATA
234 .get()
235 .expect("metadata has been initialized on startup; qed")
236 }
237
238 fn frontend_package(&self) -> &Package {
240 FRONTEND_PACKAGE
241 .get()
242 .expect("frontend_package has been initialized on startup; qed")
243 }
244
245 fn backend_package(&self) -> Option<&Package> {
247 BACKEND_PACKAGE
248 .get()
249 .expect("frontend_package has been initialized on startup; qed")
250 .to_owned()
251 }
252
253 fn profiling(&self) -> bool;
255
256 #[cfg(feature = "sass")]
258 fn build_sass_from_dir(
259 &self,
260 input_dir: &std::path::Path,
261 options: sass_rs::Options,
262 ) -> Result<()> {
263 use walkdir::{DirEntry, WalkDir};
264
265 let build_path = self.build_path();
266
267 fn is_sass(entry: &DirEntry) -> bool {
268 matches!(
269 entry.path().extension().map(|x| x.to_str()).flatten(),
270 Some("sass") | Some("scss")
271 )
272 }
273
274 fn should_ignore(entry: &DirEntry) -> bool {
275 entry
276 .file_name()
277 .to_str()
278 .map(|x| x.starts_with("_"))
279 .unwrap_or(false)
280 }
281
282 log::info!("Building SASS from {:?}", input_dir);
283
284 let walker = WalkDir::new(&input_dir).into_iter();
285 for entry in walker
286 .filter_map(|x| match x {
287 Ok(x) => Some(x),
288 Err(err) => {
289 log::warn!(
290 "Could not walk into directory `{}`: {}",
291 input_dir.display(),
292 err,
293 );
294 None
295 }
296 })
297 .filter(|x| x.path().is_file() && is_sass(x) && !should_ignore(x))
298 {
299 let file_path = entry.path();
300 let css_path = build_path
301 .join(file_path.strip_prefix(&input_dir).unwrap())
302 .with_extension("css");
303
304 match sass_rs::compile_file(file_path, options.clone()) {
305 Ok(css) => {
306 let _ = fs::create_dir_all(css_path.parent().unwrap());
307 fs::write(&css_path, css).with_context(|| {
308 format!("could not write CSS to file `{}`", css_path.display())
309 })?;
310 }
311 Err(err) => bail!(
312 "could not convert SASS file `{}` to `{}`: {}",
313 file_path.display(),
314 css_path.display(),
315 err,
316 ),
317 }
318 }
319
320 Ok(())
321 }
322
323 #[cfg(feature = "sass")]
325 fn sass_lookup_directories(&self, _profile: BuildProfile) -> Vec<PathBuf> {
326 const STYLE_CANDIDATES: &[&str] = &["assets", "styles", "css", "sass"];
327
328 let package_path = self.frontend_package().manifest_path.parent().unwrap();
329
330 STYLE_CANDIDATES
331 .iter()
332 .map(|x| package_path.join(x))
333 .filter(|x| x.exists())
334 .collect()
335 }
336
337 #[cfg(feature = "sass")]
339 fn sass_options(&self, profile: BuildProfile) -> sass_rs::Options {
340 sass_rs::Options {
341 output_style: match profile {
342 BuildProfile::Release | BuildProfile::Profiling => sass_rs::OutputStyle::Compressed,
343 _ => sass_rs::OutputStyle::Nested,
344 },
345 ..sass_rs::Options::default()
346 }
347 }
348
349 fn run(self) -> Result<PathBuf>
351 where
352 Self: Sized + 'static,
353 {
354 let hooks = HOOKS.get().expect("wasm_run_init() has not been called");
355 build(BuildProfile::Release, &self, hooks)?;
356 Ok(self.build_path().to_owned())
357 }
358}
359
360impl_downcast!(BuildArgs);
361
362impl BuildArgs for DefaultBuildArgs {
363 fn build_path(&self) -> &PathBuf {
364 self.build_path
365 .as_ref()
366 .unwrap_or_else(|| self.default_build_path())
367 }
368
369 fn profiling(&self) -> bool {
370 self.profiling
371 }
372}
373
374#[derive(StructOpt, Debug)]
376pub struct DefaultServeArgs {
377 #[structopt(long)]
379 pub log: bool,
380
381 #[structopt(long, short = "h", default_value = "127.0.0.1")]
385 pub ip: String,
386
387 #[structopt(long, short = "p", default_value = "3000")]
389 pub port: u16,
390
391 #[structopt(flatten)]
393 pub build_args: DefaultBuildArgs,
394}
395
396pub trait ServeArgs: Downcast + Send {
398 #[cfg(feature = "dev-server")]
400 fn log(&self) -> bool;
401
402 #[cfg(feature = "dev-server")]
406 fn ip(&self) -> &str;
407
408 #[cfg(feature = "dev-server")]
410 fn port(&self) -> u16;
411
412 fn build_args(&self) -> &dyn BuildArgs;
414
415 fn run(self) -> Result<()>
417 where
418 Self: Sync + Sized + 'static,
419 {
420 let hooks = HOOKS.get().expect("wasm_run_init() has not been called");
421 build(BuildProfile::Dev, self.build_args(), hooks)?;
424 #[cfg(feature = "dev-server")]
425 {
426 async_std::task::block_on(async {
427 let t1 = async_std::task::spawn(serve_frontend(&self, hooks)?);
428 let t2 = async_std::task::spawn_blocking(move || watch_frontend(&self, hooks));
429 futures::try_join!(t1, t2)?;
430 Err(anyhow!("server and watcher unexpectedly exited"))
431 })
432 }
433 #[cfg(not(feature = "dev-server"))]
434 {
435 use std::sync::Arc;
436 use std::thread;
437
438 if self.build_args().backend_package().is_none() {
439 bail!("missing backend crate name");
440 }
441
442 let args = Arc::new(self);
443 let t1 = {
444 let args = Arc::clone(&args);
445 thread::spawn(move || watch_frontend(&*args, hooks))
446 };
447 let t2 = thread::spawn(move || watch_backend(&*args, hooks));
448 let _ = t1.join();
449 let _ = t2.join();
450
451 Err(anyhow!("server and watcher unexpectedly exited"))
452 }
453 }
454}
455
456impl_downcast!(ServeArgs);
457
458impl ServeArgs for DefaultServeArgs {
459 #[cfg(feature = "dev-server")]
460 fn log(&self) -> bool {
461 self.log
462 }
463
464 #[cfg(feature = "dev-server")]
465 fn ip(&self) -> &str {
466 &self.ip
467 }
468
469 #[cfg(feature = "dev-server")]
470 fn port(&self) -> u16 {
471 self.port
472 }
473
474 fn build_args(&self) -> &dyn BuildArgs {
475 &self.build_args
476 }
477}
478
479pub struct Hooks {
486 pub pre_build:
490 Box<dyn Fn(&dyn BuildArgs, BuildProfile, &mut Command) -> Result<()> + Send + Sync>,
491
492 #[allow(clippy::type_complexity)]
495 pub post_build:
496 Box<dyn Fn(&dyn BuildArgs, BuildProfile, String, Vec<u8>) -> Result<()> + Send + Sync>,
497
498 #[cfg(feature = "dev-server")]
501 #[allow(clippy::type_complexity)]
502 pub serve: Box<dyn Fn(&dyn ServeArgs, &mut Server<()>) -> Result<()> + Send + Sync>,
503
504 pub frontend_watch:
508 Box<dyn Fn(&dyn ServeArgs, &mut RecommendedWatcher) -> Result<()> + Send + Sync>,
509
510 pub backend_watch:
514 Box<dyn Fn(&dyn ServeArgs, &mut RecommendedWatcher) -> Result<()> + Send + Sync>,
515
516 pub backend_command: Box<dyn Fn(&dyn ServeArgs, &mut Command) -> Result<()> + Send + Sync>,
521}
522
523impl Default for Hooks {
524 fn default() -> Self {
525 Self {
526 backend_command: Box::new(|args, command| {
527 command.args(&[
528 "run",
529 "-p",
530 &args
531 .build_args()
532 .backend_package()
533 .context("missing backend crate name")?
534 .name,
535 ]);
536 Ok(())
537 }),
538 backend_watch: Box::new(|args, watcher| {
539 use notify::{RecursiveMode, Watcher};
540
541 let metadata = args.build_args().metadata();
542 let backend = args
543 .build_args()
544 .backend_package()
545 .context("missing backend crate name")?;
546 let packages: HashMap<_, _> = metadata
547 .packages
548 .iter()
549 .map(|x| (x.name.as_str(), x))
550 .collect();
551 let members: HashSet<_> = HashSet::from_iter(&metadata.workspace_members);
552
553 backend
554 .dependencies
555 .iter()
556 .map(|x| packages.get(x.name.as_str()).unwrap())
557 .filter(|x| members.contains(&x.id))
558 .map(|x| x.manifest_path.parent().unwrap())
559 .chain(iter::once(backend.manifest_path.parent().unwrap()))
560 .try_for_each(|x| watcher.watch(x, RecursiveMode::Recursive))?;
561
562 Ok(())
563 }),
564 frontend_watch: Box::new(|args, watcher| {
565 use notify::{RecursiveMode, Watcher};
566
567 let metadata = args.build_args().metadata();
568 let frontend = args.build_args().frontend_package();
569 let packages: HashMap<_, _> = metadata
570 .packages
571 .iter()
572 .map(|x| (x.name.as_str(), x))
573 .collect();
574 let members: HashSet<_> = HashSet::from_iter(&metadata.workspace_members);
575
576 frontend
577 .dependencies
578 .iter()
579 .filter_map(|x| packages.get(x.name.as_str()))
580 .filter(|x| members.contains(&x.id))
581 .map(|x| x.manifest_path.parent().unwrap())
582 .chain(iter::once(frontend.manifest_path.parent().unwrap()))
583 .try_for_each(|x| watcher.watch(x, RecursiveMode::Recursive))?;
584
585 Ok(())
586 }),
587 pre_build: Box::new(|_, _, _| Ok(())),
588 post_build: Box::new(
589 |args, #[allow(unused_variables)] profile, wasm_js, wasm_bin| {
590 let build_path = args.build_path();
591 let wasm_js_path = build_path.join("app.js");
592 let wasm_bin_path = build_path.join("app_bg.wasm");
593
594 fs::write(&wasm_js_path, wasm_js).with_context(|| {
595 format!("could not write JS file to `{}`", wasm_js_path.display())
596 })?;
597 fs::write(&wasm_bin_path, wasm_bin).with_context(|| {
598 format!("could not write WASM file to `{}`", wasm_bin_path.display())
599 })?;
600
601 let index_path = build_path.join("index.html");
602 let static_dir = args
603 .frontend_package()
604 .manifest_path
605 .parent()
606 .unwrap()
607 .join("static");
608
609 if index_path.exists() {
610 fs::copy("index.html", &index_path).context(format!(
611 "could not copy index.html to `{}`",
612 index_path.display()
613 ))?;
614 } else if static_dir.exists() {
615 dir::copy(
616 &static_dir,
617 &build_path,
618 &dir::CopyOptions {
619 content_only: true,
620 ..dir::CopyOptions::new()
621 },
622 )
623 .with_context(|| {
624 format!(
625 "could not copy content of directory static: `{}` to `{}`",
626 static_dir.display(),
627 build_path.display()
628 )
629 })?;
630 } else {
631 fs::write(&index_path, DEFAULT_INDEX).with_context(|| {
632 format!(
633 "could not write default index.html to `{}`",
634 index_path.display()
635 )
636 })?;
637 }
638
639 #[cfg(feature = "sass")]
640 {
641 let options = args.sass_options(profile);
642 for style_path in args.sass_lookup_directories(profile) {
643 args.build_sass_from_dir(&style_path, options.clone())?;
644 }
645 }
646
647 Ok(())
648 },
649 ),
650 #[cfg(feature = "dev-server")]
651 serve: Box::new(|args, server| {
652 use tide::{Body, Request, Response};
653
654 let build_path = args.build_args().build_path().to_owned();
655 let index_path = build_path.join("index.html");
656
657 server.at("/").serve_dir(args.build_args().build_path())?;
658 server.at("/").get(move |_| {
659 let index_path = index_path.clone();
660 async move { Ok(Response::from(Body::from_file(index_path).await?)) }
661 });
662 server.at("/*path").get(move |req: Request<()>| {
663 let build_path = build_path.clone();
664 async move {
665 match Body::from_file(build_path.join(req.param("path").unwrap())).await {
666 Ok(body) => Ok(Response::from(body)),
667 Err(_) => Ok(Response::from(
668 Body::from_file(build_path.join("index.html")).await?,
669 )),
670 }
671 }
672 });
673
674 Ok(())
675 }),
676 }
677 }
678}
679
680fn build(mut profile: BuildProfile, args: &dyn BuildArgs, hooks: &Hooks) -> Result<()> {
681 use wasm_bindgen_cli_support::Bindgen;
682
683 if args.profiling() {
684 profile = BuildProfile::Profiling;
685 }
686
687 let frontend_package = args.frontend_package();
688
689 let build_path = args.build_path();
690 let _ = fs::remove_dir_all(build_path);
691 fs::create_dir_all(build_path).with_context(|| {
692 format!(
693 "could not create build directory `{}`",
694 build_path.display()
695 )
696 })?;
697
698 let mut command = Command::new("cargo");
699
700 command
701 .args(&[
702 "build",
703 "--lib",
704 "--target",
705 "wasm32-unknown-unknown",
706 "--manifest-path",
707 ])
708 .arg(&frontend_package.manifest_path)
709 .args(match profile {
710 BuildProfile::Profiling => &["--release"] as &[&str],
711 BuildProfile::Release => &["--release"],
712 BuildProfile::Dev => &[],
713 });
714
715 log::info!("Running pre-build hook");
716 (hooks.pre_build)(args, profile, &mut command)?;
717
718 log::info!("Building frontend");
719 let status = command.status().context("could not start build process")?;
720
721 if !status.success() {
722 if let Some(code) = status.code() {
723 bail!("build process exit with code {}", code);
724 } else {
725 bail!("build process has been terminated by a signal");
726 }
727 }
728
729 let wasm_path = args
730 .target_path()
731 .join("wasm32-unknown-unknown")
732 .join(match profile {
733 BuildProfile::Profiling => "release",
734 BuildProfile::Release => "release",
735 BuildProfile::Dev => "debug",
736 })
737 .join(frontend_package.name.replace("-", "_"))
738 .with_extension("wasm");
739
740 let mut output = Bindgen::new()
741 .input_path(wasm_path)
742 .out_name("app")
743 .web(true)
744 .expect("fails only if multiple modes specified; qed")
745 .debug(!matches!(profile, BuildProfile::Release))
746 .generate_output()
747 .context("could not generate WASM bindgen file")?;
748
749 let wasm_js = output.js().to_owned();
750 let wasm_bin = output.wasm_mut().emit_wasm();
751
752 let wasm_bin = match profile {
753 BuildProfile::Profiling => wasm_opt(wasm_bin, 0, 2, true, args.target_path())?,
754 BuildProfile::Release => wasm_opt(wasm_bin, 1, 2, false, args.target_path())?,
755 BuildProfile::Dev => wasm_bin,
756 };
757
758 log::info!("Running post-build hook");
759 (hooks.post_build)(args, profile, wasm_js, wasm_bin)?;
760
761 Ok(())
762}
763
764#[cfg(feature = "dev-server")]
765fn serve_frontend(
766 args: &dyn ServeArgs,
767 hooks: &Hooks,
768) -> Result<Pin<Box<impl std::future::Future<Output = Result<()>> + Send + 'static>>> {
769 use futures::TryFutureExt;
770
771 if args.log() {
772 tide::log::start();
773 }
774 let mut app = tide::new();
775
776 (hooks.serve)(args, &mut app)?;
777
778 log::info!(
779 "Development server started: http://{}:{}",
780 args.ip(),
781 args.port()
782 );
783
784 Ok(Box::pin(
785 app.listen(format!("{}:{}", args.ip(), args.port()))
786 .map_err(Into::into),
787 ))
788}
789
790#[cfg(not(feature = "dev-server"))]
791fn watch_backend(args: &dyn ServeArgs, hooks: &Hooks) -> Result<()> {
792 let (tx, rx) = mpsc::channel();
793
794 let mut watcher: RecommendedWatcher = notify::Watcher::new(tx, time::Duration::from_secs(2))
795 .context("could not initialize watcher")?;
796
797 (hooks.backend_watch)(args, &mut watcher)?;
798
799 struct BackgroundProcess(std::process::Child);
800
801 impl Drop for BackgroundProcess {
802 fn drop(&mut self) {
803 let _ = self.0.kill();
805 let _ = self.0.wait();
806 }
807 }
808
809 let run_server = || -> Result<BackgroundProcess> {
810 let mut command = Command::new("cargo");
811 (hooks.backend_command)(args, &mut command)?;
812 Ok(command.spawn().map(BackgroundProcess)?)
813 };
814
815 let mut process_guard = Some(run_server()?);
816
817 watch_loop(args, rx, || {
818 drop(process_guard.take());
819 process_guard.replace(run_server()?);
820 Ok(())
821 });
822}
823
824fn watch_frontend(args: &dyn ServeArgs, hooks: &Hooks) -> Result<()> {
825 let (tx, rx) = mpsc::channel();
826
827 let mut watcher: RecommendedWatcher = notify::Watcher::new(tx, time::Duration::from_secs(2))
828 .context("could not initialize watcher")?;
829
830 (hooks.frontend_watch)(args, &mut watcher)?;
831
832 let build_args = args.build_args();
833
834 watch_loop(args, rx, || build(BuildProfile::Dev, build_args, hooks));
835}
836
837fn watch_loop(
838 args: &dyn ServeArgs,
839 rx: mpsc::Receiver<notify::DebouncedEvent>,
840 mut callback: impl FnMut() -> Result<()>,
841) -> ! {
842 loop {
843 use notify::DebouncedEvent::*;
844
845 let message = rx.recv();
846 match &message {
847 Ok(Create(path)) | Ok(Write(path)) | Ok(Remove(path)) | Ok(Rename(_, path))
848 if !path.starts_with(args.build_args().build_path())
849 && !path.starts_with(args.build_args().target_path())
850 && !path
851 .file_name()
852 .and_then(|x| x.to_str())
853 .map(|x| x.starts_with('.'))
854 .unwrap_or(false) =>
855 {
856 if let Err(err) = callback() {
857 log::error!("{}", err);
858 }
859 }
860 Ok(_) => {}
861 Err(e) => log::error!("Watch error: {}", e),
862 }
863 }
864}
865
866#[allow(unused_variables, unreachable_code)]
867fn wasm_opt(
868 binary: Vec<u8>,
869 shrink_level: u32,
870 optimization_level: u32,
871 debug_info: bool,
872 target_path: impl AsRef<Path>,
873) -> Result<Vec<u8>> {
874 #[cfg(feature = "binaryen")]
875 return match binaryen::Module::read(&binary) {
876 Ok(mut module) => {
877 module.optimize(&binaryen::CodegenConfig {
878 shrink_level,
879 optimization_level,
880 debug_info,
881 });
882 Ok(module.write())
883 }
884 Err(()) => bail!("could not load WASM module"),
885 };
886
887 #[cfg(feature = "prebuilt-wasm-opt")]
888 return {
889 let wasm_opt = prebuilt_wasm_opt::install_wasm_opt(target_path)?;
890
891 let mut command = Command::new(&wasm_opt);
892 command
893 .stderr(Stdio::inherit())
894 .args(&["-o", "-", "-O"])
895 .args(&["-ol", &optimization_level.to_string()])
896 .args(&["-s", &shrink_level.to_string()]);
897 if debug_info {
898 command.arg("-g");
899 }
900
901 #[cfg(target_os = "macos")]
902 {
903 command.env("DYLD_LIBRARY_PATH", wasm_opt.parent().unwrap());
904 }
905
906 #[cfg(windows)]
907 let delete_guard = {
908 use std::io::Write;
909
910 let tmp = tempfile::NamedTempFile::new()?;
911 tmp.as_file().write_all(&binary)?;
912 command.arg(tmp.path());
913 tmp
914 };
915
916 #[cfg(unix)]
917 {
918 use std::io::{Seek, SeekFrom, Write};
919
920 let mut file = tempfile::tempfile()?;
921 file.write_all(&binary)?;
922 file.seek(SeekFrom::Start(0))?;
923 command.stdin(file);
924 }
925
926 let output = command.output()?;
927 if !output.status.success() {
928 bail!("command `wasm-opt` failed.");
929 }
930 Ok(output.stdout)
931 };
932
933 log::warn!("No optimization has been done on the WASM");
934 Ok(binary)
935}
936
937pub trait PackageExt {
940 fn cargo(&self, builder: impl FnOnce(&mut Command)) -> Result<CargoChild>;
943}
944
945impl PackageExt for Package {
946 fn cargo(&self, builder: impl FnOnce(&mut Command)) -> Result<CargoChild> {
947 let mut command = Command::new("cargo");
948 command
949 .current_dir(self.manifest_path.parent().unwrap())
950 .stdout(Stdio::piped());
951
952 builder(&mut command);
953
954 Ok(CargoChild(command.spawn()?))
955 }
956}
957
958impl PackageExt for Metadata {
959 fn cargo(&self, builder: impl FnOnce(&mut Command)) -> Result<CargoChild> {
960 let mut command = Command::new("cargo");
961 command
962 .current_dir(&self.workspace_root)
963 .stdout(Stdio::piped());
964
965 builder(&mut command);
966
967 Ok(CargoChild(command.spawn()?))
968 }
969}
970
971pub struct CargoChild(Child);
975
976impl CargoChild {
977 pub fn wait_success(&mut self) -> Result<()> {
980 let status = self.0.wait()?;
981
982 if let Some(code) = status.code() {
983 if !status.success() {
984 bail!("cargo exited with status: {}", code)
985 }
986 }
987
988 if !status.success() {
989 bail!("cargo exited with error")
990 }
991
992 Ok(())
993 }
994
995 pub fn iter(&mut self) -> cargo_metadata::MessageIter<BufReader<ChildStdout>> {
998 let reader = BufReader::new(self.0.stdout.take().unwrap());
999 cargo_metadata::Message::parse_stream(reader)
1000 }
1001}
1002
1003impl Drop for CargoChild {
1004 fn drop(&mut self) {
1005 let _ = self.0.kill();
1006 let _ = self.0.wait();
1007 }
1008}
1009
1010pub mod prelude {
1019 pub use wasm_run_proc_macro::*;
1020
1021 pub use anyhow;
1022 #[cfg(feature = "dev-server")]
1023 pub use async_std;
1024 pub use cargo_metadata;
1025 pub use cargo_metadata::{Message, Metadata, Package};
1026 pub use fs_extra;
1027 #[cfg(feature = "dev-server")]
1028 pub use futures;
1029 pub use notify;
1030 pub use notify::RecommendedWatcher;
1031 #[cfg(feature = "sass")]
1032 pub use sass_rs;
1033 #[cfg(feature = "dev-server")]
1034 pub use tide;
1035 #[cfg(feature = "dev-server")]
1036 pub use tide::Server;
1037
1038 pub use super::{
1039 BuildArgs, BuildProfile, CargoChild, DefaultBuildArgs, DefaultServeArgs, Hooks, PackageExt,
1040 ServeArgs,
1041 };
1042}