use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
static INTERRUPTED: AtomicBool = AtomicBool::new(false);
#[allow(dead_code)]
fn setup_signal_handlers() {
std::panic::set_hook(Box::new(|_| {
INTERRUPTED.store(true, Ordering::SeqCst);
}));
}
#[allow(dead_code)]
pub fn was_interrupted() -> bool {
INTERRUPTED.load(Ordering::SeqCst)
}
pub fn run_automake() {
automake_rs_core::i18n::init_i18n();
let args: Vec<String> = std::env::args().collect();
let parsed = match automake_rs_core::cli::AutomakeArgs::parse(&args) {
Ok(a) => a,
Err(e) => {
eprintln!("automake-rs: {}", e);
std::process::exit(1);
}
};
if let Some(ref host) = parsed.host {
if parsed.verbose {
eprintln!("automake-rs: cross-compilation host: {}", host);
}
}
if parsed.help {
print_automake_help();
return;
}
if parsed.version {
print_automake_version();
return;
}
if parsed.print_libdir {
print_native_libdir();
return;
}
let input_files = if parsed.input_files.is_empty() {
vec![PathBuf::from("Makefile.am")]
} else {
parsed.input_files.clone()
};
let configure_ac = find_configure_ac();
let mut exit_code = 0;
for input in &input_files {
match process_makefile(&parsed, input, &configure_ac) {
Ok(output_path) => {
if parsed.verbose {
eprintln!("automake-rs: generated {}", output_path.display());
}
}
Err(e) => {
eprintln!("automake-rs: {}: {}", input.display(), e);
exit_code = 1;
}
}
}
std::process::exit(exit_code);
}
fn find_configure_ac() -> PathBuf {
let mut dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
loop {
for name in &["configure.ac", "configure.in"] {
let path = dir.join(name);
if path.exists() {
return path;
}
}
if !dir.pop() {
break;
}
}
PathBuf::from("configure.ac")
}
fn process_makefile(
parsed: &automake_rs_core::cli::AutomakeArgs,
makefile_path: &Path,
configure_ac: &Path,
) -> Result<PathBuf, String> {
let parent = makefile_path.parent().unwrap_or(Path::new("."));
let stem = makefile_path
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "Makefile".to_string());
let output_path = parent.join(format!("{}.in", stem));
if parsed.no_force && output_path.exists() {
if let (Ok(am_meta), Ok(in_meta)) =
(fs::metadata(makefile_path), fs::metadata(&output_path))
{
if let (Ok(am_time), Ok(in_time)) = (am_meta.modified(), in_meta.modified()) {
if in_time >= am_time {
if parsed.verbose {
eprintln!(
"automake-rs: {} is up to date, skipping",
output_path.display()
);
}
return Ok(output_path);
}
}
}
}
if parsed.verbose {
eprintln!(
"automake-rs: extracting traces from {}",
configure_ac.display()
);
}
let bridge = automake_rs_core::autoconf_bridge::AutoconfBridge::new();
let traces = bridge
.extract_traces(configure_ac)
.map_err(|e| format!("trace extraction failed: {}", e))?;
let mut config = automake_rs_core::automake_macros::AutomakeConfig::from_options(&format!(
"{} {} {}",
if parsed.foreign {
"foreign"
} else if parsed.gnits {
"gnits"
} else if parsed.gnu {
"gnu"
} else {
traces.strictness.as_deref().unwrap_or("gnu")
},
parsed
.warnings
.iter()
.map(|w| w.as_str())
.collect::<Vec<_>>()
.join(" "),
""
));
if let Ok(ac) = std::fs::read_to_string(configure_ac) {
if let Some(start) = ac.find("AM_INIT_AUTOMAKE") {
let tail = &ac[start..];
if let (Some(o), Some(c)) = (tail.find('('), tail.find(')')) {
if c > o {
let opts = &tail[o + 1..c];
if opts.contains("no-dependencies") {
config.dependency_tracking = false;
}
if opts.contains("subdir-objects") {
config.subdir_objects = true;
}
}
}
}
}
if let Some(enable) = parsed.dependency_tracking_enabled() {
config.dependency_tracking = enable;
}
if parsed.verbose {
eprintln!("automake-rs: parsing {}", makefile_path.display());
}
let am = automake_rs_core::makefile_am::MakefileAm::from_file(makefile_path)
.map_err(|e| format!("parse error: {}", e))?;
let strictness = if parsed.foreign {
"foreign"
} else if parsed.gnits {
"gnits"
} else if parsed.gnu {
"gnu"
} else {
traces.strictness.as_deref().unwrap_or("gnu")
};
let mut diag =
automake_rs_core::diagnostics::DiagnosticManager::from_config(strictness, &parsed.warnings);
automake_rs_core::diagnostics::run_makefile_diagnostics(&am, &mut diag);
automake_rs_core::diagnostics::check_missing_standard_files(&mut diag, strictness);
diag.print_all();
if diag.has_errors() {
return Err("errors encountered — stopping".to_string());
}
if parsed.verbose {
eprintln!("automake-rs: generating {}", output_path.display());
}
let gen = automake_rs_core::makefile_in::MakefileInGenerator::new(am, config, traces);
let output = gen.generate();
fs::write(&output_path, output).map_err(|e| format!("write error: {}", e))?;
if parsed.add_missing {
if parsed.verbose {
eprintln!("automake-rs: delegating --add-missing to oracle");
}
add_missing_files(parsed, makefile_path)?;
}
Ok(output_path)
}
fn add_missing_files(
parsed: &automake_rs_core::cli::AutomakeArgs,
makefile_path: &Path,
) -> Result<(), String> {
use automake_rs_core::aux_scripts;
let dir = makefile_path.parent().unwrap_or(Path::new("."));
match aux_scripts::install_aux_files(dir, parsed.copy, parsed.force_missing) {
Ok(installed) => {
if parsed.verbose {
for name in &installed {
eprintln!("automake-rs: installed auxiliary file '{}'", name);
}
}
Ok(())
}
Err(e) => Err(format!("aux file generation failed: {}", e)),
}
}
fn print_native_libdir() {
let dir = native_libdir();
println!("{}", dir);
}
fn native_libdir() -> String {
let candidates = &[
"/usr/share/automake-1.18",
"/usr/share/automake-1.17",
"/usr/share/automake-1.16",
"/usr/share/automake",
];
for path in candidates {
if Path::new(path).exists() {
return path.to_string();
}
}
if let Ok(out) = std::process::Command::new("automake")
.arg("--print-libdir")
.output()
{
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
if !s.is_empty() {
return s;
}
}
"/usr/share/automake-1.18".to_string()
}
pub fn effective_libdir(parsed: &automake_rs_core::cli::AutomakeArgs) -> String {
if let Some(ref dir) = parsed.libdir {
dir.clone()
} else {
native_libdir()
}
}
pub fn detect_platform() -> String {
let arch = std::env::consts::ARCH;
let os = std::env::consts::OS;
let vendor = "unknown";
match (arch, os) {
("x86_64", "linux") => "x86_64-unknown-linux-gnu".into(),
("aarch64", "linux") => "aarch64-unknown-linux-gnu".into(),
("x86_64", "macos") => "x86_64-apple-darwin".into(),
("aarch64", "macos") => "aarch64-apple-darwin".into(),
_ => format!("{}-{}-{}", arch, vendor, os),
}
}
pub fn run_aclocal() {
automake_rs_core::i18n::init_i18n();
let args: Vec<String> = std::env::args().collect();
let parsed = match automake_rs_core::cli::AclocalArgs::parse(&args) {
Ok(a) => a,
Err(e) => {
eprintln!("aclocal-rs: {}", e);
std::process::exit(1);
}
};
if parsed.help {
print_aclocal_help();
return;
}
if parsed.version {
print_aclocal_version();
return;
}
if parsed.print_ac_dir {
match std::process::Command::new("aclocal")
.arg("--print-ac-dir")
.output()
{
Ok(out) => {
std::io::Write::write_all(&mut std::io::stdout(), &out.stdout).ok();
return;
}
Err(e) => {
eprintln!("aclocal-rs: cannot query oracle: {}", e);
std::process::exit(1);
}
}
}
if parsed.verbose {
eprintln!("aclocal-rs: using native engine");
}
let engine = automake_rs_core::aclocal::Aclocal::from_args(&parsed);
if let Err(e) = engine.run() {
eprintln!("aclocal-rs: {}", e);
std::process::exit(1);
}
}
fn print_automake_version() {
let version = automake_rs_core::cli::oracle_version();
print!("{}", version);
}
fn print_aclocal_version() {
let version = automake_rs_core::cli::oracle_version_aclocal();
print!("{}", version);
}
fn print_automake_help() {
println!("Usage: /usr/bin/automake [OPTION]... [Makefile]...");
println!();
println!("Generate Makefile.in for configure from Makefile.am.");
println!();
println!("Operation modes:");
println!(" --help print this help, then exit");
println!(" --version print version number, then exit");
println!(" -v, --verbose verbosely list files processed");
println!(" --no-force only update Makefile.in's that are out of date");
println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
println!();
println!("Dependency tracking:");
println!(" -i, --ignore-deps disable dependency tracking code");
println!(" --include-deps enable dependency tracking code");
println!();
println!("Flavors:");
println!(" --foreign set strictness to foreign");
println!(" --gnits set strictness to gnits");
println!(" --gnu set strictness to gnu");
println!();
println!("Library files:");
println!(" -a, --add-missing add missing standard files to package");
println!(" --libdir=DIR set directory storing library files");
println!(" --print-libdir print directory storing library files");
println!(" -c, --copy with -a, copy missing files (default is symlink)");
println!(" -f, --force-missing force update of standard files");
println!();
println!(" --host=TRIPLE cross-compilation host triple");
println!(" --build=TRIPLE cross-compilation build triple");
println!();
println!("automake-rs: native Rust reimplementation. Clean-room forensic parity.");
}
fn print_aclocal_help() {
println!("Usage: aclocal [OPTION]...");
println!();
println!("Generate 'aclocal.m4' by scanning 'configure.ac' or 'configure.in'");
println!();
println!("Options:");
println!(" --automake-acdir=DIR directory holding automake-provided m4 files");
println!(" --aclocal-path=PATH colon-separated list of directories to");
println!(" search for third-party local files");
println!(" --system-acdir=DIR directory holding third-party system-wide files");
println!(" --diff[=COMMAND] run COMMAND [diff -u] on M4 files that would be");
println!(" changed (implies --install and --dry-run)");
println!(" --dry-run pretend to, but do not actually update any file");
println!(" --force always update output file");
println!(" --help print this help, then exit");
println!(" -I DIR add directory to search list for .m4 files");
println!(" --install copy third-party files to the first -I directory");
println!(" --output=FILE put output in FILE (default aclocal.m4)");
println!(" --print-ac-dir print name of directory holding system-wide");
println!(" third-party m4 files, then exit");
println!(" --verbose don't be silent");
println!(" --version print version number, then exit");
println!(" -W, --warnings=CATEGORY report the warnings falling in CATEGORY");
println!();
println!("aclocal-rs: native Rust reimplementation. Clean-room forensic parity.");
}