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(
266 parsed: &automake_rs_core::cli::AutomakeArgs,
267 makefile_path: &Path,
268) -> Result<(), String> {
269 use automake_rs_core::aux_scripts;
270
271 let dir = makefile_path.parent().unwrap_or(Path::new("."));
272
273 match aux_scripts::install_aux_files(dir, parsed.copy, parsed.force_missing) {
274 Ok(installed) => {
275 if parsed.verbose {
276 for name in &installed {
277 eprintln!("automake-rs: installed auxiliary file '{}'", name);
278 }
279 }
280 Ok(())
281 }
282 Err(e) => Err(format!("aux file generation failed: {}", e)),
283 }
284}
285
286fn print_native_libdir() {
288 let dir = native_libdir();
289 println!("{}", dir);
290}
291
292fn native_libdir() -> String {
294 let candidates = &[
295 "/usr/share/automake-1.18",
296 "/usr/share/automake-1.17",
297 "/usr/share/automake-1.16",
298 "/usr/share/automake",
299 ];
300 for path in candidates {
301 if Path::new(path).exists() {
302 return path.to_string();
303 }
304 }
305 if let Ok(out) = std::process::Command::new("automake")
307 .arg("--print-libdir")
308 .output()
309 {
310 let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
311 if !s.is_empty() {
312 return s;
313 }
314 }
315 "/usr/share/automake-1.18".to_string()
316}
317
318pub fn effective_libdir(parsed: &automake_rs_core::cli::AutomakeArgs) -> String {
320 if let Some(ref dir) = parsed.libdir {
321 dir.clone()
322 } else {
323 native_libdir()
324 }
325}
326
327pub fn detect_platform() -> String {
331 let arch = std::env::consts::ARCH;
332 let os = std::env::consts::OS;
333 let vendor = "unknown";
334 match (arch, os) {
335 ("x86_64", "linux") => "x86_64-unknown-linux-gnu".into(),
336 ("aarch64", "linux") => "aarch64-unknown-linux-gnu".into(),
337 ("x86_64", "macos") => "x86_64-apple-darwin".into(),
338 ("aarch64", "macos") => "aarch64-apple-darwin".into(),
339 _ => format!("{}-{}-{}", arch, vendor, os),
340 }
341}
342
343pub fn run_aclocal() {
345 automake_rs_core::i18n::init_i18n();
346 let args: Vec<String> = std::env::args().collect();
347 let parsed = match automake_rs_core::cli::AclocalArgs::parse(&args) {
348 Ok(a) => a,
349 Err(e) => {
350 eprintln!("aclocal-rs: {}", e);
351 std::process::exit(1);
352 }
353 };
354
355 if parsed.help {
356 print_aclocal_help();
357 return;
358 }
359
360 if parsed.version {
361 print_aclocal_version();
362 return;
363 }
364
365 if parsed.print_ac_dir {
367 match std::process::Command::new("aclocal")
368 .arg("--print-ac-dir")
369 .output()
370 {
371 Ok(out) => {
372 std::io::Write::write_all(&mut std::io::stdout(), &out.stdout).ok();
373 return;
374 }
375 Err(e) => {
376 eprintln!("aclocal-rs: cannot query oracle: {}", e);
377 std::process::exit(1);
378 }
379 }
380 }
381
382 if parsed.verbose {
383 eprintln!("aclocal-rs: using native engine");
384 }
385
386 let engine = automake_rs_core::aclocal::Aclocal::from_args(&parsed);
387 if let Err(e) = engine.run() {
388 eprintln!("aclocal-rs: {}", e);
389 std::process::exit(1);
390 }
391}
392
393fn print_automake_version() {
394 let version = automake_rs_core::cli::oracle_version();
395 print!("{}", version);
396}
397
398fn print_aclocal_version() {
399 let version = automake_rs_core::cli::oracle_version_aclocal();
400 print!("{}", version);
401}
402
403fn print_automake_help() {
404 println!("Usage: /usr/bin/automake [OPTION]... [Makefile]...");
405 println!();
406 println!("Generate Makefile.in for configure from Makefile.am.");
407 println!();
408 println!("Operation modes:");
409 println!(" --help print this help, then exit");
410 println!(" --version print version number, then exit");
411 println!(" -v, --verbose verbosely list files processed");
412 println!(" --no-force only update Makefile.in's that are out of date");
413 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
414 println!();
415 println!("Dependency tracking:");
416 println!(" -i, --ignore-deps disable dependency tracking code");
417 println!(" --include-deps enable dependency tracking code");
418 println!();
419 println!("Flavors:");
420 println!(" --foreign set strictness to foreign");
421 println!(" --gnits set strictness to gnits");
422 println!(" --gnu set strictness to gnu");
423 println!();
424 println!("Library files:");
425 println!(" -a, --add-missing add missing standard files to package");
426 println!(" --libdir=DIR set directory storing library files");
427 println!(" --print-libdir print directory storing library files");
428 println!(" -c, --copy with -a, copy missing files (default is symlink)");
429 println!(" -f, --force-missing force update of standard files");
430 println!();
431 println!(" --host=TRIPLE cross-compilation host triple");
432 println!(" --build=TRIPLE cross-compilation build triple");
433 println!();
434 println!("automake-rs: native Rust reimplementation. Clean-room forensic parity.");
435}
436
437fn print_aclocal_help() {
438 println!("Usage: aclocal [OPTION]...");
439 println!();
440 println!("Generate 'aclocal.m4' by scanning 'configure.ac' or 'configure.in'");
441 println!();
442 println!("Options:");
443 println!(" --automake-acdir=DIR directory holding automake-provided m4 files");
444 println!(" --aclocal-path=PATH colon-separated list of directories to");
445 println!(" search for third-party local files");
446 println!(" --system-acdir=DIR directory holding third-party system-wide files");
447 println!(" --diff[=COMMAND] run COMMAND [diff -u] on M4 files that would be");
448 println!(" changed (implies --install and --dry-run)");
449 println!(" --dry-run pretend to, but do not actually update any file");
450 println!(" --force always update output file");
451 println!(" --help print this help, then exit");
452 println!(" -I DIR add directory to search list for .m4 files");
453 println!(" --install copy third-party files to the first -I directory");
454 println!(" --output=FILE put output in FILE (default aclocal.m4)");
455 println!(" --print-ac-dir print name of directory holding system-wide");
456 println!(" third-party m4 files, then exit");
457 println!(" --verbose don't be silent");
458 println!(" --version print version number, then exit");
459 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
460 println!();
461 println!("aclocal-rs: native Rust reimplementation. Clean-room forensic parity.");
462}