#[cfg(feature = "uses_wasm")]
use crate::plugins::wasm_plugin::WasmPlugin;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{path::Path, process::Command};
#[allow(unused_imports)]
use toml;
use walkdir::WalkDir;
use crate::e_processmanager::ProcessManager;
use crate::e_target::CargoTarget;
#[cfg(feature = "uses_lua")]
use crate::plugins::lua_plugin::LuaPlugin;
#[cfg(feature = "uses_rhai")]
use crate::plugins::rhai_plugin::RhaiPlugin;
#[cfg(feature = "uses_wasm")]
use crate::plugins::wasm_export_plugin::WasmExportPlugin;
use crate::Cli;
use std::fs;
use std::path::PathBuf;
use std::process::ExitStatus;
use std::sync::Arc;
fn plugin_directories() -> Vec<PathBuf> {
let mut dirs = Vec::new();
let dev_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("plugins");
if dev_dir.is_dir() {
dirs.push(dev_dir);
}
#[cfg(unix)]
if let Some(home) = std::env::var_os("HOME") {
let global = PathBuf::from(home).join(".cargo-e").join("plugins");
if global.is_dir() {
dirs.push(global);
}
}
#[cfg(windows)]
if let Some(userprof) = std::env::var_os("USERPROFILE") {
let global = PathBuf::from(userprof).join(".cargo-e").join("plugins");
if global.is_dir() {
dirs.push(global);
}
}
if let Ok(cwd) = std::env::current_dir() {
let proj_dir = cwd.join(".cargo-e").join("plugins");
if proj_dir.is_dir() {
dirs.push(proj_dir);
}
}
dirs
}
pub fn find_wasm_plugins() -> Vec<PathBuf> {
let mut wasm_paths = Vec::new();
for base in plugin_directories() {
if !base.is_dir() {
continue;
}
for entry in WalkDir::new(&base)
.into_iter()
.filter_map(Result::ok)
.filter(|e| {
let path = e.path();
let is_wasm = path.extension().is_some_and(|ext| ext == "wasm");
let is_dll = path.extension().is_some_and(|ext| ext == "dll");
let is_wasm_or_dll = is_wasm || is_dll;
let not_in_deps = !path
.components()
.any(|c| c.as_os_str().to_string_lossy() == "deps");
is_wasm_or_dll && not_in_deps
})
{
wasm_paths.push(entry.into_path());
}
}
wasm_paths
}
impl From<crate::e_target::CargoTarget> for Target {
fn from(ct: crate::e_target::CargoTarget) -> Self {
Target {
name: ct.name.clone(),
metadata: None,
cargo_target: Some(ct),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Target {
pub name: String,
pub metadata: Option<String>,
#[serde(default)]
#[serde(skip_serializing)]
#[serde(skip_deserializing)]
pub cargo_target: Option<CargoTarget>,
}
pub trait Plugin {
fn name(&self) -> &str;
fn matches(&self, dir: &Path) -> bool;
fn collect_targets(&self, dir: &Path) -> Result<Vec<Target>>;
fn build_command(&self, dir: &Path, target: &Target) -> Result<Command>;
fn run_command(&self, dir: &Path, target: &Target) -> Result<Command> {
self.build_command(dir, target)
}
fn should_build(&self, _dir: &Path, _target: &Target) -> bool {
true
}
fn run(&self, dir: &Path, target: &Target) -> Result<Vec<String>> {
let mut cmd = self.run_command(dir, target)?;
let output = cmd.output()?;
let mut result = Vec::new();
let code = output.status.code().unwrap_or(0);
result.push(code.to_string());
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
result.push(line.to_string());
}
Ok(result)
}
fn source(&self) -> Option<String> {
None
}
fn run_with_manager(
&self,
manager: Arc<ProcessManager>,
cli: &Cli,
cargo_target: &CargoTarget,
) -> Result<Option<ExitStatus>> {
crate::e_runner::run_example(manager, cli, cargo_target)
}
}
pub fn load_plugins(cli: &Cli, manager: Arc<ProcessManager>) -> Result<Vec<Box<dyn Plugin>>> {
let mut plugins: Vec<Box<dyn Plugin>> = Vec::new();
log::trace!(
"Initializing plugin loading; current dir = {:?}",
std::env::current_dir()?
);
let cwd = std::env::current_dir()?;
for base in plugin_directories() {
log::trace!("Scanning plugin directory: {:?}", base);
if !base.is_dir() {
continue;
}
for entry in fs::read_dir(&base)? {
let path = entry?.path();
log::trace!("Found plugin candidate: {:?}", path);
if let Some(ext) = path.extension().and_then(|s| s.to_str()) {
#[cfg(feature = "uses_lua")]
if ext == "lua" {
log::trace!("Loading Lua plugin at {:?}", path);
let plugin = LuaPlugin::load(&path, cli, manager.clone())?;
plugins.push(Box::new(plugin));
}
#[cfg(feature = "uses_rhai")]
if ext == "rhai" {
log::trace!("Loading Rhai plugin at {:?}", path);
let plugin = RhaiPlugin::load(&path, cli, manager.clone())?;
plugins.push(Box::new(plugin));
}
}
}
}
log::trace!("Loaded {} script plugins", plugins.len());
#[cfg(feature = "uses_wasm")]
for wasm_path in find_wasm_plugins() {
log::trace!("Trying WASM plugin at {}", wasm_path.display());
if let Some(wp) = WasmPlugin::load(&wasm_path)? {
if wp.matches(&cwd) {
plugins.push(Box::new(wp));
continue;
}
}
if let Some(gp) = WasmExportPlugin::load(&wasm_path)? {
plugins.push(Box::new(gp));
}
}
Ok(plugins)
}
#[allow(dead_code)]
pub struct PluginManager {
cli: Cli,
manager: Arc<ProcessManager>,
cwd: PathBuf,
plugins: Vec<Box<dyn Plugin>>,
}
impl PluginManager {
pub fn new(cli: &Cli) -> Result<Self> {
let manager = ProcessManager::new(cli);
let plugins = load_plugins(cli, manager.clone())?;
let cwd = std::env::current_dir()?;
Ok(PluginManager {
cli: cli.clone(),
manager,
cwd,
plugins,
})
}
pub fn plugins(&self) -> &[Box<dyn Plugin>] {
&self.plugins
}
pub fn collect_targets(&self) -> Result<Vec<CargoTarget>> {
use crate::e_target::{TargetKind, TargetOrigin};
let mut results = Vec::new();
for plugin in &self.plugins {
if plugin.matches(&self.cwd) {
let plugin_path = plugin
.source()
.map(PathBuf::from)
.unwrap_or_else(|| self.cwd.clone());
for pt in plugin.collect_targets(&self.cwd)? {
let ct = if let Some(ct) = pt.cargo_target {
ct
} else {
let reported = pt
.metadata
.as_ref()
.map(PathBuf::from)
.unwrap_or_else(|| self.cwd.clone());
CargoTarget {
name: pt.name.clone(),
display_name: pt.name.clone(),
manifest_path: self.cwd.clone(),
kind: TargetKind::Plugin,
extended: false,
toml_specified: false,
origin: Some(TargetOrigin::Plugin {
plugin_path: plugin_path.clone(),
reported,
}),
}
};
results.push(ct);
}
}
}
Ok(results)
}
}
#[derive(serde::Deserialize)]
pub struct CommandSpec {
pub prog: String,
pub args: Vec<String>,
pub cwd: Option<String>,
}
impl CommandSpec {
pub fn into_command(self, default_dir: &Path) -> Command {
let mut cmd = Command::new(self.prog);
for arg in self.args {
cmd.arg(arg);
}
if let Some(cwd) = self.cwd {
cmd.current_dir(cwd);
} else {
cmd.current_dir(default_dir);
}
cmd
}
}