Public Access
223 lines
7.2 KiB
Rust
223 lines
7.2 KiB
Rust
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
println!("cargo:rerun-if-changed=plugins.toml");
|
|
println!("cargo:rerun-if-changed=proto/entities.proto.template");
|
|
println!("cargo:rerun-if-changed=proto/core/");
|
|
println!("cargo:rerun-if-changed=proto/ingestion.proto");
|
|
println!("cargo:rerun-if-changed=plugins/");
|
|
|
|
// Parse plugins.toml to get enabled plugins
|
|
let plugins_config = fs::read_to_string("plugins.toml").expect("Failed to read plugins.toml");
|
|
|
|
let plugins = parse_plugins_config(&plugins_config)?;
|
|
|
|
// Generate entities.proto from template
|
|
generate_entities_proto(&plugins)?;
|
|
|
|
// Compile all proto files
|
|
let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
|
|
|
|
// Collect all proto files to compile
|
|
let mut proto_files = vec!["proto/entities.proto".to_string()];
|
|
|
|
// Add core proto files
|
|
proto_files.push("proto/core/service.proto".to_string());
|
|
proto_files.push("proto/core/system.proto".to_string());
|
|
proto_files.push("proto/core/component.proto".to_string());
|
|
proto_files.push("proto/core/api.proto".to_string());
|
|
proto_files.push("proto/core/user.proto".to_string());
|
|
proto_files.push("proto/core/group.proto".to_string());
|
|
proto_files.push("proto/core/domain.proto".to_string());
|
|
proto_files.push("proto/core/resource.proto".to_string());
|
|
proto_files.push("proto/core/finding.proto".to_string());
|
|
proto_files.push("proto/ingestion.proto".to_string());
|
|
|
|
// Add enabled plugin proto files
|
|
for plugin in &plugins {
|
|
if plugin.enabled {
|
|
let proto_path = format!("{}/{}.proto", plugin.proto_path, plugin.name);
|
|
if std::path::Path::new(&proto_path).exists() {
|
|
proto_files.push(proto_path.clone());
|
|
println!("cargo:rerun-if-changed={}", proto_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compile protos with tonic (0.14+ uses tonic-prost-build)
|
|
let includes = vec!["proto".to_string(), ".".to_string()];
|
|
tonic_prost_build::configure()
|
|
.file_descriptor_set_path(out_dir.join("entity_descriptor.bin"))
|
|
.compile_protos(&proto_files, &includes)?;
|
|
|
|
println!(
|
|
"✓ Generated entities.proto with {} plugins",
|
|
plugins.iter().filter(|p| p.enabled).count()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct PluginConfig {
|
|
name: String,
|
|
enabled: bool,
|
|
proto_path: String,
|
|
metadata_field_number: u32,
|
|
spec_field_number: u32,
|
|
}
|
|
|
|
fn parse_plugins_config(config: &str) -> Result<Vec<PluginConfig>, Box<dyn std::error::Error>> {
|
|
let mut plugins = Vec::new();
|
|
let mut current_section = String::new();
|
|
let mut current_plugin: Option<HashMap<String, String>> = None;
|
|
|
|
for line in config.lines() {
|
|
let line = line.trim();
|
|
|
|
// Skip comments and empty lines
|
|
if line.is_empty() || line.starts_with('#') {
|
|
continue;
|
|
}
|
|
|
|
// Section header [plugins.name]
|
|
if line.starts_with('[') && line.ends_with(']') {
|
|
// Save previous plugin if exists
|
|
if let Some(plugin_data) = current_plugin.take()
|
|
&& let Some(plugin) = build_plugin_config(¤t_section, plugin_data)
|
|
{
|
|
plugins.push(plugin);
|
|
}
|
|
|
|
current_section = line[1..line.len() - 1].to_string();
|
|
|
|
// Start collecting data for plugins.* sections
|
|
if current_section.starts_with("plugins.") {
|
|
current_plugin = Some(HashMap::new());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Key = value
|
|
if let Some(eq_pos) = line.find('=') {
|
|
let key = line[..eq_pos].trim();
|
|
let value = line[eq_pos + 1..]
|
|
.trim()
|
|
.trim_matches('"')
|
|
.trim_matches('\'');
|
|
|
|
if let Some(ref mut plugin_data) = current_plugin {
|
|
plugin_data.insert(key.to_string(), value.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't forget the last plugin
|
|
if let Some(plugin_data) = current_plugin.take()
|
|
&& let Some(plugin) = build_plugin_config(¤t_section, plugin_data)
|
|
{
|
|
plugins.push(plugin);
|
|
}
|
|
|
|
Ok(plugins)
|
|
}
|
|
|
|
fn build_plugin_config(section: &str, data: HashMap<String, String>) -> Option<PluginConfig> {
|
|
if !section.starts_with("plugins.") {
|
|
return None;
|
|
}
|
|
|
|
let name = section.strip_prefix("plugins.")?.to_string();
|
|
|
|
let enabled = data
|
|
.get("enabled")
|
|
.and_then(|v| v.parse::<bool>().ok())
|
|
.unwrap_or(false);
|
|
|
|
let proto_path = data.get("proto_path")?.clone();
|
|
|
|
let metadata_field_number = data
|
|
.get("metadata_field_number")
|
|
.and_then(|v| v.parse::<u32>().ok())?;
|
|
|
|
let spec_field_number = data
|
|
.get("spec_field_number")
|
|
.and_then(|v| v.parse::<u32>().ok())?;
|
|
|
|
Some(PluginConfig {
|
|
name,
|
|
enabled,
|
|
proto_path,
|
|
metadata_field_number,
|
|
spec_field_number,
|
|
})
|
|
}
|
|
|
|
fn generate_entities_proto(plugins: &[PluginConfig]) -> Result<(), Box<dyn std::error::Error>> {
|
|
let template = fs::read_to_string("proto/entities.proto.template")
|
|
.expect("Failed to read proto/entities.proto.template");
|
|
|
|
// Generate import statements for enabled plugins
|
|
let mut imports = String::new();
|
|
for plugin in plugins {
|
|
if plugin.enabled {
|
|
let import_line = format!(
|
|
"import \"plugins/{}/proto/{}.proto\";\n",
|
|
plugin.name, plugin.name
|
|
);
|
|
imports.push_str(&import_line);
|
|
}
|
|
}
|
|
|
|
// Generate metadata oneof fields for enabled plugins
|
|
let mut metadata_fields = String::new();
|
|
for plugin in plugins {
|
|
if plugin.enabled {
|
|
// Convert plugin name to PascalCase for message type
|
|
let pascal_name = to_pascal_case(&plugin.name);
|
|
let field_line = format!(
|
|
" charybdis.plugins.{}.{}Metadata {}_metadata = {};\n",
|
|
plugin.name, pascal_name, plugin.name, plugin.metadata_field_number
|
|
);
|
|
metadata_fields.push_str(&field_line);
|
|
}
|
|
}
|
|
|
|
// Generate spec oneof fields for enabled plugins
|
|
let mut spec_fields = String::new();
|
|
for plugin in plugins {
|
|
if plugin.enabled {
|
|
let pascal_name = to_pascal_case(&plugin.name);
|
|
let field_line = format!(
|
|
" charybdis.plugins.{}.{}Spec {}_spec = {};\n",
|
|
plugin.name, pascal_name, plugin.name, plugin.spec_field_number
|
|
);
|
|
spec_fields.push_str(&field_line);
|
|
}
|
|
}
|
|
|
|
// Replace placeholders in template
|
|
let mut output = template.replace("{{PLUGIN_IMPORTS}}", &imports);
|
|
output = output.replace("{{PLUGIN_METADATA_FIELDS}}", &metadata_fields);
|
|
output = output.replace("{{PLUGIN_SPEC_FIELDS}}", &spec_fields);
|
|
|
|
// Write generated file
|
|
fs::write("proto/entities.proto", output).expect("Failed to write proto/entities.proto");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn to_pascal_case(s: &str) -> String {
|
|
s.split('_')
|
|
.map(|word| {
|
|
let mut chars = word.chars();
|
|
match chars.next() {
|
|
None => String::new(),
|
|
Some(first) => first.to_uppercase().chain(chars).collect(),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|