embed_wasm_build/
lib.rs

1#![deny(warnings, missing_docs)]
2//! This crate provides compile-time utilities for packaging 'cargo-web' build output
3//! (rust compiled as wasm and associated html/css/etc files) inside native binaries
4//! and is meant to be invoked from custom build.rs scripts
5//!
6//! Designed for use with the [`embed-wasm` crate](https://crates.io/crates/embed-wasm).
7//! See [embed-wasm-example](https://github.com/inanna-malick/embed-wasm-example) for a full example.
8
9use cargo_web::{CargoWebOpts, DeployOpts};
10use ignore::Walk;
11use std::env;
12use std::fs;
13use std::io::{BufWriter, Write};
14use std::path::{Path, PathBuf};
15use structopt::StructOpt;
16
17/// Compile the cargo executable in the 'wasm' subdir using cargo-web and
18/// generate a static hashmap using 'phf' containing all static files
19/// output by the cargo-web build process
20pub fn compile_wasm<X: AsRef<Path>>(cargo_web_dir: X) {
21    let profile = std::env::var("PROFILE").expect("expected env var PROFILE for build.rs");
22
23    let out_dir = env::var("OUT_DIR").unwrap();
24    let dest_path = Path::new(&out_dir).join("wasm_blobs_output_dir");
25    // TODO: maybe not this? might wipe out resources, requiring extra recompile work?
26    let _ = fs::remove_dir_all(&dest_path); // may already exist, nuke if that is the case
27    fs::create_dir(&dest_path).unwrap();
28
29    println!("dest path: {:?}", &dest_path);
30
31    let current_dir = std::env::current_dir().unwrap();
32    env::set_current_dir(current_dir.join(cargo_web_dir)).unwrap();
33
34    //Build struct in DeployOpts is private so only way to create is this structopt method?
35    let opts = if profile == "release" {
36        DeployOpts::from_iter_safe(&[
37            "--release",
38            "--target=wasm32-unknown-unknown",
39            "--output",
40            dest_path.to_str().unwrap(),
41        ])
42    } else {
43        DeployOpts::from_iter_safe(&[
44            "--target=wasm32-unknown-unknown",
45            "--output",
46            dest_path.to_str().unwrap(),
47        ])
48    }
49    .expect("expected hardcoded cargo-web args to be valid");
50
51    cargo_web::run(CargoWebOpts::Deploy(opts)).unwrap();
52
53    env::set_current_dir(current_dir).unwrap();
54
55    let f_dest_path = Path::new(&out_dir).join("wasm_blobs.rs");
56    let f = fs::File::create(&f_dest_path).unwrap();
57    let mut file = BufWriter::new(f);
58
59    let blobs: Vec<(String, PathBuf)> = (0..)
60        .zip(Walk::new(dest_path.clone()))
61        .filter_map(|(idx, result)| {
62            // Each item yielded by the iterator is either a directory entry or an
63            // error, so either print the path or the error.
64            match result {
65                Ok(entry) => {
66                    if entry.metadata().unwrap().is_file() {
67                        Some((format!("ENTRY_{}", idx), entry.into_path()))
68                    } else {
69                        None
70                    }
71                }
72                Err(err) => {
73                    eprintln!("error traversing wasm directory: {}", err);
74                    None
75                }
76            }
77        })
78        .collect();
79
80    for (identifier, path) in &blobs {
81        writeln!(
82            &mut file,
83            "static {}: &'static [u8] = include_bytes!(\"{}\");",
84            identifier,
85            path.to_str().unwrap()
86        )
87        .unwrap();
88    }
89
90    let mut codegen = phf_codegen::Map::new();
91
92    let dest_path = dest_path.to_str().unwrap();
93    for (identifier, path) in &blobs {
94        let key = &path.to_str().unwrap()[dest_path.len() + 1..];
95        codegen.entry(key, identifier);
96    }
97
98    writeln!(
99        &mut file,
100        "static WASM: ::phf::Map<&'static str, &'static [u8]> =\n{};\n",
101        codegen.build()
102    )
103    .unwrap();
104
105    // register rerun-if-changed hooks for all wasm directory entries not in gitignore
106    for result in Walk::new("wasm") {
107        // Each item yielded by the iterator is either a directory entry or an
108        // error, so either print the path or the error.
109        match result {
110            Ok(entry) => {
111                println!("cargo:rerun-if-changed={}", entry.path().display());
112            }
113            Err(err) => panic!("error traversing wasm directory: {}", err),
114        }
115    }
116
117    // panic!("afaik only way to get println output from build.rs is to fail here");
118}