Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a536d873e6 |
@@ -265,6 +265,11 @@ The reply is `{"type": "built", "wasm": "/actor/cargo/public/builds/<n>/<name>.w
|
||||
or `{"type": "build_failed", "errors": "...", "tests": {...}}`. Pass
|
||||
`wasm`, `source`, and `built_by` on to `spawn` or `upgrade`.
|
||||
|
||||
Build scripts can compile C for `wasm32-wasip2`: the host sets
|
||||
`CC_wasm32_wasip2` and `AR_wasm32_wasip2` to a WASI clang with wasi-libc, so
|
||||
the `cc` crate (for example `rusqlite` with `bundled`) works. Its headers are
|
||||
WASI 0.1: C code that needs sockets does not build.
|
||||
|
||||
A Rust actor depends on the SDK with `sausage-actor = "0.1"`. `cargo` points
|
||||
that dependency at the SDK of the host, which it publishes at
|
||||
`/actor/cargo/public/sdk/`.
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
//! The broker knows nothing about Rust. This actor is one way to get a
|
||||
//! `.wasm` file. Other actors can add other languages.
|
||||
//!
|
||||
//! Build scripts compile C for WASI with the clang in `CC_wasm32_wasip2`
|
||||
//! and `AR_wasm32_wasip2`. The Nix package and the dev shell set both.
|
||||
//!
|
||||
//! Actors depend on the SDK with `sausage-actor = "0.1"`. The SDK is not
|
||||
//! published: this actor copies the SDK of this host to `public/sdk/` (so
|
||||
//! that every actor can read it), and points the dependency there in the
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "cargo-c"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
sausage-actor = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,5 @@
|
||||
// Compile C with the `cc` crate: natively for the unit test, and with the
|
||||
// WASI clang (`CC_wasm32_wasip2`) for the actor.
|
||||
fn main() {
|
||||
cc::Build::new().file("src/add.c").compile("add");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
int c_add(int a, int b) { return a + b; }
|
||||
@@ -0,0 +1,25 @@
|
||||
//! Adds two numbers with a C function: `{"type": "add", "a", "b"}` -> `{"type": "sum", "value"}`.
|
||||
use sausage_actor::{json, messages, reply};
|
||||
|
||||
unsafe extern "C" {
|
||||
fn c_add(a: i32, b: i32) -> i32;
|
||||
}
|
||||
|
||||
fn add(a: i32, b: i32) -> i32 {
|
||||
unsafe { c_add(a, b) }
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for message in messages() {
|
||||
let (a, b) = (message.body["a"].as_i64().unwrap_or(0), message.body["b"].as_i64().unwrap_or(0));
|
||||
let _ = reply(&message, json!({"type": "sum", "value": add(a as i32, b as i32)}));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn c_code_runs_on_the_host_target() {
|
||||
assert_eq!(super::add(2, 3), 5);
|
||||
}
|
||||
}
|
||||
@@ -769,6 +769,22 @@ async fn cargo_actor_runs_unit_tests_by_default_and_can_skip_them() {
|
||||
host.stop().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cargo_actor_compiles_c_code_for_wasi() {
|
||||
let world = create_world("cargo-c");
|
||||
let (host, mut mailbox) = start(&world).await;
|
||||
let source = cargo_source("cargo-c");
|
||||
let id = host.broker.send("console", Some("cargo"), None, None, json!({"type": "build", "source": source.display().to_string()}));
|
||||
let built = receive_reply(&mut mailbox, &id, Duration::from_secs(300)).await;
|
||||
assert_eq!(built.body["type"], "built", "{built:?}");
|
||||
assert_eq!(built.body["tests"]["passed"], 1, "{built:?}");
|
||||
let wasm = host.world.resolve("console", built.body["wasm"].as_str().unwrap()).unwrap();
|
||||
spawn_type(&host, &mut mailbox, "c-add", &wasm, &source).await;
|
||||
let sum = ask_type(&host, &mut mailbox, "c-add", json!({"type": "add", "a": 2, "b": 3}), "sum").await;
|
||||
assert_eq!(sum.body["value"], 5);
|
||||
host.stop().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_invalid_actor_ids() {
|
||||
let world = create_world("invalid-id");
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
# PATH. Keep this same runtime path in the packaged host and exercise
|
||||
# the installed binary below with PATH deliberately empty.
|
||||
runtimePath = pkgs.lib.makeBinPath [ rust pkgs.stdenv.cc ];
|
||||
# A WASI clang with wasi-libc, so that build scripts (the `cc` crate)
|
||||
# can compile C for wasm32-wasip2. Its headers are WASI 0.1: C code
|
||||
# that needs sockets does not build.
|
||||
wasiCc = pkgs.pkgsCross.wasi32.stdenv.cc;
|
||||
wasiCcEnv = {
|
||||
CC_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}cc";
|
||||
AR_wasm32_wasip2 = "${wasiCc}/bin/${wasiCc.targetPrefix}ar";
|
||||
};
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
|
||||
common = {
|
||||
pname = "sausage";
|
||||
@@ -48,9 +56,12 @@
|
||||
cargoExtraArgs = "--locked -p sausage";
|
||||
doCheck = false;
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
# The `cargo` actor runs cargo at runtime. Build scripts need a C linker.
|
||||
# The `cargo` actor runs cargo at runtime. Build scripts need a C linker,
|
||||
# and C code for WASI needs the WASI clang.
|
||||
postInstall = ''
|
||||
wrapProgram $out/bin/sausage --prefix PATH : ${runtimePath}
|
||||
wrapProgram $out/bin/sausage --prefix PATH : ${runtimePath} \
|
||||
--set-default CC_wasm32_wasip2 ${wasiCcEnv.CC_wasm32_wasip2} \
|
||||
--set-default AR_wasm32_wasip2 ${wasiCcEnv.AR_wasm32_wasip2}
|
||||
'';
|
||||
meta.mainProgram = "sausage";
|
||||
});
|
||||
@@ -84,7 +95,7 @@
|
||||
# The binary the host loop runs (`nix run`), built from the same
|
||||
# cached dependencies as the tests.
|
||||
checks.package = sausage;
|
||||
checks.tests = craneLib.cargoTest (common // {
|
||||
checks.tests = craneLib.cargoTest (common // wasiCcEnv // {
|
||||
inherit cargoArtifacts;
|
||||
cargoExtraArgs = "--locked ${testPackages}";
|
||||
# Nothing builds on the test run's target folder: don't store it.
|
||||
@@ -114,7 +125,35 @@
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
TOML
|
||||
printf 'fn main() {}\n' > "$source/src/main.rs"
|
||||
# C code for WASI, compiled with the packaged WASI clang. The native
|
||||
# test build has no C, so it needs no C compiler for the host.
|
||||
printf 'int probe_add(int a, int b) { return a + b; }\n' > "$source/src/add.c"
|
||||
cat > "$source/build.rs" <<'RUST'
|
||||
use std::process::Command;
|
||||
fn main() {
|
||||
if std::env::var("TARGET").unwrap() != "wasm32-wasip2" {
|
||||
return;
|
||||
}
|
||||
let out = std::env::var("OUT_DIR").unwrap();
|
||||
let cc = std::env::var("CC_wasm32_wasip2").expect("CC_wasm32_wasip2");
|
||||
let ar = std::env::var("AR_wasm32_wasip2").expect("AR_wasm32_wasip2");
|
||||
let object = format!("{out}/add.o");
|
||||
assert!(Command::new(cc).args(["-c", "src/add.c", "-o", &object]).status().unwrap().success());
|
||||
assert!(Command::new(ar).args(["crs", &format!("{out}/libadd.a"), &object]).status().unwrap().success());
|
||||
println!("cargo:rustc-link-search=native={out}");
|
||||
println!("cargo:rustc-link-lib=static=add");
|
||||
}
|
||||
RUST
|
||||
cat > "$source/src/main.rs" <<'RUST'
|
||||
#[cfg(target_os = "wasi")]
|
||||
unsafe extern "C" {
|
||||
fn probe_add(a: i32, b: i32) -> i32;
|
||||
}
|
||||
fn main() {
|
||||
#[cfg(target_os = "wasi")]
|
||||
assert_eq!(unsafe { probe_add(2, 3) }, 5);
|
||||
}
|
||||
RUST
|
||||
cat > "$source/tests/probe.rs" <<'RUST'
|
||||
#[test]
|
||||
fn native_test_harness_runs() {
|
||||
@@ -161,11 +200,11 @@
|
||||
apps.default = { type = "app"; program = "${sausage}/bin/sausage"; };
|
||||
apps.loop = { type = "app"; program = "${loop}/bin/sausage-loop"; };
|
||||
|
||||
devShells.default = pkgs.mkShell {
|
||||
devShells.default = pkgs.mkShell (wasiCcEnv // {
|
||||
packages = [ rust pkgs.wasmtime pkgs.wasm-tools ];
|
||||
shellHook = ''
|
||||
export CARGO_HOME="$PWD/.cargo-home"
|
||||
'';
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user