bac 0.1.0

Rust bindings for the BAC compiler backend
use std::{
    env,
    path::{Path, PathBuf},
    process::Command,
};

fn run(command: &mut Command) {
    let status = command
        .status()
        .unwrap_or_else(|error| panic!("{command:?}: {error}"));
    assert!(status.success(), "{command:?} failed: {status}");
}

fn watch(path: &Path) {
    if path.is_dir() {
        for entry in path.read_dir().unwrap() {
            let path = entry.unwrap().path();
            if path
                .file_name()
                .is_some_and(|name| name == "rust" || name == "c")
            {
                continue;
            }
            watch(&path);
        }
    } else {
        println!("cargo:rerun-if-changed={}", path.display());
    }
}

fn main() {
    for key in ["BAC_LIB_PATH", "ODIN"] {
        println!("cargo:rerun-if-env-changed={key}");
    }
    let manifest = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap());
    let root = manifest.join("../../..").canonicalize().unwrap();
    let out = PathBuf::from(env::var_os("OUT_DIR").unwrap());
    let library = if env::var_os("CARGO_FEATURE_BUILD_FROM_SOURCE").is_some() {
        assert_eq!(
            env::var("HOST").unwrap(),
            env::var("TARGET").unwrap(),
            "build-from-source currently builds for the host; supply a target BAC_LIB_PATH for cross compilation"
        );
        watch(&root.join("bac"));
        watch(&root.join("utils"));
        let object = out.join("bac.o");
        run(
            Command::new(env::var_os("ODIN").unwrap_or_else(|| "odin".into()))
                .current_dir(&root)
                .args([
                    "build",
                    "bac/bindgen/lib",
                    "-build-mode:obj",
                    "-no-entry-point",
                    "-use-single-module",
                    "-o:size",
                    "-source-code-locations:none",
                    "-disable-assert",
                    "-no-bounds-check",
                    "-no-type-assert",
                    "-define:NO_FMT=true",
                    "-default-to-nil-allocator",
                    "-define:ARNA_DISABLE_VIRTUAL=true",
                    "-define:ARNA_DISABLE_THREAD_LOCAL=true",
                    "-disable-init-fini",
                    "-define:INIT_FINI=false",
                ])
                .arg(format!("-out:{}", object.display())),
        );
        object
    } else {
        env::var_os("BAC_LIB_PATH")
            .map(PathBuf::from)
            .unwrap_or_else(|| root.join("bac/bindgen/lib/lib.o"))
    };
    assert!(
        library.is_file(),
        "BAC library not found at {}. Set BAC_LIB_PATH to a BAC object/static archive, run build-bac, or enable --features build-from-source (requires Odin).",
        library.display()
    );
    if env::var_os("CARGO_FEATURE_BUILD_FROM_SOURCE").is_none() {
        println!("cargo:rerun-if-changed={}", library.display());
    }
    let library = library.canonicalize().unwrap();
    let modifiers = if library.extension().is_some_and(|ext| ext == "a") {
        "+verbatim"
    } else {
        "-bundle,+verbatim"
    };
    println!(
        "cargo:rustc-link-search=native={}",
        library.parent().unwrap().display()
    );
    println!(
        "cargo:rustc-link-lib=static:{modifiers}={}",
        library.file_name().unwrap().to_str().unwrap()
    );
}