a121-sys 0.4.0

Raw bindings to the A121 radar sensor C SDK
Documentation
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

fn main() {
    let rss_path = get_rss_path()
        .expect("Error determining rss directory path")
        .canonicalize()
        .unwrap();
    let lib_path = if cfg!(feature = "stub_library") {
        let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
        run_python_script(&rss_path);
        generate_stub_libraries(&rss_path, &out_dir);
        out_dir
    } else {
        get_acc_rss_libs_path().expect("Error determining Acconeer static libs path")
    };

    setup_linking(&lib_path); // Now always called regardless of stubs or actual libs
    rerun_if_changed(&rss_path);
    check_headers_existence(&rss_path);
    generate_bindings(&rss_path)
        .map_err(|e| panic!("{}", e))
        .unwrap();
}

fn run_python_script(rss_path: &Path) {
    let script_path = rss_path.join("generate_bindings.py");
    if !script_path.exists() {
        panic!(
            "Expected Python script does not exist at: {}",
            script_path.display()
        );
    }

    let output = Command::new("python3")
        .current_dir(rss_path)
        .arg("generate_bindings.py")
        .output()
        .expect("Failed to run Python script for generating bindings");

    if !output.status.success() {
        eprintln!(
            "Python script failed with output:\n{}",
            String::from_utf8_lossy(&output.stderr)
        );
        panic!("Python script execution failed");
    }
}

fn generate_stub_libraries(rss_path: &Path, out_dir: &Path) {
    compile_and_archive(
        out_dir,
        rss_path,
        "acconeer_a121_stubs.c",
        "acconeer_a121_stubs.o",
        "libacconeer_a121.a",
    );

    if cfg!(feature = "distance") {
        compile_and_archive(
            out_dir,
            rss_path,
            "acc_detector_distance_a121_stubs.c",
            "acc_detector_distance_a121_stubs.o",
            "libacc_detector_distance_a121.a",
        );
    }

    if cfg!(feature = "presence") {
        compile_and_archive(
            out_dir,
            rss_path,
            "acc_detector_presence_a121_stubs.c",
            "acc_detector_presence_a121_stubs.o",
            "libacc_detector_presence_a121.a",
        );
    }
}

fn compile_and_archive(
    out_dir: &Path,
    rss_path: &Path,
    source_file: &str,
    obj_file_name: &str,
    lib_name: &str,
) {
    let source_path = rss_path.join(source_file);
    let obj_path = out_dir.join(obj_file_name);
    let lib_path = out_dir.join(lib_name);

    Command::new("arm-none-eabi-gcc")
        .args([
            "-c",
            source_path.to_str().unwrap(),
            "-o",
            obj_path.to_str().unwrap(),
            "-I",
            rss_path.join("include").to_str().unwrap(),
            "-mcpu=cortex-m4",
            "-mthumb",
            "-mfloat-abi=hard",
            "-mfpu=fpv4-sp-d16",
            "-DTARGET_ARCH_cm4",
            "-DFLOAT_ABI_HARD",
            "-std=c99",
            "-MMD",
            "-MP",
            "-O2",
            "-g",
            "-fno-math-errno",
            "-ffunction-sections",
            "-fdata-sections",
            "-flto=auto",
            "-ffat-lto-objects",
        ])
        .status()
        .expect("Failed to compile C source file");

    Command::new("arm-none-eabi-ar")
        .args([
            "rcs",
            lib_path.to_str().unwrap(),
            obj_path.to_str().unwrap(),
        ])
        .status()
        .expect("Failed to create static library");
}

fn setup_linking(lib_path: &Path) {
    if cfg!(feature = "stub_library") {
        println!("cargo:rustc-linker=arm-none-eabi-gcc");
        // pass flags to the linker
        println!("cargo:rustc-link-arg=-mcpu=cortex-m4");
        println!("cargo:rustc-link-arg=-mthumb");
        println!("cargo:rustc-link-arg=-mfloat-abi=hard");
        println!("cargo:rustc-link-arg=-mfpu=fpv4-sp-d16");
    }

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

    if cfg!(feature = "distance") {
        println!("cargo:rustc-link-lib=static=acc_detector_distance_a121");
    }

    if cfg!(feature = "presence") {
        println!("cargo:rustc-link-lib=static=acc_detector_presence_a121");
    }
}

fn get_acc_rss_libs_path() -> Result<PathBuf, String> {
    let acc_rss_libs = env::var("ACC_RSS_LIBS").unwrap_or_else(|_| ".".to_string());
    PathBuf::from(acc_rss_libs)
        .canonicalize()
        .map_err(|_| "Error pointing to Acconeer static libs path.".to_string())
}

fn get_rss_path() -> Result<PathBuf, String> {
    PathBuf::from("rss")
        .canonicalize()
        .map_err(|_| "RSS directory not found".to_string())
}

fn rerun_if_changed(rss_path: &Path) {
    println!(
        "cargo:rerun-if-changed={}",
        rss_path.join("include").display()
    );
}

fn check_headers_existence(rss_path: &Path) {
    let headers = rss_path.join("include");
    if !headers.exists() {
        panic!("Headers not found");
    }
}

fn generate_bindings(rss_path: &Path) -> Result<(), String> {
    let headers = rss_path.join("include");
    let mut bindings = bindgen::Builder::default()
        .use_core()
        .clang_arg("-I/usr/lib/arm-none-eabi/include/")
        .clang_arg(format!("-I{}", headers.display()))
        .generate_cstr(true);

    for entry in
        fs::read_dir(&headers).map_err(|_| "Unable to read headers directory".to_string())?
    {
        let entry = entry.map_err(|_| "Error iterating headers directory".to_string())?;
        let path = entry.path();
        if path.is_file() && path.extension() == Some(OsStr::new("h")) {
            let filename = path.file_name().unwrap().to_str().unwrap();
            let is_distance = filename.contains("distance") && cfg!(feature = "distance");
            let is_presence = filename.contains("presence") && cfg!(feature = "presence");
            let is_other = !filename.contains("distance") && !filename.contains("presence");

            if is_distance || is_presence || is_other {
                bindings = bindings.header(path.to_str().unwrap());
            }
        }
    }
    bindings = c_log_wrapper(bindings);

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .generate()
        .map_err(|_| "Unable to generate bindings".to_string())?
        .write_to_file(out_path.join("bindings.rs"))
        .map_err(|_| "Unable to write bindings to file".to_string())
        .expect("Unable to write bindings to file");
    Ok(())
}

fn c_log_wrapper(mut bindings: bindgen::Builder) -> bindgen::Builder {
    cc::Build::new()
        .file("c_src/logging.c")
        .include("c_src")
        .warnings_into_errors(true)
        .extra_warnings(true)
        .compile("log");
    println!("cargo:rerun-if-changed=c_src/logging.c");
    println!("cargo:rustc-link-lib=static=log");
    bindings = bindings.header("c_src/logging.h");
    bindings
}