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 Cargo.toml
.
[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 wapm.toml
manifest.
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-Bryan/hello-world@1.0.0`
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 Cargo.toml
.
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-unknown-unknown
to
wasm32-wasi
, we’ll need to update the path appropriately. There’s also the
technicality that the source path can’t go outside the wapm.toml
file’s
directory for security reasons. This means you can’t easily publish anything in
a Cargo Workspace because the WebAssembly module will typically be
in ../target/
.
Publishing a WebAssembly Package With Cargo WAPM Link to heading
Now, let’s go through the same process with cargo wapm
.
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-Bryan/hello-world@0.1.0`
[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
workspace-aware.
When publishing a new version of our processing blocks, we will typically run
the following command from the root directory of the hotg-ai/proc-blocks
repository.
$ cargo wapm --workspace --exclude xtask --exclude hotg-rune-proc-blocks
(the hotg-rune-proc-blocks
crate just contains shared abstractions and
utilities, while xtask
is an internal tool following the xtask
pattern)
This --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.