anaxa_builder/
build_rs.rs1use crate::{codegen, config_io, parser};
2use anyhow::{Context, Result};
3use std::env;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7pub 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 let tree = parser::build_config_tree(&self.kconfig_dir)?;
36 let configs = parser::flatten_configs(&tree);
37 let values = config_io::load_config(&self.config_file, &configs)?;
38
39 let out_path = self.out_dir.join("config.rs");
40 let rust_code = codegen::rust::generate_consts(&configs, &values)?;
41 fs::write(&out_path, rust_code)
42 .with_context(|| format!("Failed to write to {:?}", out_path))?;
43
44 println!("cargo:rerun-if-changed={}", self.config_file.display());
45 emit_rerun_if_changed(&self.kconfig_dir)?;
46
47 for item in &configs {
48 if let Some(val) = values.get(&item.name) {
49 if val.as_bool() == Some(true) {
50 println!("cargo:rustc-cfg={}", item.name);
51 }
52 }
53 }
54
55 for (k, v) in values {
56 let v_str = match v {
57 toml::Value::String(s) => s,
58 toml::Value::Integer(i) => i.to_string(),
59 toml::Value::Float(f) => f.to_string(),
60 toml::Value::Boolean(b) => b.to_string(),
61 _ => continue,
62 };
63 println!("cargo:rustc-env=ANAXA_{}={}", k.to_uppercase(), v_str);
64 }
65
66 Ok(())
67 }
68}
69
70pub fn emit_cargo_instructions<P1, P2>(kconfig_dir: P1, config_file: P2) -> Result<()>
79where
80 P1: AsRef<Path>,
81 P2: AsRef<Path>,
82{
83 let kconfig_dir = kconfig_dir.as_ref();
84 let config_file = config_file.as_ref();
85
86 let tree = parser::build_config_tree(kconfig_dir)?;
87 let configs = parser::flatten_configs(&tree);
88
89 let values = config_io::load_config(config_file, &configs)?;
90
91 let out_dir = env::var_os("OUT_DIR").context("OUT_DIR not set")?;
92 let out_path = PathBuf::from(out_dir).join("config.rs");
93 let rust_code = codegen::rust::generate_consts(&configs, &values)?;
94 fs::write(&out_path, rust_code)
95 .with_context(|| format!("Failed to write to {:?}", out_path))?;
96
97 println!("cargo:rerun-if-changed={}", config_file.display());
98
99 emit_rerun_if_changed(kconfig_dir)?;
100
101 for item in &configs {
102 if let Some(val) = values.get(&item.name) {
103 if val.as_bool() == Some(true) {
104 println!("cargo:rustc-cfg={}", item.name);
105 }
106 }
107 }
108
109 Ok(())
110}
111
112fn emit_rerun_if_changed(dir: &Path) -> Result<()> {
113 use walkdir::WalkDir;
114 for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
115 if entry.file_name() == "Kconfig.toml" {
116 println!("cargo:rerun-if-changed={}", entry.path().display());
117 }
118 }
119 Ok(())
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use std::fs;
126 use tempfile::tempdir;
127
128 #[test]
129 fn test_emit_cargo_instructions() -> Result<()> {
130 let dir = tempdir()?;
131 let kconfig_path = dir.path().join("Kconfig.toml");
132 fs::write(
133 &kconfig_path,
134 r#"
135[[config]]
136name = "TEST_BOOL"
137type = "bool"
138default = true
139desc = "Test"
140"#,
141 )?;
142
143 let config_file = dir.path().join(".config");
144 fs::write(&config_file, "TEST_BOOL = true\n")?;
145
146 let out_dir = tempdir()?;
147 unsafe {
148 env::set_var("OUT_DIR", out_dir.path());
149 }
150
151 emit_cargo_instructions(dir.path(), &config_file)?;
152
153 let config_rs = out_dir.path().join("config.rs");
154 assert!(config_rs.exists());
155 let content = fs::read_to_string(config_rs)?;
156 assert!(content.contains("pub const TEST_BOOL: bool = true;"));
157
158 Ok(())
159 }
160}