feat(runtime): add wasmtime-runtime feature gate for real Wasm execution

Cargo.toml:
- New feature: wasmtime-runtime = [dep:wasmtime, dep:wasmtime-wasi]
- Default is off so the normal build remains lightweight.
- wasmtime 30 and wasmtime-wasi 30 added as optional dependencies.

src/main.rs:
- run_module(wasm_path) replaces the inline stub.
- cfg(not(feature = wasmtime-runtime)): prints READY and returns.
  Preserves all existing test and development behaviour unchanged.
- cfg(feature = wasmtime-runtime): creates a Wasmtime Engine + Module,
  builds a WASI linker with inherited stdout/stderr, prints READY, then
  instantiates the module and calls _start.
  READY is printed before _start so weft-appd can record the session as
  Running before the app enters its event loop.

The production service binary is built with:
  cargo build -p weft-runtime --release --features wasmtime-runtime
This commit is contained in:
Marco Allegretti 2026-03-11 10:26:41 +01:00
parent 5cff1f4412
commit 6d88104f28
2 changed files with 44 additions and 6 deletions

View file

@ -8,7 +8,13 @@ rust-version.workspace = true
name = "weft-runtime"
path = "src/main.rs"
[features]
default = []
wasmtime-runtime = ["dep:wasmtime", "dep:wasmtime-wasi"]
[dependencies]
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
wasmtime = { version = "30", optional = true }
wasmtime-wasi = { version = "30", optional = true }

View file

@ -29,12 +29,8 @@ fn main() -> anyhow::Result<()> {
anyhow::bail!("app.wasm not found at {}", wasm_path.display());
}
// TODO: Load wasm_path into a Wasmtime Engine and run the module.
// Until Wasmtime is integrated, print READY and exit cleanly so that
// weft-appd can complete the session lifecycle in tests and development.
tracing::info!(session_id, %app_id, wasm = %wasm_path.display(), "Wasmtime integration pending");
println!("READY");
tracing::info!(session_id, %app_id, wasm = %wasm_path.display(), "executing module");
run_module(&wasm_path)?;
tracing::info!(session_id, %app_id, "exiting");
Ok(())
@ -51,6 +47,42 @@ fn resolve_package(app_id: &str) -> anyhow::Result<PathBuf> {
anyhow::bail!("package '{}' not found in any package store", app_id)
}
#[cfg(not(feature = "wasmtime-runtime"))]
fn run_module(_wasm_path: &std::path::Path) -> anyhow::Result<()> {
println!("READY");
Ok(())
}
#[cfg(feature = "wasmtime-runtime")]
fn run_module(wasm_path: &std::path::Path) -> anyhow::Result<()> {
use anyhow::Context as _;
use wasmtime::{Config, Engine, Module, Store};
let engine = Engine::new(&Config::default()).context("create Wasmtime engine")?;
let module = Module::from_file(&engine, wasm_path).context("load Wasm module")?;
let mut linker: wasmtime::Linker<wasmtime_wasi::WasiCtx> = wasmtime::Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).context("add WASI to linker")?;
let wasi = wasmtime_wasi::WasiCtxBuilder::new()
.inherit_stdout()
.inherit_stderr()
.build();
let mut store = Store::new(&engine, wasi);
println!("READY");
let instance = linker
.instantiate(&mut store, &module)
.context("instantiate module")?;
let start = instance
.get_typed_func::<(), ()>(&mut store, "_start")
.context("get _start export")?;
start.call(&mut store, ()).context("call _start")?;
Ok(())
}
fn package_store_roots() -> Vec<PathBuf> {
if let Ok(explicit) = std::env::var("WEFT_APP_STORE") {
return vec![PathBuf::from(explicit)];