mol_core/
plugin.rs

1use std::ffi::OsStr;
2use std::path::Path;
3use std::rc::Rc;
4
5use anyhow::Context;
6use libloading::Library;
7
8use crate::changesets::Changesets;
9use crate::error::PluginLoadError;
10
11pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION");
12pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION");
13
14pub struct PluginContext<'a> {
15  pub root_dir: &'a Path,
16  pub dry_run: bool,
17  pub config: &'a Changesets,
18}
19
20pub trait Plugin {
21  fn name(&self) -> &str;
22
23  fn on_load(&mut self, _context: &PluginContext) {}
24
25  fn on_unload(&mut self) {}
26
27  fn pre_command(&self, _command: &str, _context: &PluginContext) -> anyhow::Result<()> {
28    Ok(())
29  }
30
31  fn post_command(&self, _command: &str, _context: &PluginContext) -> anyhow::Result<()> {
32    Ok(())
33  }
34}
35
36pub struct PluginProxy {
37  plugin: Box<dyn Plugin>,
38  _lib: Rc<Library>,
39}
40
41impl Plugin for PluginProxy {
42  fn name(&self) -> &str {
43    self.plugin.name()
44  }
45
46  fn on_load(&mut self, context: &PluginContext) {
47    self.plugin.on_load(context)
48  }
49
50  fn on_unload(&mut self) {
51    self.plugin.on_unload()
52  }
53
54  fn pre_command(&self, command: &str, context: &PluginContext) -> anyhow::Result<()> {
55    self.plugin.pre_command(command, context)
56  }
57
58  fn post_command(&self, command: &str, context: &PluginContext) -> anyhow::Result<()> {
59    self.plugin.post_command(command, context)
60  }
61}
62
63#[repr(C)]
64pub struct PluginRegistrar {
65  plugins: Vec<PluginProxy>,
66  lib: Rc<Library>,
67}
68
69impl PluginRegistrar {
70  fn new(lib: Rc<Library>) -> PluginRegistrar {
71    PluginRegistrar {
72      lib,
73      plugins: Default::default(),
74    }
75  }
76
77  fn consume(self) -> Vec<PluginProxy> {
78    self.plugins
79  }
80
81  pub fn register(&mut self, plugin: Box<dyn Plugin>) {
82    let proxy = PluginProxy {
83      plugin,
84      _lib: Rc::clone(&self.lib),
85    };
86    self.plugins.push(proxy);
87  }
88}
89
90pub struct PluginDeclaration {
91  pub rustc_version: &'static str,
92  pub core_version: &'static str,
93  pub register: unsafe extern "C" fn(&mut PluginRegistrar),
94}
95
96#[macro_export]
97macro_rules! declare_plugin {
98  ($register:expr) => {
99    #[doc(hidden)]
100    #[no_mangle]
101    pub static plugin_declaration: $crate::plugin::PluginDeclaration =
102      $crate::plugin::PluginDeclaration {
103        rustc_version: $crate::plugin::RUSTC_VERSION,
104        core_version: $crate::plugin::CORE_VERSION,
105        register: $register,
106      };
107  };
108}
109
110#[derive(Default)]
111pub struct PluginManager {
112  pub plugins: Vec<PluginProxy>,
113  libraries: Vec<Rc<Library>>,
114}
115
116impl PluginManager {
117  /// # Safety
118  ///
119  /// This function opens a compiled cdylib and thus should not be called on cdylib that doesn't implement declare_plugin! macro
120  pub unsafe fn load<P: AsRef<OsStr>>(
121    &mut self,
122    library_path: P,
123    context: &PluginContext,
124  ) -> anyhow::Result<()> {
125    let library = Rc::new(Library::new(library_path)?);
126
127    let decl = library
128      .get::<*mut PluginDeclaration>(b"plugin_declaration\0")?
129      .read();
130
131    // version checks to prevent accidental ABI incompatibilities
132    if decl.rustc_version != RUSTC_VERSION || decl.core_version != CORE_VERSION {
133      return Err(PluginLoadError::IncompatibleVersion.into());
134    }
135
136    let mut registrar = PluginRegistrar::new(Rc::clone(&library));
137
138    (decl.register)(&mut registrar);
139
140    let mut plugins = registrar.consume();
141
142    for plugin in &mut plugins {
143      plugin.on_load(context);
144    }
145
146    self.plugins.extend(plugins);
147    self.libraries.push(library);
148
149    Ok(())
150  }
151}
152
153unsafe impl Send for PluginManager {}
154
155unsafe impl Sync for PluginManager {}
156
157impl Drop for PluginManager {
158  fn drop(&mut self) {
159    for mut plugin in self.plugins.drain(..) {
160      plugin.on_unload();
161    }
162
163    for library in self.libraries.drain(..) {
164      drop(library);
165    }
166  }
167}
168
169impl Plugin for PluginManager {
170  fn name(&self) -> &str {
171    "PluginManager"
172  }
173
174  fn pre_command(&self, command: &str, context: &PluginContext) -> anyhow::Result<()> {
175    for plugin in &self.plugins {
176      plugin
177        .pre_command(command, context)
178        .with_context(|| format!("Faliure at pre-command for {} plugin", plugin.name()))?;
179    }
180
181    Ok(())
182  }
183
184  fn post_command(&self, command: &str, context: &PluginContext) -> anyhow::Result<()> {
185    for plugin in &self.plugins {
186      plugin
187        .post_command(command, context)
188        .with_context(|| format!("Faliure at post-command for {} plugin", plugin.name()))?;
189    }
190
191    Ok(())
192  }
193}