devrc_plugins/
execution.rs

1use std::ffi::OsStr;
2
3use devrc_core::{logging::LogLevel, workshop::Designer};
4use libloading::{Library, Symbol};
5
6use crate::{
7    config::{Config, ExecutionConfig},
8    errors::{DevrcPluginError, DevrcPluginResult},
9    plugin::Plugin,
10};
11
12pub trait ExecutionPlugin: Plugin {
13    fn execute(
14        &self,
15        execution_config: ExecutionConfig,
16        code: &str,
17        environment: &indexmap::IndexMap<String, String>,
18    ) -> DevrcPluginResult<i32>;
19}
20
21/// Declare a plugin type and its constructor.
22///
23/// # Notes
24///
25/// This works by automatically generating an `extern "C"` function with a
26/// pre-defined signature and symbol name. Therefore you will only be able to
27/// declare one plugin per library.
28#[macro_export]
29macro_rules! declare_execution_plugin {
30    ($plugin_type:ty, $constructor:path) => {
31        #[no_mangle]
32        pub extern "C" fn _plugin_create() -> *mut $crate::ExecutionPlugin {
33            // make sure the constructor is the correct type.
34            let constructor: fn() -> $plugin_type = $constructor;
35
36            let object = constructor();
37            let boxed: Box<$crate::ExecutionPlugin> = Box::new(object);
38            Box::into_raw(boxed)
39        }
40    };
41}
42
43#[derive(Default)]
44pub struct ExecutionPluginManager {
45    plugins: Vec<(String, Box<dyn ExecutionPlugin>)>,
46    loaded_libraries: Vec<Library>,
47    designer: Designer,
48    logger: LogLevel,
49}
50
51impl std::fmt::Debug for ExecutionPluginManager {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("PluginManager").finish()
54    }
55}
56
57impl ExecutionPluginManager {
58    pub fn new() -> ExecutionPluginManager {
59        ExecutionPluginManager {
60            plugins: Vec::new(),
61            loaded_libraries: Vec::new(),
62            designer: Designer::default(),
63            logger: LogLevel::default(),
64        }
65    }
66
67    pub fn setup_logger(&mut self, logger: LogLevel) {
68        self.logger = logger;
69    }
70
71    /// # Safety
72    ///
73    /// This function load plugin from dynamic library
74    pub unsafe fn load_plugin<P: AsRef<OsStr>>(
75        &mut self,
76        name: &str,
77        filename: P,
78        logger: LogLevel,
79    ) -> DevrcPluginResult<()> {
80        type PluginCreate = unsafe fn() -> *mut dyn ExecutionPlugin;
81
82        let lib = Library::new(filename.as_ref())?;
83
84        // We need to keep the library around otherwise our plugin's vtable will
85        // point to garbage. We do this little dance to make sure the library
86        // doesn't end up getting moved.
87        self.loaded_libraries.push(lib);
88
89        let lib = self.loaded_libraries.last().unwrap();
90
91        let constructor: Symbol<PluginCreate> = lib.get(b"_plugin_create")?;
92        let boxed_raw = constructor();
93
94        let mut plugin = Box::from_raw(boxed_raw);
95
96        logger.debug(
97            &format!(
98                "\n==> Loading PLUGIN: `{}` as `{}` from `{:?}` ...",
99                plugin.name(),
100                name,
101                filename.as_ref()
102            ),
103            &self.designer.banner(),
104        );
105
106        plugin.on_plugin_load(Config {
107            logger,
108            designer: self.designer,
109        });
110        self.plugins.push((name.to_string(), plugin));
111        Ok(())
112    }
113
114    /// Unload all plugins and loaded plugin libraries, making sure to fire
115    /// their `on_plugin_unload()` methods so they can do any necessary cleanup.
116    pub fn unload(&mut self) {
117        self.logger
118            .debug("\n==> Unloading PLUGINS ...", &self.designer.banner());
119
120        for (name, plugin) in self.plugins.drain(..) {
121            self.logger.debug(
122                &format!("\n==> Upload PLUGIN: `{}` named `{}` ", plugin.name(), name),
123                &self.designer.banner(),
124            );
125            plugin.on_plugin_unload();
126        }
127
128        for lib in self.loaded_libraries.drain(..) {
129            drop(lib);
130        }
131    }
132
133    pub fn get_plugin(
134        &mut self,
135        plugin_name: &str,
136    ) -> DevrcPluginResult<&Box<dyn ExecutionPlugin>> {
137        for (name, plugin) in &self.plugins {
138            if name == plugin_name {
139                return Ok(plugin);
140            }
141        }
142
143        Err(DevrcPluginError::NotFound(plugin_name.to_string()))
144    }
145}
146
147impl Drop for ExecutionPluginManager {
148    fn drop(&mut self) {
149        if !self.plugins.is_empty() || !self.loaded_libraries.is_empty() {
150            self.unload();
151        }
152    }
153}