Add build.rs to rebuild plugins, and a test plugin

Isaac Clayton created

Change summary

Cargo.lock                       |  1 
crates/plugin_runtime/Cargo.toml |  1 
crates/plugin_runtime/build.rs   | 47 ++++++++++++++++++++++++
crates/plugin_runtime/src/lib.rs | 64 ++++++++++++++++++++++++++++-----
plugins/Cargo.lock               |  7 +++
plugins/Cargo.toml               |  2 
plugins/test_plugin/Cargo.toml   | 10 +++++
plugins/test_plugin/src/lib.rs   | 44 +++++++++++++++++++++++
8 files changed, 164 insertions(+), 12 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3717,6 +3717,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bincode",
+ "pollster",
  "serde",
  "serde_json",
  "wasi-common",

crates/plugin_runtime/build.rs 🔗

@@ -0,0 +1,47 @@
+use std::path::Path;
+
+fn main() {
+    let base = Path::new("../../plugins");
+
+    // println!("cargo:rerun-if-changed=../../plugins/*");
+    println!("cargo:warning=Rebuilding plugins...");
+
+    let _ = std::fs::remove_dir_all(base.join("bin"));
+    let _ =
+        std::fs::create_dir_all(base.join("bin")).expect("Could not make plugins bin directory");
+
+    std::process::Command::new("cargo")
+        .args([
+            "build",
+            "--release",
+            "--target",
+            "wasm32-wasi",
+            "--manifest-path",
+            base.join("Cargo.toml").to_str().unwrap(),
+        ])
+        .status()
+        .expect("Could not build plugins");
+
+    let binaries = std::fs::read_dir(base.join("target/wasm32-wasi/release"))
+        .expect("Could not find compiled plugins in target");
+    println!("cargo:warning={:?}", binaries);
+
+    for file in binaries {
+        let is_wasm = || {
+            let path = file.ok()?.path();
+            if path.extension()? == "wasm" {
+                Some(path)
+            } else {
+                None
+            }
+        };
+
+        if let Some(path) = is_wasm() {
+            std::fs::copy(&path, base.join("bin").join(path.file_name().unwrap()))
+                .expect("Could not copy compiled plugin to bin");
+        }
+    }
+
+    // TODO: create .wat versions
+    // TODO: optimize with wasm-opt
+}

crates/plugin_runtime/src/lib.rs 🔗

@@ -1,14 +1,56 @@
 pub mod wasi;
+use pollster::FutureExt as _;
 pub use wasi::*;
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-
-//     pub fn init_wasi() {
-//         let plugin = WasiPluginBuilder::new().init(todo!()).unwrap();
-//         let handle: WasiFn<u32, String> = plugin.function("hello").unwrap();
-//         let result = plugin.call(handle, 27).unwrap();
-//         assert_eq!(result, "world 27");
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    pub fn test_plugin() {
+        pub struct TestPlugin {
+            noop: WasiFn<(), ()>,
+            constant: WasiFn<(), u32>,
+            identity: WasiFn<u32, u32>,
+            add: WasiFn<(u32, u32), u32>,
+            swap: WasiFn<(u32, u32), (u32, u32)>,
+            sort: WasiFn<Vec<u32>, Vec<u32>>,
+            print: WasiFn<String, ()>,
+            // and_back: WasiFn<u32, u32>,
+        }
+
+        async {
+            let mut runtime = WasiPluginBuilder::new_with_default_ctx()
+                .unwrap()
+                .host_function("mystery_number", |input: u32| input + 7)
+                .unwrap()
+                .init(include_bytes!("../../../plugins/bin/test_plugin.wasm"))
+                .await
+                .unwrap();
+
+            let plugin = TestPlugin {
+                noop: runtime.function("noop").unwrap(),
+                constant: runtime.function("constant").unwrap(),
+                identity: runtime.function("identity").unwrap(),
+                add: runtime.function("add").unwrap(),
+                swap: runtime.function("swap").unwrap(),
+                sort: runtime.function("sort").unwrap(),
+                print: runtime.function("print").unwrap(),
+                // and_back: runtime.function("and_back").unwrap(),
+            };
+
+            let unsorted = vec![1, 3, 4, 2, 5];
+            let sorted = vec![1, 2, 3, 4, 5];
+
+            assert_eq!(runtime.call(&plugin.noop, ()).await.unwrap(), ());
+            assert_eq!(runtime.call(&plugin.constant, ()).await.unwrap(), 27);
+            assert_eq!(runtime.call(&plugin.identity, 58).await.unwrap(), 58);
+            assert_eq!(runtime.call(&plugin.add, (3, 4)).await.unwrap(), 7);
+            assert_eq!(runtime.call(&plugin.swap, (1, 2)).await.unwrap(), (2, 1));
+            assert_eq!(runtime.call(&plugin.sort, unsorted).await.unwrap(), sorted);
+            assert_eq!(runtime.call(&plugin.print, "Hi!".into()).await.unwrap(), ());
+            // assert_eq!(runtime.call(&plugin.and_back, 1).await.unwrap(), 8);
+        }
+        .block_on()
+    }
+}

plugins/Cargo.lock 🔗

@@ -112,6 +112,13 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "test_plugin"
+version = "0.1.0"
+dependencies = [
+ "plugin",
+]
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.0"

plugins/Cargo.toml 🔗

@@ -1,2 +1,2 @@
 [workspace]
-members = ["./json_language"]
+members = ["./json_language", "./test_plugin"]

plugins/test_plugin/Cargo.toml 🔗

@@ -0,0 +1,10 @@
+[package]
+name = "test_plugin"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+plugin = { path = "../../crates/plugin" }
+
+[lib]
+crate-type = ["cdylib"]

plugins/test_plugin/src/lib.rs 🔗

@@ -0,0 +1,44 @@
+use plugin::prelude::*;
+
+#[export]
+pub fn noop() {}
+
+#[export]
+pub fn constant() -> u32 {
+    27
+}
+
+#[export]
+pub fn identity(i: u32) -> u32 {
+    i
+}
+
+#[export]
+pub fn add(a: u32, b: u32) -> u32 {
+    a + b
+}
+
+#[export]
+pub fn swap(a: u32, b: u32) -> (u32, u32) {
+    (b, a)
+}
+
+#[export]
+pub fn sort(mut list: Vec<u32>) -> Vec<u32> {
+    list.sort();
+    list
+}
+
+#[export]
+pub fn print(string: String) {
+    println!("to stdout: {}", string);
+    eprintln!("to stderr: {}", string);
+}
+
+// #[import]
+// fn mystery_number(input: u32) -> u32;
+
+// #[export]
+// pub fn and_back(secret: u32) -> u32 {
+//     mystery_number(secret)
+// }