zenyx-engine/plugin_api/build.rs
Caznix 252527ebc0
exec .zenshell files + shell extensions
Co-authored-by: Tristan Poland (Trident_For_U) <tristanpoland@users.noreply.github.com>
2025-04-19 21:01:02 +02:00

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(())
}