use std::env;
use std::fs;
use std::path::Path;
use serde_json::Value;
fn main() {
println!("cargo:rerun-if-changed=mecha10.json");
println!("cargo:rerun-if-changed=configs");
println!("cargo:rerun-if-changed=models");
println!("cargo:rerun-if-changed=nodes");
// Generate node registry from mecha10.json
generate_node_registry();
// Generate embedded resources
generate_embedded();
}
/// Check if a node is a framework node (@mecha10/*)
fn is_framework_node(name: &str) -> bool {
name.starts_with("@mecha10/")
}
/// Extract short name from framework node ("@mecha10/speaker" -> "speaker")
fn framework_node_short_name(name: &str) -> Option<&str> {
name.strip_prefix("@mecha10/")
}
/// Map a custom node name to its module name
/// For custom nodes in ./nodes/my-node/, the module would be my_node
fn custom_node_to_module(name: &str) -> String {
name.replace('-', "_")
}
fn generate_node_registry() {
let manifest_str = fs::read_to_string("mecha10.json")
.unwrap_or_else(|_| r#"{"nodes":[]}"#.to_string());
let manifest: Value = serde_json::from_str(&manifest_str)
.expect("Failed to parse mecha10.json");
let mut code = String::from("// Auto-generated from mecha10.json\n");
code.push_str("// Framework nodes are spawned via NodeResolver (pre-compiled binaries)\n");
code.push_str("// Custom nodes are compiled into this binary\n\n");
// Detect run target from features
let is_robot = env::var("CARGO_FEATURE_TARGET_ROBOT").is_ok();
let is_remote = env::var("CARGO_FEATURE_TARGET_REMOTE").is_ok();
let is_dev = env::var("CARGO_FEATURE_TARGET_DEV").is_ok();
let run_target = if is_dev {
"dev"
} else if is_robot {
"robot"
} else if is_remote {
"remote"
} else {
"dev"
};
code.push_str(&format!("pub const RUN_TARGET: &str = \"{}\";\n\n", run_target));
// Collect all node names from the flat "nodes" array
let mut all_nodes: Vec<String> = Vec::new();
// New format: flat array of node names
if let Some(nodes) = manifest["nodes"].as_array() {
for node in nodes {
if let Some(name) = node.as_str() {
all_nodes.push(name.to_string());
}
}
}
// Legacy format: nodes.drivers and nodes.custom arrays
if let Some(drivers) = manifest["nodes"]["drivers"].as_array() {
for driver in drivers {
if driver["enabled"].as_bool().unwrap_or(false) {
if let Some(name) = driver["name"].as_str() {
all_nodes.push(name.to_string());
}
}
}
}
if let Some(custom) = manifest["nodes"]["custom"].as_array() {
for node in custom {
if node["enabled"].as_bool().unwrap_or(false) {
if let Some(name) = node["name"].as_str() {
all_nodes.push(name.to_string());
}
}
}
}
// Categorize nodes
let framework_nodes: Vec<_> = all_nodes.iter()
.filter(|n| is_framework_node(n))
.cloned()
.collect();
let custom_nodes: Vec<_> = all_nodes.iter()
.filter(|n| !is_framework_node(n))
.cloned()
.collect();
// Generate ENABLED_NODES const
code.push_str("pub const ENABLED_NODES: &[&str] = &[\n");
for name in &all_nodes {
code.push_str(&format!(" \"{}\",\n", name));
}
code.push_str("];\n\n");
// Generate FRAMEWORK_NODES const
code.push_str("pub const FRAMEWORK_NODES: &[&str] = &[\n");
for name in &framework_nodes {
code.push_str(&format!(" \"{}\",\n", name));
}
code.push_str("];\n\n");
// Generate CUSTOM_NODES const
code.push_str("pub const CUSTOM_NODES: &[&str] = &[\n");
for name in &custom_nodes {
code.push_str(&format!(" \"{}\",\n", name));
}
code.push_str("];\n\n");
// Generate function to check if a node is a framework node
code.push_str("pub fn is_framework_node(name: &str) -> bool {\n");
code.push_str(" name.starts_with(\"@mecha10/\")\n");
code.push_str("}\n\n");
// Generate function to get framework node short name
code.push_str("pub fn framework_short_name(name: &str) -> Option<&str> {\n");
code.push_str(" name.strip_prefix(\"@mecha10/\")\n");
code.push_str("}\n\n");
// Generate custom node runner (only for custom nodes)
if !custom_nodes.is_empty() {
code.push_str("/// Run a custom node (compiled into this binary)\n");
code.push_str("#[allow(unused_variables)]\n");
code.push_str("pub async fn run_custom_node(name: &str) -> anyhow::Result<()> {\n");
code.push_str(" match name {\n");
for name in &custom_nodes {
let module_name = custom_node_to_module(name);
code.push_str(&format!(
" \"{}\" => {}::run().await.map_err(|e| anyhow::anyhow!(\"{{:?}}\", e)),\n",
name, module_name
));
}
code.push_str(" _ => Err(anyhow::anyhow!(\"Unknown custom node: {}\", name)),\n");
code.push_str(" }\n");
code.push_str("}\n");
} else {
code.push_str("/// Run a custom node (none defined in this project)\n");
code.push_str("#[allow(unused_variables)]\n");
code.push_str("pub async fn run_custom_node(name: &str) -> anyhow::Result<()> {\n");
code.push_str(" Err(anyhow::anyhow!(\"No custom nodes defined. Node '{}' not found.\", name))\n");
code.push_str("}\n");
}
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("node_registry.rs");
fs::write(&dest_path, code).expect("Failed to write node_registry.rs");
}
fn generate_embedded() {
let mut code = String::from("// Auto-generated embedded resources\n\n");
// Collect config files from configs/ directory
let mut configs: Vec<String> = Vec::new();
if Path::new("configs").exists() {
if let Ok(entries) = fs::read_dir("configs") {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".json") || name.ends_with(".yaml") || name.ends_with(".toml") {
configs.push(name.to_string());
}
}
}
}
}
configs.sort();
// Collect model files from models/ directory
let mut models: Vec<String> = Vec::new();
if Path::new("models").exists() {
if let Ok(entries) = fs::read_dir("models") {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".onnx") || name.ends_with(".pt") || name.ends_with(".safetensors") {
models.push(name.to_string());
}
}
}
}
}
models.sort();
// Generate Embedded struct
code.push_str("pub struct Embedded;\n\n");
code.push_str("impl Embedded {\n");
// Generate list_configs method
code.push_str(" pub fn list_configs() -> Vec<&'static str> {\n");
code.push_str(" vec![\n");
for config in &configs {
code.push_str(&format!(" \"{}\",\n", config));
}
code.push_str(" ]\n");
code.push_str(" }\n\n");
// Generate list_models method
code.push_str(" pub fn list_models() -> Vec<&'static str> {\n");
code.push_str(" vec![\n");
for model in &models {
code.push_str(&format!(" \"{}\",\n", model));
}
code.push_str(" ]\n");
code.push_str(" }\n");
code.push_str("}\n");
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("embedded.rs");
fs::write(&dest_path, code).expect("Failed to write embedded.rs");
}