An experimental Gradle plugin that allows you to compile .wasm files into .class files with AOT bytecode for
Chicory WebAssembly runtime, making it possible to run WebAssembly modules on JVM and Android platforms.
The plugin is based on the original Chicory AOT Maven Plugin — be sure to review its documentation for further details.
The plugin provides integration with key Gradle plugins, including JVM Plugin, Android Gradle Plugin and Kotlin Multiplatform plugin (for JVM and Android targets).
- Gradle
8.0or newer - Android Gradle Plugin
8.0.2or newer (for Android projects) - Kotlin
2.0.21or newer (when using Kotlin or Kotlin Multiplatform)
The latest release is available on Maven Central. Add the following to your plugins block:
plugins {
id("at.released.wasm2class.plugin") version "0.5.0"
}
Assume you have a helloworld.wasm file, compiled from the following C code:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}You can compile it using Emscripten:
emcc -O3 -mbulk-memory helloworld.c -o helloworld.wasmDefine the compiled WASM module in the wasm2class block of your Gradle project, along with the target package:
wasm2class {
targetPackage = "com.example.wasm"
modules {
create("helloworld") {
wasm = file("helloworld.wasm")
}
}
}This configuration generates the com.example.wasm.HelloworldModule class, which includes factory methods
for initializing a Chicory WebAssembly instance.
The following example demonstrates how to instantiate and execute the compiled module:
import com.dylibso.chicory.runtime.Instance
import com.dylibso.chicory.runtime.Store
import com.dylibso.chicory.wasi.WasiExitException
import com.dylibso.chicory.wasi.WasiOptions
import com.dylibso.chicory.wasi.WasiPreview1
private fun helloworldWasm() {
val wasiOptions = WasiOptions.builder().withStdout(System.out).withStderr(System.err).build()
WasiPreview1.builder().withOptions(wasiOptions).build().use { wasi ->
val store = Store().addFunction(*wasi.toHostFunctions())
val instance = store.instantiate("helloworld") { importValues ->
Instance.builder(HelloworldModule.load())
.withMachineFactory(HelloworldModule::create)
.withImportValues(importValues)
.withStart(false)
.build()
}
try {
instance.export("_start").apply()
} catch (ex: WasiExitException) {
if (ex.exitCode() != 0) {
throw ex
}
}
}
}For more examples, including usage with different Gradle plugins, check out the test projects
The plugin supports per-module customization, including custom target package and custom naming for generated classes.
For Android projects, the plugin supports variant-scoped configurations, allowing selective generation of WebAssembly modules for specific build types or product flavors.
Example:
androidComponents {
onVariants(selector().withName("fullPaid")) { variant ->
variant.getExtension(Wasm2ClassVariantExtension::class.java)?.apply {
modules {
create("paid") {
wasm = file("paid.wasm")
targetPackage = "com.example.full"
}
}
} ?: error("Wasm2ClassExtension extension not registered")
}
}If you're using R8 or ProGuard in your project, you may need to add the following rules:
-dontwarn com.google.errorprone.annotations.FormatMethod
-dontwarn java.lang.System$Logger$Level
-dontwarn java.lang.System$Logger
-if public final class ** { public static com.dylibso.chicory.wasm.WasmModule load(); }
-keepnames class <1>
The **Module.load() method in generated modules relies on XXXModule.class.getResourceAsStream()
with a relative path to load a stripped version of the WASM binary. Because of this, it's important to keep
the original class name and package structure intact. The final ProGuard rule ensures these classes are
neither renamed nor relocated.
Alternatively, if you manually load the stripped WASM file content instead of using the load() method,
this rule is not required.
Project has 3 test suites:
test: a suite of basic unit tests.functionalTest: a set of functional tests running on one fixed configuration of AGP, Gradle, Kotlin Multiplatform and Java versions.functionalMatrixTest: a separate set of functional tests running on different combinations of AGP, Gradle, Kotlin Multiplatform and Java versions. Not executed with thetestGradle lifecycle task.
The source code of the plugin is located in the plugin module.
Basic commands:
- Build the plugin:
./gradlew assemble - Run unit tests and basic functional tests:
./gradlew test - Running matrix tests:
./gradlew functionalMatrixTest
By default, all tests in the functionalMatrixTest suite will be executed against all compatible versions of the
Android Gradle Plugin and Gradle. You can restrict execution to specific versions by setting the following environment
variables:
TEST_GRADLE_VERSIONTEST_AGP_VERSIONTEST_JDK_VERSIONTEST_KOTLIN_VERSION
Example: TEST_GRADLE_VERSION=8.13 TEST_AGP_VERSION=8.2.0 TEST_JDK_VERSION=17 ./gradlew functionalMatrixTest
Any type of contributions are welcome. Please see the contribution guide.
Wasm2class Gradle Plugin is distributed under the terms of the Apache License (Version 2.0). See the license for more information.