helenos-ui 0.1.2

Rust bindings for HelenOS UI libraries
Documentation
use std::{env, path::PathBuf};
fn get_env_var(var: &str) -> Option<String> {
    println!("cargo::rerun-if-env-changed={var}");
    env::var(var).ok()
}

fn get_linker_from_json(json: &[u8]) -> Option<String> {
    use serde_json::*;
    Some(
        from_slice::<serde_json::Value>(json)
            .ok()?
            .as_object()?
            .get("linker")?
            .as_str()?
            .to_owned(),
    )
}

fn get_native_include_flags() -> Result<Vec<String>, String> {
    let rustc = env::var("RUSTC").unwrap();
    let Some(linker) = get_linker_from_json(
        &std::process::Command::new(rustc)
            .arg("-Zunstable-options")
            .arg("--print")
            .arg("target-spec-json")
            .arg("--target")
            .arg(env::var("TARGET").unwrap())
            .output()
            .expect("failed to execute rustc")
            .stdout,
    ) else {
        return Err("failed to get linker from rustc for this HelenOS target".to_owned());
    };

    let Some(output) = std::process::Command::new(&linker)
        .arg("-xc")
        .arg("-E")
        .arg("-v")
        .arg("-")
        .output()
        .ok()
        .and_then(|x| String::from_utf8(x.stderr).ok())
    else {
        return Err(format!(
            "failed to run linker {linker} to detect include paths"
        ));
    };
    let mut lines = output.lines();
    loop {
        let Some(line) = lines.next() else {
            return Err("failed to detect include paths from linker output".to_owned());
        };

        if line.contains("#include <...> search starts here:") {
            break;
        }
    }
    let mut flags = Vec::new();
    while let Some(l) = lines.next() {
        if !l.starts_with(' ') {
            break;
        }
        flags.push(format!("-I{}", l.trim()));
    }
    if flags.is_empty() {
        return Err("failed to detect include paths from linker output".to_owned());
    }
    Ok(flags)
}

fn main() {
    let target = env::var("TARGET").unwrap();
    if !target.contains("helenos") {
        println!("cargo::warning=Not a HelenOS target ({target}), this package will be empty");
        return;
    }

    let Some(include_base) = get_env_var("HELENOS_INCLUDE_BASE") else {
        println!("cargo::error=HELENOS_INCLUDE_BASE not set");
        return;
    };

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let libextern_file = out_path.join("extern.c");

    for lib in [
        "ui", "gfx", "gfxfont", "riff", "memgfx", "display", "console", "congfx", "ipcgfx",
    ] {
        println!("cargo::rustc-link-lib={lib}");
    }

    let include_flags = [
        "ui", "display", "gfx", "input", "c", "console", "output", "gfxfont",
    ]
    .map(|lib| format!("-I{include_base}/lib{lib}"));

    let native_include_flags = match get_native_include_flags() {
        Ok(paths) => paths,
        Err(e) => {
            println!("cargo::error={e}");
            return;
        }
    };

    let builder = bindgen::Builder::default()
        .header("wrapper.h")
        .sort_semantically(true)
        .allowlist_function(
            "\
                ui_(\
                    create|destroy|\
                    control_.*|\
                    window_.*|\
                    wnd_params_init|\
                    wdecor_.*\
                )|\
                display_.*|\
                gfx_.*|\
                pixelmap_.*|\
                fibril_usleep\
                ",
        )
        .allowlist_var("UI_.*")
        .blocklist_type("sysarg_t|errno_t|console_ctrl_t")
        .bitfield_enum("keymod_t")
        .bitfield_enum("ui_wdecor_style_t")
        .bitfield_enum("ui_wdecor_rsztype_t")
        .bitfield_enum("ui_wnd_flags_t")
        .newtype_enum("keycode_t")
        .newtype_enum("pos_event_type_t")
        .newtype_enum("ui_stock_cursor_t")
        .newtype_enum("ui_evclaim_t")
        .newtype_enum("ui_wnd_placement_t")
        .impl_debug(true)
        .wrap_static_fns(true)
        .wrap_static_fns_path(&libextern_file)
        .clang_args(&include_flags)
        .clang_args(&native_include_flags)
        .clang_arg("-nostdinc")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
    let bindings = builder.generate().expect("Unable to generate bindings");

    let mut libextern_build = cc::Build::new();
    libextern_build
        .file(&libextern_file)
        .include(env::var("CARGO_MANIFEST_DIR").unwrap());
    for flag in include_flags {
        libextern_build.flag(&flag);
    }
    for flag in native_include_flags {
        libextern_build.flag(&flag);
    }
    libextern_build.compile("extern");

    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}