1use std::env;
2use std::fs::File;
3use std::io::{BufRead as _, BufReader, Write as _};
4use std::path::PathBuf;
5
6pub enum State {
7 Bool { active: bool },
8 String { value: String },
9 Number { value: String },
10}
11
12pub struct Config {
13 pub key: String,
14 pub state: State,
15}
16
17pub struct Generated {
18 pub path: PathBuf,
19 pub cfgs: Vec<Config>,
20}
21
22macro_rules! check_result {
23 ($e:expr, $($args:expr),+) => {
24 {
25 match $e {
26 Ok(r) => r,
27 Err(error) => {
28 print!("cargo::error=");
29 print!($($args, )*);
30 println!(": {error}");
31 return None;
32 }
33 }
34 }
35 };
36}
37
38macro_rules! expect_env {
39 ($var:literal) => {
40 env::var($var).expect("must run from build.rs")
41 };
42}
43
44pub fn generate() -> Option<Generated> {
46 println!("cargo::rerun-if-env-changed=CARGO_KCONFIG_DOTCONFIG");
47
48 let out = PathBuf::from(expect_env!("OUT_DIR"));
49 let config = PathBuf::from(env::var("CARGO_KCONFIG_DOTCONFIG").unwrap_or(".config".into()));
50
51 if config.is_relative() {
52 println!("cargo::warning={}: relative path", config.display());
53 println!("cargo::warning=This may cause unexpected configurations for some crates.");
54 println!("cargo::warning=Wrong or missing CARGO_KCONFIG_DOTCONFIG?");
55 }
56
57 let config = check_result!(config.canonicalize(), "{}", config.display());
58
59 if !config.is_file() {
60 println!(
61 "cargo::error={}: not a file. Wrong CARGO_KCONFIG_DOTCONFIG?",
62 config.display()
63 );
64 return None;
65 }
66
67 println!("cargo::rerun-if-changed={}", config.display());
68
69 let config = check_result!(File::open(&config), "Failed to open {}", config.display());
70 let config = BufReader::new(config);
71
72 let mut cfgs = Vec::new();
73 for line in config.lines() {
74 let Ok(line) = line else {
75 continue;
76 };
77
78 if line.starts_with('#') {
79 if line.starts_with("# CONFIG_") && line.ends_with(" is not set") {
80 let key = line
81 .strip_prefix("# CONFIG_")
82 .unwrap()
83 .strip_suffix(" is not set")
84 .unwrap();
85
86 cfgs.push(Config {
87 key: key.to_lowercase(),
88 state: State::Bool { active: false },
89 });
90
91 continue;
92 }
93 }
94
95 let Some((key, value)) = line.split_once('=') else {
96 continue;
97 };
98
99 let Some(key) = key.strip_prefix("CONFIG_") else {
100 continue;
101 };
102
103 let key = key.to_lowercase();
104
105 let state = match value {
106 "y" | "m" => State::Bool { active: true },
107 "n" => State::Bool { active: false },
108 _ => {
109 if value.starts_with('"') && value.ends_with('"') {
110 State::String {
111 value: value.to_owned(),
112 }
113 } else {
114 State::Number {
115 value: value.to_owned(),
116 }
117 }
118 }
119 };
120
121 cfgs.push(Config { key, state });
122 }
123
124 let kconfig_rs_path = out.join("kconfig.rs");
125 let mut kconfig_rs = match File::create(out.join("kconfig.rs")) {
126 Ok(f) => f,
127 Err(error) => {
128 println!("cargo::error=Failed to generate kconfig.rs: {}", error);
129 return None;
130 }
131 };
132
133 for cfg in &cfgs {
134 println!("cargo::rustc-check-cfg=cfg({}, values(any()))", cfg.key);
135
136 if matches!(cfg.state, State::Bool { active: false }) {
137 continue;
138 }
139
140 check_result!(
141 writeln!(&mut kconfig_rs, "#[allow(dead_code)]"),
142 "{}",
143 kconfig_rs_path.display()
144 );
145
146 match &cfg.state {
147 State::Bool { .. } => {
148 println!("cargo::rustc-cfg={}", cfg.key);
149 }
152 State::String { value } => {
153 println!("cargo::rustc-cfg={}={}", cfg.key, value);
154 check_result!(
155 writeln!(
156 &mut kconfig_rs,
157 "pub const CONFIG_{}: &str = {};",
158 cfg.key.to_uppercase(),
159 value,
160 ),
161 "{}",
162 kconfig_rs_path.display()
163 );
164 }
165 State::Number { value } => {
166 if value.is_empty() {
167 println!(
168 "cargo::warning=Skipping unassigned config {}. Check your .config",
169 cfg.key
170 );
171 continue;
172 }
173
174 println!("cargo::rustc-cfg={}=\"{}\"", cfg.key, value);
175 check_result!(
176 writeln!(
177 &mut kconfig_rs,
178 "pub const CONFIG_{}: u32 = {};",
179 cfg.key.to_uppercase(),
180 value,
181 ),
182 "{}",
183 kconfig_rs_path.display()
184 );
185 }
186 };
187 }
188
189 Some(Generated {
190 path: kconfig_rs_path,
191 cfgs,
192 })
193}