diff --git a/Cargo.lock b/Cargo.lock index 31ebe5ac..b4ff8375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,6 +377,15 @@ dependencies = [ "scoped-tls", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -396,6 +405,29 @@ dependencies = [ "virtue", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.0", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.48", + "which 4.4.2", +] + [[package]] name = "biome_console" version = "0.5.7" @@ -436,7 +468,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fc1244cc5f0cc267bd26b601e9ccd6851c6a4d395bba07e27c2de641dc84479" dependencies = [ - "convert_case 0.6.0", + "convert_case", "proc-macro-error", "proc-macro2", "quote", @@ -872,7 +904,7 @@ dependencies = [ name = "brioche-pack" version = "0.1.1" dependencies = [ - "bincode", + "bincode 2.0.0-rc.3", "bstr 1.8.0", "serde", "serde_with", @@ -919,9 +951,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "bzip2" @@ -960,6 +992,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -1024,6 +1065,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.4.11" @@ -1131,12 +1183,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1146,6 +1192,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1301,7 +1353,7 @@ dependencies = [ "crossterm_winapi", "libc", "mio", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "signal-hook 0.3.17", "signal-hook-mio", "winapi", @@ -1461,30 +1513,42 @@ dependencies = [ [[package]] name = "deno_core" -version = "0.237.0" +version = "0.303.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ea708c221abdb5734e3c4b72075379c3046eb0ac54afa0ecb5e58509cce72c" +checksum = "a9cd2cc931f61dee2db67ce9d032d229dda981be29d68dfd530a3dc1187ddd6b" dependencies = [ "anyhow", + "bincode 1.3.3", + "bit-set", + "bit-vec", "bytes", + "cooked-waker", + "deno_core_icudata", "deno_ops", "deno_unsync", "futures", "libc", - "log", - "parking_lot 0.12.1", + "memoffset 0.9.0", + "parking_lot 0.12.3", + "percent-encoding", "pin-project", "serde", "serde_json", "serde_v8", "smallvec", - "sourcemap 7.1.1", + "sourcemap", "static_assertions", "tokio", "url", "v8", ] +[[package]] +name = "deno_core_icudata" +version = "0.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13951ea98c0a4c372f162d669193b4c9d991512de9f2381dd161027f34b26b1" + [[package]] name = "deno_media_type" version = "0.1.4" @@ -1498,9 +1562,9 @@ dependencies = [ [[package]] name = "deno_ops" -version = "0.113.0" +version = "0.179.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5b9c0f6360795fb625774a8b5955c87c470c43159670cf5d2052df5ce9d84bc" +checksum = "1dbb9802b8b976e73872ae6e03303532e6056236467053aa6df02f7deb33488c" dependencies = [ "proc-macro-rules", "proc-macro2", @@ -1523,10 +1587,11 @@ dependencies = [ [[package]] name = "deno_unsync" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba44060d41759ee53118341170e29f2e112bd1c616e5b2878e7aee566949c82" +checksum = "03ee1607db298c8f12124b345a52d5f2f504a7504c9d535f1d8f07127b237010" dependencies = [ + "parking_lot 0.12.3", "tokio", ] @@ -1551,19 +1616,6 @@ dependencies = [ "serde", ] -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - [[package]] name = "diff" version = "0.1.13" @@ -1831,9 +1883,9 @@ dependencies = [ [[package]] name = "fslock" -version = "0.1.8" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57eafdd0c16f57161105ae1b98a1238f97645f2f588438b2949c99a2af9616bf" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" dependencies = [ "libc", "winapi", @@ -1901,7 +1953,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -1999,6 +2051,15 @@ dependencies = [ "regex-syntax 0.8.2", ] +[[package]] +name = "gzip-header" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +dependencies = [ + "crc32fast", +] + [[package]] name = "h2" version = "0.3.26" @@ -2518,12 +2579,28 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets 0.52.0", +] + [[package]] name = "libm" version = "0.2.8" @@ -2706,9 +2783,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -3107,9 +3184,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core 0.9.9", @@ -3393,6 +3470,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +dependencies = [ + "proc-macro2", + "syn 2.0.48", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -3901,20 +3988,11 @@ dependencies = [ "semver 0.9.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.20", -] - [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", "errno", @@ -4136,12 +4214,6 @@ dependencies = [ "semver-parser 0.10.2", ] -[[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" - [[package]] name = "semver-parser" version = "0.7.0" @@ -4234,12 +4306,10 @@ dependencies = [ [[package]] name = "serde_v8" -version = "0.146.0" +version = "0.212.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78309bd1ec4d14d165f271e203bdc45ad5bf45525da57bb70901f57942f6c0f7" +checksum = "bf9a8693e4e54bf21fe51b953b10f98a0e32040671f18c4065f6014f0317ae80" dependencies = [ - "bytes", - "derive_more", "num-bigint", "serde", "smallvec", @@ -4329,6 +4399,12 @@ dependencies = [ "dirs 5.0.1", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.1.17" @@ -4450,23 +4526,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "sourcemap" -version = "7.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7768edd06c02535e0d50653968f46e1e0d3aa54742190d35dd9466f59de9c71" -dependencies = [ - "base64-simd", - "data-encoding", - "debugid", - "if_chain", - "rustc_version 0.2.3", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - [[package]] name = "sourcemap" version = "8.0.1" @@ -4479,7 +4538,7 @@ dependencies = [ "debugid", "if_chain", "rustc-hash", - "rustc_version 0.2.3", + "rustc_version", "serde", "serde_json", "unicode-id-start", @@ -4894,7 +4953,7 @@ dependencies = [ "rustc-hash", "serde", "siphasher", - "sourcemap 8.0.1", + "sourcemap", "swc_allocator", "swc_atoms", "swc_eq_ignore_macros", @@ -4958,7 +5017,7 @@ dependencies = [ "num-bigint", "once_cell", "serde", - "sourcemap 8.0.1", + "sourcemap", "swc_allocator", "swc_atoms", "swc_common", @@ -5510,7 +5569,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2 0.5.5", @@ -5961,14 +6020,19 @@ dependencies = [ [[package]] name = "v8" -version = "0.82.0" +version = "0.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f53dfb242f4c0c39ed3fc7064378a342e57b5c9bd774636ad34ffe405b808121" +checksum = "88a710d5b95bff79a90708203cf9f74384e080d21fc6664aa4df463f2c66ac83" dependencies = [ - "bitflags 1.3.2", + "bindgen", + "bitflags 2.6.0", "fslock", + "gzip-header", + "home", + "miniz_oxide", "once_cell", - "which", + "paste", + "which 6.0.2", ] [[package]] @@ -6273,6 +6337,18 @@ dependencies = [ "rustix", ] +[[package]] +name = "which" +version = "6.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d9c5ed668ee1f17edb3b627225343d210006a90bb1e3745ce1f30b1fb115075" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + [[package]] name = "whoami" version = "1.4.1" @@ -6470,6 +6546,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wyz" version = "0.5.1" diff --git a/crates/brioche-core/Cargo.toml b/crates/brioche-core/Cargo.toml index 776f3ec6..5f0a29a1 100644 --- a/crates/brioche-core/Cargo.toml +++ b/crates/brioche-core/Cargo.toml @@ -19,7 +19,7 @@ cfg-if = "1.0.0" console-subscriber = "0.4.0" debug-ignore = "1.0.5" deno_ast = { version = "0.41.1", features = ["transpiling"] } -deno_core = "0.237.0" +deno_core = "0.303.0" directories = "5.0.1" futures = "0.3.29" globset = "0.4.14" @@ -42,7 +42,7 @@ reqwest-retry = "0.6.0" rust-embed = { version = "8.1.0", features = ["debug-embed", "interpolate-folder-path", "include-exclude"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" -serde_v8 = "0.146.0" +serde_v8 = "0.212.0" serde_with = { version = "3.4.0", features = ["hex"] } sha2 = "0.10.8" sqlx = { version = "0.7.3", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "json"] } diff --git a/crates/brioche-core/src/script.rs b/crates/brioche-core/src/script.rs index 47313e03..48d76c1a 100644 --- a/crates/brioche-core/src/script.rs +++ b/crates/brioche-core/src/script.rs @@ -1,28 +1,25 @@ use std::{ cell::RefCell, collections::{hash_map::Entry, HashMap}, + path::PathBuf, rc::Rc, sync::Arc, }; use anyhow::Context as _; +use bridge::RuntimeBridge; use deno_core::OpState; -use joinery::JoinableIterator as _; use specifier::BriocheModuleSpecifier; -use crate::{ - bake::BakeScope, - project::analyze::{StaticInclude, StaticQuery}, -}; +use crate::{bake::BakeScope, project::analyze::StaticQuery}; use super::{ blob::BlobHash, - project::Projects, recipe::{Artifact, Recipe, WithMeta}, script::specifier::BriocheImportSpecifier, - Brioche, }; +mod bridge; pub mod check; mod compiler_host; pub mod evaluate; @@ -33,19 +30,74 @@ pub mod specifier; #[derive(Clone)] struct BriocheModuleLoader { - pub brioche: Brioche, - pub projects: Projects, + pub bridge: RuntimeBridge, pub sources: Rc>>, } impl BriocheModuleLoader { - fn new(brioche: &Brioche, projects: &Projects) -> Self { + fn new(bridge: RuntimeBridge) -> Self { Self { - brioche: brioche.clone(), - projects: projects.clone(), + bridge, sources: Rc::new(RefCell::new(HashMap::new())), } } + + fn load_module_source( + &self, + module_specifier: &deno_core::ModuleSpecifier, + ) -> Result { + let brioche_module_specifier: BriocheModuleSpecifier = module_specifier.try_into()?; + let contents = self + .bridge + .read_specifier_contents(brioche_module_specifier.clone())?; + + let code = std::str::from_utf8(&contents) + .context("failed to parse module contents as UTF-8 string")?; + + let parsed = deno_ast::parse_module(deno_ast::ParseParams { + specifier: brioche_module_specifier.clone().into(), + text: code.into(), + media_type: deno_ast::MediaType::TypeScript, + capture_tokens: false, + scope_analysis: false, + maybe_syntax: None, + })?; + let transpiled = parsed.transpile( + &deno_ast::TranspileOptions { + imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Preserve, + ..Default::default() + }, + &deno_ast::EmitOptions { + source_map: deno_ast::SourceMapOption::Separate, + ..Default::default() + }, + )?; + + if let Entry::Vacant(entry) = self + .sources + .borrow_mut() + .entry(brioche_module_specifier.clone()) + { + let source_map = transpiled + .clone() + .into_source() + .source_map + .context("source map not generated")?; + entry.insert(ModuleSource { + source_contents: contents.clone(), + source_map, + }); + } + + Ok(deno_core::ModuleSource::new( + deno_core::ModuleType::JavaScript, + deno_core::ModuleSourceCode::Bytes( + transpiled.into_source().source.into_boxed_slice().into(), + ), + module_specifier, + None, + )) + } } impl deno_core::ModuleLoader for BriocheModuleLoader { @@ -63,7 +115,10 @@ impl deno_core::ModuleLoader for BriocheModuleLoader { let referrer: BriocheModuleSpecifier = referrer.parse()?; let specifier: BriocheImportSpecifier = specifier.parse()?; - let resolved = specifier::resolve(&self.projects, &specifier, &referrer)?; + + let resolved = self + .bridge + .resolve_specifier(specifier.clone(), referrer.clone())?; tracing::debug!(%specifier, %referrer, %resolved, "resolved module"); @@ -76,62 +131,11 @@ impl deno_core::ModuleLoader for BriocheModuleLoader { module_specifier: &deno_core::ModuleSpecifier, _maybe_referrer: Option<&deno_core::ModuleSpecifier>, _is_dyn_import: bool, - ) -> std::pin::Pin> { - let module_specifier: Result = module_specifier.try_into(); - let sources = self.sources.clone(); - let vfs = self.brioche.vfs.clone(); - let future = async move { - let module_specifier = module_specifier?; - let contents = specifier::read_specifier_contents(&vfs, &module_specifier)?; - - let code = std::str::from_utf8(&contents) - .context("failed to parse module contents as UTF-8 string")?; - - let parsed = deno_ast::parse_module(deno_ast::ParseParams { - specifier: module_specifier.clone().into(), - text: code.into(), - media_type: deno_ast::MediaType::TypeScript, - capture_tokens: false, - scope_analysis: false, - maybe_syntax: None, - })?; - let transpiled = parsed.transpile( - &deno_ast::TranspileOptions { - imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Preserve, - ..Default::default() - }, - &deno_ast::EmitOptions { - source_map: deno_ast::SourceMapOption::Separate, - ..Default::default() - }, - )?; - - let mut sources = sources.borrow_mut(); - if let Entry::Vacant(entry) = sources.entry(module_specifier.clone()) { - let source_map = transpiled - .clone() - .into_source() - .source_map - .context("source map not generated")?; - entry.insert(ModuleSource { - source_contents: contents.clone(), - source_map, - }); - } - - let module_specifier: deno_core::ModuleSpecifier = module_specifier.into(); - Ok(deno_core::ModuleSource::new( - deno_core::ModuleType::JavaScript, - transpiled.into_source().into_string()?.text.into(), - &module_specifier, - )) - }; - - Box::pin(future) + _requested_module_type: deno_core::RequestedModuleType, + ) -> deno_core::ModuleLoadResponse { + deno_core::ModuleLoadResponse::Sync(self.load_module_source(module_specifier)) } -} -impl deno_core::SourceMapGetter for BriocheModuleLoader { fn get_source_map(&self, file_name: &str) -> Option> { let sources = self.sources.borrow(); let specifier: BriocheModuleSpecifier = file_name.parse().ok()?; @@ -139,7 +143,7 @@ impl deno_core::SourceMapGetter for BriocheModuleLoader { Some(code.source_map.clone()) } - fn get_source_line(&self, file_name: &str, line_number: usize) -> Option { + fn get_source_mapped_source_line(&self, file_name: &str, line_number: usize) -> Option { let sources = self.sources.borrow(); let specifier: BriocheModuleSpecifier = file_name.parse().ok()?; let source = sources.get(&specifier)?; @@ -158,6 +162,22 @@ struct ModuleSource { pub source_map: Vec, } +pub enum ModuleLoaderMessage { + ReadSpecifierContents { + specifier: BriocheModuleSpecifier, + contents_tx: std::sync::mpsc::Sender>>>, + }, + ResolveSpecifier { + specifier: BriocheImportSpecifier, + referrer: BriocheModuleSpecifier, + resolved_tx: std::sync::mpsc::Sender>, + }, + ProjectRootForModulePath { + module_path: PathBuf, + project_path_tx: tokio::sync::oneshot::Sender>, + }, +} + deno_core::extension!(brioche_rt, ops = [ op_brioche_bake_all, @@ -166,13 +186,11 @@ deno_core::extension!(brioche_rt, op_brioche_get_static, ], options = { - brioche: Brioche, - projects: Projects, + bridge: RuntimeBridge, bake_scope: BakeScope, }, state = |state, options| { - state.put(options.brioche); - state.put(options.projects); + state.put(options.bridge); state.put(options.bake_scope); }, ); @@ -183,11 +201,11 @@ pub async fn op_brioche_bake_all( state: Rc>, #[serde] recipes: Vec>, ) -> Result, deno_core::error::AnyError> { - let brioche = { + let bridge = { let state = state.try_borrow()?; state - .try_borrow::() - .context("failed to get brioche instance")? + .try_borrow::() + .context("failed to get runtime bridge")? .clone() }; let bake_scope = { @@ -198,11 +216,9 @@ pub async fn op_brioche_bake_all( .clone() }; - let mut results = vec![]; - for recipe in recipes { - let result = super::bake::bake(&brioche, recipe, &bake_scope).await?; - results.push(result.value); - } + let results = bridge.bake_all(recipes.clone(), bake_scope.clone()).await?; + let results = results.into_iter().map(|result| result.value).collect(); + Ok(results) } @@ -212,15 +228,15 @@ pub async fn op_brioche_create_proxy( state: Rc>, #[serde] recipe: Recipe, ) -> Result { - let brioche = { + let bridge = { let state = state.try_borrow()?; state - .try_borrow::() - .context("failed to get brioche instance")? + .try_borrow::() + .context("failed to get runtime bridge")? .clone() }; - let result = super::bake::create_proxy(&brioche, recipe).await?; + let result = bridge.create_proxy(recipe).await?; Ok(result) } @@ -231,20 +247,15 @@ pub async fn op_brioche_read_blob( state: Rc>, #[serde] blob_hash: BlobHash, ) -> Result>, deno_core::error::AnyError> { - let brioche = { + let bridge = { let state = state.try_borrow()?; state - .try_borrow::() - .context("failed to get brioche instance")? + .try_borrow::() + .context("failed to get runtime bridge")? .clone() }; - let permit = crate::blob::get_save_blob_permit().await?; - let path = crate::blob::blob_path(&brioche, permit, blob_hash).await?; - let bytes = tokio::fs::read(path) - .await - .with_context(|| format!("failed to read blob {blob_hash}"))?; - + let bytes = bridge.read_blob(blob_hash).await?; Ok(crate::encoding::TickEncode(bytes)) } @@ -255,39 +266,16 @@ pub async fn op_brioche_get_static( #[string] url: String, #[serde] static_: StaticQuery, ) -> Result { - let (brioche, projects) = { + let bridge = { let state = state.try_borrow()?; - let brioche = state - .try_borrow::() - .context("failed to get brioche instance")? - .clone(); - let projects = state - .try_borrow::() - .context("failed to get projects instance")? - .clone(); - (brioche, projects) + state + .try_borrow::() + .context("failed to get runtime bridge")? + .clone() }; let specifier: BriocheModuleSpecifier = url.parse()?; - let recipe_hash = projects - .get_static(&specifier, &static_)? - .with_context(|| match static_ { - StaticQuery::Include(StaticInclude::File { path }) => { - format!("failed to resolve Brioche.includeFile({path:?}) from {specifier}, was the path passed in as a string literal?") - } - StaticQuery::Include(StaticInclude::Directory { path }) => { - format!("failed to resolve Brioche.includeDirectory({path:?}) from {specifier}, was the path passed in as a string literal?") - } - StaticQuery::Glob { patterns } => { - let patterns = patterns.iter().map(|pattern| lazy_format::lazy_format!("{pattern:?}")).join_with(", "); - format!("failed to resolve Brioche.glob({patterns}) from {specifier}, were the patterns passed in as string literals?") - } - StaticQuery::Download { url } => { - format!("failed to resolve Brioche.download({url:?}) from {specifier}, was the URL passed in as a string literal?") - } - - })?; - let recipe = crate::recipe::get_recipe(&brioche, recipe_hash).await?; + let recipe = bridge.get_static(specifier, static_).await?; Ok(recipe) } diff --git a/crates/brioche-core/src/script/bridge.rs b/crates/brioche-core/src/script/bridge.rs new file mode 100644 index 00000000..7a7d2b60 --- /dev/null +++ b/crates/brioche-core/src/script/bridge.rs @@ -0,0 +1,468 @@ +use std::{path::PathBuf, sync::Arc}; + +use anyhow::Context as _; +use futures::{StreamExt as _, TryStreamExt as _}; +use joinery::JoinableIterator as _; + +use crate::{ + bake::BakeScope, + blob::BlobHash, + project::{ + analyze::{StaticInclude, StaticQuery}, + ProjectHash, Projects, + }, + recipe::{Artifact, Recipe, WithMeta}, + Brioche, +}; + +use super::specifier::{self, BriocheImportSpecifier, BriocheModuleSpecifier}; + +/// A type used to call Brioche functions across Tokio runtimes. This is used +/// to interact with Brioche from JavaScript code, due to restrictions that +/// require the V8 runtime to run in its own Tokio runtime. +/// +/// This type works by spawning a Tokio task that uses a channel for +/// communicating across runtimes, which is explicitly supported by Tokio's +/// channel types. +#[derive(Clone)] +pub struct RuntimeBridge { + tx: tokio::sync::mpsc::UnboundedSender, +} + +impl RuntimeBridge { + pub fn new(brioche: Brioche, projects: Projects) -> Self { + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + + tokio::spawn(async move { + while let Some(message) = rx.recv().await { + let brioche = brioche.clone(); + let projects = projects.clone(); + + tokio::spawn(async move { + match message { + RuntimeBridgeMessage::LoadProjectFromModulePath { path, result_tx } => { + let result = + projects.load_from_module_path(&brioche, &path, false).await; + let _ = result_tx.send(result); + } + RuntimeBridgeMessage::LoadSpecifierContents { + specifier, + contents_tx, + } => { + let contents = + specifier::load_specifier_contents(&brioche.vfs, &specifier).await; + let _ = contents_tx.send(contents); + } + RuntimeBridgeMessage::ResolveSpecifier { + specifier, + referrer, + resolved_tx, + } => { + let resolved = specifier::resolve(&projects, &specifier, &referrer); + let _ = resolved_tx.send(resolved); + } + RuntimeBridgeMessage::UpdateVfsContents { + specifier, + contents, + result_tx, + } => { + let path = match specifier { + BriocheModuleSpecifier::Runtime { .. } => { + let _ = result_tx.send(Ok(false)); + return; + } + BriocheModuleSpecifier::File { path } => path, + }; + + let file_id = match brioche.vfs.load_cached(&path) { + Ok(Some((file_id, _))) => file_id, + Ok(None) => { + let _ = result_tx.send(Ok(false)); + return; + } + Err(error) => { + let _ = result_tx.send(Err(error)); + return; + } + }; + + let result = brioche.vfs.update(file_id, contents).map(|_| true); + let _ = result_tx.send(result); + } + RuntimeBridgeMessage::ReloadProjectFromSpecifier { + specifier, + result_tx, + } => { + let path = match specifier { + BriocheModuleSpecifier::Runtime { .. } => { + let _ = result_tx.send(Ok(false)); + return; + } + BriocheModuleSpecifier::File { path } => path, + }; + + let project = projects.find_containing_project(&path); + let project = match project { + Ok(project) => project, + Err(error) => { + let _ = result_tx.send(Err(error).with_context(|| { + format!("no project found for path '{}'", path.display()) + })); + return; + } + }; + + if let Some(project) = project { + let result = projects.clear(project).await; + match result { + Ok(_) => {} + Err(error) => { + let _ = result_tx.send(Err(error)); + return; + } + } + } + + let result = projects + .load_from_module_path(&brioche, &path, false) + .await + .map(|_| true); + let _ = result_tx.send(result); + } + RuntimeBridgeMessage::ReadSpecifierContents { + specifier, + contents_tx, + } => { + let contents = + specifier::read_specifier_contents(&brioche.vfs, &specifier); + let _ = contents_tx.send(contents); + } + RuntimeBridgeMessage::ProjectRootForModulePath { + module_path, + project_path_tx, + } => { + let project_hash = projects.find_containing_project(&module_path); + let project_hash = match project_hash { + Ok(Some(project_hash)) => project_hash, + Ok(None) => { + let error = anyhow::anyhow!("containing project not found"); + let _ = project_path_tx.send(Err(error)); + return; + } + Err(error) => { + let _ = project_path_tx + .send(Err(error).context("failed to get project path")); + return; + } + }; + let project_path = projects.project_root(project_hash); + let project_path = match project_path { + Ok(project_path) => project_path, + Err(error) => { + let _ = project_path_tx.send(Err(error)); + return; + } + }; + let _ = project_path_tx.send(Ok(project_path)); + } + RuntimeBridgeMessage::BakeAll { + recipes, + bake_scope, + results_tx, + } => { + let results = futures::stream::iter(recipes) + .then(|recipe| async { + let brioche = brioche.clone(); + crate::bake::bake(&brioche, recipe, &bake_scope).await + }) + .try_collect::>() + .await; + + let _ = results_tx.send(results); + } + RuntimeBridgeMessage::CreateProxy { recipe, result_tx } => { + let result = crate::bake::create_proxy(&brioche, recipe).await; + let _ = result_tx.send(result); + } + RuntimeBridgeMessage::ReadBlob { + blob_hash, + result_tx, + } => { + let permit = crate::blob::get_save_blob_permit().await; + let permit = match permit { + Ok(permit) => permit, + Err(error) => { + let _ = result_tx.send(Err(error)); + return; + } + }; + + let path = crate::blob::blob_path(&brioche, permit, blob_hash).await; + let path = match path { + Ok(path) => path, + Err(error) => { + let _ = result_tx.send(Err(error)); + return; + } + }; + + let result = tokio::fs::read(path) + .await + .with_context(|| format!("failed to read blob {blob_hash}")); + + let _ = result_tx.send(result); + } + RuntimeBridgeMessage::GetStatic { + specifier, + static_, + result_tx, + } => { + let recipe_hash = projects.get_static(&specifier, &static_); + let recipe_hash = match recipe_hash { + Ok(Some(recipe_hash)) => recipe_hash, + Ok(None) => { + let error = match static_ { + StaticQuery::Include(StaticInclude::File { path }) => { + anyhow::anyhow!("failed to resolve Brioche.includeFile({path:?}) from {specifier}, was the path passed in as a string literal?") + } + StaticQuery::Include(StaticInclude::Directory { path }) => { + anyhow::anyhow!("failed to resolve Brioche.includeDirectory({path:?}) from {specifier}, was the path passed in as a string literal?") + } + StaticQuery::Glob { patterns } => { + let patterns = patterns + .iter() + .map(|pattern| { + lazy_format::lazy_format!("{pattern:?}") + }) + .join_with(", "); + anyhow::anyhow!("failed to resolve Brioche.glob({patterns}) from {specifier}, were the patterns passed in as string literals?") + } + StaticQuery::Download { url } => { + anyhow::anyhow!("failed to resolve Brioche.download({url:?}) from {specifier}, was the URL passed in as a string literal?") + } + }; + let _ = result_tx.send(Err(error)); + return; + } + Err(error) => { + let _ = result_tx.send(Err(error)); + return; + } + }; + + let result = crate::recipe::get_recipe(&brioche, recipe_hash).await; + let _ = result_tx.send(result); + } + } + }); + } + }); + + Self { tx } + } + + pub async fn load_project_from_module_path( + &self, + path: std::path::PathBuf, + ) -> anyhow::Result { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx + .send(RuntimeBridgeMessage::LoadProjectFromModulePath { path, result_tx })?; + let result = result_rx.await??; + Ok(result) + } + + pub async fn load_specifier_contents( + &self, + specifier: BriocheModuleSpecifier, + ) -> anyhow::Result>> { + let (contents_tx, contents_rx) = tokio::sync::oneshot::channel(); + + self.tx.send(RuntimeBridgeMessage::LoadSpecifierContents { + specifier, + contents_tx, + })?; + let contents = contents_rx.await??; + Ok(contents) + } + + pub fn resolve_specifier( + &self, + specifier: BriocheImportSpecifier, + referrer: BriocheModuleSpecifier, + ) -> anyhow::Result { + let (resolved_tx, resolved_rx) = std::sync::mpsc::channel(); + + self.tx.send(RuntimeBridgeMessage::ResolveSpecifier { + specifier, + referrer, + resolved_tx, + })?; + let resolved = resolved_rx.recv()??; + Ok(resolved) + } + + pub async fn update_vfs_contents( + &self, + specifier: BriocheModuleSpecifier, + contents: Arc>, + ) -> anyhow::Result { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx.send(RuntimeBridgeMessage::UpdateVfsContents { + specifier, + contents, + result_tx, + })?; + let result = result_rx.await??; + Ok(result) + } + + pub async fn reload_project_from_specifier( + &self, + specifier: BriocheModuleSpecifier, + ) -> anyhow::Result { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx + .send(RuntimeBridgeMessage::ReloadProjectFromSpecifier { + specifier, + result_tx, + })?; + let result = result_rx.await??; + Ok(result) + } + + pub fn read_specifier_contents( + &self, + specifier: BriocheModuleSpecifier, + ) -> anyhow::Result>> { + let (contents_tx, contents_rx) = std::sync::mpsc::channel(); + + self.tx.send(RuntimeBridgeMessage::ReadSpecifierContents { + specifier, + contents_tx, + })?; + let contents = contents_rx.recv()??; + Ok(contents) + } + + pub async fn project_root_for_module_path( + &self, + module_path: PathBuf, + ) -> anyhow::Result { + let (project_path_tx, project_path_rx) = tokio::sync::oneshot::channel(); + + self.tx + .send(RuntimeBridgeMessage::ProjectRootForModulePath { + module_path, + project_path_tx, + })?; + let project_path = project_path_rx.await??; + Ok(project_path) + } + + pub async fn bake_all( + &self, + recipes: Vec>, + bake_scope: BakeScope, + ) -> anyhow::Result>> { + let (results_tx, results_rx) = tokio::sync::oneshot::channel(); + + self.tx.send(RuntimeBridgeMessage::BakeAll { + recipes, + bake_scope, + results_tx, + })?; + let results = results_rx.await??; + Ok(results) + } + + pub async fn create_proxy(&self, recipe: Recipe) -> anyhow::Result { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx + .send(RuntimeBridgeMessage::CreateProxy { recipe, result_tx })?; + let result = result_rx.await??; + Ok(result) + } + + pub async fn read_blob(&self, blob_hash: BlobHash) -> anyhow::Result> { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx.send(RuntimeBridgeMessage::ReadBlob { + blob_hash, + result_tx, + })?; + let result = result_rx.await??; + Ok(result) + } + + pub async fn get_static( + &self, + specifier: BriocheModuleSpecifier, + static_: StaticQuery, + ) -> anyhow::Result { + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + self.tx.send(RuntimeBridgeMessage::GetStatic { + specifier, + static_, + result_tx, + })?; + let result = result_rx.await??; + Ok(result) + } +} + +enum RuntimeBridgeMessage { + LoadProjectFromModulePath { + path: std::path::PathBuf, + result_tx: tokio::sync::oneshot::Sender>, + }, + LoadSpecifierContents { + specifier: BriocheModuleSpecifier, + contents_tx: tokio::sync::oneshot::Sender>>>, + }, + ResolveSpecifier { + specifier: BriocheImportSpecifier, + referrer: BriocheModuleSpecifier, + resolved_tx: std::sync::mpsc::Sender>, + }, + UpdateVfsContents { + specifier: BriocheModuleSpecifier, + contents: Arc>, + result_tx: tokio::sync::oneshot::Sender>, + }, + ReloadProjectFromSpecifier { + specifier: BriocheModuleSpecifier, + result_tx: tokio::sync::oneshot::Sender>, + }, + ReadSpecifierContents { + specifier: BriocheModuleSpecifier, + contents_tx: std::sync::mpsc::Sender>>>, + }, + ProjectRootForModulePath { + module_path: PathBuf, + project_path_tx: tokio::sync::oneshot::Sender>, + }, + BakeAll { + recipes: Vec>, + bake_scope: BakeScope, + results_tx: tokio::sync::oneshot::Sender>>>, + }, + CreateProxy { + recipe: Recipe, + result_tx: tokio::sync::oneshot::Sender>, + }, + ReadBlob { + blob_hash: BlobHash, + result_tx: tokio::sync::oneshot::Sender>>, + }, + GetStatic { + specifier: BriocheModuleSpecifier, + static_: StaticQuery, + result_tx: tokio::sync::oneshot::Sender>, + }, +} diff --git a/crates/brioche-core/src/script/check.rs b/crates/brioche-core/src/script/check.rs index f18c9e13..557289d9 100644 --- a/crates/brioche-core/src/script/check.rs +++ b/crates/brioche-core/src/script/check.rs @@ -8,7 +8,7 @@ use crate::{ Brioche, }; -use super::specifier::BriocheModuleSpecifier; +use super::{bridge::RuntimeBridge, specifier::BriocheModuleSpecifier}; #[tracing::instrument(skip(brioche, projects), err)] pub async fn check( @@ -16,77 +16,128 @@ pub async fn check( projects: &Projects, project_hash: ProjectHash, ) -> anyhow::Result { - let specifier = projects.project_root_module_specifier(project_hash)?; - - let module_loader = super::BriocheModuleLoader::new(brioche, projects); - let compiler_host = - super::compiler_host::BriocheCompilerHost::new(brioche.clone(), projects.clone()).await; - compiler_host.load_document(&specifier).await?; - - let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { - module_loader: Some(Rc::new(module_loader.clone())), - source_map_getter: Some(Box::new(module_loader.clone())), - extensions: vec![ - super::compiler_host::brioche_compiler_host::init_ops(compiler_host), - super::js::brioche_js::init_ops(), - ], - ..Default::default() - }); - - let main_module: deno_core::ModuleSpecifier = "briocheruntime:///dist/index.js".parse()?; - - tracing::debug!(%specifier, %main_module, "evaluating module"); + let project_specifiers = projects.project_module_specifiers(project_hash)?; - let module_id = js_runtime.load_main_module(&main_module, None).await?; - let result = js_runtime.mod_evaluate(module_id); - js_runtime.run_event_loop(false).await?; - result.await?; + let runtime_bridge = RuntimeBridge::new(brioche.clone(), projects.clone()); - let module_namespace = js_runtime.get_module_namespace(module_id)?; - - let mut js_scope = js_runtime.handle_scope(); - let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); - - let export_key_name = "check"; - let export_key = deno_core::v8::String::new(&mut js_scope, export_key_name) - .context("failed to create V8 string")?; - let export = module_namespace - .get(&mut js_scope, export_key.into()) - .with_context(|| format!("expected module to have an export named {export_key_name:?}"))?; - let export: deno_core::v8::Local = export - .try_into() - .with_context(|| format!("expected export {export_key_name:?} to be a function"))?; - - tracing::debug!(%specifier, %main_module, ?export_key_name, "running function"); - - let files = projects.project_module_specifiers(project_hash)?; - let files = serde_v8::to_v8(&mut js_scope, &files)?; - - let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); - - let result = export.call(&mut js_scope, module_namespace.into(), &[files]); - let result = match result { - Some(result) => result, - None => { - let error_message = js_scope - .exception() - .map(|exception| { - anyhow::anyhow!(deno_core::error::JsError::from_v8_exception( - &mut js_scope, - exception - )) - }) - .unwrap_or_else(|| anyhow::anyhow!("unknown error when calling function")); - return Err(error_message) - .with_context(|| format!("error when calling {export_key_name:?}")); - } - }; + let result = check_with_deno(project_specifiers, runtime_bridge).await?; + Ok(result) +} - let diagnostics: Vec = serde_v8::from_v8(&mut js_scope, result)?; +async fn check_with_deno( + project_specifiers: Vec, + bridge: RuntimeBridge, +) -> anyhow::Result { + // Create a channel to get the result from the other Tokio runtime + let (result_tx, result_rx) = tokio::sync::oneshot::channel(); + + // Spawn a new thread for the new Tokio runtime + std::thread::spawn(move || { + // Create a new Tokio runtime + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build(); + let runtime = match runtime { + Ok(runtime) => runtime, + Err(error) => { + let _ = result_tx.send(Err(error.into())); + return; + } + }; - tracing::debug!(%specifier, %main_module, "finished evaluating module"); + // Spawn the main JS task on the new runtime. See this issue for + // more context on why this is required: + // https://github.com/brioche-dev/brioche/pull/105#issuecomment-2241289605 + let result = runtime.block_on(async move { + let module_loader = super::BriocheModuleLoader::new(bridge.clone()); + let compiler_host = super::compiler_host::BriocheCompilerHost::new(bridge).await; + + // Load all of the provided specifiers + compiler_host + .load_documents(project_specifiers.clone()) + .await?; + + let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { + module_loader: Some(Rc::new(module_loader)), + extensions: vec![ + super::compiler_host::brioche_compiler_host::init_ops(compiler_host), + super::js::brioche_js::init_ops(), + ], + ..Default::default() + }); + + // Get the specifier for the built-in runtime module + let main_module: deno_core::ModuleSpecifier = + "briocheruntime:///dist/index.js".parse()?; + + tracing::debug!(%main_module, "evaluating module"); + + // Load and evaluate the runtime module + let module_id = js_runtime.load_main_es_module(&main_module).await?; + let result = js_runtime.mod_evaluate(module_id); + js_runtime + .run_event_loop(deno_core::PollEventLoopOptions::default()) + .await?; + result.await?; + + let module_namespace = js_runtime.get_module_namespace(module_id)?; + + let mut js_scope = js_runtime.handle_scope(); + let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); + + // Get the `check` function from the module + let export_key_name = "check"; + let export_key = deno_core::v8::String::new(&mut js_scope, export_key_name) + .context("failed to create V8 string")?; + let export = module_namespace + .get(&mut js_scope, export_key.into()) + .with_context(|| { + format!("expected module to have an export named {export_key_name:?}") + })?; + let export: deno_core::v8::Local = export + .try_into() + .with_context(|| format!("expected export {export_key_name:?} to be a function"))?; + + tracing::debug!(%main_module, ?export_key_name, "running function"); + + // Call `check` with the project specifiers + + let files = serde_v8::to_v8(&mut js_scope, &project_specifiers)?; + + let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); + + let result = export.call(&mut js_scope, module_namespace.into(), &[files]); + let result = match result { + Some(result) => result, + None => { + let error_message = js_scope + .exception() + .map(|exception| { + anyhow::anyhow!(deno_core::error::JsError::from_v8_exception( + &mut js_scope, + exception + )) + }) + .unwrap_or_else(|| anyhow::anyhow!("unknown error when calling function")); + return Err(error_message) + .with_context(|| format!("error when calling {export_key_name:?}")); + } + }; + + // Deserialize the result as an array of `Dignostic` values + + let diagnostics: Vec = serde_v8::from_v8(&mut js_scope, result)?; + + tracing::debug!(%main_module, "finished evaluating module"); + + Ok(CheckResult { diagnostics }) + }); + + let _ = result_tx.send(result); + }); - Ok(CheckResult { diagnostics }) + let result = result_rx.await??; + Ok(result) } pub struct CheckResult { diff --git a/crates/brioche-core/src/script/compiler_host.rs b/crates/brioche-core/src/script/compiler_host.rs index e8eb23f6..90c18a7f 100644 --- a/crates/brioche-core/src/script/compiler_host.rs +++ b/crates/brioche-core/src/script/compiler_host.rs @@ -8,22 +8,21 @@ use std::{ use anyhow::Context as _; use deno_core::OpState; -use crate::{ - project::{analyze::find_imports, Projects}, - Brioche, -}; +use crate::project::analyze::find_imports; -use super::specifier::{self, resolve, BriocheImportSpecifier, BriocheModuleSpecifier}; +use super::{ + bridge::RuntimeBridge, + specifier::{self, BriocheImportSpecifier, BriocheModuleSpecifier}, +}; #[derive(Clone)] pub struct BriocheCompilerHost { - pub brioche: Brioche, - pub projects: Projects, + pub bridge: RuntimeBridge, pub documents: Arc>>, } impl BriocheCompilerHost { - pub async fn new(brioche: Brioche, projects: Projects) -> Self { + pub async fn new(bridge: RuntimeBridge) -> Self { let documents: HashMap<_, _> = specifier::runtime_specifiers_with_contents() .map(|(specifier, contents)| { let contents = std::str::from_utf8(&contents) @@ -38,15 +37,17 @@ impl BriocheCompilerHost { .collect(); Self { - brioche, - projects, + bridge, documents: Arc::new(RwLock::new(documents)), } } - pub async fn load_document(&self, specifier: &BriocheModuleSpecifier) -> anyhow::Result<()> { + pub async fn load_documents( + &self, + specifiers: Vec, + ) -> anyhow::Result<()> { let mut already_visited = HashSet::new(); - let mut specifiers_to_load = vec![specifier.clone()]; + let mut specifiers_to_load = specifiers; while let Some(specifier) = specifiers_to_load.pop() { if !already_visited.insert(specifier.clone()) { @@ -63,12 +64,9 @@ impl BriocheCompilerHost { match &specifier { BriocheModuleSpecifier::File { path } => { - self.projects - .load_from_module_path(&self.brioche, path, false) - .await - .inspect_err(|err| { - tracing::warn!("failed to load project from module path: {err:#}"); - })?; + self.bridge + .load_project_from_module_path(path.clone()) + .await?; } BriocheModuleSpecifier::Runtime { .. } => {} } @@ -76,9 +74,10 @@ impl BriocheCompilerHost { let contents = match contents { Some(contents) => contents, None => { - let contents = - super::specifier::load_specifier_contents(&self.brioche.vfs, &specifier) - .await?; + let contents = self + .bridge + .load_specifier_contents(specifier.clone()) + .await?; let contents = std::str::from_utf8(&contents).with_context(|| { format!("failed to parse module '{specifier}' contents as UTF-8 string") })?; @@ -131,7 +130,10 @@ impl BriocheCompilerHost { continue; } }; - let resolved = resolve(&self.projects, &import_specifier, &specifier); + + let resolved = self + .bridge + .resolve_specifier(import_specifier.clone(), specifier.clone()); let resolved = match resolved { Ok(resolved) => resolved, Err(error) => { @@ -174,18 +176,11 @@ impl BriocheCompilerHost { }); } - match &specifier { - BriocheModuleSpecifier::Runtime { .. } => {} - BriocheModuleSpecifier::File { path } => { - if let Some((file_id, _)) = self.brioche.vfs.load_cached(path)? { - self.brioche - .vfs - .update(file_id, Arc::new(contents.as_bytes().to_vec()))?; - }; - } - } + self.bridge + .update_vfs_contents(specifier.clone(), Arc::new(contents.as_bytes().to_vec())) + .await?; - self.load_document(&specifier).await?; + self.load_documents(vec![specifier]).await?; Ok(()) } @@ -210,32 +205,55 @@ impl BriocheCompilerHost { pub async fn reload_module_project(&self, uri: &url::Url) -> anyhow::Result<()> { let specifier: BriocheModuleSpecifier = uri.try_into()?; - match &specifier { - BriocheModuleSpecifier::File { path } => { - let project = self - .projects - .find_containing_project(path) - .with_context(|| format!("no project found for path '{}'", path.display()))?; - - if let Some(project) = project { - self.projects.clear(project).await?; - } - - self.projects - .load_from_module_path(&self.brioche, path, false) - .await?; + let did_update = self + .bridge + .reload_project_from_specifier(specifier.clone()) + .await?; - let mut documents = self - .documents - .write() - .map_err(|_| anyhow::anyhow!("failed to acquire lock on documents"))?; - documents.entry(specifier.clone()).and_modify(|doc| { - doc.version += 1; - }); - } - BriocheModuleSpecifier::Runtime { .. } => {} + if did_update { + let mut documents = self + .documents + .write() + .map_err(|_| anyhow::anyhow!("failed to acquire lock on documents"))?; + documents.entry(specifier.clone()).and_modify(|doc| { + doc.version += 1; + }); } + // let mut documents = self + // .documents + // .write() + // .map_err(|_| anyhow::anyhow!("failed to acquire lock on documents"))?; + // documents.entry(specifier.clone()).and_modify(|doc| { + // doc.version += 1; + // }); + + // match &specifier { + // BriocheModuleSpecifier::File { path } => { + // let project = self + // .projects + // .find_containing_project(path) + // .with_context(|| format!("no project found for path '{}'", path.display()))?; + + // if let Some(project) = project { + // self.projects.clear(project).await?; + // } + + // self.projects + // .load_from_module_path(&self.brioche, path, false) + // .await?; + + // let mut documents = self + // .documents + // .write() + // .map_err(|_| anyhow::anyhow!("failed to acquire lock on documents"))?; + // documents.entry(specifier.clone()).and_modify(|doc| { + // doc.version += 1; + // }); + // } + // BriocheModuleSpecifier::Runtime { .. } => {} + // } + Ok(()) } } @@ -323,8 +341,11 @@ pub fn op_brioche_resolve_module( let referrer: BriocheModuleSpecifier = referrer.parse().ok()?; let specifier: BriocheImportSpecifier = specifier.parse().ok()?; - let resolved = - super::specifier::resolve(&compiler_host.projects, &specifier, &referrer).ok()?; + + let resolved = compiler_host + .bridge + .resolve_specifier(specifier, referrer) + .ok()?; Some(resolved.to_string()) } diff --git a/crates/brioche-core/src/script/evaluate.rs b/crates/brioche-core/src/script/evaluate.rs index 7ad3f058..5c07b289 100644 --- a/crates/brioche-core/src/script/evaluate.rs +++ b/crates/brioche-core/src/script/evaluate.rs @@ -9,7 +9,7 @@ use crate::{ Brioche, }; -use super::BriocheModuleLoader; +use super::{bridge::RuntimeBridge, BriocheModuleLoader}; #[tracing::instrument(skip(brioche, projects, project_hash), fields(%project_hash), err)] pub async fn evaluate( @@ -18,118 +18,173 @@ pub async fn evaluate( project_hash: ProjectHash, export: &str, ) -> anyhow::Result> { - let module_loader = BriocheModuleLoader::new(brioche, projects); + let bridge = RuntimeBridge::new(brioche.clone(), projects.clone()); + let main_module = projects.project_root_module_specifier(project_hash)?; + + let result = evaluate_with_deno(project_hash, export.to_string(), main_module, bridge).await?; + Ok(result) +} + +async fn evaluate_with_deno( + project_hash: ProjectHash, + export: String, + main_module: super::specifier::BriocheModuleSpecifier, + bridge: RuntimeBridge, +) -> anyhow::Result> { + // Create a channel to get the result from the other Tokio runtime + let (result_tx, result_rx) = + tokio::sync::oneshot::channel::>>(); + let bake_scope = BakeScope::Project { project_hash, - export: export.to_string(), + export: export.clone(), }; - let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { - module_loader: Some(Rc::new(module_loader.clone())), - source_map_getter: Some(Box::new(module_loader.clone())), - extensions: vec![ - super::brioche_rt::init_ops(brioche.clone(), projects.clone(), bake_scope), - super::js::brioche_js::init_ops(), - ], - ..Default::default() - }); - let main_module = projects.project_root_module_specifier(project_hash)?; - let main_module: deno_core::ModuleSpecifier = main_module.into(); - - tracing::debug!(%main_module, "evaluating module"); - - let module_id = js_runtime.load_main_module(&main_module, None).await?; - let result = js_runtime.mod_evaluate(module_id); - js_runtime.run_event_loop(false).await?; - result.await?; - - let module_namespace = js_runtime.get_module_namespace(module_id)?; - - let result = { - let mut js_scope = js_runtime.handle_scope(); - let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); - - let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); - - let export_key = deno_core::v8::String::new(&mut js_scope, export) - .context("failed to create V8 string")?; - let export_value = module_namespace - .get(&mut js_scope, export_key.into()) - .with_context(|| format!("expected module to have an export named {export}"))?; - let export_value: deno_core::v8::Local = - export_value - .try_into() - .with_context(|| format!("expected export named {export} to be a function"))?; - - tracing::debug!(%main_module, %export, "running exported function"); - - let result = export_value.call(&mut js_scope, module_namespace.into(), &[]); - let result = match result { - Some(result) => result, - None => { - if let Some(exception) = js_scope.exception() { - return Err(anyhow::anyhow!( - deno_core::error::JsError::from_v8_exception(&mut js_scope, exception) - )) - .with_context(|| format!("error when calling {export}")); - } else { - anyhow::bail!("unknown error when calling {export}"); - } + // Spawn a new thread for the new Tokio runtime + std::thread::spawn(move || { + // Create a new Tokio runtime + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build(); + let runtime = match runtime { + Ok(runtime) => runtime, + Err(error) => { + let _ = result_tx.send(Err(error.into())); + return; } }; - deno_core::v8::Global::new(&mut js_scope, result) - }; - let resolved_result = js_runtime.resolve_value(result).await?; - - let serialized_result = { - let mut js_scope = js_runtime.handle_scope(); - let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); - - let resolved_result = deno_core::v8::Local::new(&mut js_scope, resolved_result); - let resolved_result: deno_core::v8::Local = resolved_result - .try_into() - .context("expected result to be an object")?; - - let serialize_key = deno_core::v8::String::new(&mut js_scope, "briocheSerialize") - .context("failed to create V8 string")?; - let result_serialize = resolved_result - .get(&mut js_scope, serialize_key.into()) - .context("expected value to have a `briocheSerialize` function")?; - let result_serialize: deno_core::v8::Local = result_serialize - .try_into() - .context("expected `briocheSerialize` to be a function")?; - - let serialized_result = result_serialize.call(&mut js_scope, resolved_result.into(), &[]); - let serialized_result = match serialized_result { - Some(serialized_result) => serialized_result, - None => { - if let Some(exception) = js_scope.exception() { - return Err(anyhow::anyhow!( - deno_core::error::JsError::from_v8_exception(&mut js_scope, exception) - )) - .with_context(|| format!("error when serializing result from {export}")); - } else { - anyhow::bail!("unknown error when serializing result from {export}"); - } - } - }; - deno_core::v8::Global::new(&mut js_scope, serialized_result) - }; - - let serialized_resolved_result = js_runtime.resolve_value(serialized_result).await?; - - let mut js_scope = js_runtime.handle_scope(); - - let serialized_resolved_result = - deno_core::v8::Local::new(&mut js_scope, serialized_resolved_result); - - let recipe: WithMeta = serde_v8::from_v8(&mut js_scope, serialized_resolved_result) - .with_context(|| { - format!("invalid recipe returned when serializing result from {export}") - })?; - - tracing::debug!(%main_module, recipe_hash = %recipe.hash(), "finished evaluating module"); + // Spawn the main JS task on the new runtime. See this issue for + // more context on why this is required: + // https://github.com/brioche-dev/brioche/pull/105#issuecomment-2241289605 + let result = runtime.block_on(async move { + // Create the runtime + let module_loader = BriocheModuleLoader::new(bridge.clone()); + let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { + module_loader: Some(Rc::new(module_loader)), + extensions: vec![ + super::brioche_rt::init_ops(bridge, bake_scope), + super::js::brioche_js::init_ops(), + ], + ..Default::default() + }); + + let main_module: deno_core::ModuleSpecifier = main_module.into(); + + tracing::debug!(%main_module, "evaluating module"); + + // Load and evaluate the main module + let module_id = js_runtime.load_main_es_module(&main_module).await?; + let result = js_runtime.mod_evaluate(module_id); + js_runtime + .run_event_loop(deno_core::PollEventLoopOptions::default()) + .await?; + result.await?; + + let module_namespace = js_runtime.get_module_namespace(module_id)?; + + // Call the provided export from the module + let result = { + let mut js_scope = js_runtime.handle_scope(); + let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); + + let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); + + let export_key = deno_core::v8::String::new(&mut js_scope, &export) + .context("failed to create V8 string")?; + let export_value = module_namespace + .get(&mut js_scope, export_key.into()) + .with_context(|| format!("expected module to have an export named {export}"))?; + let export_value: deno_core::v8::Local = + export_value + .try_into() + .with_context(|| format!("expected export named {export} to be a function"))?; + + tracing::debug!(%main_module, %export, "running exported function"); + + let result = export_value.call(&mut js_scope, module_namespace.into(), &[]); + + let result = match result { + Some(result) => result, + None => { + if let Some(exception) = js_scope.exception() { + return Err(anyhow::anyhow!( + deno_core::error::JsError::from_v8_exception(&mut js_scope, exception) + )) + .with_context(|| format!("error when calling {export}")); + } else { + anyhow::bail!("unknown error when calling {export}"); + } + } + }; + deno_core::v8::Global::new(&mut js_scope, result) + }; + + // Resolve the export if it's a promise + let resolved_result_fut = js_runtime.resolve(result); + let resolved_result = js_runtime.with_event_loop_promise(resolved_result_fut, Default::default()).await?; + + // Call the `briocheSerialize` function on the result + let serialized_result = { + let mut js_scope = js_runtime.handle_scope(); + let mut js_scope = deno_core::v8::TryCatch::new(&mut js_scope); + + let resolved_result = deno_core::v8::Local::new(&mut js_scope, resolved_result); + let resolved_result: deno_core::v8::Local = resolved_result + .try_into() + .context("expected result to be an object")?; + + let serialize_key = deno_core::v8::String::new(&mut js_scope, "briocheSerialize") + .context("failed to create V8 string")?; + let result_serialize = resolved_result + .get(&mut js_scope, serialize_key.into()) + .context("expected value to have a `briocheSerialize` function")?; + let result_serialize: deno_core::v8::Local = result_serialize + .try_into() + .context("expected `briocheSerialize` to be a function")?; + + let serialized_result = result_serialize.call(&mut js_scope, resolved_result.into(), &[]); + + let serialized_result = match serialized_result { + Some(serialized_result) => serialized_result, + None => { + if let Some(exception) = js_scope.exception() { + return Err(anyhow::anyhow!( + deno_core::error::JsError::from_v8_exception(&mut js_scope, exception) + )) + .with_context(|| format!("error when serializing result from {export}")); + } else { + anyhow::bail!("unknown error when serializing result from {export}"); + } + } + }; + + deno_core::v8::Global::new(&mut js_scope, serialized_result) + }; + + // Resolve the result of `briocheSerialize` if it's a promise + let serialized_resolved_result_fut = js_runtime.resolve(serialized_result); + let serialized_resolved_result = js_runtime.with_event_loop_promise(serialized_resolved_result_fut, Default::default()).await?; + + let mut js_scope = js_runtime.handle_scope(); + + let serialized_resolved_result = + deno_core::v8::Local::new(&mut js_scope, serialized_resolved_result); + + // Deserialize the result as a recipe + let recipe: WithMeta = serde_v8::from_v8(&mut js_scope, serialized_resolved_result) + .with_context(|| { + format!("invalid recipe returned when serializing result from {export}") + })?; + + tracing::debug!(%main_module, recipe_hash = %recipe.hash(), "finished evaluating module"); + + Ok(recipe) + }); + + let _ = result_tx.send(result); + }); - Ok(recipe) + let result = result_rx.await??; + Ok(result) } diff --git a/crates/brioche-core/src/script/lsp.rs b/crates/brioche-core/src/script/lsp.rs index a7978bbd..6fc94db5 100644 --- a/crates/brioche-core/src/script/lsp.rs +++ b/crates/brioche-core/src/script/lsp.rs @@ -12,6 +12,7 @@ use crate::script::compiler_host::{brioche_compiler_host, BriocheCompilerHost}; use crate::script::format::format_code; use crate::{Brioche, BriocheBuilder}; +use super::bridge::RuntimeBridge; use super::specifier::BriocheModuleSpecifier; /// The maximum time we spend resolving projects when regenerating a @@ -19,22 +20,21 @@ use super::specifier::BriocheModuleSpecifier; const LOCKFILE_LOAD_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); pub struct BriocheLspServer { + bridge: RuntimeBridge, compiler_host: BriocheCompilerHost, client: Client, js_lsp: JsLspTask, } impl BriocheLspServer { - pub async fn new( - local_pool: &tokio_util::task::LocalPoolHandle, - brioche: Brioche, - projects: Projects, - client: Client, - ) -> anyhow::Result { - let compiler_host = BriocheCompilerHost::new(brioche.clone(), projects.clone()).await; - let js_lsp = js_lsp_task(local_pool, compiler_host.clone()); + pub async fn new(brioche: Brioche, projects: Projects, client: Client) -> anyhow::Result { + let bridge = RuntimeBridge::new(brioche.clone(), projects.clone()); + + let compiler_host = BriocheCompilerHost::new(bridge.clone()).await; + let js_lsp = js_lsp_task(compiler_host.clone(), bridge.clone()); Ok(Self { + bridge, compiler_host, client, js_lsp, @@ -103,7 +103,10 @@ impl LanguageServer for BriocheLspServer { let specifier = lsp_uri_to_module_specifier(¶ms.text_document.uri); match specifier { Ok(specifier) => { - let result = self.compiler_host.load_document(&specifier).await; + let result = self + .compiler_host + .load_documents(vec![specifier.clone()]) + .await; match result { Ok(()) => {} Err(error) => { @@ -146,14 +149,14 @@ impl LanguageServer for BriocheLspServer { let (lockfile_tx, lockfile_rx) = tokio::sync::oneshot::channel(); std::thread::spawn({ - let compiler_host = self.compiler_host.clone(); + let bridge = self.bridge.clone(); let module_path = module_path.clone(); move || { let local_set = tokio::task::LocalSet::new(); local_set.spawn_local(async move { let result = try_update_lockfile_for_module( - compiler_host.clone(), + bridge, module_path, LOCKFILE_LOAD_TIMEOUT, ) @@ -458,16 +461,11 @@ impl LanguageServer for BriocheLspServer { } async fn try_update_lockfile_for_module( - compiler_host: BriocheCompilerHost, + bridge: RuntimeBridge, module_path: PathBuf, load_timeout: std::time::Duration, ) -> anyhow::Result { - let project_hash = compiler_host - .projects - .find_containing_project(&module_path) - .context("failed to get project path")? - .context("containing project not found")?; - let project_path = compiler_host.projects.project_root(project_hash)?.clone(); + let project_path = bridge.project_root_for_module_path(module_path).await?; // The instances of `Brioche` and `Projects` used for the LSP aren't // suitable for generating lockfiles (access to the registry is disabled, @@ -538,120 +536,130 @@ impl JsLspTask { } } -fn js_lsp_task( - local_pool: &tokio_util::task::LocalPoolHandle, - compiler_host: BriocheCompilerHost, -) -> JsLspTask { +fn js_lsp_task(compiler_host: BriocheCompilerHost, bridge: RuntimeBridge) -> JsLspTask { let (tx, mut rx) = tokio::sync::mpsc::channel::<( JsLspMessage, tokio::sync::oneshot::Sender, )>(1); - let js_lsp_task = local_pool.spawn_pinned(|| async move { - let module_loader = - super::BriocheModuleLoader::new(&compiler_host.brioche, &compiler_host.projects); - - tracing::info!("building JS LSP"); - - let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { - module_loader: Some(Rc::new(module_loader.clone())), - source_map_getter: Some(Box::new(module_loader.clone())), - extensions: vec![ - brioche_compiler_host::init_ops(compiler_host), - super::js::brioche_js::init_ops(), - ], - ..Default::default() - }); - - let main_module: deno_core::ModuleSpecifier = "briocheruntime:///dist/index.js".parse()?; - - tracing::info!(%main_module, "evaluating module"); - - tracing::info!("loading module"); - - let module_id = js_runtime.load_main_module(&main_module, None).await?; - let result = js_runtime.mod_evaluate(module_id); - js_runtime.run_event_loop(false).await?; - result.await?; - - let module_namespace = js_runtime.get_module_namespace(module_id)?; + std::thread::spawn(move || { + let module_loader = super::BriocheModuleLoader::new(bridge); - tracing::info!("calling JS"); - - let js_lsp = { - let mut js_scope = js_runtime.handle_scope(); - let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build(); + let runtime = match runtime { + Ok(runtime) => runtime, + Err(error) => { + tracing::error!("failed to create runtime: {error:#}"); + return; + } + }; - let export_key_name = "buildLsp"; - let export_key = deno_core::v8::String::new(&mut js_scope, export_key_name) - .context("failed to create V8 string")?; - let export = module_namespace - .get(&mut js_scope, export_key.into()) - .with_context(|| { - format!("expected module to have an export named {export_key_name:?}") - })?; - let export: deno_core::v8::Local = export - .try_into() - .with_context(|| format!("expected export {export_key_name:?} to be a function"))?; + let result = runtime.block_on(async move { + tracing::info!("building JS LSP"); - tracing::info!(%main_module, ?export_key_name, "running function"); + let mut js_runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions { + module_loader: Some(Rc::new(module_loader)), + extensions: vec![ + brioche_compiler_host::init_ops(compiler_host), + super::js::brioche_js::init_ops(), + ], + ..Default::default() + }); - let js_lsp = export - .call(&mut js_scope, module_namespace.into(), &[]) - .context("failed to build LSP")?; - let js_lsp: deno_core::v8::Local = - js_lsp.try_into().context("expected LSP to be an object")?; - deno_core::v8::Global::new(&mut js_scope, js_lsp) - }; + let main_module: deno_core::ModuleSpecifier = + "briocheruntime:///dist/index.js".parse()?; + + tracing::info!(%main_module, "evaluating module"); + + tracing::info!("loading module"); + + let module_id = js_runtime.load_main_es_module(&main_module).await?; + let result = js_runtime.mod_evaluate(module_id); + js_runtime + .run_event_loop(deno_core::PollEventLoopOptions::default()) + .await?; + result.await?; + + let module_namespace = js_runtime.get_module_namespace(module_id)?; + + tracing::info!("calling JS"); + + let js_lsp = { + let mut js_scope = js_runtime.handle_scope(); + let module_namespace = deno_core::v8::Local::new(&mut js_scope, module_namespace); + + let export_key_name = "buildLsp"; + let export_key = deno_core::v8::String::new(&mut js_scope, export_key_name) + .context("failed to create V8 string")?; + let export = module_namespace + .get(&mut js_scope, export_key.into()) + .with_context(|| { + format!("expected module to have an export named {export_key_name:?}") + })?; + let export: deno_core::v8::Local = + export.try_into().with_context(|| { + format!("expected export {export_key_name:?} to be a function") + })?; + + tracing::info!(%main_module, ?export_key_name, "running function"); + + let js_lsp = export + .call(&mut js_scope, module_namespace.into(), &[]) + .context("failed to build LSP")?; + let js_lsp: deno_core::v8::Local = + js_lsp.try_into().context("expected LSP to be an object")?; + deno_core::v8::Global::new(&mut js_scope, js_lsp) + }; - tracing::info!("built JS LSP"); + tracing::info!("built JS LSP"); - while let Some((message, response_tx)) = rx.recv().await { - tracing::info!(?message, "got message"); - let response = match message { - JsLspMessage::Completion(params) => { - call_method_1(&mut js_runtime, &js_lsp, "completion", ¶ms) - } - JsLspMessage::Diagnostic(params) => { - call_method_1(&mut js_runtime, &js_lsp, "diagnostic", ¶ms) - } - JsLspMessage::GotoDefinition(params) => { - call_method_1(&mut js_runtime, &js_lsp, "gotoDefinition", ¶ms) - } - JsLspMessage::Hover(params) => { - call_method_1(&mut js_runtime, &js_lsp, "hover", ¶ms) - } - JsLspMessage::DocumentHighlight(params) => { - call_method_1(&mut js_runtime, &js_lsp, "documentHighlight", ¶ms) - } - JsLspMessage::References(params) => { - call_method_1(&mut js_runtime, &js_lsp, "references", ¶ms) - } - JsLspMessage::PrepareRename(params) => { - call_method_1(&mut js_runtime, &js_lsp, "prepareRename", ¶ms) - } - JsLspMessage::Rename(params) => { - call_method_1(&mut js_runtime, &js_lsp, "rename", ¶ms) - } - }; + while let Some((message, response_tx)) = rx.recv().await { + tracing::info!(?message, "got message"); + let response = match message { + JsLspMessage::Completion(params) => { + call_method_1(&mut js_runtime, &js_lsp, "completion", ¶ms) + } + JsLspMessage::Diagnostic(params) => { + call_method_1(&mut js_runtime, &js_lsp, "diagnostic", ¶ms) + } + JsLspMessage::GotoDefinition(params) => { + call_method_1(&mut js_runtime, &js_lsp, "gotoDefinition", ¶ms) + } + JsLspMessage::Hover(params) => { + call_method_1(&mut js_runtime, &js_lsp, "hover", ¶ms) + } + JsLspMessage::DocumentHighlight(params) => { + call_method_1(&mut js_runtime, &js_lsp, "documentHighlight", ¶ms) + } + JsLspMessage::References(params) => { + call_method_1(&mut js_runtime, &js_lsp, "references", ¶ms) + } + JsLspMessage::PrepareRename(params) => { + call_method_1(&mut js_runtime, &js_lsp, "prepareRename", ¶ms) + } + JsLspMessage::Rename(params) => { + call_method_1(&mut js_runtime, &js_lsp, "rename", ¶ms) + } + }; - match response { - Ok(response) => { - let _ = response_tx.send(response); - } - Err(error) => { - tracing::error!("failed to call method: {error:#}"); - return Err(error); - } - }; - } + match response { + Ok(response) => { + let _ = response_tx.send(response); + } + Err(error) => { + tracing::error!("failed to call method: {error:#}"); + return Err(error); + } + }; + } - anyhow::Ok(()) - }); + anyhow::Ok(()) + }); - tokio::task::spawn(async move { - if let Err(error) = js_lsp_task.await { - tracing::error!("error in JS LSP task: {error}"); + if let Err(error) = result { + tracing::error!("failed to run runtime: {error:#}"); } }); diff --git a/crates/brioche/src/lsp.rs b/crates/brioche/src/lsp.rs index bf8fcba0..734a5cda 100644 --- a/crates/brioche/src/lsp.rs +++ b/crates/brioche/src/lsp.rs @@ -11,10 +11,7 @@ pub async fn lsp(_args: LspArgs) -> anyhow::Result<()> { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); - let local_pool = tokio_util::task::LocalPoolHandle::new(5); - let (service, socket) = tower_lsp::LspService::new(move |client| { - let local_pool = &local_pool; futures::executor::block_on(async move { let (reporter, _guard) = brioche_core::reporter::start_lsp_reporter(client.clone()); let brioche = brioche_core::BriocheBuilder::new(reporter) @@ -23,10 +20,8 @@ pub async fn lsp(_args: LspArgs) -> anyhow::Result<()> { .build() .await?; let projects = brioche_core::project::Projects::default(); - let lsp_server = brioche_core::script::lsp::BriocheLspServer::new( - local_pool, brioche, projects, client, - ) - .await?; + let lsp_server = + brioche_core::script::lsp::BriocheLspServer::new(brioche, projects, client).await?; anyhow::Ok(lsp_server) }) .expect("failed to build LSP") diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 37d0dc61..0be1738e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.77" +channel = "1.79" profile = "default" targets = ["x86_64-unknown-linux-musl"]