autoconf_rs_cli/
main_autoheader.rs1use crate::read_input;
10use autoconf_rs_core::trace::AutoconfEvent;
11use autoconf_rs_core::M4Engine;
12use std::env;
13use std::process::ExitCode;
14
15pub fn run_autoheader() -> ExitCode {
16 let args: Vec<String> = env::args().collect();
17 let input_path = args.get(1).map(|s| s.as_str()).unwrap_or("configure.ac");
18
19 if input_path == "--help" || input_path == "-h" {
21 println!("autoheader-rs {}", env!("CARGO_PKG_VERSION"));
22 println!("Generate config.h.in from configure.ac");
23 println!("Usage: autoheader [configure.ac]");
24 println!(" -h, --help Show this help");
25 println!(" --version Show version");
26 return ExitCode::SUCCESS;
27 }
28 if input_path == "--version" {
29 println!("autoheader-rs {}", env!("CARGO_PKG_VERSION"));
30 return ExitCode::SUCCESS;
31 }
32
33 let input = match read_input(input_path) {
34 Ok(s) => s,
35 Err(e) => {
36 eprintln!("autoheader: {}", e);
37 return ExitCode::from(2);
38 }
39 };
40
41 let mut engine = M4Engine::new();
42 match engine.process(&input) {
43 Ok(_output) => {
44 let trace_log = &engine.trace_log;
46
47 let headers: Vec<&str> = trace_log
49 .events
50 .iter()
51 .filter_map(|e| match e {
52 AutoconfEvent::ConfigHeader { output, .. } => Some(output.as_str()),
53 _ => None,
54 })
55 .collect();
56
57 if headers.is_empty() {
58 eprintln!("autoheader: no AC_CONFIG_HEADERS in configure.ac");
59 eprintln!(
60 " (checked {} trace events, 0 ConfigHeader found)",
61 trace_log.events.len()
62 );
63 return ExitCode::from(1);
64 }
65
66 let defines: Vec<(&str, Option<&str>)> = trace_log
68 .events
69 .iter()
70 .filter_map(|e| match e {
71 AutoconfEvent::Define { name, value, .. } => {
72 Some((name.as_str(), value.as_deref()))
73 }
74 _ => None,
75 })
76 .collect();
77
78 let header_file = headers.first().unwrap_or(&"config.h");
79 let mut out = String::new();
85 macro_rules! oline {
86 () => {{ out.push('\n'); }};
87 ($($a:tt)*) => {{ out.push_str(&format!($($a)*)); out.push('\n'); }};
88 }
89
90 oline!(
91 "/* {} — Generated by autoconf-rs autoheader (trace-driven). */",
92 header_file
93 );
94 oline!(
95 "/* Source: {} — {} trace events, {} AC_DEFINE, {} AC_CONFIG_HEADERS */",
96 input_path,
97 trace_log.events.len(),
98 defines.len(),
99 headers.len()
100 );
101 oline!();
102
103 if defines.is_empty() {
104 oline!("/* No AC_DEFINE calls found in trace events. */");
105 oline!("/* Try: autom4te --trace=AC_DEFINE {} */", input_path);
106 } else {
107 for (var, _value) in &defines {
108 oline!("#undef {}", var);
109 }
110 }
111
112 let mut have_seen = std::collections::BTreeSet::new();
117 for e in &trace_log.events {
118 let macro_name = match e {
119 AutoconfEvent::CheckHeader { header, .. } => Some(have_macro(header)),
120 AutoconfEvent::CheckFunc { function, .. } => Some(have_macro(function)),
121 AutoconfEvent::CheckLib { library, .. } => {
122 Some(format!("HAVE_LIB{}", library.to_ascii_uppercase()))
123 }
124 _ => None,
125 };
126 if let Some(m) = macro_name {
127 if have_seen.insert(m.clone()) {
128 oline!("#undef {}", m);
129 }
130 }
131 }
132
133 let (pname, pver) = parse_ac_init(&input);
139 oline!("#define PACKAGE_NAME \"{}\"", pname);
141 oline!("#define PACKAGE_TARNAME \"{}\"", pname);
142 oline!("#define PACKAGE_VERSION \"{}\"", pver);
143 oline!("#define PACKAGE_STRING \"{} {}\"", pname, pver);
144 oline!("#define PACKAGE_BUGREPORT \"\"");
145 oline!("#define PACKAGE_URL \"\"");
146 let amopts = init_automake_options(&input);
151 let emit_bare = amopts.is_some() && !amopts.as_deref().unwrap_or("").contains("no-define");
152 if emit_bare {
153 oline!("#define PACKAGE \"{}\"", pname);
154 oline!("#define VERSION \"{}\"", pver);
155 }
156
157 let out_path = {
164 let dir = std::path::Path::new(input_path)
165 .parent()
166 .filter(|p| !p.as_os_str().is_empty())
167 .map(|p| p.to_path_buf())
168 .unwrap_or_else(|| std::path::PathBuf::from("."));
169 dir.join(format!("{}.in", header_file))
170 };
171 if let Err(e) = std::fs::write(&out_path, &out) {
172 eprintln!("autoheader: cannot write {}: {}", out_path.display(), e);
173 return ExitCode::from(2);
174 }
175 eprintln!(
176 "autoheader: generated {} with {} #undef entries (trace-driven)",
177 out_path.display(),
178 defines.len()
179 );
180 ExitCode::SUCCESS
181 }
182 Err(e) => {
183 eprintln!("autoheader: {}", e);
184 ExitCode::from(2)
185 }
186 }
187}
188
189fn parse_ac_init(input: &str) -> (String, String) {
192 if let Some(pos) = input.find("AC_INIT") {
193 let after = &input[pos + "AC_INIT".len()..];
194 if let Some(open) = after.find('(') {
195 let mut depth = 0i32;
197 let mut end = None;
198 for (i, c) in after[open..].char_indices() {
199 match c {
200 '(' => depth += 1,
201 ')' => { depth -= 1; if depth == 0 { end = Some(open + i); break; } }
202 _ => {}
203 }
204 }
205 if let Some(e) = end {
206 let args_str = &after[open + 1..e];
207 let strip = |s: &str| s.trim().trim_start_matches('[').trim_end_matches(']').trim().to_string();
208 let parts: Vec<&str> = args_str.splitn(3, ',').collect();
209 let name = sanitize_token(&parts.first().map(|s| strip(s)).unwrap_or_default(), "config");
210 let version = sanitize_token(&parts.get(1).map(|s| strip(s)).unwrap_or_default(), "0");
211 return (name, version);
212 }
213 }
214 }
215 (String::new(), String::new())
216}
217
218fn sanitize_token(v: &str, fallback: &str) -> String {
223 let no_dnl: String = v
225 .lines()
226 .map(|l| l.split("dnl").next().unwrap_or(""))
227 .collect::<Vec<_>>()
228 .join(" ");
229 let t = no_dnl.trim().trim_matches('"').trim();
230 if t.is_empty()
231 || t.contains("m4_")
232 || t.contains("esyscmd")
233 || t.contains('[')
234 || t.contains(']')
235 || t.contains('(')
236 || t.chars().any(|c| c.is_whitespace())
237 {
238 return fallback.to_string();
239 }
240 t.to_string()
241}
242
243fn init_automake_options(input: &str) -> Option<String> {
246 let pos = input.find("AM_INIT_AUTOMAKE")?;
247 let after = &input[pos + "AM_INIT_AUTOMAKE".len()..];
248 let open = after.find('(')?;
249 let mut depth = 0i32;
250 for (i, c) in after[open..].char_indices() {
251 match c {
252 '(' => depth += 1,
253 ')' => {
254 depth -= 1;
255 if depth == 0 {
256 return Some(after[open + 1..open + i].to_string());
257 }
258 }
259 _ => {}
260 }
261 }
262 Some(String::new())
263}
264
265fn have_macro(name: &str) -> String {
268 let up: String = name
269 .trim()
270 .chars()
271 .map(|c| if c.is_ascii_alphanumeric() { c.to_ascii_uppercase() } else { '_' })
272 .collect();
273 format!("HAVE_{}", up)
274}