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 {
100 for name in &["configure.ac", "configure.in"] {
101 let path = Path::new(name);
102 if path.exists() {
103 return path.to_path_buf();
104 }
105 }
106 PathBuf::from("configure.ac")
107}
108
109fn process_makefile(
111 parsed: &automake_rs_core::cli::AutomakeArgs,
112 makefile_path: &Path,
113 configure_ac: &Path,
114) -> Result<PathBuf, String> {
115 let parent = makefile_path.parent().unwrap_or(Path::new("."));
117 let stem = makefile_path
118 .file_stem()
119 .map(|s| s.to_string_lossy().to_string())
120 .unwrap_or_else(|| "Makefile".to_string());
121 let output_path = parent.join(format!("{}.in", stem));
122
123 if parsed.no_force && output_path.exists() {
125 if let (Ok(am_meta), Ok(in_meta)) =
126 (fs::metadata(makefile_path), fs::metadata(&output_path))
127 {
128 if let (Ok(am_time), Ok(in_time)) = (am_meta.modified(), in_meta.modified()) {
129 if in_time >= am_time {
130 if parsed.verbose {
131 eprintln!(
132 "automake-rs: {} is up to date, skipping",
133 output_path.display()
134 );
135 }
136 return Ok(output_path);
137 }
138 }
139 }
140 }
141
142 if parsed.verbose {
144 eprintln!(
145 "automake-rs: extracting traces from {}",
146 configure_ac.display()
147 );
148 }
149
150 let bridge = automake_rs_core::autoconf_bridge::AutoconfBridge::new();
151 let traces = bridge
152 .extract_traces(configure_ac)
153 .map_err(|e| format!("trace extraction failed: {}", e))?;
154
155 let mut config = automake_rs_core::automake_macros::AutomakeConfig::from_options(&format!(
157 "{} {} {}",
158 if parsed.foreign {
159 "foreign"
160 } else if parsed.gnits {
161 "gnits"
162 } else if parsed.gnu {
163 "gnu"
164 } else {
165 traces.strictness.as_deref().unwrap_or("gnu")
166 },
167 parsed
168 .warnings
169 .iter()
170 .map(|w| w.as_str())
171 .collect::<Vec<_>>()
172 .join(" "),
173 ""
174 ));
175
176 if let Some(enable) = parsed.dependency_tracking_enabled() {
178 config.dependency_tracking = enable;
179 }
180
181 if parsed.verbose {
183 eprintln!("automake-rs: parsing {}", makefile_path.display());
184 }
185
186 let am = automake_rs_core::makefile_am::MakefileAm::from_file(makefile_path)
187 .map_err(|e| format!("parse error: {}", e))?;
188
189 let strictness = if parsed.foreign {
191 "foreign"
192 } else if parsed.gnits {
193 "gnits"
194 } else if parsed.gnu {
195 "gnu"
196 } else {
197 traces.strictness.as_deref().unwrap_or("gnu")
198 };
199
200 let mut diag =
201 automake_rs_core::diagnostics::DiagnosticManager::from_config(strictness, &parsed.warnings);
202
203 automake_rs_core::diagnostics::run_makefile_diagnostics(&am, &mut diag);
204 automake_rs_core::diagnostics::check_missing_standard_files(&mut diag, strictness);
205
206 diag.print_all();
208
209 if diag.has_errors() {
210 return Err("errors encountered — stopping".to_string());
211 }
212
213 if parsed.verbose {
215 eprintln!("automake-rs: generating {}", output_path.display());
216 }
217
218 let gen = automake_rs_core::makefile_in::MakefileInGenerator::new(am, config, traces);
219 let output = gen.generate();
220
221 fs::write(&output_path, output).map_err(|e| format!("write error: {}", e))?;
223
224 if parsed.add_missing {
226 if parsed.verbose {
227 eprintln!("automake-rs: delegating --add-missing to oracle");
228 }
229 add_missing_files(parsed, makefile_path)?;
230 }
231
232 Ok(output_path)
233}
234
235fn add_missing_files(
237 parsed: &automake_rs_core::cli::AutomakeArgs,
238 makefile_path: &Path,
239) -> Result<(), String> {
240 use automake_rs_core::aux_scripts;
241
242 let dir = makefile_path.parent().unwrap_or(Path::new("."));
243
244 match aux_scripts::install_aux_files(dir, parsed.copy, parsed.force_missing) {
245 Ok(installed) => {
246 if parsed.verbose {
247 for name in &installed {
248 eprintln!("automake-rs: installed auxiliary file '{}'", name);
249 }
250 }
251 Ok(())
252 }
253 Err(e) => Err(format!("aux file generation failed: {}", e)),
254 }
255}
256
257fn print_native_libdir() {
259 let dir = native_libdir();
260 println!("{}", dir);
261}
262
263fn native_libdir() -> String {
265 let candidates = &[
266 "/usr/share/automake-1.18",
267 "/usr/share/automake-1.17",
268 "/usr/share/automake-1.16",
269 "/usr/share/automake",
270 ];
271 for path in candidates {
272 if Path::new(path).exists() {
273 return path.to_string();
274 }
275 }
276 if let Ok(out) = std::process::Command::new("automake")
278 .arg("--print-libdir")
279 .output()
280 {
281 let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
282 if !s.is_empty() {
283 return s;
284 }
285 }
286 "/usr/share/automake-1.18".to_string()
287}
288
289pub fn effective_libdir(parsed: &automake_rs_core::cli::AutomakeArgs) -> String {
291 if let Some(ref dir) = parsed.libdir {
292 dir.clone()
293 } else {
294 native_libdir()
295 }
296}
297
298pub fn detect_platform() -> String {
302 let arch = std::env::consts::ARCH;
303 let os = std::env::consts::OS;
304 let vendor = "unknown";
305 match (arch, os) {
306 ("x86_64", "linux") => "x86_64-unknown-linux-gnu".into(),
307 ("aarch64", "linux") => "aarch64-unknown-linux-gnu".into(),
308 ("x86_64", "macos") => "x86_64-apple-darwin".into(),
309 ("aarch64", "macos") => "aarch64-apple-darwin".into(),
310 _ => format!("{}-{}-{}", arch, vendor, os),
311 }
312}
313
314pub fn run_aclocal() {
316 automake_rs_core::i18n::init_i18n();
317 let args: Vec<String> = std::env::args().collect();
318 let parsed = match automake_rs_core::cli::AclocalArgs::parse(&args) {
319 Ok(a) => a,
320 Err(e) => {
321 eprintln!("aclocal-rs: {}", e);
322 std::process::exit(1);
323 }
324 };
325
326 if parsed.help {
327 print_aclocal_help();
328 return;
329 }
330
331 if parsed.version {
332 print_aclocal_version();
333 return;
334 }
335
336 if parsed.print_ac_dir {
338 match std::process::Command::new("aclocal")
339 .arg("--print-ac-dir")
340 .output()
341 {
342 Ok(out) => {
343 std::io::Write::write_all(&mut std::io::stdout(), &out.stdout).ok();
344 return;
345 }
346 Err(e) => {
347 eprintln!("aclocal-rs: cannot query oracle: {}", e);
348 std::process::exit(1);
349 }
350 }
351 }
352
353 if parsed.verbose {
354 eprintln!("aclocal-rs: using native engine");
355 }
356
357 let engine = automake_rs_core::aclocal::Aclocal::from_args(&parsed);
358 if let Err(e) = engine.run() {
359 eprintln!("aclocal-rs: {}", e);
360 std::process::exit(1);
361 }
362}
363
364fn print_automake_version() {
365 let version = automake_rs_core::cli::oracle_version();
366 print!("{}", version);
367}
368
369fn print_aclocal_version() {
370 let version = automake_rs_core::cli::oracle_version_aclocal();
371 print!("{}", version);
372}
373
374fn print_automake_help() {
375 println!("Usage: /usr/bin/automake [OPTION]... [Makefile]...");
376 println!();
377 println!("Generate Makefile.in for configure from Makefile.am.");
378 println!();
379 println!("Operation modes:");
380 println!(" --help print this help, then exit");
381 println!(" --version print version number, then exit");
382 println!(" -v, --verbose verbosely list files processed");
383 println!(" --no-force only update Makefile.in's that are out of date");
384 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
385 println!();
386 println!("Dependency tracking:");
387 println!(" -i, --ignore-deps disable dependency tracking code");
388 println!(" --include-deps enable dependency tracking code");
389 println!();
390 println!("Flavors:");
391 println!(" --foreign set strictness to foreign");
392 println!(" --gnits set strictness to gnits");
393 println!(" --gnu set strictness to gnu");
394 println!();
395 println!("Library files:");
396 println!(" -a, --add-missing add missing standard files to package");
397 println!(" --libdir=DIR set directory storing library files");
398 println!(" --print-libdir print directory storing library files");
399 println!(" -c, --copy with -a, copy missing files (default is symlink)");
400 println!(" -f, --force-missing force update of standard files");
401 println!();
402 println!(" --host=TRIPLE cross-compilation host triple");
403 println!(" --build=TRIPLE cross-compilation build triple");
404 println!();
405 println!("automake-rs: native Rust reimplementation. Clean-room forensic parity.");
406}
407
408fn print_aclocal_help() {
409 println!("Usage: aclocal [OPTION]...");
410 println!();
411 println!("Generate 'aclocal.m4' by scanning 'configure.ac' or 'configure.in'");
412 println!();
413 println!("Options:");
414 println!(" --automake-acdir=DIR directory holding automake-provided m4 files");
415 println!(" --aclocal-path=PATH colon-separated list of directories to");
416 println!(" search for third-party local files");
417 println!(" --system-acdir=DIR directory holding third-party system-wide files");
418 println!(" --diff[=COMMAND] run COMMAND [diff -u] on M4 files that would be");
419 println!(" changed (implies --install and --dry-run)");
420 println!(" --dry-run pretend to, but do not actually update any file");
421 println!(" --force always update output file");
422 println!(" --help print this help, then exit");
423 println!(" -I DIR add directory to search list for .m4 files");
424 println!(" --install copy third-party files to the first -I directory");
425 println!(" --output=FILE put output in FILE (default aclocal.m4)");
426 println!(" --print-ac-dir print name of directory holding system-wide");
427 println!(" third-party m4 files, then exit");
428 println!(" --verbose don't be silent");
429 println!(" --version print version number, then exit");
430 println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
431 println!();
432 println!("aclocal-rs: native Rust reimplementation. Clean-room forensic parity.");
433}