use std::ffi::OsString;
use std::path::Path;
use crate_plugin_kit::{CratePluginKit, LoadedPlugin};
use pmpx_plugin::abi::{
self, PmpxCommand, PmpxPluginV1, PmpxStr, ABI_VERSION, PMPX_ERR_INTERNAL,
PMPX_ERR_INVALID_ARGS, PMPX_ERR_UNSUPPORTED_VERB, PMPX_OK,
};
use pmpx_plugin::{CommandSpec, Verb};
use super::error::BackendError;
use super::strings::{read_bytes, read_plugin_str};
use super::BackendDiagnostics;
use crate::error::{PmpxError, Result};
use crate::plugins::InstalledPlugin;
pub struct Backend {
loaded: LoadedPlugin<PmpxPluginV1>,
pub name: String,
}
impl Backend {
pub fn load(kit: &CratePluginKit<PmpxPluginV1>, plugin: &InstalledPlugin) -> Result<Self> {
let loaded = kit.load(&plugin.crate_name).map_err(|e| {
PmpxError::not_found(format!(
"failed to load plugin {}: {e}\n\
It is installed at {} -- try `pmpx plugin rm {}` and install it again.",
plugin.crate_name,
plugin.dir.display(),
plugin.name
))
})?;
let entry = unsafe { &*loaded.entry() };
if entry.abi_version != ABI_VERSION {
return Err(PmpxError::not_found(format!(
"plugin {} has ABI version {}, pmpx needs {}.\n\
They are two separately compiled worlds; when the layouts do not line up it \
cannot be loaded -- that is better than crashing.\n\
Use `pmpx plugin update {}` to upgrade the plugin, or upgrade pmpx to a \
matching version.",
plugin.crate_name, entry.abi_version, ABI_VERSION, plugin.name
)));
}
let self_reported = unsafe { read_plugin_str(entry.name, entry.free_str) };
if self_reported != plugin.name {
return Err(PmpxError::not_found(format!(
"the plugin calls itself \"{self_reported}\", but the manifest declares \
\"{}\" -- refusing to load.\n\
Delete {} and install it again.",
plugin.name,
plugin.dir.display()
)));
}
Ok(Self {
loaded,
name: plugin.name.clone(),
})
}
fn entry(&self) -> &PmpxPluginV1 {
unsafe { &*self.loaded.entry() }
}
pub fn family(&self) -> String {
let e = self.entry();
unsafe { read_plugin_str(e.family, e.free_str) }
}
pub fn rustc_version(&self) -> String {
unsafe { read_bytes(self.entry().rustc_version) }
}
pub fn target(&self) -> String {
unsafe { read_bytes(self.entry().target) }
}
pub fn command(
&self,
project_root: &Path,
matched: &[String],
verb: Verb,
args: &[OsString],
) -> Result<std::result::Result<CommandSpec, BackendError>> {
let entry = self.entry();
let root_bytes = abi::os_to_bytes(project_root.as_os_str());
let root = PmpxStr {
ptr: root_bytes.as_ptr(),
len: root_bytes.len(),
};
let matched_bytes: Vec<Vec<u8>> = matched.iter().map(|m| m.as_bytes().to_vec()).collect();
let matched_raw: Vec<PmpxStr> = matched_bytes
.iter()
.map(|b| PmpxStr {
ptr: b.as_ptr(),
len: b.len(),
})
.collect();
let arg_bytes: Vec<Vec<u8>> = args
.iter()
.map(|a| abi::os_to_bytes(a.as_os_str()))
.collect();
let args_raw: Vec<PmpxStr> = arg_bytes
.iter()
.map(|b| PmpxStr {
ptr: b.as_ptr(),
len: b.len(),
})
.collect();
let mut out = std::mem::MaybeUninit::<PmpxCommand>::uninit();
let code = {
let out_ptr = out.as_mut_ptr();
abi::guard(move || {
unsafe {
(entry.command)(
root,
matched_raw.as_ptr(),
matched_raw.len(),
verb.to_abi(),
args_raw.as_ptr(),
args_raw.len(),
out_ptr,
)
}
})
};
match code {
PMPX_OK => {}
PMPX_ERR_UNSUPPORTED_VERB => return Ok(Err(BackendError::UnsupportedVerb)),
PMPX_ERR_INVALID_ARGS => {
return Ok(Err(BackendError::InvalidArgs(format!(
"plugin {} thinks the arguments are invalid",
self.name
))))
}
PMPX_ERR_INTERNAL => {
return Ok(Err(BackendError::Internal(format!(
"plugin {} failed internally or panicked (details on its own stderr)",
self.name
))))
}
other => {
return Ok(Err(BackendError::Internal(format!(
"plugin {} returned unknown error code {other}",
self.name
))))
}
}
let mut cmd = unsafe { out.assume_init() };
let spec = unsafe {
let program = abi::read_os(cmd.program);
let cwd = if cmd.cwd.is_empty() {
None
} else {
Some(std::path::PathBuf::from(abi::read_os(cmd.cwd)))
};
let mut argv = Vec::with_capacity(cmd.args_len);
for i in 0..cmd.args_len {
argv.push(abi::read_os(*cmd.args.add(i)));
}
CommandSpec {
program,
args: argv,
cwd,
}
};
unsafe { (entry.free_command)(&mut cmd as *mut _) };
Ok(Ok(spec))
}
pub fn diagnostics(&self) -> BackendDiagnostics {
BackendDiagnostics {
name: self.name.clone(),
family: self.family(),
rustc_version: self.rustc_version(),
target: self.target(),
}
}
}