Public Access
initial-commit
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
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() {
|
||||
if 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() {
|
||||
if 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()
|
||||
}
|
||||
Reference in New Issue
Block a user