Co-authored-by: Tristan Poland (Trident_For_U) <tristanpoland@users.noreply.github.com>
224 lines
7.6 KiB
Rust
224 lines
7.6 KiB
Rust
use std::fs::{self, File};
|
|
use std::io::{Read, Write};
|
|
use std::path::Path;
|
|
|
|
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
|
|
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
|
|
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)> {
|
|
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;
|
|
}
|
|
|
|
let plugin_name = path
|
|
.file_name()
|
|
.and_then(|n| n.to_str())
|
|
.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");
|
|
|
|
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;
|
|
|
|
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) {
|
|
println!(
|
|
"cargo:warning=Found plugin: {} v{} in {}",
|
|
name, version, plugin_name
|
|
);
|
|
valid_plugins.push((name, version, plugin_name));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
valid_plugins
|
|
}
|
|
|
|
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<()> {
|
|
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_str(AUTO_GENERATED_START);
|
|
new_section.push('\n'); // Add newline after start marker
|
|
|
|
// 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 {
|
|
new_section.push_str(&format!(
|
|
"{} = {{ path = \"../plugins/{}\", version = \"{}\" }}\n",
|
|
name, plugin_dir, version
|
|
));
|
|
}
|
|
|
|
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
|
|
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<()> {
|
|
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() {
|
|
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");
|
|
|
|
// Use the macro with discovered plugins
|
|
writeln!(file, "// Invoke the macro with all discovered plugins")?;
|
|
writeln!(
|
|
file,
|
|
"pub fn load_plugins() -> HashMap<String, (Pluginstate, Plugin)> {{"
|
|
)?;
|
|
write!(file, " let plugins = crate::load_plugins!(")?;
|
|
|
|
// Add each plugin to the macro invocation
|
|
for (i, (name, _, _)) in plugin_paths.iter().enumerate() {
|
|
if i > 0 {
|
|
write!(file, ",")?;
|
|
}
|
|
write!(file, "\n {}", name)?;
|
|
}
|
|
|
|
writeln!(file, "\n );")?;
|
|
writeln!(file, " plugins")?;
|
|
writeln!(file, "}}")?;
|
|
|
|
Ok(())
|
|
}
|