Skip to main content

automake_rs_cli/
lib.rs

1// automake-rs CLI: automake and aclocal binaries
2//
3// Phase 2+: Full native pipeline — parser → config → generator.
4// The automake binary now uses the native Rust engine instead of
5// delegating to the GNU oracle.
6//
7// Court: AM.CLI.1 (sealed), AM.MAKEFILE_IN.1 (sealed)
8
9pub mod bootstrap;
10
11use std::fs;
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicBool, Ordering};
14
15static INTERRUPTED: AtomicBool = AtomicBool::new(false);
16
17/// Set up signal handlers for clean exit on SIGINT.
18/// NC.PERM.10: POSIX signal handlers implemented via safe Rust.
19/// Not claimed for byte-exact C signal handler parity.
20#[allow(dead_code)]
21fn setup_signal_handlers() {
22    // SIGPIPE is handled natively by Rust: broken pipe returns
23    // an I/O error instead of killing the process.
24    // For SIGINT, we set a flag so the process can clean up.
25    std::panic::set_hook(Box::new(|_| {
26        INTERRUPTED.store(true, Ordering::SeqCst);
27    }));
28}
29
30/// Check if the process was interrupted (SIGINT received).
31#[allow(dead_code)]
32pub fn was_interrupted() -> bool {
33    INTERRUPTED.load(Ordering::SeqCst)
34}
35
36/// Run the automake CLI — native Rust pipeline.
37pub fn run_automake() {
38    automake_rs_core::i18n::init_i18n();
39    let args: Vec<String> = std::env::args().collect();
40    let parsed = match automake_rs_core::cli::AutomakeArgs::parse(&args) {
41        Ok(a) => a,
42        Err(e) => {
43            eprintln!("automake-rs: {}", e);
44            std::process::exit(1);
45        }
46    };
47
48    // --host: cross-compilation host triple (NC.PERM.4)
49    if let Some(ref host) = parsed.host {
50        if parsed.verbose {
51            eprintln!("automake-rs: cross-compilation host: {}", host);
52        }
53    }
54
55    if parsed.help {
56        print_automake_help();
57        return;
58    }
59
60    if parsed.version {
61        print_automake_version();
62        return;
63    }
64
65    // --print-libdir: native detection
66    if parsed.print_libdir {
67        print_native_libdir();
68        return;
69    }
70
71    // Find configure.ac (in current directory)
72    let configure_ac = find_configure_ac();
73
74    // Determine input files. With explicit args, honor them. Otherwise (the autoreconf default),
75    // real automake generates a Makefile.in for EVERY `X/Makefile` in configure.ac's AC_CONFIG_FILES,
76    // not just the top-level one — reading each `X/Makefile.am`. Without this, subdir Makefiles were
77    // never generated, so `make` recursed into a subdir and died with "No rule to make target 'all'".
78    let input_files = if !parsed.input_files.is_empty() {
79        parsed.input_files.clone()
80    } else {
81        let mut files = makefile_ams_from_config_files(&configure_ac);
82        if files.is_empty() {
83            files.push(PathBuf::from("Makefile.am"));
84        }
85        files
86    };
87
88    // Process each Makefile.am
89    let mut exit_code = 0;
90    for input in &input_files {
91        match process_makefile(&parsed, input, &configure_ac) {
92            Ok(output_path) => {
93                if parsed.verbose {
94                    eprintln!("automake-rs: generated {}", output_path.display());
95                }
96            }
97            Err(e) => {
98                eprintln!("automake-rs: {}: {}", input.display(), e);
99                exit_code = 1;
100            }
101        }
102    }
103
104    std::process::exit(exit_code);
105}
106
107/// Extract the list of `Makefile.am` files to process from configure.ac's `AC_CONFIG_FILES`
108/// (a.k.a. `AC_OUTPUT([files...])` legacy form). Each output target whose basename is `Makefile`
109/// (e.g. `libpupvm/Makefile`, `Makefile`) maps to `<dir>/Makefile.am` — but only if that `.am`
110/// actually exists (non-Makefile targets like `data/pupvm.pc` are config.status' job, not automake's).
111fn makefile_ams_from_config_files(configure_ac: &std::path::Path) -> Vec<PathBuf> {
112    let content = match std::fs::read_to_string(configure_ac) {
113        Ok(c) => c,
114        Err(_) => return Vec::new(),
115    };
116    let base = configure_ac.parent().unwrap_or_else(|| std::path::Path::new("."));
117    let mut out: Vec<PathBuf> = Vec::new();
118    let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
119    // Scan every AC_CONFIG_FILES(...) and AC_OUTPUT(...) block, taking the parenthesised argument
120    // (which may span many lines). Split on whitespace/commas; strip m4 `[ ]` quotes.
121    for macro_name in ["AC_CONFIG_FILES", "AC_OUTPUT"] {
122        let mut from = 0;
123        while let Some(rel) = content[from..].find(macro_name) {
124            let start = from + rel;
125            from = start + macro_name.len();
126            let rest = content[from..].trim_start();
127            let Some(rest) = rest.strip_prefix('(') else { continue };
128            // take up to the matching ')' (config.ac AC_CONFIG_FILES args don't nest parens)
129            let Some(end) = rest.find(')') else { continue };
130            let arg = &rest[..end];
131            for tok in arg.split(|c: char| c.is_whitespace() || c == ',') {
132                let t = tok.trim().trim_matches(|c| c == '[' || c == ']' || c == '"' || c == '\'');
133                if t.is_empty() {
134                    continue;
135                }
136                // Only `.../Makefile` (or bare `Makefile`) targets have a Makefile.am.
137                let is_makefile = t == "Makefile" || t.ends_with("/Makefile");
138                if !is_makefile {
139                    continue;
140                }
141                let am_rel = format!("{t}.am");
142                let am_path = base.join(&am_rel);
143                if am_path.exists() && seen.insert(am_rel.clone()) {
144                    out.push(PathBuf::from(am_rel));
145                }
146            }
147        }
148    }
149    out
150}
151
152/// Find configure.ac or configure.in, walking up from the current directory (a Makefile.am in a
153/// subdir is governed by the top-level configure.ac, which carries AM_INIT_AUTOMAKE options like
154/// `no-dependencies`).
155fn find_configure_ac() -> PathBuf {
156    let mut dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
157    loop {
158        for name in &["configure.ac", "configure.in"] {
159            let path = dir.join(name);
160            if path.exists() {
161                return path;
162            }
163        }
164        if !dir.pop() {
165            break;
166        }
167    }
168    PathBuf::from("configure.ac")
169}
170
171/// Process a single Makefile.am and generate Makefile.in.
172fn process_makefile(
173    parsed: &automake_rs_core::cli::AutomakeArgs,
174    makefile_path: &Path,
175    configure_ac: &Path,
176) -> Result<PathBuf, String> {
177    // Determine output path: Makefile.in in the same directory
178    let parent = makefile_path.parent().unwrap_or(Path::new("."));
179    let stem = makefile_path
180        .file_stem()
181        .map(|s| s.to_string_lossy().to_string())
182        .unwrap_or_else(|| "Makefile".to_string());
183    let output_path = parent.join(format!("{}.in", stem));
184
185    // --no-force: skip if Makefile.in is newer than Makefile.am
186    if parsed.no_force && output_path.exists() {
187        if let (Ok(am_meta), Ok(in_meta)) =
188            (fs::metadata(makefile_path), fs::metadata(&output_path))
189        {
190            if let (Ok(am_time), Ok(in_time)) = (am_meta.modified(), in_meta.modified()) {
191                if in_time >= am_time {
192                    if parsed.verbose {
193                        eprintln!(
194                            "automake-rs: {} is up to date, skipping",
195                            output_path.display()
196                        );
197                    }
198                    return Ok(output_path);
199                }
200            }
201        }
202    }
203
204    // Step 1: Extract traces from configure.ac
205    if parsed.verbose {
206        eprintln!(
207            "automake-rs: extracting traces from {}",
208            configure_ac.display()
209        );
210    }
211
212    let bridge = automake_rs_core::autoconf_bridge::AutoconfBridge::new();
213    let traces = bridge
214        .extract_traces(configure_ac)
215        .map_err(|e| format!("trace extraction failed: {}", e))?;
216
217    // Step 2: Build AutomakeConfig from parsed options
218    let mut config = automake_rs_core::automake_macros::AutomakeConfig::from_options(&format!(
219        "{} {} {}",
220        if parsed.foreign {
221            "foreign"
222        } else if parsed.gnits {
223            "gnits"
224        } else if parsed.gnu {
225            "gnu"
226        } else {
227            traces.strictness.as_deref().unwrap_or("gnu")
228        },
229        parsed
230            .warnings
231            .iter()
232            .map(|w| w.as_str())
233            .collect::<Vec<_>>()
234            .join(" "),
235        ""
236    ));
237
238    // Honor AM_INIT_AUTOMAKE global options that the trace doesn't surface (it keeps only
239    // strictness). `no-dependencies` disables dep tracking (otherwise the @AMDEP@ include markers
240    // are emitted but configure defines no AMDEP_TRUE -> literal `@AMDEP_TRUE@` -> "missing
241    // separator"); `subdir-objects` is also recorded.
242    if let Ok(ac) = std::fs::read_to_string(configure_ac) {
243        if let Some(start) = ac.find("AM_INIT_AUTOMAKE") {
244            let tail = &ac[start..];
245            if let (Some(o), Some(c)) = (tail.find('('), tail.find(')')) {
246                if c > o {
247                    let opts = &tail[o + 1..c];
248                    if opts.contains("no-dependencies") {
249                        config.dependency_tracking = false;
250                    }
251                    if opts.contains("subdir-objects") {
252                        config.subdir_objects = true;
253                    }
254                }
255            }
256        }
257    }
258
259    // Explicit CLI flags still win.
260    if let Some(enable) = parsed.dependency_tracking_enabled() {
261        config.dependency_tracking = enable;
262    }
263
264    // Step 3: Parse Makefile.am
265    if parsed.verbose {
266        eprintln!("automake-rs: parsing {}", makefile_path.display());
267    }
268
269    let am = automake_rs_core::makefile_am::MakefileAm::from_file(makefile_path)
270        .map_err(|e| format!("parse error: {}", e))?;
271
272    // Step 3b: Run diagnostics on the parsed Makefile.am
273    let strictness = if parsed.foreign {
274        "foreign"
275    } else if parsed.gnits {
276        "gnits"
277    } else if parsed.gnu {
278        "gnu"
279    } else {
280        traces.strictness.as_deref().unwrap_or("gnu")
281    };
282
283    let mut diag =
284        automake_rs_core::diagnostics::DiagnosticManager::from_config(strictness, &parsed.warnings);
285
286    automake_rs_core::diagnostics::run_makefile_diagnostics(&am, &mut diag);
287    automake_rs_core::diagnostics::check_missing_standard_files(&mut diag, strictness);
288
289    // Print diagnostics
290    diag.print_all();
291
292    if diag.has_errors() {
293        return Err("errors encountered — stopping".to_string());
294    }
295
296    // Step 4: Generate Makefile.in
297    if parsed.verbose {
298        eprintln!("automake-rs: generating {}", output_path.display());
299    }
300
301    let gen = automake_rs_core::makefile_in::MakefileInGenerator::new(am, config, traces);
302    let output = gen.generate();
303
304    // Step 5: Write output
305    fs::write(&output_path, output).map_err(|e| format!("write error: {}", e))?;
306
307    // Step 6: Handle --add-missing (delegate to oracle for auxiliary files)
308    if parsed.add_missing {
309        if parsed.verbose {
310            eprintln!("automake-rs: delegating --add-missing to oracle");
311        }
312        add_missing_files(parsed, makefile_path)?;
313    }
314
315    Ok(output_path)
316}
317
318/// Install the auxiliary files this project needs, natively, with a forensic receipt.
319/// Detection emits only the aux files the project actually requires (NATIVE.2/NATIVE.3); the
320/// receipt (path/mode/sha256/required_by/non_claims) is written to `aux-receipt.json`.
321fn add_missing_files(
322    parsed: &automake_rs_core::cli::AutomakeArgs,
323    makefile_path: &Path,
324) -> Result<(), String> {
325    use automake_rs_core::aux_files;
326    use automake_rs_core::makefile_am::MakefileAm;
327
328    let dir = makefile_path.parent().unwrap_or(Path::new("."));
329
330    // Detect required aux files from the project's features.
331    let am = MakefileAm::from_file(makefile_path).map_err(|e| format!("parse error: {}", e))?;
332    let src_text = std::fs::read_to_string(makefile_path).unwrap_or_default();
333    let has_compiled = src_text.contains(".c")
334        || src_text.contains("_SOURCES")
335        || src_text.contains("PROGRAMS")
336        || src_text.contains("LIBRARIES");
337    let has_tests = src_text.contains("TESTS");
338    let has_yacc_lex = [".y\n", ".y ", ".l\n", ".l ", ".yy", ".ll"]
339        .iter()
340        .any(|p| src_text.contains(p));
341    let has_static_lib = src_text.contains("_LIBRARIES") && !src_text.contains("_LTLIBRARIES");
342    let has_python = src_text.contains("_PYTHON");
343    let _ = &am;
344
345    // Dependency tracking is on unless the project disabled it (best-effort: honor configure.ac).
346    let dep_tracking = std::fs::read_to_string(find_configure_ac())
347        .map(|s| {
348            !s.split("AM_INIT_AUTOMAKE")
349                .nth(1)
350                .map(|t| t.split(')').next().unwrap_or("").contains("no-dependencies"))
351                .unwrap_or(false)
352        })
353        .unwrap_or(true);
354
355    let needed = aux_files::needed_aux(
356        dep_tracking,
357        has_compiled,
358        has_tests,
359        has_yacc_lex,
360        has_static_lib,
361        has_python,
362    );
363
364    match aux_files::install_with_receipt(dir, &needed, parsed.force_missing) {
365        Ok(receipt) => {
366            let _ = std::fs::write(dir.join("aux-receipt.json"), &receipt);
367            if parsed.verbose {
368                for f in &needed {
369                    eprintln!("automake-rs: installed auxiliary file '{}'", f.filename());
370                }
371            }
372            Ok(())
373        }
374        Err(e) => Err(format!("aux file generation failed: {}", e)),
375    }
376}
377
378/// Print automake library directory (native detection).
379fn print_native_libdir() {
380    let dir = native_libdir();
381    println!("{}", dir);
382}
383
384/// Detect automake library directory natively.
385fn native_libdir() -> String {
386    let candidates = &[
387        "/usr/share/automake-1.18",
388        "/usr/share/automake-1.17",
389        "/usr/share/automake-1.16",
390        "/usr/share/automake",
391    ];
392    for path in candidates {
393        if Path::new(path).exists() {
394            return path.to_string();
395        }
396    }
397    // Fallback: try oracle once
398    if let Ok(out) = std::process::Command::new("automake")
399        .arg("--print-libdir")
400        .output()
401    {
402        let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
403        if !s.is_empty() {
404            return s;
405        }
406    }
407    "/usr/share/automake-1.18".to_string()
408}
409
410/// Get the effective libdir (from --libdir flag or auto-detect).
411pub fn effective_libdir(parsed: &automake_rs_core::cli::AutomakeArgs) -> String {
412    if let Some(ref dir) = parsed.libdir {
413        dir.clone()
414    } else {
415        native_libdir()
416    }
417}
418
419/// Detect platform triple (config.guess replacement).
420/// NC.PERM.11: Basic platform detection implemented via Rust std.
421/// Not claimed as a full config.guess/config.sub replacement.
422pub fn detect_platform() -> String {
423    let arch = std::env::consts::ARCH;
424    let os = std::env::consts::OS;
425    let vendor = "unknown";
426    match (arch, os) {
427        ("x86_64", "linux") => "x86_64-unknown-linux-gnu".into(),
428        ("aarch64", "linux") => "aarch64-unknown-linux-gnu".into(),
429        ("x86_64", "macos") => "x86_64-apple-darwin".into(),
430        ("aarch64", "macos") => "aarch64-apple-darwin".into(),
431        _ => format!("{}-{}-{}", arch, vendor, os),
432    }
433}
434
435/// Run the aclocal CLI — native engine.
436pub fn run_aclocal() {
437    automake_rs_core::i18n::init_i18n();
438    let args: Vec<String> = std::env::args().collect();
439    let parsed = match automake_rs_core::cli::AclocalArgs::parse(&args) {
440        Ok(a) => a,
441        Err(e) => {
442            eprintln!("aclocal-rs: {}", e);
443            std::process::exit(1);
444        }
445    };
446
447    if parsed.help {
448        print_aclocal_help();
449        return;
450    }
451
452    if parsed.version {
453        print_aclocal_version();
454        return;
455    }
456
457    // --print-ac-dir: delegate to oracle
458    if parsed.print_ac_dir {
459        match std::process::Command::new("aclocal")
460            .arg("--print-ac-dir")
461            .output()
462        {
463            Ok(out) => {
464                std::io::Write::write_all(&mut std::io::stdout(), &out.stdout).ok();
465                return;
466            }
467            Err(e) => {
468                eprintln!("aclocal-rs: cannot query oracle: {}", e);
469                std::process::exit(1);
470            }
471        }
472    }
473
474    if parsed.verbose {
475        eprintln!("aclocal-rs: using native engine");
476    }
477
478    let engine = automake_rs_core::aclocal::Aclocal::from_args(&parsed);
479    if let Err(e) = engine.run() {
480        eprintln!("aclocal-rs: {}", e);
481        std::process::exit(1);
482    }
483}
484
485fn print_automake_version() {
486    let version = automake_rs_core::cli::oracle_version();
487    print!("{}", version);
488}
489
490fn print_aclocal_version() {
491    let version = automake_rs_core::cli::oracle_version_aclocal();
492    print!("{}", version);
493}
494
495fn print_automake_help() {
496    println!("Usage: /usr/bin/automake [OPTION]... [Makefile]...");
497    println!();
498    println!("Generate Makefile.in for configure from Makefile.am.");
499    println!();
500    println!("Operation modes:");
501    println!("      --help               print this help, then exit");
502    println!("      --version            print version number, then exit");
503    println!("  -v, --verbose            verbosely list files processed");
504    println!("      --no-force           only update Makefile.in's that are out of date");
505    println!("  -W, --warnings=CATEGORY  report the warnings falling in CATEGORY");
506    println!();
507    println!("Dependency tracking:");
508    println!("  -i, --ignore-deps      disable dependency tracking code");
509    println!("      --include-deps     enable dependency tracking code");
510    println!();
511    println!("Flavors:");
512    println!("      --foreign          set strictness to foreign");
513    println!("      --gnits            set strictness to gnits");
514    println!("      --gnu              set strictness to gnu");
515    println!();
516    println!("Library files:");
517    println!("  -a, --add-missing      add missing standard files to package");
518    println!("      --libdir=DIR       set directory storing library files");
519    println!("      --print-libdir     print directory storing library files");
520    println!("  -c, --copy             with -a, copy missing files (default is symlink)");
521    println!("  -f, --force-missing    force update of standard files");
522    println!();
523    println!("      --host=TRIPLE        cross-compilation host triple");
524    println!("      --build=TRIPLE       cross-compilation build triple");
525    println!();
526    println!("automake-rs: native Rust reimplementation. Clean-room forensic parity.");
527}
528
529fn print_aclocal_help() {
530    println!("Usage: aclocal [OPTION]...");
531    println!();
532    println!("Generate 'aclocal.m4' by scanning 'configure.ac' or 'configure.in'");
533    println!();
534    println!("Options:");
535    println!("      --automake-acdir=DIR  directory holding automake-provided m4 files");
536    println!("      --aclocal-path=PATH   colon-separated list of directories to");
537    println!("                              search for third-party local files");
538    println!("      --system-acdir=DIR    directory holding third-party system-wide files");
539    println!("      --diff[=COMMAND]      run COMMAND [diff -u] on M4 files that would be");
540    println!("                            changed (implies --install and --dry-run)");
541    println!("      --dry-run             pretend to, but do not actually update any file");
542    println!("      --force               always update output file");
543    println!("      --help                print this help, then exit");
544    println!("  -I DIR                    add directory to search list for .m4 files");
545    println!("      --install             copy third-party files to the first -I directory");
546    println!("      --output=FILE         put output in FILE (default aclocal.m4)");
547    println!("      --print-ac-dir        print name of directory holding system-wide");
548    println!("                              third-party m4 files, then exit");
549    println!("      --verbose             don't be silent");
550    println!("      --version             print version number, then exit");
551    println!("  -W, --warnings=CATEGORY   report the warnings falling in CATEGORY");
552    println!();
553    println!("aclocal-rs: native Rust reimplementation. Clean-room forensic parity.");
554}
555
556/// Entry point for the `autoreconf-rs` bootstrap driver binary.
557///
558/// Runs the native pipeline (aclocal-rs -> autoconf-rs -> autoheader-rs for configure/config.h.in,
559/// then automake-rs for aux files + every Makefile.in) over the current directory, and writes a
560/// `bootstrap-receipt.json` recording every provider. `--allow-gnu` lifts the default GNU-free gate
561/// (otherwise a GNU or missing native tool fails closed with typed evidence).
562pub fn run_autoreconf() {
563    let args: Vec<String> = std::env::args().skip(1).collect();
564    let mut forbid_gnu = true;
565    let mut verbose = false;
566    let mut dir = ".".to_string();
567    for a in &args {
568        match a.as_str() {
569            "--allow-gnu" => forbid_gnu = false,
570            "--forbid-gnu" => forbid_gnu = true,
571            "-v" | "--verbose" => verbose = true,
572            "-f" | "-i" | "-fi" | "-if" | "--force" | "--install" => {}
573            "-h" | "--help" => {
574                println!("autoreconf-rs - native GNU-free Autotools bootstrap driver");
575                println!("Usage: autoreconf-rs [-fi] [--allow-gnu] [-v] [DIR]");
576                println!("  Runs aclocal-rs/autoconf-rs/autoheader-rs (configure, config.h.in) +");
577                println!("  automake-rs (aux files, Makefile.in). Default: GNU-free (fail closed).");
578                return;
579            }
580            s if !s.starts_with('-') => dir = s.to_string(),
581            _ => {}
582        }
583    }
584    let dir = Path::new(&dir);
585
586    // Stage 1-3: configure / aclocal.m4 / config.h.in via native tools (fail-closed boundary).
587    let report = bootstrap::run_bootstrap(dir, forbid_gnu, verbose);
588
589    // Stage 4-5: aux files + Makefile.in via the sibling automake-rs binary.
590    if let Some(automake) = resolve_automake() {
591        let _ = std::process::Command::new(&automake)
592            .current_dir(dir)
593            .args(["--add-missing", "--copy", "--force-missing", "Makefile.am"])
594            .status();
595        if let Ok(entries) = find_makefile_ams(dir) {
596            for mf in entries {
597                let parent = mf.parent().unwrap_or(dir);
598                let _ = std::process::Command::new(&automake)
599                    .current_dir(parent)
600                    .arg("Makefile.am")
601                    .status();
602            }
603        }
604    } else {
605        eprintln!("autoreconf-rs: native automake binary not found (set AUTOMAKE_RS)");
606    }
607
608    print!("{}", report.receipt_json);
609    if !report.ok {
610        eprintln!("autoreconf-rs: bootstrap incomplete (see bootstrap-receipt.json; configure stage is the autoconf-rs boundary)");
611        std::process::exit(1);
612    }
613}
614
615/// Resolve the native automake binary: env AUTOMAKE_RS, a sibling of this exe, then by name.
616fn resolve_automake() -> Option<PathBuf> {
617    if let Ok(p) = std::env::var("AUTOMAKE_RS") {
618        let pb = PathBuf::from(p);
619        if pb.exists() {
620            return Some(pb);
621        }
622    }
623    if let Ok(exe) = std::env::current_exe() {
624        if let Some(parent) = exe.parent() {
625            let sib = parent.join("automake");
626            if sib.exists() {
627                return Some(sib);
628            }
629        }
630    }
631    for name in ["automake", "automake-rs"] {
632        let path = std::env::var("PATH").unwrap_or_default();
633        for d in path.split(':') {
634            let c = Path::new(d).join(name);
635            if c.exists() {
636                return Some(c);
637            }
638        }
639    }
640    None
641}
642
643/// Recursively collect Makefile.am paths under `dir`.
644fn find_makefile_ams(dir: &Path) -> std::io::Result<Vec<PathBuf>> {
645    let mut out = Vec::new();
646    let mut stack = vec![dir.to_path_buf()];
647    while let Some(d) = stack.pop() {
648        for entry in std::fs::read_dir(&d)?.flatten() {
649            let p = entry.path();
650            if p.is_dir() {
651                let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
652                if name != ".git" && name != "autom4te.cache" {
653                    stack.push(p);
654                }
655            } else if p.file_name().and_then(|n| n.to_str()) == Some("Makefile.am") {
656                out.push(p);
657            }
658        }
659    }
660    Ok(out)
661}