1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use std::ffi::OsStr;

use devrc_core::{logging::LogLevel, workshop::Designer};
use libloading::{Library, Symbol};

use crate::{
    config::{Config, ExecutionConfig},
    errors::{DevrcPluginError, DevrcPluginResult},
    plugin::Plugin,
};

pub trait ExecutionPlugin: Plugin {
    fn execute(
        &self,
        execution_config: ExecutionConfig,
        code: &str,
        environment: &indexmap::IndexMap<String, String>,
    ) -> DevrcPluginResult<i32>;
}

/// Declare a plugin type and its constructor.
///
/// # Notes
///
/// This works by automatically generating an `extern "C"` function with a
/// pre-defined signature and symbol name. Therefore you will only be able to
/// declare one plugin per library.
#[macro_export]
macro_rules! declare_execution_plugin {
    ($plugin_type:ty, $constructor:path) => {
        #[no_mangle]
        pub extern "C" fn _plugin_create() -> *mut $crate::ExecutionPlugin {
            // make sure the constructor is the correct type.
            let constructor: fn() -> $plugin_type = $constructor;

            let object = constructor();
            let boxed: Box<$crate::ExecutionPlugin> = Box::new(object);
            Box::into_raw(boxed)
        }
    };
}

#[derive(Default)]
pub struct ExecutionPluginManager {
    plugins: Vec<(String, Box<dyn ExecutionPlugin>)>,
    loaded_libraries: Vec<Library>,
    designer: Designer,
    logger: LogLevel,
}

impl std::fmt::Debug for ExecutionPluginManager {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PluginManager").finish()
    }
}

impl ExecutionPluginManager {
    pub fn new() -> ExecutionPluginManager {
        ExecutionPluginManager {
            plugins: Vec::new(),
            loaded_libraries: Vec::new(),
            designer: Designer::default(),
            logger: LogLevel::default(),
        }
    }

    pub fn setup_logger(&mut self, logger: LogLevel) {
        self.logger = logger;
    }

    /// # Safety
    ///
    /// This function load plugin from dynamic library
    pub unsafe fn load_plugin<P: AsRef<OsStr>>(
        &mut self,
        name: &str,
        filename: P,
        logger: LogLevel,
    ) -> DevrcPluginResult<()> {
        type PluginCreate = unsafe fn() -> *mut dyn ExecutionPlugin;

        let lib = Library::new(filename.as_ref())?;

        // We need to keep the library around otherwise our plugin's vtable will
        // point to garbage. We do this little dance to make sure the library
        // doesn't end up getting moved.
        self.loaded_libraries.push(lib);

        let lib = self.loaded_libraries.last().unwrap();

        let constructor: Symbol<PluginCreate> = lib.get(b"_plugin_create")?;
        let boxed_raw = constructor();

        let mut plugin = Box::from_raw(boxed_raw);

        logger.debug(
            &format!(
                "\n==> Loading PLUGIN: `{}` as `{}` from `{:?}` ...",
                plugin.name(),
                name,
                filename.as_ref()
            ),
            &self.designer.banner(),
        );

        plugin.on_plugin_load(Config {
            logger,
            designer: self.designer,
        });
        self.plugins.push((name.to_string(), plugin));
        Ok(())
    }

    /// Unload all plugins and loaded plugin libraries, making sure to fire
    /// their `on_plugin_unload()` methods so they can do any necessary cleanup.
    pub fn unload(&mut self) {
        self.logger
            .debug("\n==> Unloading PLUGINS ...", &self.designer.banner());

        for (name, plugin) in self.plugins.drain(..) {
            self.logger.debug(
                &format!("\n==> Upload PLUGIN: `{}` named `{}` ", plugin.name(), name),
                &self.designer.banner(),
            );
            plugin.on_plugin_unload();
        }

        for lib in self.loaded_libraries.drain(..) {
            drop(lib);
        }
    }

    pub fn get_plugin(
        &mut self,
        plugin_name: &str,
    ) -> DevrcPluginResult<&Box<dyn ExecutionPlugin>> {
        for (name, plugin) in &self.plugins {
            if name == plugin_name {
                return Ok(plugin);
            }
        }

        Err(DevrcPluginError::NotFound(plugin_name.to_string()))
    }
}

impl Drop for ExecutionPluginManager {
    fn drop(&mut self) {
        if !self.plugins.is_empty() || !self.loaded_libraries.is_empty() {
            self.unload();
        }
    }
}