simavr-ffi 1.0.0

Low-level bindings to simavr
Documentation
use anyhow::Context;
use std::{
    env, fs,
    path::{Path, PathBuf},
    process::Command,
};
use walkdir::WalkDir;

fn main() {
    println!("cargo:rerun-if-changed=build.rs");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());

    build_simavr(&out_path);
    generate_simavr_bindings(&out_path);
    link_libelf();
    link_zlib();
}

fn build_simavr(out: &Path) {
    println!("=> Building simavr");

    if !Path::new("vendor")
        .join("simavr")
        .join("README.md")
        .exists()
    {
        panic!(
            "\
            `vendor/simavr` doesn't seem to contain the expected source code. \
            If you're cloning simavr-ffi by hand, please use `git clone ... \
            --recurse-submodules`."
        );
    }

    #[cfg(target_family = "unix")]
    build_simavr_unix(out);

    #[cfg(not(target_family = "unix"))]
    panic!("Sorry, I don't know how to build simavr on this architecture");
}

#[cfg(target_family = "unix")]
fn build_simavr_unix(out: &Path) {
    let out_simavr = out.join("simavr");

    if !out_simavr.exists() {
        fs::create_dir(&out_simavr)
            .with_context(|| format!("Couldn't create directory: {}", out_simavr.display()))
            .unwrap();

        fs_extra::copy_items(&["vendor/simavr"], &out_simavr, &Default::default())
            .with_context(|| {
                format!(
                    "Couldn't copy simavr's sources to: {}",
                    out_simavr.display()
                )
            })
            .unwrap();
    }

    let result = Command::new("make")
        .current_dir(out_simavr.join("simavr").join("simavr"))
        .env("OBJ", out_simavr.as_os_str())
        .arg("-e")
        .arg("libsimavr")
        .status()
        .expect("Couldn't build simavr");

    if !result.success() {
        panic!("Couldn't build simavr: `make` failed");
    }

    println!("cargo:rustc-link-search={}", out_simavr.display());
    println!("cargo:rustc-link-lib=static=simavr");
}

fn generate_simavr_bindings(out: &Path) {
    println!("=> Generating simavr bindings");

    let mut builder = bindgen::Builder::default();

    let headers = WalkDir::new("vendor/simavr/simavr/sim")
        .max_depth(1)
        .into_iter()
        .map(|header| header.unwrap())
        .filter(|entry| {
            let metadata = entry.metadata().unwrap();
            let name = entry.file_name().to_string_lossy();

            metadata.is_file()
                && name.ends_with(".h")
                && name != "sim_cmds.h"
                && name != "sim_core.h"
        })
        .map(|header| header.path().to_string_lossy().to_string());

    for header in headers {
        println!("-> Found header: {}", header);
        builder = builder.header(header);
    }

    let bindings = builder
        .generate()
        .expect("Couldn't generate simavr's bindings");

    bindings
        .write_to_file(out.join("bindings.rs"))
        .expect("Couldn't write simavr's bindings");
}

fn link_libelf() {
    println!("=> Linking libelf");

    pkg_config::probe_library("libelf").unwrap();

    println!("cargo:rustc-link-lib=static=elf");
}

fn link_zlib() {
    println!("=> Linking zlib");

    pkg_config::probe_library("zlib").unwrap();

    println!("cargo:rustc-link-lib=z");
}