While at Hammer of the Gods, we wrote a lot of Rust code that gets compiled to WebAssembly for use in our containerization technology, Rune.
We have over 30 different processing blocks (chunks of code that can be used in a data processing pipeline) and as you can imagine, publishing and versioning each of WebAssembly module manually isn’t practical. For that, we lean on the WebAssembly Package Manager to do all the heavy lifting.
The nice thing is that WAPM is a language-agnostic package manager, meaning it’s just as easy for Rune to run WebAssembly modules written in C++ as it is to use Rust. However, because WAPM is language-agnostic it means it’s… *ahem*… not specific to any one language.
This presents a dilemma though, because manually compiling your Rust crate to WebAssembly and packaging it in the form that WAPM expects is a bit of a pain.
Therefore, we made a simple
cargo wapm subcommand to automate
the process and we’ve spent the last two months internally dogfooding it.
The code written in this article is available on GitHub. Feel free to browse through and steal code or inspiration.
If you found this useful or spotted a bug in the article, let me know on the blog’s issue tracker!
Publishing a WebAssembly Package Manually Link to heading
The Publishing your Package page from WAPM’s documentation already does a good job of explaining the publishing process, but let’s have a look at what is involved from a Rust developer’s perspective.
First, we need to tell the Rust compiler to generate a
*.wasm file by setting
the crate-type key in our
[package] name = "hello-world" version = "0.1.0" description = "A hello world example" license = "MIT OR Apache-2.0" repository = "https://github.com/hotg-ai/proc-blocks" homepage = "https://hotg.ai/" readme = "README.md" edition = "2021" [lib] crate-type = ["cdylib", "rlib"] ...
Now we can cross-compile the crate to WebAssembly.
$ cargo build --release --target=wasm32-unknown-unknown Compiling hello-world v0.1.0 (/tmp/hello-world) Finished release [optimized] target(s) in 0.35s
Next, we need to create a
The Manifest docs go through all the fields you can set, but these are the most common ones:
[package] name = "Michael-F-Bryan/hello-world" version = "0.1.0" description = "A hello world example" license = "MIT OR Apache-2.0" repository = "https://github.com/hotg-ai/proc-blocks" homepage = "https://hotg.ai/" readme = "README.md" [[module]] name = "hello_world" source = "target/wasm32-unknown-unknown/release/hello_world.wasm" # Note: processing blocks use a Rune-specific ABI, not WASI abi = "none"
Finally, assuming you’ve already installed the wapm CLI and authenticated with WAPM, the package is ready for publishing.
$ wapm publish Successfully published package `Michael-F-Bryanfirstname.lastname@example.org`
From there, users can see the package on WAPM and use it like normal.
For those familiar with other package managers like Cargo and NPM this process won’t be surprising. However, I would like to draw your attention to something…
Except for the Michael-F-Bryan namespace, the
wapm.toml we wrote is almost
entirely copied from our
What’s worse is that whenever we update
Cargo.toml (e.g. to change the version
number), we’ll need to manually update
wapm.toml to match.
We’re also hard-coding the path to the compiled binary, so if we want to publish
a version with debug symbols or switch from
wasm32-wasi, we’ll need to update the path appropriately. There’s also the
technicality that the source path can’t go outside the
directory for security reasons. This means you can’t easily publish anything in
a Cargo Workspace because the WebAssembly module will typically be
Publishing a WebAssembly Package With Cargo WAPM Link to heading
Now, let’s go through the same process with
First, you will need to install the
cargo wapm subcommand.
$ cargo install cargo-wapm Updating crates.io index Downloaded cargo-wapm v0.1.3 ... Installed package `cargo-wapm v0.1.3` (executable `cargo-wapm`) $ cargo wapm --help Publish a crate to the WebAssembly Package Manager USAGE: wapm [OPTIONS] OPTIONS: --all-features -d, --dry-run [env: DRY_RUN=] --debug Compile in debug mode --exclude <EXCLUDE> Packages to ignore --features <FEATURES> A comma-delimited list of features to enable -h, --help Print help information --manifest-path <MANIFEST_PATH> [env: MANIFEST_PATH=] --no-default-features -w, --workspace [env: WORKSPACE=]
Next, add a
[package.metadata.wapm] section to your Cargo.toml so we can tell
cargo wapm any extra information it needs to know.
... [package.metadata.wapm] namespace = "Michael-F-Bryan" abi = "none" # This tells `cargo wapm` to compile to wasm32-unknown-unknown
Now, let’s do a dry run to see what would be published.
$ cargo wapm --dry-run 2022-06-28T11:59:29.025090Z INFO publish: cargo_wapm: Publishing dry_run=true pkg="hello-world" Successfully published package `Michael-F-Bryanemail@example.com` [INFO] Publish succeeded, but package was not published because it was run in dry-run mode 2022-06-28T11:59:29.067902Z INFO publish: cargo_wapm: Published! pkg="hello-world"
All the files that would normally be published to WAPM have been collected into a single folder.
$ tree target/wapm target/wapm └── hello-world ├── hello_world.wasm ├── README.md └── wapm.toml 1 directory, 3 files
We can also look at the generated wapm.toml.
$ cat target/wapm/hello-world/wapm.toml [package] name = "Michael-F-Bryan/hello-world" version = "0.1.0" description = "A hello world example" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hotg-ai/proc-blocks" homepage = "https://hotg.ai/" [[module]] name = "hello-world" source = "hello_world.wasm" abi = "none"
Unsurprisingly, it’s almost identical to the handwritten
wapm.toml except the
README, repository, and homepage keys are in a different order.
Publishing a WebAssembly module that should be compiled in debug mode or with certain Cargo Features enabled is just a case of adding some extra flags.
$ cargo wapm --debug --features some-feature
Something that sets
cargo wapm aside from the normal
wapm CLI is that it is
When publishing a new version of our processing blocks, we will typically run
the following command from the root directory of the
$ cargo wapm --workspace --exclude xtask --exclude hotg-rune-proc-blocks
hotg-rune-proc-blocks crate just contains shared abstractions and
xtask is an internal tool following the
--workspace flag is worth its weight in gold when you’ve got 30 different
packages to publish!
The neat part is that because the
cargo metadata command gives us
direct access to all the information Cargo discovers about a crate through its
Cargo.toml file, this slots right into the tools and conventions that Rust
developers are familiar with.
Next Steps Link to heading
The Cargo WAPM tool fits into our workflow quite well and we’re happy with it, so now we’d like you to give it a go.
We’d especially like to hear from other people in the industry that are wanting to do cool things with WebAssembly.