diff --git a/Cargo.lock b/Cargo.lock index 6ebb638..873d928 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,14 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "component-init" +version = "0.1.0" +dependencies = [ + "component-init-macro", + "wit-bindgen", +] + [[package]] name = "component-init-cli" version = "0.1.0" @@ -363,6 +371,14 @@ dependencies = [ "tokio", ] +[[package]] +name = "component-init-macro" +version = "0.1.0" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "component-init-transform" version = "0.1.0" @@ -1740,6 +1756,7 @@ dependencies = [ name = "test-programs" version = "0.1.0" dependencies = [ + "component-init", "wit-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 0f0e211..f9f4adf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["cli", "transform", "wasmtime", "test-programs", "test-programs/artifacts"] +members = [ + "cli", + "guest-rust", + "guest-rust/macro", + "test-programs", + "test-programs/artifacts", + "transform", + "wasmtime", +] resolver = "3" [workspace.package] @@ -12,9 +20,13 @@ repository = "https://github.com/dicej/component-init" anyhow = "1.0.89" async-trait = "0.1.83" clap = { version = "4", features = ["derive"] } +component-init = { version = "0.1.0", path = "./guest-rust" } +component-init-macro = { version = "0.1.0", path = "./guest-rust/macro" } component-init-transform = { version = "0.1.0", path = "./transform" } component-init-wasmtime = { version = "0.1.0", path = "./wasmtime" } futures = "0.3.31" +quote = "1.0" +syn = "2.0" test-programs = { path = "./test-programs" } test-programs-artifacts = { path = "./test-programs/artifacts" } tokio = { version = "1", features = ["full"] } diff --git a/guest-rust/Cargo.toml b/guest-rust/Cargo.toml new file mode 100644 index 0000000..4ad9cc9 --- /dev/null +++ b/guest-rust/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "component-init" +version.workspace = true +edition.workspace = true + +[dependencies] +component-init-macro.workspace = true +wit-bindgen.workspace = true diff --git a/guest-rust/macro/Cargo.toml b/guest-rust/macro/Cargo.toml new file mode 100644 index 0000000..b856c16 --- /dev/null +++ b/guest-rust/macro/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "component-init-macro" +version.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +syn = { workspace = true, features = ["full"] } +quote.workspace = true diff --git a/guest-rust/macro/src/lib.rs b/guest-rust/macro/src/lib.rs new file mode 100644 index 0000000..9eadd30 --- /dev/null +++ b/guest-rust/macro/src/lib.rs @@ -0,0 +1,45 @@ +use proc_macro::TokenStream; +use quote::{quote, quote_spanned}; +use syn::{ItemFn, parse_macro_input, spanned::Spanned}; + +#[proc_macro_attribute] +pub fn init(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as ItemFn); + if input.sig.asyncness.is_some() { + return quote_spanned! { input.sig.fn_token.span()=> + compile_error!("fn cannot be async"); + } + .into(); + } + if !input.sig.inputs.is_empty() { + return quote_spanned! { input.sig.inputs.span()=> + compile_error!("function cannot have arguments"); + } + .into(); + } + let callee = &input.sig.ident; + + quote! { + #input + + mod __component_init { + component_init::__bindgen::generate!({ + inline: r" + package this:wit; + world w { + export component-init: func(); + } + ", + runtime_path: "component_init::__bindgen::rt", + }); + struct __S; + impl Guest for __S { + fn component_init() { + super::#callee() + } + } + export!(__S); + } + } + .into() +} diff --git a/guest-rust/src/lib.rs b/guest-rust/src/lib.rs new file mode 100644 index 0000000..2bf8fc7 --- /dev/null +++ b/guest-rust/src/lib.rs @@ -0,0 +1,6 @@ +pub use component_init_macro::init; + +#[doc(hidden)] +pub mod __bindgen { + pub use wit_bindgen::*; +} diff --git a/test-programs/Cargo.toml b/test-programs/Cargo.toml index a106af2..2373369 100644 --- a/test-programs/Cargo.toml +++ b/test-programs/Cargo.toml @@ -5,4 +5,5 @@ edition.workspace = true publish = false [dependencies] +component-init.workspace = true wit-bindgen.workspace = true diff --git a/test-programs/src/bin/test.rs b/test-programs/src/bin/raw.rs similarity index 100% rename from test-programs/src/bin/test.rs rename to test-programs/src/bin/raw.rs diff --git a/test-programs/src/bin/using_macro.rs b/test-programs/src/bin/using_macro.rs new file mode 100644 index 0000000..880e53c --- /dev/null +++ b/test-programs/src/bin/using_macro.rs @@ -0,0 +1,14 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +static IS_INITIALIZED: AtomicBool = AtomicBool::new(false); + +#[component_init::init] +fn init() { + let before = IS_INITIALIZED.swap(true, Ordering::Relaxed); + assert!(!before, "component should only be initialized once"); +} + +fn main() { + let initialized = IS_INITIALIZED.load(Ordering::Relaxed); + assert!(initialized, "component was not initialized") +} diff --git a/wasmtime/tests/rust.rs b/wasmtime/tests/rust.rs index 46016d4..d734359 100644 --- a/wasmtime/tests/rust.rs +++ b/wasmtime/tests/rust.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result, anyhow}; -use test_programs_artifacts::TEST; +use test_programs_artifacts::{RAW, USING_MACRO}; async fn execute(component: &[u8]) -> Result<()> { use wasmtime::{ @@ -66,14 +66,9 @@ async fn execute(component: &[u8]) -> Result<()> { Ok(()) } -#[tokio::test] -async fn init_rust() -> Result<()> { - println!("test component: {TEST:?}"); - - let component = std::fs::read(TEST)?; - +async fn inits_properly(component: &[u8]) -> Result<()> { // Without initialization, run will trap. - let err = execute(&component) + let err = execute(component) .await .err() .context("uninitialized run should trap")?; @@ -82,11 +77,26 @@ async fn init_rust() -> Result<()> { "should die with an unreachable trap, got: {err:?}" ); - let initialized_component = component_init_wasmtime::initialize(&component).await?; + let initialized_component = component_init_wasmtime::initialize(component).await?; + // After initialization, will not trap. execute(&initialized_component) .await .context("execute initialized component")?; Ok(()) } + +#[tokio::test] +async fn raw() -> Result<()> { + println!("test component: {RAW:?}"); + let component = std::fs::read(RAW)?; + inits_properly(&component).await +} + +#[tokio::test] +async fn using_macro() -> Result<()> { + println!("test component: {USING_MACRO:?}"); + let component = std::fs::read(USING_MACRO)?; + inits_properly(&component).await +}