Aaron Roth
Aaron Roth

Reputation: 51

How to embed a wasm artifact generated by one crate in a cargo workspace in a rust binary generated by another crate in the same workspace?

I have a cargo workspace organized like this:

Cargo.toml
|-foo
| |-Cargo.toml
|  -src
|   |-main.rs
|-foo-runtime
| |-Cargo.toml
|  -src
|   |-lib.rs
|-target

main.rs has code somewhere that looks like

use std::sync::LazyLock;

use wasmer::{imports, Instance, Module, Store};

static RUNTIME_WASM: &[u8] = include_bytes!(???);

static INSTANCE: LazyLock<wasmer::Instance> = LazyLock::new(|| {
    let mut store = Store::default();
    let module = Module::from_binary(&store, RUNTIME_WASM)
        .unwrap_or_else(|e| panic!("couldn't load WASM module: {e}"));
    let import_object = imports! {};
    Instance::new(&mut store, &module, &import_object)
        .unwrap_or_else(|e| panic!("failed to create wasmer Instance: {e}"))
});

while lib.rs has code that looks like

#[no_mangle]
pub extern fn add_i64(a: i64, b: i64) -> i64 {
    a + b
}

foo-runtime/Cargo.toml looks like

cargo-features = ["per-package-target"]

[package]
default-target = "wasm32-unknown-unknown"

[lib]
crate-type = ["cdylib"]

so that cargo build on the foo-runtime crate produces target/wasm32-unknown-unknown/debug/foo_runtime.wasm. So far so good.

Now I want the rust binary crate foo to depend on the foo-runtime crate and in particular be able to include foo_runtime.wasm at compile-time, possibly using include_bytes!() as above. Loading at runtime would also be fine, but the questions in either case are (1) how do I correctly construct the path to foo_runtime.wasm during compile- (or run-) time in main.rs; and (2) how do I trigger all the necessary rebuilds when something in the dependencies changes?

I had thought the bindeps cargo experimental feature was the answer, but adding this to foo's Cargo.toml

[dependencies]
foo-runtime = { path = "../foo-runtime", artifact = "cdylib", target = "wasm32-unknown-unknown" }

doesn't work because this only causes cargo to compile a .so/.dylib shared library, not a .wasm binary, although it places it exactly where include_bytes!() would find it. (And I think it also correctly manages dependency rebuilds.)

Upvotes: 5

Views: 577

Answers (1)

capveg
capveg

Reputation: 107

I have the same issue and I've somewhat solved it with calling 'wasm-pack' from the build.rs in foo, e.g., :

$ cat build.rs

use std::path::Path;
use std::process::Command;

fn main() {
    let dir = "<your-wasmdir-here>"; // update to your directory
    println!("cargo:rerun-if-changed={}/", dir);
    // don't write into the targets directory for now - figure that out later
    // let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&dir).join("pkg");
    let output = Command::new("wasm-pack")
        .args(&["build", "--target", "web"])
        .arg(dir)
        .output()
        .expect("To build wasm files successfully");

    if !output.status.success() {
        panic!(
            "Error while compiling:\n{}",
            String::from_utf8_lossy(&output.stdout)
        );
    }

    let js_file = dest_path.join("web_client.js");
    let wasm_file = dest_path.join("web_client_bg.wasm");

    for file in &[&js_file, &wasm_file] {
        let file = std::fs::metadata(file).expect("file to exist");
        assert!(file.is_file());
    }

    println!("cargo:rustc-env=PROJECT_NAME_JS={}", js_file.display());
    println!("cargo:rustc-env=PROJECT_NAME_WASM={}", wasm_file.display());

}

The benefit of calling wasm-pack is in addition to building the .wasm binary, it also does the javascript wrapping magic which I can't seem to get cargo to do natively. Let me know if this works for you or if you found something better.

Upvotes: 1

Related Questions