Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a wasmtime::component::bindgen! macro #5317

Merged
merged 13 commits into from
Dec 6, 2022
Merged
54 changes: 54 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ wasi-tokio = { path = "crates/wasi-common/tokio", version = "=5.0.0" }
wasi-cap-std-sync = { path = "crates/wasi-common/cap-std-sync", version = "=5.0.0" }
wasmtime-fuzzing = { path = "crates/fuzzing" }
wasmtime-jit-icache-coherence = { path = "crates/jit-icache-coherence", version = "=5.0.0" }
wasmtime-wit-bindgen = { path = "crates/wit-bindgen", version = "=5.0.0" }

cranelift-wasm = { path = "cranelift/wasm", version = "0.92.0" }
cranelift-codegen = { path = "cranelift/codegen", version = "0.92.0" }
Expand Down Expand Up @@ -162,6 +163,7 @@ wasmprinter = "0.2.44"
wasm-encoder = "0.20.0"
wasm-smith = "0.11.9"
wasm-mutate = "0.2.12"
wit-parser = "0.3"
windows-sys = "0.42.0"
env_logger = "0.9"
rustix = "0.36.0"
Expand All @@ -179,6 +181,7 @@ tracing = "0.1.26"
bitflags = "1.2"
thiserror = "1.0.15"
async-trait = "0.1.42"
heck = "0.4"

[features]
default = [
Expand Down
12 changes: 12 additions & 0 deletions crates/component-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,24 @@ edition.workspace = true

[lib]
proc-macro = true
test = false
doctest = false

[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "1.0", features = ["extra-traits"] }
wasmtime-component-util = { workspace = true }
wasmtime-wit-bindgen = { workspace = true }
wit-parser = { workspace = true }

[badges]
maintenance = { status = "actively-developed" }

[dev-dependencies]
wasmtime = { path = '../wasmtime', features = ['component-model'] }
component-macro-test-helpers = { path = 'test-helpers' }
tracing = { workspace = true }

[features]
async = []
137 changes: 137 additions & 0 deletions crates/component-macro/src/bindgen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use proc_macro2::{Span, TokenStream};
use std::path::{Path, PathBuf};
use syn::parse::{Error, Parse, ParseStream, Result};
use syn::punctuated::Punctuated;
use syn::token;
use syn::Token;
use wasmtime_wit_bindgen::Opts;
use wit_parser::World;

#[derive(Default)]
pub struct Config {
opts: Opts, // ...
world: World,
files: Vec<String>,
}

pub fn expand(input: &Config) -> Result<TokenStream> {
if !cfg!(feature = "async") && input.opts.async_ {
return Err(Error::new(
Span::call_site(),
"cannot enable async bindings unless `async` crate feature is active",
));
}

let src = input.opts.generate(&input.world);
let mut contents = src.parse::<TokenStream>().unwrap();

// Include a dummy `include_str!` for any files we read so rustc knows that
// we depend on the contents of those files.
let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap();
for file in input.files.iter() {
contents.extend(
format!(
"const _: &str = include_str!(r#\"{}\"#);\n",
Path::new(&cwd).join(file).display()
)
.parse::<TokenStream>()
.unwrap(),
);
}

Ok(contents)
}

impl Parse for Config {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let call_site = Span::call_site();
let mut world = None;
let mut ret = Config::default();

if input.peek(token::Brace) {
let content;
syn::braced!(content in input);
let fields = Punctuated::<Opt, Token![,]>::parse_terminated(&content)?;
for field in fields.into_pairs() {
match field.into_value() {
Opt::Path(path) => {
if world.is_some() {
return Err(Error::new(path.span(), "cannot specify second world"));
}
world = Some(ret.parse(path)?);
}
Opt::Inline(span, w) => {
if world.is_some() {
return Err(Error::new(span, "cannot specify second world"));
}
world = Some(w);
}
Opt::Tracing(val) => ret.opts.tracing = val,
Opt::Async(val) => ret.opts.async_ = val,
}
}
} else {
let s = input.parse::<syn::LitStr>()?;
world = Some(ret.parse(s)?);
}
ret.world = world.ok_or_else(|| {
Error::new(
call_site,
"must specify a `*.wit` file to generate bindings for",
)
})?;
Ok(ret)
}
}

impl Config {
fn parse(&mut self, path: syn::LitStr) -> Result<World> {
let span = path.span();
let path = path.value();
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let path = manifest_dir.join(path);
self.files.push(path.to_str().unwrap().to_string());
World::parse_file(path).map_err(|e| Error::new(span, e))
}
}

mod kw {
syn::custom_keyword!(path);
syn::custom_keyword!(inline);
syn::custom_keyword!(tracing);
}

enum Opt {
Path(syn::LitStr),
Inline(Span, World),
Tracing(bool),
Async(bool),
}

impl Parse for Opt {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let l = input.lookahead1();
if l.peek(kw::path) {
input.parse::<kw::path>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Path(input.parse()?))
} else if l.peek(kw::inline) {
let span = input.parse::<kw::inline>()?.span;
input.parse::<Token![:]>()?;
let s = input.parse::<syn::LitStr>()?;
let world =
World::parse("<macro-input>", &s.value()).map_err(|e| Error::new(s.span(), e))?;
Ok(Opt::Inline(span, world))
} else if l.peek(kw::tracing) {
input.parse::<kw::tracing>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Tracing(input.parse::<syn::LitBool>()?.value))
} else if l.peek(Token![async]) {
input.parse::<Token![async]>()?;
input.parse::<Token![:]>()?;
Ok(Opt::Async(input.parse::<syn::LitBool>()?.value))
} else {
Err(l.error())
}
}
}
Loading