#![allow(unsafe_code)]
use std::path::{Path, PathBuf};
use pmpx_loader::{CallError, ContextSource, Plugin};
use pmpx_plugin::abi::PmpxHost;
use pmpx_plugin::{CommandSpec, SelectionReason, Verb};
use crate::error::Result;
use crate::files::Declared;
use crate::log::{self, Levels};
use crate::{EngineError, Event};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginIdentity {
pub name: String,
pub crate_name: String,
pub dir: PathBuf,
pub wanted: Vec<String>,
pub declared_abi: Option<u32>,
}
pub struct Call<'a> {
pub root: &'a Path,
pub start_dir: &'a Path,
pub matched: &'a [String],
pub config_files: &'a [PathBuf],
pub pins: &'a std::collections::BTreeMap<String, String>,
pub args: &'a [std::ffi::OsString],
pub verb: Verb,
pub reason: SelectionReason,
pub score: u32,
pub files: &'a Declared,
}
pub struct Backend {
plugin: Plugin,
name: String,
wanted: Vec<String>,
}
impl Backend {
pub fn load(
identity: &PluginIdentity,
library: &Path,
levels: Levels,
events: &mut dyn FnMut(Event),
) -> Result<Self> {
let loaded = unsafe { Plugin::open(library) }.map_err(|error| {
EngineError::NotFound(format!(
"cannot load {}\n{error}\n\
The file is {} -- removing and installing the plugin again usually settles it.",
identity.crate_name,
library.display()
))
})?;
let self_reported = loaded.name();
if self_reported == pmpx_plugin::shell::PANIC_MARKER {
return Err(EngineError::NotFound(format!(
"plugin {} panicked while reporting its name -- its own panic message is on stderr \
above.\nDelete {} and install it again.",
identity.name,
identity.dir.display()
)));
}
if self_reported != identity.name {
return Err(EngineError::NotFound(format!(
"the plugin calls itself \"{self_reported}\", but the manifest declares \"{}\" -- \
refusing to load.\nDelete {} and install it again.",
identity.name,
identity.dir.display()
)));
}
if let Some(declared) = identity.declared_abi {
if declared != pmpx_plugin::abi::PMPX_ABI_MAJOR {
events(Event::Warning(format!(
"plugin {} declares ABI {declared} in its manifest but its library reports {}",
identity.name,
pmpx_plugin::abi::PMPX_ABI_MAJOR
)));
}
}
loaded.tables().attach(log::hooks(levels));
Ok(Self {
plugin: loaded,
name: identity.name.clone(),
wanted: identity.wanted.clone(),
})
}
pub fn name(&self) -> &str {
&self.name
}
pub fn family(&self) -> String {
self.plugin.family().to_string()
}
pub fn build_info(&self) -> (String, String) {
self.plugin.tables().build_info()
}
pub fn wanted_files(&self) -> &[String] {
&self.wanted
}
pub fn hooks(levels: Levels) -> &'static PmpxHost {
log::hooks(levels)
}
pub fn command(
&self,
call: &Call<'_>,
events: &mut dyn FnMut(Event),
) -> std::result::Result<CommandSpec, CallError> {
let source = ContextSource {
root: call.root,
start_dir: call.start_dir,
matched: call.matched,
config_files: call.config_files,
pins: call.pins,
args: call.args,
verb: call.verb.to_abi(),
reason: call.reason.to_abi(),
score: call.score,
files: call.files,
};
log::clear();
let answer = self.plugin.tables().call(&source);
log::drain(events);
for note in call.files.take_notes() {
events(Event::Note(note));
}
answer.map(|command| CommandSpec {
program: command.program,
args: command.args,
cwd: command.cwd,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_identity_mismatch_is_a_setup_error() {
let error = EngineError::NotFound(format!(
"the plugin calls itself \"other\", but the manifest declares \"{}\" -- refusing to load.",
"wanted"
));
assert!(error.is_not_found());
assert!(error.message().contains("wanted"));
}
#[test]
fn an_identity_is_data() {
let identity = PluginIdentity {
name: "pnpm".to_string(),
crate_name: "pmpx-plugin-pnpm".to_string(),
dir: PathBuf::from("/plugins/pnpm"),
wanted: vec!["package.json".to_string()],
declared_abi: Some(3),
};
assert_eq!(identity.wanted.len(), 1);
assert_eq!(identity.name, "pnpm");
}
}