use super::Loader;
pub type Entrypoint = fn() -> rhai::Shared<rhai::Module>;
pub const MODULE_ENTRYPOINT: &str = "module_entrypoint";
pub struct Libloading {
libraries: Vec<libloading::Library>,
}
impl Default for Libloading {
fn default() -> Self {
Self { libraries: vec![] }
}
}
impl Libloading {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl Loader for Libloading {
fn load(
&mut self,
path: impl AsRef<std::path::Path>,
) -> Result<rhai::Shared<rhai::Module>, Box<rhai::EvalAltResult>> {
let library = unsafe {
#[cfg(target_os = "linux")]
{
libloading::os::unix::Library::open(
Some(path.as_ref()),
0x2 | 0x1000,
)
.map(libloading::Library::from)
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
libloading::Library::new(path.as_ref())
}
}
.map_err(|error| {
rhai::EvalAltResult::ErrorInModule(
path.as_ref()
.to_str()
.map_or(String::default(), std::string::ToString::to_string),
error.to_string().into(),
rhai::Position::NONE,
)
})?;
self.libraries.push(library);
let library = self.libraries.last().expect("library just got inserted");
let module_entrypoint = unsafe { library.get::<Entrypoint>(MODULE_ENTRYPOINT.as_bytes()) }
.map_err(|error| {
rhai::EvalAltResult::ErrorInModule(
path.as_ref()
.to_str()
.map_or(String::default(), std::string::ToString::to_string),
error.to_string().into(),
rhai::Position::NONE,
)
})?;
Ok(module_entrypoint())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::loader::Loader;
fn build_test_plugin() -> &'static std::path::PathBuf {
static PATH: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
PATH.get_or_init(|| {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let status = std::process::Command::new("cargo")
.args(["build", "--example", "test_plugin"])
.current_dir(manifest_dir)
.status()
.expect("failed to execute cargo build");
assert!(status.success(), "building test_plugin failed");
let target_dir = std::env::var("CARGO_TARGET_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| manifest_dir.join("target"));
#[cfg(target_os = "linux")]
return target_dir.join("debug/examples/libtest_plugin.so");
#[cfg(target_os = "macos")]
return target_dir.join("debug/examples/libtest_plugin.dylib");
#[cfg(target_os = "windows")]
return target_dir.join("debug/examples/test_plugin.dll");
})
}
#[test]
fn new() {
let _ = Libloading::new();
}
#[test]
fn load_success() {
let mut loader = Libloading::new();
loader
.load(build_test_plugin().as_path())
.expect("failed to load test_plugin");
}
#[test]
fn load_nonexistent_returns_error() {
let mut loader = Libloading::new();
let err = loader.load("nonexistent.so").unwrap_err();
assert!(matches!(*err, rhai::EvalAltResult::ErrorInModule(..)));
}
}