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 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 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}