1use std::fs;
10use std::path::{Path, PathBuf};
11use std::sync::atomic::{AtomicBool, Ordering};
12
13static INTERRUPTED: AtomicBool = AtomicBool::new(false);
14
15#[allow(dead_code)]
19fn setup_signal_handlers() {
20 std::panic::set_hook(Box::new(|_| {
24 INTERRUPTED.store(true, Ordering::SeqCst);
25 }));
26}
27
28#[allow(dead_code)]
30pub fn was_interrupted() -> bool {
31 INTERRUPTED.load(Ordering::SeqCst)
32}
33
34pub fn run_automake() {
36 automake_rs_core::i18n::init_i18n();
37 let args: Vec<String> = std::env::args().collect();
38 let parsed = match automake_rs_core::cli::AutomakeArgs::parse(&args) {
39 Ok(a) => a,
40 Err(e) => {
41 eprintln!("automake-rs: {}", e);
42 std::process::exit(1);
43 }
44 };
45
46 if let Some(ref host) = parsed.host {
48 if parsed.verbose {
49 eprintln!("automake-rs: cross-compilation host: {}", host);
50 }
51 }
52
53 if parsed.help {
54 print_automake_help();
55 return;
56 }
57
58 if parsed.version {
59 print_automake_version();
60 return;
61 }
62
63 if parsed.print_libdir {
65 print_native_libdir();
66 return;
67 }
68
69 let input_files = if parsed.input_files.is_empty() {
71 vec![PathBuf::from("Makefile.am")]
72 } else {
73 parsed.input_files.clone()
74 };
75
76 let configure_ac = find_configure_ac();
78
79 let mut exit_code = 0;
81 for input in &input_files {
82 match process_makefile(&parsed, input, &configure_ac) {
83 Ok(output_path) => {
84 if parsed.verbose {
85 eprintln!("automake-rs: generated {}", output_path.display());
86 }
87 }
88 Err(e) => {
89 eprintln!("automake-rs: {}: {}", input.display(), e);
90 exit_code = 1;
91 }
92 }
93 }
94
95 std::process::exit(exit_code);
96}
97
98fn find_configure_ac() -> PathBuf {
102 let mut dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
103 loop {
104 for name in &["configure.ac", "configure.in"] {
105 let path = dir.join(name);
106 if path.exists() {
107 return path;
108 }
109 }
110 if !dir.pop() {
111 break;
112 }
113 }
114 PathBuf::from("configure.ac")
115}
116
117fn process_makefile(
119 parsed: &automake_rs_core::cli::AutomakeArgs,
120 makefile_path: &Path,
121 configure_ac: &Path,
122) -> Result<PathBuf, String> {
123 let parent = makefile_path.parent().unwrap_or(Path::new("."));
125 let stem = makefile_path
126 .file_stem()
127 .map(|s| s.to_string_lossy().to_string())
128 .unwrap_or_else(|| "Makefile".to_string());
129 let output_path = parent.join(format!("{}.in", stem));
130
131 if parsed.no_force && output_path.exists() {
133 if let (Ok(am_meta), Ok(in_meta)) =
134 (fs::metadata(makefile_path), fs::metadata(&output_path))
135 {
136 if let (Ok(am_time), Ok(in_time)) = (am_meta.modified(), in_meta.modified()) {
137 if in_time >= am_time {
138 if parsed.verbose {
139 eprintln!(
140 "automake-rs: {} is up to date, skipping",
141 output_path.display()
142 );
143 }
144 return Ok(output_path);
145 }
146 }
147 }
148 }
149
150 if parsed.verbose {
152 eprintln!(
153 "automake-rs: extracting traces from {}",
154 configure_ac.display()
155 );
156 }
157
158 let bridge = automake_rs_core::autoconf_bridge::AutoconfBridge::new();
159 let traces = bridge
160 .extract_traces(configure_ac)
161 .map_err(|e| format!("trace extraction failed: {}", e))?;
162
163 let mut config = automake_rs_core::automake_macros::AutomakeConfig::from_options(&format!(
165 "{} {} {}",
166 if parsed.foreign {
167 "foreign"
168 } else if parsed.gnits {
169 "gnits"
170 } else if parsed.gnu {
171 "gnu"
172 } else {
173 traces.strictness.as_deref().unwrap_or("gnu")
174 },
175 parsed
176 .warnings
177 .iter()
178 .map(|w| w.as_str())
179 .collect::<Vec<_>>()
180 .join(" "),
181 ""
182 ));
183
184 if let Ok(ac) = std::fs::read_to_string(configure_ac) {
189 if let Some(start) = ac.find("AM_INIT_AUTOMAKE") {
190 let tail = &ac[start..];
191 if let (Some(o), Some(c)) = (tail.find('('), tail.find(')')) {
192 if c > o {
193 let opts = &tail[o + 1..c];
194 if opts.contains("no-dependencies") {
195 config.dependency_tracking = false;
196 }
197 if opts.contains("subdir-objects") {
198 config.subdir_objects = true;
199 }
200 }
201 }
202 }
203 }
204
205 if let Some(enable) = parsed.dependency_tracking_enabled() {
207 config.dependency_tracking = enable;
208 }
209
210 if parsed.verbose {
212 eprintln!("automake-rs: parsing {}", makefile_path.display());
213 }
214
215 let am = automake_rs_core::makefile_am::MakefileAm::from_file(makefile_path)
216 .map_err(|e| format!("parse error: {}", e))?;
217
218 let strictness = if parsed.foreign {
220 "foreign"
221 } else if parsed.gnits {
222 "gnits"
223 } else if parsed.gnu {
224 "gnu"
225 } else {
226 traces.strictness.as_deref().unwrap_or("gnu")
227 };
228
229 let mut diag =
230 automake_rs_core::diagnostics::DiagnosticManager::from_config(strictness, &parsed.warnings);
231
232 automake_rs_core::diagnostics::run_makefile_diagnostics(&am, &mut diag);
233 automake_rs_core::diagnostics::check_missing_standard_files(&mut diag, strictness);
234
235 diag.print_all();
237
238 if diag.has_errors() {
239 return Err("errors encountered — stopping".to_string());
240 }
241
242 if parsed.verbose {
244 eprintln!("automake-rs: generating {}", output_path.display());
245 }
246
247 let gen = automake_rs_core::makefile_in::MakefileInGenerator::new(am, config, traces);
248 let output = gen.generate();
249
250 fs::write(&output_path, output).map_err(|e| format!("write error: {}", e))?;
252
253 if parsed.add_missing {
255 if parsed.verbose {
256 eprintln!("automake-rs: delegating --add-missing to oracle");
257 }
258 add_missing_files(parsed, makefile_path)?;
259 }
260
261 Ok(output_path)
262}
263
264fn add_missing_files(
268 parsed: &automake_rs_core::cli::AutomakeArgs,
269 makefile_path: &Path,
270) -> Result<(), String> {
271 use automake_rs_core::aux_files;
272 use automake_rs_core::makefile_am::MakefileAm;
273
274 let dir = makefile_path.parent().unwrap_or(Path::new("."));
275
276 let am = MakefileAm::from_file(makefile_path).map_err(|e| format!("parse error: {}", e))?;
278 let src_text = std::fs::read_to_string(makefile_path).unwrap_or_default();
279 let has_compiled = src_text.contains(".c")
280 || src_text.contains("_SOURCES")
281 || src_text.contains("PROGRAMS")
282 || src_text.contains("LIBRARIES");
283 let has_tests = src_text.contains("TESTS");
284 let has_yacc_lex = [".y\n", ".y ", ".l\n", ".l ", ".yy", ".ll"]
285 .iter()
286 .any(|p| src_text.contains(p));
287 let has_static_lib = src_text.contains("_LIBRARIES") && !src_text.contains("_LTLIBRARIES");
288 let has_python = src_text.contains("_PYTHON");
289 let _ = &am;
290
291 let dep_tracking = std::fs::read_to_string(find_configure_ac())
293 .map(|s| {
294 !s.split("AM_INIT_AUTOMAKE")
295 .nth(1)
296 .map(|t| t.split(')').next().unwrap_or("").contains("no-dependencies"))
297 .unwrap_or(false)
298 })
299 .unwrap_or(true);
300
301 let needed = aux_files::needed_aux(
302 dep_tracking,
303 has_compiled,
304 has_tests,
305 has_yacc_lex,
306 has_static_lib,
307 has_python,
308 );
309
310 match aux_files::install_with_receipt(dir, &needed, parsed.force_missing) {
311 Ok(receipt) => {
312 let _ = std::fs::write(dir.join("aux-receipt.json"), &receipt);
313 if parsed.verbose {
314 for f in &needed {
315 eprintln!("automake-rs: installed auxiliary file '{}'", f.filename());
316 }
317 }
318 Ok(())
319 }
320 Err(e) => Err(format!("aux file generation failed: {}", e)),
321 }
322}
323
324fn print_native_libdir() {
326 let dir = native_libdir();
327 println!("{}", dir);
328}
329
330fn native_libdir() -> String {
332 let candidates = &[
333 "/usr/share/automake-1.18",
334 "/usr/share/automake-1.17",
335 "/usr/share/automake-1.16",
336 "/usr/share/automake",
337 ];
338 for path in candidates {
339 if Path::new(path).exists() {
340 return path.to_string();
341 }
342 }
343 if let Ok(out) = std::process::Command::new("automake")
345 .arg("--print-libdir")
346 .output()
347 {
348 let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
349 if !s.is_empty() {
350 return s;
351 }
352 }
353 "/usr/share/automake-1.18".to_string()
354}
355
356pub fn effective_libdir(parsed: &automake_rs_core::cli::AutomakeArgs) -> String {
358 if let Some(ref dir) = parsed.libdir {
359 dir.clone()
360 } else {
361 native_libdir()
362 }
363}
364
365pub fn detect_platform() -> String {
369 let arch = std::env::consts::ARCH;
370 let os = std::env::consts::OS;
371 let vendor = "unknown";
372 match (arch, os) {
373 ("x86_64", "linux") => "x86_64-unknown-linux-gnu".into(),
374 ("aarch64", "linux") => "aarch64-unknown-linux-gnu".into(),
375 ("x86_64", "macos") => "x86_64-apple-darwin".into(),
376 ("aarch64", "macos") => "aarch64-apple-darwin".into(),
377 _ => format!("{}-{}-{}", arch, vendor, os),
378 }
379}
380
381pub fn run_aclocal() {
383 automake_rs_core::i18n::init_i18n();
384 let args: Vec<String> = std::env::args().collect();
385 let parsed = match automake_rs_core::cli::AclocalArgs::parse(&args) {
386 Ok(a) => a,
387 Err(e) => {
388 eprintln!("aclocal-rs: {}", e);
389 std::process::exit(1);
390 }
391 };
392
393 if parsed.help {
394 print_aclocal_help();
395 return;
396 }
397
398 if parsed.version {
399 print_aclocal_version();
400 return;
401 }
402
403 if parsed.print_ac_dir {
405 match std::process::Command::new("aclocal")
406 .arg("--print-ac-dir")
407 .output()
408 {
409 Ok(out) => {
410 std::io::Write::write_all(&mut std::io::stdout(), &out.stdout).ok();
411 return;
412 }
413 Err(e) => {
414 eprintln!("aclocal-rs: cannot query oracle: {}", e);
415 std::process::exit(1);
416 }
417 }
418 }
419
420 if parsed.verbose {
421 eprintln!("aclocal-rs: using native engine");
422 }
423
424 let engine = automake_rs_core::aclocal::Aclocal::from_args(&parsed);
425 if let Err(e) = engine.run() {
426 eprintln!("aclocal-rs: {}", e);
427 std::process::exit(1);
428 }
429}
430
431fn print_automake_version() {
432 let version = automake_rs_core::cli::oracle_version();
433 print!("{}", version);
434}
435
436fn print_aclocal_version() {
437 let version = automake_rs_core::cli::oracle_version_aclocal();
438 print!("{}", version);
439}
440
441fn print_automake_help() {
442 println!("Usage: /usr/bin/automake [OPTION]... [Makefile]...");
443 println!();
444 println!("Generate Makefile.in for configure from Makefile.am.");
445 println!();
446 println!("Operation modes:");
447 println!(" --help print this help, then exit");
448 println!(" --version print version number, then exit");
449 println!(" -v, --verbose verbosely list files processed");
450 println!(" --no-force only update Makefile.in's that are out of date");
451 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
452 println!();
453 println!("Dependency tracking:");
454 println!(" -i, --ignore-deps disable dependency tracking code");
455 println!(" --include-deps enable dependency tracking code");
456 println!();
457 println!("Flavors:");
458 println!(" --foreign set strictness to foreign");
459 println!(" --gnits set strictness to gnits");
460 println!(" --gnu set strictness to gnu");
461 println!();
462 println!("Library files:");
463 println!(" -a, --add-missing add missing standard files to package");
464 println!(" --libdir=DIR set directory storing library files");
465 println!(" --print-libdir print directory storing library files");
466 println!(" -c, --copy with -a, copy missing files (default is symlink)");
467 println!(" -f, --force-missing force update of standard files");
468 println!();
469 println!(" --host=TRIPLE cross-compilation host triple");
470 println!(" --build=TRIPLE cross-compilation build triple");
471 println!();
472 println!("automake-rs: native Rust reimplementation. Clean-room forensic parity.");
473}
474
475fn print_aclocal_help() {
476 println!("Usage: aclocal [OPTION]...");
477 println!();
478 println!("Generate 'aclocal.m4' by scanning 'configure.ac' or 'configure.in'");
479 println!();
480 println!("Options:");
481 println!(" --automake-acdir=DIR directory holding automake-provided m4 files");
482 println!(" --aclocal-path=PATH colon-separated list of directories to");
483 println!(" search for third-party local files");
484 println!(" --system-acdir=DIR directory holding third-party system-wide files");
485 println!(" --diff[=COMMAND] run COMMAND [diff -u] on M4 files that would be");
486 println!(" changed (implies --install and --dry-run)");
487 println!(" --dry-run pretend to, but do not actually update any file");
488 println!(" --force always update output file");
489 println!(" --help print this help, then exit");
490 println!(" -I DIR add directory to search list for .m4 files");
491 println!(" --install copy third-party files to the first -I directory");
492 println!(" --output=FILE put output in FILE (default aclocal.m4)");
493 println!(" --print-ac-dir print name of directory holding system-wide");
494 println!(" third-party m4 files, then exit");
495 println!(" --verbose don't be silent");
496 println!(" --version print version number, then exit");
497 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
498 println!();
499 println!("aclocal-rs: native Rust reimplementation. Clean-room forensic parity.");
500}