From 17d15b2f08b01956cc0af108271e707d4d1acde0 Mon Sep 17 00:00:00 2001 From: Isaac Clayton Date: Mon, 6 Jun 2022 08:40:56 +0200 Subject: [PATCH] Get Wasi working --- Cargo.lock | 287 +++++++++++++++++++- crates/plugin_runtime/Cargo.toml | 1 + crates/plugin_runtime/src/lib.rs | 3 + crates/plugin_runtime/src/main.rs | 1 - crates/plugin_runtime/src/wasi.rs | 194 +++++++++++++ crates/plugin_runtime/src/wasm.rs | 17 +- crates/zed/src/languages/language_plugin.rs | 11 +- plugins/json_language/src/lib.rs | 99 +++++++ script/build-plugins | 5 +- 9 files changed, 597 insertions(+), 21 deletions(-) create mode 100644 crates/plugin_runtime/src/wasi.rs diff --git a/Cargo.lock b/Cargo.lock index b774889ee2..35e88625f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ambient-authority" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" + [[package]] name = "ansi_term" version = "0.12.1" @@ -376,7 +382,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -674,6 +680,72 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "cap-fs-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54b86398b5852ddd45784b1d9b196b98beb39171821bad4b8b44534a1e87927" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "winapi 0.3.9", +] + +[[package]] +name = "cap-primitives" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" +dependencies = [ + "ambient-authority", + "errno", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "winapi 0.3.9", + "winapi-util", + "winx", +] + +[[package]] +name = "cap-rand" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3b27294116983d706f4c8168f6d10c84f9f5daed0c28bc7d0296cf16bcf971" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "ipnet", + "rustix", +] + +[[package]] +name = "cap-time-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50472b6ebc302af0401fa3fb939694cd8ff00e0d4c9182001e434fc822ab83a" +dependencies = [ + "cap-primitives", + "once_cell", + "rustix", + "winx", +] + [[package]] name = "castaway" version = "0.1.2" @@ -1826,6 +1898,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fs-set-times" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" +dependencies = [ + "io-lifetimes", + "rustix", + "winapi 0.3.9", +] + [[package]] name = "fsevent" version = "2.0.2" @@ -2244,6 +2327,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d37fb7dc756218a0559bfc21e4381f03cbb696cdaf959e7e95e927496f0564cd" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -2456,11 +2548,25 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "io-extras" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" +dependencies = [ + "io-lifetimes", + "winapi 0.3.9", +] + [[package]] name = "io-lifetimes" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" +dependencies = [ + "libc", + "winapi 0.3.9", +] [[package]] name = "iovec" @@ -2496,6 +2602,18 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +[[package]] +name = "is-terminal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c89a757e762896bdbdfadf2860d0f8b0cea5e363d8cf3e7bdfeb63d1d976352" +dependencies = [ + "hermit-abi 0.2.3", + "io-lifetimes", + "rustix", + "winapi 0.3.9", +] + [[package]] name = "isahc" version = "1.7.2" @@ -2881,6 +2999,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -3246,7 +3370,7 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", ] @@ -3639,6 +3763,7 @@ dependencies = [ "mlua", "serde", "wasmtime", + "wasmtime-wasi", ] [[package]] @@ -4361,8 +4486,10 @@ dependencies = [ "bitflags", "errno", "io-lifetimes", + "itoa", "libc", "linux-raw-sys", + "once_cell", "winapi 0.3.9", ] @@ -4803,6 +4930,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + [[package]] name = "shlex" version = "1.1.0" @@ -5191,6 +5327,22 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-interface" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e09bb3fb4e02ec4b87e182ea9718fadbc0fa3e50085b40a9af9690572b67f9e" +dependencies = [ + "atty", + "bitflags", + "cap-fs-ext", + "cap-std", + "io-lifetimes", + "rustix", + "winapi 0.3.9", + "winx", +] + [[package]] name = "target-lexicon" version = "0.12.4" @@ -6200,6 +6352,48 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi-cap-std-sync" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1029972e08194fe0ca67a83221945fff9d6d1b0dd8b752c6073b45d0254ac71b" +dependencies = [ + "anyhow", + "async-trait", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "is-terminal", + "lazy_static", + "rustix", + "system-interface", + "tracing", + "wasi-common", + "winapi 0.3.9", +] + +[[package]] +name = "wasi-common" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396cc8d920f924474f589f6a2202ad9783579ef12f96e6b675d0b2f3917c7126" +dependencies = [ + "anyhow", + "bitflags", + "cap-rand", + "cap-std", + "io-extras", + "rustix", + "thiserror", + "tracing", + "wiggle", + "winapi 0.3.9", +] + [[package]] name = "wasm-bindgen" version = "0.2.81" @@ -6468,6 +6662,28 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wasmtime-wasi" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d507b07263a4923440da662c611affc2e671714bb6e5f68f6b068a5736bcd7f" +dependencies = [ + "anyhow", + "wasi-cap-std-sync", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + [[package]] name = "wast" version = "43.0.0" @@ -6486,7 +6702,7 @@ version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b70bfff0cfaf33dc9d641196dbcd0023a2da8b4b9030c59535cb44e2884983b" dependencies = [ - "wast", + "wast 43.0.0", ] [[package]] @@ -6573,6 +6789,48 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wiggle" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e618e90d9f3d6d76943d9f2903c609b62f2ab5d16dedcff4816d38db726dd" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534d70579cd9aca243e94eeb14a348dbc9ae87e7758ffebfdd4bb4a98d59b9b0" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro2", + "quote", + "shellexpand", + "syn", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87216b1f58eeee44a6385de39e8c03190c209942cfa34344c9ba69426f146d8d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + [[package]] name = "winapi" version = "0.2.8" @@ -6668,6 +6926,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "winx" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" +dependencies = [ + "bitflags", + "io-lifetimes", + "winapi 0.3.9", +] + [[package]] name = "wio" version = "0.2.2" @@ -6677,6 +6946,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror", + "wast 35.0.2", +] + [[package]] name = "workspace" version = "0.1.0" diff --git a/crates/plugin_runtime/Cargo.toml b/crates/plugin_runtime/Cargo.toml index 5637610070..c3a99ca007 100644 --- a/crates/plugin_runtime/Cargo.toml +++ b/crates/plugin_runtime/Cargo.toml @@ -8,5 +8,6 @@ mlua = { version = "0.8.0-beta.5", features = ["lua54", "vendored", "serialize"] serde = "1.0" map-macro = "0.2" wasmtime = "0.37.0" +wasmtime-wasi = "0.37.0" anyhow = { version = "1.0", features = ["std"] } bincode = "1.3" diff --git a/crates/plugin_runtime/src/lib.rs b/crates/plugin_runtime/src/lib.rs index de6544f6fa..cac99797bf 100644 --- a/crates/plugin_runtime/src/lib.rs +++ b/crates/plugin_runtime/src/lib.rs @@ -11,3 +11,6 @@ pub use lua::*; pub mod wasm; pub use wasm::*; + +pub mod wasi; +pub use wasi::*; diff --git a/crates/plugin_runtime/src/main.rs b/crates/plugin_runtime/src/main.rs index 002e4cb08e..4819c1420a 100644 --- a/crates/plugin_runtime/src/main.rs +++ b/crates/plugin_runtime/src/main.rs @@ -8,7 +8,6 @@ pub fn main() -> anyhow::Result<()> { "../plugin/target/wasm32-unknown-unknown/release/cargo_test.wasm" ) .to_vec(), - store_data: (), }; let mut sum = Wasm::init(plugin)?; diff --git a/crates/plugin_runtime/src/wasi.rs b/crates/plugin_runtime/src/wasi.rs new file mode 100644 index 0000000000..745b6364fa --- /dev/null +++ b/crates/plugin_runtime/src/wasi.rs @@ -0,0 +1,194 @@ +use std::collections::HashMap; + +use anyhow::anyhow; + +use wasmtime::{Engine, Func, Instance, Linker, Memory, MemoryType, Module, Store, TypedFunc}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; + +use crate::*; + +pub struct Wasi { + engine: Engine, + module: Module, + store: Store, + instance: Instance, + alloc_buffer: TypedFunc, + // free_buffer: TypedFunc<(i32, i32), ()>, +} + +pub struct WasiPlugin { + pub source_bytes: Vec, +} + +impl Wasi { + pub fn dump_memory(data: &[u8]) { + for (i, byte) in data.iter().enumerate() { + if i % 32 == 0 { + println!(); + } + if i % 4 == 0 { + print!("|"); + } + if *byte == 0 { + print!("__") + } else { + print!("{:02x}", byte); + } + } + println!(); + } +} + +impl Runtime for Wasi { + type Plugin = WasiPlugin; + type Error = anyhow::Error; + + fn init(plugin: WasiPlugin) -> Result { + let engine = Engine::default(); + let mut linker = Linker::new(&engine); + println!("linking"); + wasmtime_wasi::add_to_linker(&mut linker, |s| s)?; + println!("linked"); + let wasi = WasiCtxBuilder::new() + .inherit_stdout() + .inherit_stderr() + .build(); + + let mut store: Store<_> = Store::new(&engine, wasi); + println!("moduling"); + let module = Module::new(&engine, plugin.source_bytes)?; + println!("moduled"); + + linker.module(&mut store, "", &module)?; + println!("linked again"); + let instance = linker.instantiate(&mut store, &module)?; + println!("instantiated"); + + let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; + // let free_buffer = instance.get_typed_func(&mut store, "__free_buffer")?; + println!("can alloc"); + + Ok(Wasi { + engine, + module, + store, + instance, + alloc_buffer, + // free_buffer, + }) + } + + // fn constant(&mut self, handle: &Handle) -> Result { + // let export = self + // .instance + // .get_export(&mut self.store, handle.inner()) + // .ok_or_else(|| anyhow!("Could not get export"))?; + + // todo!() + // } + + // So this call function is kinda a dance, I figured it'd be a good idea to document it. + // the high level is we take a serde type, serialize it to a byte array, + // (we're doing this using bincode for now) + // then toss that byte array into webassembly. + // webassembly grabs that byte array, does some magic, + // and serializes the result into yet another byte array. + // we then grab *that* result byte array and deserialize it into a result. + // + // phew... + // + // now the problem is, webassambly doesn't support buffers. + // only really like i32s, that's it (yeah, it's sad. Not even unsigned!) + // (ok, I'm exaggerating a bit). + // + // the Wasm function that this calls must have a very specific signature: + // + // fn(pointer to byte array: i32, length of byte array: i32) + // -> pointer to ( + // pointer to byte_array: i32, + // length of byte array: i32, + // ): i32 + // + // This pair `(pointer to byte array, length of byte array)` is called a `Buffer` + // and can be found in the cargo_test plugin. + // + // so on the wasm side, we grab the two parameters to the function, + // stuff them into a `Buffer`, + // and then pray to the `unsafe` Rust gods above that a valid byte array pops out. + // + // On the flip side, when returning from a wasm function, + // we convert whatever serialized result we get into byte array, + // which we stuff into a Buffer and allocate on the heap, + // which pointer to we then return. + // Note the double indirection! + // + // So when returning from a function, we actually leak memory *twice*: + // + // 1) once when we leak the byte array + // 2) again when we leak the allocated `Buffer` + // + // This isn't a problem because Wasm stops executing after the function returns, + // so the heap is still valid for our inspection when we want to pull things out. + + // TODO: dont' use as for conversions + fn call( + &mut self, + handle: &str, + arg: A, + ) -> Result { + // serialize the argument using bincode + let arg = bincode::serialize(&arg)?; + let arg_buffer_len = arg.len(); + + // allocate a buffer and write the argument to that buffer + let arg_buffer_ptr = self + .alloc_buffer + .call(&mut self.store, arg_buffer_len as i32)?; + let plugin_memory = self + .instance + .get_memory(&mut self.store, "memory") + .ok_or_else(|| anyhow!("Could not grab slice of plugin memory"))?; + plugin_memory.write(&mut self.store, arg_buffer_ptr as usize, &arg)?; + + // get the webassembly function we want to actually call + // TODO: precompute handle + let fun_name = format!("__{}", handle); + let fun = self + .instance + .get_typed_func::<(i32, i32), i32, _>(&mut self.store, &fun_name)?; + + // call the function, passing in the buffer and its length + // this should return a pointer to a (ptr, lentgh) pair + let arg_buffer = (arg_buffer_ptr, arg_buffer_len as i32); + let result_buffer = fun.call(&mut self.store, arg_buffer)?; + + // create a buffer to read the (ptr, length) pair into + // this is a total of 4 + 4 = 8 bytes. + let buffer = &mut [0; 8]; + plugin_memory.read(&mut self.store, result_buffer as usize, buffer)?; + + // use these bytes (wasm stores things little-endian) + // to get a pointer to the buffer and its length + let b = buffer; + let result_buffer_ptr = u32::from_le_bytes([b[0], b[1], b[2], b[3]]) as usize; + let result_buffer_len = u32::from_le_bytes([b[4], b[5], b[6], b[7]]) as usize; + let result_buffer_end = result_buffer_ptr + result_buffer_len; + + // read the buffer at this point into a byte array + // deserialize the byte array into the provided serde type + let result = &plugin_memory.data(&mut self.store)[result_buffer_ptr..result_buffer_end]; + let result = bincode::deserialize(result)?; + + // TODO: this is handled wasm-side, but I'd like to double-check + // // deallocate the argument buffer + // self.free_buffer.call(&mut self.store, arg_buffer); + + return Ok(result); + } + + // fn register_handle>(&mut self, name: T) -> bool { + // self.instance + // .get_export(&mut self.store, name.as_ref()) + // .is_some() + // } +} diff --git a/crates/plugin_runtime/src/wasm.rs b/crates/plugin_runtime/src/wasm.rs index dc5541d113..34064d975e 100644 --- a/crates/plugin_runtime/src/wasm.rs +++ b/crates/plugin_runtime/src/wasm.rs @@ -6,21 +6,20 @@ use wasmtime::{Engine, Func, Instance, Memory, MemoryType, Module, Store, TypedF use crate::*; -pub struct Wasm { +pub struct Wasm { engine: Engine, module: Module, - store: Store, + store: Store<()>, instance: Instance, alloc_buffer: TypedFunc, // free_buffer: TypedFunc<(i32, i32), ()>, } -pub struct WasmPlugin { +pub struct WasmPlugin { pub source_bytes: Vec, - pub store_data: T, } -impl Wasm { +impl Wasm { pub fn dump_memory(data: &[u8]) { for (i, byte) in data.iter().enumerate() { if i % 32 == 0 { @@ -39,14 +38,14 @@ impl Wasm { } } -impl Runtime for Wasm { - type Plugin = WasmPlugin; +impl Runtime for Wasm { + type Plugin = WasmPlugin; type Error = anyhow::Error; - fn init(plugin: WasmPlugin) -> Result { + fn init(plugin: WasmPlugin) -> Result { let engine = Engine::default(); let module = Module::new(&engine, plugin.source_bytes)?; - let mut store: Store = Store::new(&engine, plugin.store_data); + let mut store: Store<()> = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[])?; let alloc_buffer = instance.get_typed_func(&mut store, "__alloc_buffer")?; diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 0a9cb5592b..9d25954487 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -4,28 +4,27 @@ use client::http::HttpClient; use futures::{future::BoxFuture, FutureExt, StreamExt}; use language::{LanguageServerName, LspAdapter}; use parking_lot::{Mutex, RwLock}; -use plugin_runtime::{Runtime, Wasm, WasmPlugin}; +use plugin_runtime::{Runtime, Wasi, WasiPlugin}; use serde_json::json; use smol::fs; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ResultExt, TryFutureExt}; pub fn new_json() -> LanguagePluginLspAdapter { - let plugin = WasmPlugin { + let plugin = WasiPlugin { source_bytes: include_bytes!("../../../../plugins/bin/json_language.wasm").to_vec(), - store_data: (), }; LanguagePluginLspAdapter::new(plugin) } pub struct LanguagePluginLspAdapter { - runtime: Mutex>, + runtime: Mutex, } impl LanguagePluginLspAdapter { - pub fn new(plugin: WasmPlugin<()>) -> Self { + pub fn new(plugin: WasiPlugin) -> Self { Self { - runtime: Mutex::new(Wasm::init(plugin).unwrap()), + runtime: Mutex::new(Wasi::init(plugin).unwrap()), } } } diff --git a/plugins/json_language/src/lib.rs b/plugins/json_language/src/lib.rs index f8796907be..7801b81b70 100644 --- a/plugins/json_language/src/lib.rs +++ b/plugins/json_language/src/lib.rs @@ -1,7 +1,17 @@ use plugin::prelude::*; +use std::fs; + +#[import] +fn command(string: String) -> Option; #[bind] pub fn name(_: ()) -> &'static str { + println!("huh, let me see..."); + Command::new("sh") + .arg("-c") + .arg("echo hello") + .output() + .expect("failed to execute process"); "vscode-json-languageserver" } @@ -9,3 +19,92 @@ pub fn name(_: ()) -> &'static str { pub fn server_args(_: ()) -> Vec { vec!["--stdio".into()] } + +#[bind] +fn fetch_latest_server_version() -> Option { + #[derive(Deserialize)] + struct NpmInfo { + versions: Vec, + } + + let output = command("npm info vscode-json-languageserver --json")?; + if !output.status.success() { + return None; + } + + let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; + info.versions.pop() +} + +#[bind] +pub fn fetch_server_binary(version: String) -> Option { + let version_dir = container_dir.join(version.as_str()); + fs::create_dir_all(&version_dir) + .await + .context("failed to create version directory")?; + let binary_path = version_dir.join(Self::BIN_PATH); + + if fs::metadata(&binary_path).await.is_err() { + let output = smol::process::Command::new("npm") + .current_dir(&version_dir) + .arg("install") + .arg(format!("vscode-json-languageserver@{}", version)) + .output() + .await + .context("failed to run npm install")?; + if !output.status.success() { + Err(anyhow!("failed to install vscode-json-languageserver"))?; + } + + if let Some(mut entries) = fs::read_dir(&container_dir).await.log_err() { + while let Some(entry) = entries.next().await { + if let Some(entry) = entry.log_err() { + let entry_path = entry.path(); + if entry_path.as_path() != version_dir { + fs::remove_dir_all(&entry_path).await.log_err(); + } + } + } + } + } + + Ok(binary_path) +} + +#[bind] +pub fn cached_server_binary(container_dir: PathBuf) -> Option { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let bin_path = last_version_dir.join(Self::BIN_PATH); + if bin_path.exists() { + Ok(bin_path) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } +} + +#[bind] +pub fn initialization_options(_: ()) -> Option { + Some(json!({ + "provideFormatter": true + })) +} + +#[bind] +fn id_for_language(name: String) -> Option { + if name == "JSON" { + Some("jsonc".into()) + } else { + None + } +} diff --git a/script/build-plugins b/script/build-plugins index ee5baa93c3..8417828c1f 100755 --- a/script/build-plugins +++ b/script/build-plugins @@ -6,14 +6,15 @@ echo "Clearing cached plugins..." cargo clean --manifest-path plugins/Cargo.toml echo "Building Wasm plugins..." -cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml +# cargo build --release --target wasm32-unknown-unknown --manifest-path plugins/Cargo.toml +cargo build --release --target wasm32-wasi --manifest-path plugins/Cargo.toml echo echo "Extracting binaries..." rm -rf plugins/bin mkdir plugins/bin -for f in plugins/target/wasm32-unknown-unknown/release/*.wasm +for f in plugins/target/wasm32-wasi/release/*.wasm do name=$(basename $f) cp $f plugins/bin/$name