1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use libloading::{Library, Symbol};
8use thiserror::Error;
9
10use crate::{CreatePluginFn, VanguardPlugin, CREATE_PLUGIN_SYMBOL};
11
12#[derive(Error, Debug)]
14pub enum DylibError {
15 #[error("Failed to load library: {0}")]
17 LoadError(#[from] libloading::Error),
18
19 #[error("Failed to find plugin entry point: {0}")]
21 EntryPointNotFound(String),
22
23 #[error("Invalid plugin library: {0}")]
25 InvalidLibrary(String),
26}
27
28pub struct PluginLibrary {
30 _lib: Library, plugin: Arc<dyn VanguardPlugin>,
32}
33
34impl PluginLibrary {
35 pub unsafe fn load<P: AsRef<OsStr>>(path: P) -> Result<Self, DylibError> {
46 let lib = Library::new(path).map_err(DylibError::LoadError)?;
48
49 let create_plugin: Symbol<CreatePluginFn> = lib
51 .get(CREATE_PLUGIN_SYMBOL)
52 .map_err(|_| DylibError::EntryPointNotFound("create_plugin".into()))?;
53
54 let raw_plugin = create_plugin();
56 if raw_plugin.is_null() {
57 return Err(DylibError::InvalidLibrary(
58 "Plugin creation returned null".into(),
59 ));
60 }
61
62 let raw_plugin = Box::from_raw(raw_plugin);
64 let plugin = Arc::from(raw_plugin.instance);
65
66 Ok(Self { _lib: lib, plugin })
67 }
68
69 pub fn plugin(&self) -> Arc<dyn VanguardPlugin> {
71 self.plugin.clone()
72 }
73}
74
75pub fn get_dylib_name(name: &str) -> String {
77 #[cfg(target_os = "windows")]
78 {
79 format!("{}.dll", name)
80 }
81 #[cfg(target_os = "linux")]
82 {
83 format!("lib{}.so", name)
84 }
85 #[cfg(target_os = "macos")]
86 {
87 format!("lib{}.dylib", name)
88 }
89}
90
91pub fn get_dylib_path(plugin_dir: &Path, name: &str) -> PathBuf {
93 plugin_dir.join("lib").join(get_dylib_name(name))
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use tempfile::TempDir;
100
101 #[test]
102 fn test_dylib_name() {
103 let name = get_dylib_name("test-plugin");
104 #[cfg(target_os = "windows")]
105 assert_eq!(name, "test-plugin.dll");
106 #[cfg(target_os = "linux")]
107 assert_eq!(name, "libtest-plugin.so");
108 #[cfg(target_os = "macos")]
109 assert_eq!(name, "libtest-plugin.dylib");
110 }
111
112 #[test]
113 fn test_dylib_path() {
114 let temp_dir = TempDir::new().unwrap();
115 let path = get_dylib_path(temp_dir.path(), "test-plugin");
116 let expected = temp_dir
117 .path()
118 .join("lib")
119 .join(get_dylib_name("test-plugin"));
120 assert_eq!(path, expected);
121 }
122}