Skip to main content

anaxa_builder/
build_rs.rs

1use crate::{codegen, config_io, parser};
2use anyhow::{Context, Result};
3use std::env;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7/// Helper for `build.rs` to integrate Anaxa configuration.
8pub struct BuildHelper {
9    kconfig_dir: PathBuf,
10    config_file: PathBuf,
11    out_dir: PathBuf,
12}
13
14impl BuildHelper {
15    pub fn new() -> Result<Self> {
16        let out_dir = env::var_os("OUT_DIR").context("OUT_DIR not set")?.into();
17        Ok(Self {
18            kconfig_dir: PathBuf::from("src"),
19            config_file: PathBuf::from(".config"),
20            out_dir,
21        })
22    }
23
24    pub fn with_kconfig_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
25        self.kconfig_dir = dir.into();
26        self
27    }
28
29    pub fn with_config_file<P: Into<PathBuf>>(mut self, file: P) -> Self {
30        self.config_file = file.into();
31        self
32    }
33
34    pub fn build(self) -> Result<()> {
35        check_version_compatibility()?;
36        let tree = parser::build_config_tree(&self.kconfig_dir)?;
37        let configs = parser::flatten_configs(&tree);
38        parser::validate_configs(&configs)?;
39        let values = config_io::load_config(&self.config_file, &configs)?;
40
41        let out_path = self.out_dir.join("config.rs");
42        let rust_code = codegen::rust::generate_consts(&configs, &values)?;
43        fs::write(&out_path, rust_code)
44            .with_context(|| format!("Failed to write to {:?}", out_path))?;
45
46        println!("cargo:rerun-if-changed={}", self.config_file.display());
47        emit_rerun_if_changed(&self.kconfig_dir)?;
48
49        for item in &configs {
50            if let Some(val) = values.get(&item.name) {
51                if val.as_bool() == Some(true) {
52                    println!("cargo:rustc-cfg={}", item.name);
53                } else if val.as_bool() == None {
54                    continue;
55                }
56                println!("cargo::rustc-check-cfg=cfg({})", item.name);
57            }
58        }
59
60        for (k, v) in values {
61            let v_str = match v {
62                toml::Value::String(s) => s,
63                toml::Value::Integer(i) => i.to_string(),
64                toml::Value::Float(f) => f.to_string(),
65                toml::Value::Boolean(b) => b.to_string(),
66                _ => continue,
67            };
68            println!("cargo:rustc-env=ANAXA_{}={}", k.to_uppercase(), v_str);
69        }
70
71        Ok(())
72    }
73}
74
75/// Helper for `build.rs` to integrate Anaxa configuration.
76///
77/// This function:
78/// 1. Scans `kconfig_dir` for `Kconfig.toml` files.
79/// 2. Loads configuration values from `config_file`.
80/// 3. Generates `config.rs` in `OUT_DIR`.
81/// 4. Emits `cargo:rustc-cfg` for enabled boolean configs.
82/// 5. Emits `cargo:rerun-if-changed` for the config file and all `Kconfig.toml` files.
83pub fn emit_cargo_instructions<P1, P2>(kconfig_dir: P1, config_file: P2) -> Result<()>
84where
85    P1: AsRef<Path>,
86    P2: AsRef<Path>,
87{
88    check_version_compatibility()?;
89    let kconfig_dir = kconfig_dir.as_ref();
90    let config_file = config_file.as_ref();
91
92    let tree = parser::build_config_tree(kconfig_dir)?;
93    let configs = parser::flatten_configs(&tree);
94    parser::validate_configs(&configs)?;
95
96    let values = config_io::load_config(config_file, &configs)?;
97
98    let out_dir = env::var_os("OUT_DIR").context("OUT_DIR not set")?;
99    let out_path = PathBuf::from(out_dir).join("config.rs");
100    let rust_code = codegen::rust::generate_consts(&configs, &values)?;
101    fs::write(&out_path, rust_code)
102        .with_context(|| format!("Failed to write to {:?}", out_path))?;
103
104    println!("cargo:rerun-if-changed={}", config_file.display());
105
106    emit_rerun_if_changed(kconfig_dir)?;
107
108    for item in &configs {
109        if let Some(val) = values.get(&item.name) {
110            if val.as_bool() == Some(true) {
111                println!("cargo:rustc-cfg={}", item.name);
112            }
113        }
114    }
115
116    Ok(())
117}
118
119fn emit_rerun_if_changed(dir: &Path) -> Result<()> {
120    use walkdir::WalkDir;
121    for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
122        if entry.file_name() == "Kconfig.toml" {
123            println!("cargo:rerun-if-changed={}", entry.path().display());
124        }
125    }
126    Ok(())
127}
128
129fn check_version_compatibility() -> Result<()> {
130    if let Ok(cli_version) = env::var("ANAXA_CLI_VERSION") {
131        let lib_version = env!("CARGO_PKG_VERSION");
132        if cli_version != lib_version {
133            // Check if major/minor versions match
134            let cli_parts: Vec<&str> = cli_version.split('.').collect();
135            let lib_parts: Vec<&str> = lib_version.split('.').collect();
136
137            if cli_parts.len() >= 2 && lib_parts.len() >= 2 {
138                if cli_parts[0] != lib_parts[0] || cli_parts[1] != lib_parts[1] {
139                    println!(
140                        "cargo:warning=Version mismatch: anaxa-builder CLI (v{}) and library (v{}) are not compatible.",
141                        cli_version, lib_version
142                    );
143                    println!(
144                        "cargo:warning=Please ensure both CLI and library have the same major.minor version."
145                    );
146                }
147            } else if cli_version != lib_version {
148                println!(
149                    "cargo:warning=Version mismatch detected: CLI v{}, Lib v{}",
150                    cli_version, lib_version
151                );
152            }
153        }
154    }
155    Ok(())
156}