diff --git a/Cargo.toml b/Cargo.toml index 6601c8e..f040994 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["server","plugin_api", "plugins/chronos_plugin"] +members = ["server", "plugin_api", "backend_api"] resolver = "2" [profile.dev] diff --git a/backend_api/Cargo.toml b/backend_api/Cargo.toml new file mode 100644 index 0000000..0eba559 --- /dev/null +++ b/backend_api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "backend_api" +version = "0.3.0" +authors = ["Tristan Poland "] +description = "Horizon Plugins API" +license = "MIT" +edition = "2021" + +[build-dependencies] +toml_edit = "0.22.22" +pathdiff = "0.2.2" + +[dependencies] +async-trait = "0.1.83" +horizon_data_types = "0.4.0" +tokio = { version = "1.41.1", features = ["rt", "net", "rt-multi-thread"] } +uuid = "1.11.0" +socketioxide = "0.15.0" +horizon-plugin-api = "0.2.0" +# +###### PLUGIN IMPORTS, DO NOT EDIT THIS SECTION ###### +plugin_api = { path = "../plugin_api", version = "0.3.0" } +###### END PLUGIN IMPORTS ###### +# +# +# +# +###### BEGIN AUTO-GENERATED BACKEND DEPENDENCIES - DO NOT EDIT THIS SECTION ###### +stars_beyond = { path = "../backends/stars_beyond", version = "0.1.0" } +###### END AUTO-GENERATED BACKEND DEPENDENCIES ###### diff --git a/backend_api/build.rs b/backend_api/build.rs new file mode 100644 index 0000000..afe7597 --- /dev/null +++ b/backend_api/build.rs @@ -0,0 +1,345 @@ +use std::fs::{self, File}; +use std::path::Path; +use std::io::{Write, Read}; + +// Updated to include whether backend has .allow-imports +type BackendInfo = (String, String, String, bool); +type PluginInfo = (String, String, String, bool); + + +fn main() { + let backends_dir = Path::new("..").join("backends"); + let plugins_dir = Path::new("..").join("plugins"); + + println!("cargo:warning=Looking for backends in: {:?}", backends_dir); + + if !backends_dir.exists() { + println!("cargo:warning=Backends directory not found at {:?}", backends_dir); + return; + } + + let backend_paths = discover_backends(&backends_dir); + let plugin_paths = discover_plugins(&plugins_dir); + println!("cargo:warning=Found {} backends", backend_paths.len()); + + // Update main Cargo.toml + if let Err(e) = update_cargo_toml(&backend_paths) { + println!("cargo:warning=Failed to update Cargo.toml: {}", e); + std::process::exit(1); + } + + // Update individual backend Cargo.toml files if they have .allow-imports + if let Err(e) = update_backend_cargo_tomls(&plugins_dir, &plugin_paths, &backends_dir, &backend_paths) { + println!("cargo:warning=Failed to update backend Cargo.toml files: {}", e); + std::process::exit(1); + } + + if let Err(e) = generate_backend_files(&backend_paths) { + println!("cargo:warning=Failed to generate backend files: {}", e); + std::process::exit(1); + } + + println!("cargo:rerun-if-changed=../backends"); + println!("cargo:rerun-if-changed=Cargo.toml"); +} + +fn discover_plugins(plugins_dir: &Path) -> Vec { + // This is identical to discover_backends but returns PluginInfo + let mut valid_plugins = Vec::new(); + + if let Ok(entries) = fs::read_dir(plugins_dir) { + for entry in entries.flatten() { + let path = entry.path(); + + if !path.is_dir() { + continue; + } + + let plugin_name = path.file_name() + .and_then(|n| n.to_str()) + .unwrap_or("") + .to_string(); + + if plugin_name.is_empty() { + continue; + } + + let cargo_toml = path.join("Cargo.toml"); + let src_dir = path.join("src"); + let lib_rs = path.join("src").join("lib.rs"); + let allow_imports = path.join(".allow-imports"); + + if cargo_toml.exists() && src_dir.exists() && lib_rs.exists() { + if let Ok(mut file) = File::open(&cargo_toml) { + let mut contents = String::new(); + if file.read_to_string(&mut contents).is_ok() { + let mut name = None; + let mut version = None; + + for line in contents.lines() { + let line = line.trim(); + if line.starts_with("name") { + name = line.split('=') + .nth(1) + .map(|s| s.trim().trim_matches('"').to_string()); + } else if line.starts_with("version") { + version = line.split('=') + .nth(1) + .map(|s| s.trim().trim_matches('"').to_string()); + } + } + + if let (Some(name), Some(version)) = (name, version) { + let has_allow_imports = allow_imports.exists(); + println!("cargo:warning=Found plugin: {} v{} in {} (allow-imports: {})", + name, version, plugin_name, has_allow_imports); + valid_plugins.push((name, version, plugin_name, has_allow_imports)); + } + } + } + } + } + } + + valid_plugins +} + +fn discover_backends(backends_dir: &Path) -> Vec { + let mut valid_backends = Vec::new(); + + if let Ok(entries) = fs::read_dir(backends_dir) { + for entry in entries.flatten() { + let path = entry.path(); + + if !path.is_dir() { + continue; + } + + let backend_name = path.file_name() + .and_then(|n| n.to_str()) + .unwrap_or("") + .to_string(); + + if backend_name.is_empty() { + continue; + } + + let cargo_toml = path.join("Cargo.toml"); + let src_dir = path.join("src"); + let lib_rs = path.join("src").join("lib.rs"); + let allow_imports = path.join(".allow-imports"); + + if cargo_toml.exists() && src_dir.exists() && lib_rs.exists() { + if let Ok(mut file) = File::open(&cargo_toml) { + let mut contents = String::new(); + if file.read_to_string(&mut contents).is_ok() { + let mut name = None; + let mut version = None; + + for line in contents.lines() { + let line = line.trim(); + if line.starts_with("name") { + name = line.split('=') + .nth(1) + .map(|s| s.trim().trim_matches('"').to_string()); + } else if line.starts_with("version") { + version = line.split('=') + .nth(1) + .map(|s| s.trim().trim_matches('"').to_string()); + } + } + + if let (Some(name), Some(version)) = (name, version) { + let has_allow_imports = allow_imports.exists(); + println!("cargo:warning=Found backend: {} v{} in {} (allow-imports: {})", + name, version, backend_name, has_allow_imports); + valid_backends.push((name, version, backend_name, has_allow_imports)); + } + } + } + } + } + } + + valid_backends +} + +const AUTO_GENERATED_START: &str = "###### BEGIN AUTO-GENERATED BACKEND DEPENDENCIES - DO NOT EDIT THIS SECTION ######"; +const AUTO_GENERATED_END: &str = "###### END AUTO-GENERATED BACKEND DEPENDENCIES ######"; + +fn update_cargo_toml(backend_paths: &[BackendInfo]) -> std::io::Result<()> { + let cargo_path = "Cargo.toml"; + let mut contents = String::new(); + File::open(cargo_path)?.read_to_string(&mut contents)?; + + contents = contents.replace("\r\n", "\n"); + + let start_idx = contents.find(AUTO_GENERATED_START); + let end_idx = contents.find(AUTO_GENERATED_END); + + let base_contents = match (start_idx, end_idx) { + (Some(start), Some(end)) => { + contents[..start].trim_end().to_string() + } + _ => { + contents.trim_end().to_string() + } + }; + + let mut new_section = String::new(); + new_section.push('\n'); + new_section.push_str(AUTO_GENERATED_START); + new_section.push('\n'); + + let mut sorted_backends = backend_paths.to_vec(); + sorted_backends.sort_by(|a, b| a.0.cmp(&b.0)); + + for (name, version, backend_dir, _) in sorted_backends { + new_section.push_str(&format!( + "{} = {{ path = \"../backends/{}\", version = \"{}\" }}\n", + name, backend_dir, version + )); + } + + new_section.push_str(AUTO_GENERATED_END); + + let mut final_contents = base_contents; + final_contents.push_str(&new_section); + + if !final_contents.ends_with('\n') { + final_contents.push('\n'); + } + + fs::write(cargo_path, final_contents)?; + + Ok(()) +} + +fn update_backend_cargo_tomls(plugins_dir: &Path, plugin_paths: &[PluginInfo], backends_dir: &Path, backend_paths: &[BackendInfo]) -> std::io::Result<()> { + println!("cargo:warning=Starting update_backend_cargo_tomls"); + println!("cargo:warning=Found {} plugins and {} backends", plugin_paths.len(), backend_paths.len()); + + // Update each backend Cargo.toml if it has .allow-imports + for (name, version, backend_dir, has_allow) in backend_paths.iter() { + println!("cargo:warning=Checking backend {} (allow_imports: {})", name, has_allow); + + if !*has_allow { + println!("cargo:warning=Skipping backend {} - no .allow-imports", name); + continue; + } + + let backend_cargo_path = backends_dir.join(backend_dir).join("Cargo.toml"); + println!("cargo:warning=Updating Cargo.toml at {:?}", backend_cargo_path); + + if !backend_cargo_path.exists() { + println!("cargo:warning=ERROR: Cargo.toml not found at {:?}", backend_cargo_path); + continue; + } + + let mut contents = String::new(); + match File::open(&backend_cargo_path) { + Ok(mut file) => { + if let Err(e) = file.read_to_string(&mut contents) { + println!("cargo:warning=ERROR: Failed to read Cargo.toml: {}", e); + continue; + } + } + Err(e) => { + println!("cargo:warning=ERROR: Failed to open Cargo.toml: {}", e); + continue; + } + } + + contents = contents.replace("\r\n", "\n"); + + let start_idx = contents.find(AUTO_GENERATED_START); + let end_idx = contents.find(AUTO_GENERATED_END); + + println!("cargo:warning=Found markers in file: start={:?}, end={:?}", start_idx.is_some(), end_idx.is_some()); + + let base_contents = match (start_idx, end_idx) { + (Some(start), Some(end)) => { + contents[..start].trim_end().to_string() + } + _ => { + contents.trim_end().to_string() + } + }; + + let mut new_section = String::new(); + new_section.push('\n'); + new_section.push_str(AUTO_GENERATED_START); + new_section.push('\n'); + + // Add dependencies for all plugins + println!("cargo:warning=Adding {} plugin dependencies", plugin_paths.len()); + for (plugin_name, plugin_version, plugin_dir, _) in plugin_paths.iter() { + // Don't add self as dependency + if plugin_name != name { + let dep_line = format!( + "{} = {{ path = \"../../plugins/{}\", version = \"{}\" }}\n", + plugin_name, plugin_dir, plugin_version + ); + println!("cargo:warning=Adding dependency: {}", dep_line.trim()); + new_section.push_str(&dep_line); + } + } + + new_section.push_str(AUTO_GENERATED_END); + new_section.push('\n'); + + let mut final_contents = base_contents; + final_contents.push_str(&new_section); + + if !final_contents.ends_with('\n') { + final_contents.push('\n'); + } + + match fs::write(&backend_cargo_path, &final_contents) { + Ok(_) => println!("cargo:warning=Successfully updated {:?}", backend_cargo_path), + Err(e) => println!("cargo:warning=ERROR: Failed to write to {:?}: {}", backend_cargo_path, e), + } + } + + Ok(()) +} + +fn generate_backend_files(backend_paths: &[BackendInfo]) -> std::io::Result<()> { + let out_dir = Path::new("src"); + fs::create_dir_all(out_dir)?; + generate_imports_file(backend_paths, out_dir)?; + Ok(()) +} + +fn generate_imports_file(backend_paths: &[BackendInfo], out_dir: &Path) -> std::io::Result<()> { + let mut file = fs::File::create(out_dir.join("backend_imports.rs"))?; + + writeln!(file, "// This file is automatically generated by build.rs")?; + writeln!(file, "// Do not edit this file manually!\n")?; + writeln!(file, "use horizon_plugin_api::{{Pluginstate, LoadedPlugin, Plugin}};")?; + writeln!(file, "use std::collections::HashMap;\n")?; + + for (name, _, _, _) in backend_paths { + write!(file, "pub use {};\n", name)?; + write!(file, "pub use {}::*;\n", name)?; + write!(file, "pub use {}::Plugin as {}_backend;\n", name, name)?; + } + writeln!(file, "\n")?; + + writeln!(file, "// Invoke the macro with all discovered backends")?; + writeln!(file, "pub fn load_backends() -> HashMap {{")?; + write!(file, " let backends = crate::load_backends!(")?; + + for (i, (name, _, _, _)) in backend_paths.iter().enumerate() { + if i > 0 { + write!(file, ",")?; + } + write!(file, "\n {}", name)?; + } + + writeln!(file, "\n );")?; + writeln!(file, " backends")?; + writeln!(file, "}}")?; + + Ok(()) +} \ No newline at end of file diff --git a/backend_api/diagrams/API.dwawio.drawio b/backend_api/diagrams/API.dwawio.drawio new file mode 100644 index 0000000..5b7f588 --- /dev/null +++ b/backend_api/diagrams/API.dwawio.drawio @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend_api/gen_uuid.sh b/backend_api/gen_uuid.sh new file mode 100644 index 0000000..ef3eb67 --- /dev/null +++ b/backend_api/gen_uuid.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Function to get CPU info (focusing on model name and physical id) +get_cpu_info() { + grep -E "model name|physical id" /proc/cpuinfo 2>/dev/null | sort -u || echo "" +} + +# Function to get machine ID +get_machine_id() { + cat /etc/machine-id 2>/dev/null || echo "" +} + +# Function to get product UUID +get_product_uuid() { + cat /sys/class/dmi/id/product_uuid 2>/dev/null || echo "" +} + +# Function to get MAC addresses +get_mac_addresses() { + ip link | awk '/link\/ether/ {print $2}' | sort || echo "" +} + +# Function to get BIOS info +get_bios_info() { + (dmidecode -t bios 2>/dev/null || echo "") | grep -E "Vendor|Version|Release Date" | sort +} + +# Generate stable unique ID +generate_stable_unique_id() { + local data + data=$( + get_cpu_info + get_machine_id + get_product_uuid + get_mac_addresses + get_bios_info + ) + echo -n "$data" | sha256sum | awk '{print $1}' +} + +# Main execution +unique_id=$(generate_stable_unique_id) +echo "Stable Unique Hardware ID: $unique_id" diff --git a/backend_api/src/backend_imports.rs b/backend_api/src/backend_imports.rs new file mode 100644 index 0000000..e86bdd7 --- /dev/null +++ b/backend_api/src/backend_imports.rs @@ -0,0 +1,18 @@ +// This file is automatically generated by build.rs +// Do not edit this file manually! + +use horizon_plugin_api::{Pluginstate, LoadedPlugin, Plugin}; +use std::collections::HashMap; + +pub use stars_beyond; +pub use stars_beyond::*; +pub use stars_beyond::Plugin as stars_beyond_backend; + + +// Invoke the macro with all discovered backends +pub fn load_backends() -> HashMap { + let backends = crate::load_backends!( + stars_beyond + ); + backends +} diff --git a/backend_api/src/components.rs b/backend_api/src/components.rs new file mode 100644 index 0000000..d578f93 --- /dev/null +++ b/backend_api/src/components.rs @@ -0,0 +1,51 @@ +//////////////////////////////////////////////////////////////// +// Components.rs file // +// Hanndles all manditory plugin API functions, if a plugin // +// does not implement these it will refuse to compile. // +//////////////////////////////////////////////////////////////// + +use std::any::Any; +use std::fmt::Debug; +use crate::{ApiVersion, PluginContext}; + +/// Trait that all plugins must implement. +pub trait Plugin: Any + Send + Sync { + /// Called when the plugin is loaded. Perform initialization here. + fn on_load(&self); + + /// Called when the plugin is unloaded. Perform cleanup here. + fn on_unload(&self); + + /// Executes the plugin's main functionality. + fn execute(&self); + + fn initialize(&self, context: &mut PluginContext); + fn shutdown(&self, context: &mut PluginContext); + + fn on_enable(&self, context: &mut PluginContext); + fn on_disable(&self, context: &mut PluginContext); +} + +/// Struct representing metadata about the plugin. +#[derive(Debug, Clone)] +pub struct PluginMetadata { + pub name: String, + pub version: String, + pub description: String, + pub api_version: ApiVersion, +} + +impl PluginMetadata { + pub fn new(name: &str, version: &str, description: &str, api_version: ApiVersion) -> Self { + Self { + name: name.to_string(), + version: version.to_string(), + description: description.to_string(), + api_version, + } + } +} + +/// Type alias for the function used to create a plugin instance. +/// Each plugin DLL must expose this function. +pub type PluginCreateFn = fn() -> Box; \ No newline at end of file diff --git a/backend_api/src/lib.rs b/backend_api/src/lib.rs new file mode 100644 index 0000000..1f0bab7 --- /dev/null +++ b/backend_api/src/lib.rs @@ -0,0 +1,74 @@ +use std::collections::HashMap; +pub use horizon_plugin_api::{Plugin, Pluginstate, Version, get_plugin, LoadedPlugin}; + +pub mod plugin_macro; +pub mod backend_imports; + +// Define the current plugin version +const PLUGIN_API_VERSION: Version = Version { + major: 0, + minor: 1, + hotfix: 0 +}; + +#[derive(Clone)] +pub struct PluginManager { + plugins: HashMap +} + +#[macro_export] +macro_rules! load_backends { + ($($plugin:ident),* $(,)?) => { + { + let mut plugins = HashMap::new(); + $( + plugins.insert( + stringify!($plugin).to_string(), + (Pluginstate::ACTIVE, <$plugin::Plugin as $plugin::PluginConstruct>::new(plugins.clone())), + ); + )* + + plugins + } + }; +} + +impl PluginManager { + /// Allow instantiation of the ``PluginManager`` struct + pub fn new() -> PluginManager { + let new_manager = PluginManager { + plugins: HashMap::new(), + }; + + new_manager + } + + pub fn load_plugin(mut self,name: String, plugin: Plugin) { + self.plugins.insert(name, (Pluginstate::ACTIVE, plugin)); + } + + pub fn unload_plugin(mut self,name: String) { + self.plugins.remove(&name); + } + + pub fn get_plugins(self) -> HashMap { + self.plugins + } + + pub fn load_all(&mut self) -> HashMap { + self.plugins = backend_imports::load_backends(); + + //let my_test_plugin = get_plugin!(test_plugin, plugins); + //let result = my_test_plugin.thing(); + + let mut loaded_plugins = HashMap::new(); + for (name, (state, plugin)) in &self.plugins { + if *state == Pluginstate::ACTIVE { + loaded_plugins.insert(name.clone(), LoadedPlugin { + instance: plugin.clone(), + }); + } + } + loaded_plugins + } +} diff --git a/backend_api/src/plugin_macro.rs b/backend_api/src/plugin_macro.rs new file mode 100644 index 0000000..e69de29 diff --git a/backends/stars_beyond/.allow-imports b/backends/stars_beyond/.allow-imports new file mode 100644 index 0000000..e69de29 diff --git a/backends/stars_beyond/Cargo.toml b/backends/stars_beyond/Cargo.toml new file mode 100644 index 0000000..3292d94 --- /dev/null +++ b/backends/stars_beyond/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "stars_beyond" +version = "0.1.0" +edition = "2021" + +[dependencies] +PebbleVault = "0.6.1" +horizon-plugin-api = "0.2.0" +horizon_data_types = "0.4.0" +socketioxide = "0.15.1" +parking_lot = "0.12.3" +# +# +# +# +###### BEGIN AUTO-GENERATED BACKEND DEPENDENCIES - DO NOT EDIT THIS SECTION ###### +chronos_plugin = { path = "../../plugins/chronos_plugin", version = "0.1.0" } +player_lib = { path = "../../plugins/player_lib", version = "0.1.0" } +###### END AUTO-GENERATED BACKEND DEPENDENCIES ###### diff --git a/backends/stars_beyond/src/lib.rs b/backends/stars_beyond/src/lib.rs new file mode 100644 index 0000000..d5b70df --- /dev/null +++ b/backends/stars_beyond/src/lib.rs @@ -0,0 +1,39 @@ +use horizon_data_types::Player; +use socketioxide::extract::SocketRef; +pub use horizon_plugin_api::{Plugin, Pluginstate, LoadedPlugin}; +use socketioxide::packet::Str; +use parking_lot::RwLock; +use std::sync::Arc; +use std::collections::HashMap; + +pub trait PluginAPI { + fn player_joined(&self, socket: SocketRef, player: Arc>); +} + +pub trait PluginConstruct { + fn get_structs(&self) -> Vec<&str>; + // If you want default implementations, mark them with 'default' + fn new(plugins: HashMap) -> Plugin; + +} + +impl PluginConstruct for Plugin { + fn new(plugins: HashMap) -> Plugin { + Plugin {} + } + + fn get_structs(&self) -> Vec<&str> { + vec!["MyPlayer"] + } +} + +impl PluginAPI for Plugin { + fn player_joined(&self, socket: SocketRef, player: Arc>) { + println!("player_lib"); + setup_listeners(socket, player); + } +} + + +fn setup_listeners(socket: SocketRef, player: Arc>) { +} \ No newline at end of file diff --git a/plugin_api/build.rs b/plugin_api/build.rs index 2528e45..c4dca7c 100644 --- a/plugin_api/build.rs +++ b/plugin_api/build.rs @@ -2,47 +2,50 @@ use std::fs::{self, File}; use std::path::Path; use std::io::{Write, Read}; +// Updated to include whether plugin has .allow-imports +type PluginInfo = (String, String, String, bool); + fn main() { - // Get the path to the plugins directory let plugins_dir = Path::new("..").join("plugins"); println!("cargo:warning=Looking for plugins in: {:?}", plugins_dir); - // Ensure the plugins directory exists if !plugins_dir.exists() { println!("cargo:warning=Plugins directory not found at {:?}", plugins_dir); return; } - // Find all valid plugin directories let plugin_paths = discover_plugins(&plugins_dir); println!("cargo:warning=Found {} plugins", plugin_paths.len()); - // Update Cargo.toml with plugin dependencies + // Update main Cargo.toml if let Err(e) = update_cargo_toml(&plugin_paths) { println!("cargo:warning=Failed to update Cargo.toml: {}", e); std::process::exit(1); } - // Generate the plugin macro and imports files + // Update individual plugin Cargo.toml files if they have .allow-imports + if let Err(e) = update_plugin_cargo_tomls(&plugins_dir, &plugin_paths) { + println!("cargo:warning=Failed to update plugin Cargo.toml files: {}", e); + std::process::exit(1); + } + if let Err(e) = generate_plugin_files(&plugin_paths) { println!("cargo:warning=Failed to generate plugin files: {}", e); std::process::exit(1); } - // Tell Cargo to rerun this script if the plugins directory or Cargo.toml changes println!("cargo:rerun-if-changed=../plugins"); println!("cargo:rerun-if-changed=Cargo.toml"); } -fn discover_plugins(plugins_dir: &Path) -> Vec<(String, String, String)> { +fn discover_plugins(plugins_dir: &Path) -> Vec { let mut valid_plugins = Vec::new(); if let Ok(entries) = fs::read_dir(plugins_dir) { for entry in entries.flatten() { let path = entry.path(); - // Check if this is a directory if !path.is_dir() { continue; } @@ -52,22 +55,19 @@ fn discover_plugins(plugins_dir: &Path) -> Vec<(String, String, String)> { .unwrap_or("") .to_string(); - // Skip if empty plugin name if plugin_name.is_empty() { continue; } - // Check for required files/directories let cargo_toml = path.join("Cargo.toml"); let src_dir = path.join("src"); let lib_rs = path.join("src").join("lib.rs"); + let allow_imports = path.join(".allow-imports"); if cargo_toml.exists() && src_dir.exists() && lib_rs.exists() { - // Read the Cargo.toml to get the package name and version if let Ok(mut file) = File::open(&cargo_toml) { let mut contents = String::new(); if file.read_to_string(&mut contents).is_ok() { - // Simple parsing for package name and version let mut name = None; let mut version = None; @@ -85,8 +85,10 @@ fn discover_plugins(plugins_dir: &Path) -> Vec<(String, String, String)> { } if let (Some(name), Some(version)) = (name, version) { - println!("cargo:warning=Found plugin: {} v{} in {}", name, version, plugin_name); - valid_plugins.push((name, version, plugin_name)); + let has_allow_imports = allow_imports.exists(); + println!("cargo:warning=Found plugin: {} v{} in {} (allow-imports: {})", + name, version, plugin_name, has_allow_imports); + valid_plugins.push((name, version, plugin_name, has_allow_imports)); } } } @@ -100,40 +102,34 @@ fn discover_plugins(plugins_dir: &Path) -> Vec<(String, String, String)> { const AUTO_GENERATED_START: &str = "###### BEGIN AUTO-GENERATED PLUGIN DEPENDENCIES - DO NOT EDIT THIS SECTION ######"; const AUTO_GENERATED_END: &str = "###### END AUTO-GENERATED PLUGIN DEPENDENCIES ######"; -fn update_cargo_toml(plugin_paths: &[(String, String, String)]) -> std::io::Result<()> { +fn update_cargo_toml(plugin_paths: &[PluginInfo]) -> std::io::Result<()> { let cargo_path = "Cargo.toml"; let mut contents = String::new(); File::open(cargo_path)?.read_to_string(&mut contents)?; - // Normalize line endings to \n for consistent processing contents = contents.replace("\r\n", "\n"); - // Find the boundaries of the auto-generated section let start_idx = contents.find(AUTO_GENERATED_START); let end_idx = contents.find(AUTO_GENERATED_END); let base_contents = match (start_idx, end_idx) { (Some(start), Some(end)) => { - // If an existing section is found, take everything before it contents[..start].trim_end().to_string() } _ => { - // If no section exists, use all current contents contents.trim_end().to_string() } }; - // Generate the new dependencies section let mut new_section = String::new(); - new_section.push('\n'); // Add a newline before the section + new_section.push('\n'); new_section.push_str(AUTO_GENERATED_START); - new_section.push('\n'); // Add newline after start marker + new_section.push('\n'); - // Sort plugins by name for consistent output let mut sorted_plugins = plugin_paths.to_vec(); sorted_plugins.sort_by(|a, b| a.0.cmp(&b.0)); - for (name, version, plugin_dir) in sorted_plugins { + for (name, version, plugin_dir, _) in sorted_plugins { new_section.push_str(&format!( "{} = {{ path = \"../plugins/{}\", version = \"{}\" }}\n", name, plugin_dir, version @@ -142,55 +138,105 @@ fn update_cargo_toml(plugin_paths: &[(String, String, String)]) -> std::io::Resu new_section.push_str(AUTO_GENERATED_END); - // Combine the base contents with the new section let mut final_contents = base_contents; final_contents.push_str(&new_section); - // Ensure file ends with a single newline if !final_contents.ends_with('\n') { final_contents.push('\n'); } - // Write the updated Cargo.toml fs::write(cargo_path, final_contents)?; Ok(()) } -fn generate_plugin_files(plugin_paths: &[(String, String, String)]) -> std::io::Result<()> { - // Create the output directory if it doesn't exist +fn update_plugin_cargo_tomls(plugins_dir: &Path, plugin_paths: &[PluginInfo]) -> std::io::Result<()> { + // Get list of plugins that don't have .allow-imports + let regular_plugins: Vec<_> = plugin_paths.iter() + .filter(|(_, _, _, has_allow)| !has_allow) + .collect(); + + // Update each plugin that has .allow-imports + for (name, version, plugin_dir, has_allow) in plugin_paths { + if !has_allow { + continue; + } + + let plugin_cargo_path = plugins_dir.join(plugin_dir).join("Cargo.toml"); + let mut contents = String::new(); + File::open(&plugin_cargo_path)?.read_to_string(&mut contents)?; + + contents = contents.replace("\r\n", "\n"); + + let start_idx = contents.find(AUTO_GENERATED_START); + let end_idx = contents.find(AUTO_GENERATED_END); + + let base_contents = match (start_idx, end_idx) { + (Some(start), Some(end)) => { + contents[..start].trim_end().to_string() + } + _ => { + contents.trim_end().to_string() + } + }; + + let mut new_section = String::new(); + new_section.push('\n'); + new_section.push_str(AUTO_GENERATED_START); + new_section.push('\n'); + + // Add dependencies for all non-.allow-imports plugins + for (dep_name, dep_version, dep_dir, _) in ®ular_plugins { + if dep_name != name { // Don't add self as dependency + new_section.push_str(&format!( + "{} = {{ path = \"../{}\", version = \"{}\" }}\n", + dep_name, dep_dir, dep_version + )); + } + } + + new_section.push_str(AUTO_GENERATED_END); + + let mut final_contents = base_contents; + final_contents.push_str(&new_section); + + if !final_contents.ends_with('\n') { + final_contents.push('\n'); + } + + fs::write(plugin_cargo_path, final_contents)?; + } + + Ok(()) +} + +fn generate_plugin_files(plugin_paths: &[PluginInfo]) -> std::io::Result<()> { let out_dir = Path::new("src"); fs::create_dir_all(out_dir)?; - - // Then generate the imports file that uses the macro generate_imports_file(plugin_paths, out_dir)?; - Ok(()) } -fn generate_imports_file(plugin_paths: &[(String, String, String)], out_dir: &Path) -> std::io::Result<()> { +fn generate_imports_file(plugin_paths: &[PluginInfo], out_dir: &Path) -> std::io::Result<()> { let mut file = fs::File::create(out_dir.join("plugin_imports.rs"))?; - // Write the header writeln!(file, "// This file is automatically generated by build.rs")?; writeln!(file, "// Do not edit this file manually!\n")?; writeln!(file, "use horizon_plugin_api::{{Pluginstate, LoadedPlugin, Plugin}};")?; writeln!(file, "use std::collections::HashMap;\n")?; - for (i, (name, _, _)) in plugin_paths.iter().enumerate() { + + for (name, _, _, _) in plugin_paths { write!(file, "pub use {};\n", name)?; write!(file, "pub use {}::*;\n", name)?; write!(file, "pub use {}::Plugin as {}_plugin;\n", name, name)?; } - writeln!(file, "\n"); - + writeln!(file, "\n")?; - // Use the macro with discovered plugins writeln!(file, "// Invoke the macro with all discovered plugins")?; writeln!(file, "pub fn load_plugins() -> HashMap {{")?; write!(file, " let plugins = crate::load_plugins!(")?; - // Add each plugin to the macro invocation - for (i, (name, _, _)) in plugin_paths.iter().enumerate() { + for (i, (name, _, _, _)) in plugin_paths.iter().enumerate() { if i > 0 { write!(file, ",")?; } diff --git a/plugins/chronos_plugin/.allow-imports b/plugins/chronos_plugin/.allow-imports new file mode 100644 index 0000000..e69de29 diff --git a/plugins/chronos_plugin/Cargo.toml b/plugins/chronos_plugin/Cargo.toml index a13baf5..6aa12d0 100644 --- a/plugins/chronos_plugin/Cargo.toml +++ b/plugins/chronos_plugin/Cargo.toml @@ -9,3 +9,6 @@ horizon_data_types = "0.4.0" lazy_static = "1.5.0" parking_lot = "0.12.3" socketioxide = "0.15.1" +###### BEGIN AUTO-GENERATED PLUGIN DEPENDENCIES - DO NOT EDIT THIS SECTION ###### +player_lib = { path = "../player_lib", version = "0.1.0" } +###### END AUTO-GENERATED PLUGIN DEPENDENCIES ###### diff --git a/server/Cargo.toml b/server/Cargo.toml index a147f21..79629f6 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -4,8 +4,9 @@ version = "0.13.0" edition = "2021" [dependencies] -# PLUGIN API, DO NOT REMOVE +# PLUGIN AND BACKEND APIs, DO NOT REMOVE plugin_api = {path = "../plugin_api/"} +backend_api = {path = "../backend_api/"} # DEFAULT DEPENDENCIES anyhow = "1.0.93"