diff --git a/scarb/src/compiler/plugin/proc_macro/host.rs b/scarb/src/compiler/plugin/proc_macro/host.rs index 7e8765ef9..2a87f363f 100644 --- a/scarb/src/compiler/plugin/proc_macro/host.rs +++ b/scarb/src/compiler/plugin/proc_macro/host.rs @@ -33,6 +33,7 @@ use itertools::Itertools; use scarb_stable_hash::short_hash; use smol_str::SmolStr; use std::any::Any; +use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::sync::{Arc, OnceLock, RwLock}; @@ -1172,3 +1173,66 @@ impl ProcMacroHost { &self.macros } } + +/// A global storage for dynamically-loaded procedural macros. +/// Loads dynamic shared libraries and hides them beside [`ProcMacroHostPlugin`]. +/// Guarantees that every library is loaded exactly once, +/// but does not prevent loading multiple versions of the same library. +#[derive(Default)] +pub struct ProcMacroRepository { + /// A mapping between the [`PackageId`] of the package which defines the plugin + /// and the [`ProcMacroHostPlugin`] holding the underlying shared library. + macros: HashMap>, +} + +impl ProcMacroRepository { + /// Inserts the pre-loaded [`ProcMacroInstance`] to the storage, without checking + /// whether some plugin for the [`PackageId`] already exists. + pub fn insert_prebuilt_unchecked( + &mut self, + package_id: PackageId, + instance: Arc, + ) -> Result<()> { + let plugin = Arc::new(ProcMacroHostPlugin::try_new(vec![instance])?); + self.macros.insert(package_id, plugin); + Ok(()) + } + + /// Returns the [`ProcMacroHostPlugin`] representing the procedural macros defined in the [`Package`]. + /// Loads the underlying shared library if it has not been loaded yet. + pub fn get_or_load( + &mut self, + package: Package, + config: &Config, + ) -> Result> { + match self.macros.entry(package.id) { + Entry::Occupied(slot) => Ok(slot.get().clone()), + Entry::Vacant(slot) => { + let lib_path = package + .shared_lib_path(config) + .context("could not resolve shared library path")?; + + let instance = Arc::new(ProcMacroInstance::try_new(package.id, lib_path)?); + let plugin = Arc::new(ProcMacroHostPlugin::try_new(vec![instance])?); + + slot.insert(plugin.clone()); + Ok(plugin) + } + } + } + + /// Returns the [`ProcMacroHostPlugin`] corresponding to the given [`PackageId`] if it exists. + pub fn get(&self, package_id: &PackageId) -> Option> { + self.macros.get(package_id).cloned() + } + + /// Drops the [`ProcMacroRepository`], invoking the necessary + /// [`ProcMacroHostPlugin::post_process`] on each stored proc macro. + pub fn safe_drop(self, db: &dyn SemanticGroup) -> Result<()> { + for plugin in self.macros.values() { + plugin.post_process(db)?; + } + + Ok(()) + } +}