sandkiste_lua 0.4.0

Sandbox for Lua scripts
Documentation
use std::mem::take;
use std::process::{Command, Stdio};

#[cfg(not(any(feature = "Lua5_3", feature = "Lua5_4")))]
compile_error!("must specify feature \"Lua5_3\" or \"Lua5_4\"");

#[cfg(all(feature = "Lua5_3", feature = "Lua5_4"))]
compile_error!("features \"Lua5_3\" and \"Lua5_4\" are currently mutually exclusive and cannot be enabled at the same time");

fn main() {
    let mut include_dirs: Vec<String> = Vec::new();
    let mut lib_dirs: Vec<String> = Vec::new();
    let mut lib_names: Vec<String> = Vec::new();

    // use `pkg-config` binary to determine Lua library name and location
    {
        #[cfg(feature = "Lua5_3")]
        const PKG_NAME: &'static str = "lua-5.3";
        #[cfg(feature = "Lua5_4")]
        const PKG_NAME: &'static str = "lua-5.4";
        #[cfg(not(any(feature = "Lua5_3", feature = "Lua5_4")))]
        const PKG_NAME: &'static str = unreachable!();
        let mut pkgcnf_cmd = Command::new("pkg-config");
        pkgcnf_cmd
            .args(["--cflags", "--libs", PKG_NAME])
            .stderr(Stdio::inherit());
        eprintln!("using pkg-config command: {pkgcnf_cmd:?}");
        let pkgcnf = pkgcnf_cmd.output().expect("could not execute pkg-config");
        eprintln!("pkg-config status: {:?}", pkgcnf.status);
        eprintln!(
            "pkg-config stdout: {:?}",
            String::from_utf8_lossy(&pkgcnf.stdout)
        );
        if !pkgcnf.status.success() {
            panic!("pkg-config returned with failure");
        }
        let mut parse_element = |s: String| {
            if s.len() >= 2 {
                let prefix = &s[0..2];
                let value = &s[2..];
                match prefix {
                    "-I" => include_dirs.push(value.to_string()),
                    "-L" => lib_dirs.push(value.to_string()),
                    "-l" => lib_names.push(value.to_string()),
                    _ => (),
                }
            }
        };
        let mut element: String = Default::default();
        let mut escape: bool = false;
        for ch in String::from_utf8(pkgcnf.stdout)
            .expect("invalid UTF-8 from pkg-config")
            .chars()
        {
            if escape {
                element.push(ch);
                escape = false;
            } else if ch == '\\' {
                escape = true;
            } else if ch.is_ascii_whitespace() {
                parse_element(take(&mut element));
            } else {
                element.push(ch);
            }
        }
        if escape {
            panic!("unexpected EOF from pkg-config (escape character at end)");
        }
        parse_element(element);
        if lib_names.is_empty() {
            panic!("pkg-config did not return any library name");
        }
    }

    // create automatic bindings
    {
        let mut builder = bindgen::Builder::default();
        for dir in &include_dirs {
            builder = builder.clang_arg(format!("-I{dir}"));
        }
        builder = builder.header("src/cmach.c");
        builder = builder.parse_callbacks(Box::new(bindgen::CargoCallbacks));
        let bindings = builder.generate().expect("unable to generate bindings");
        let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
        bindings
            .write_to_file(out_path.join("ffi_cmach.rs"))
            .expect("unable to write bindings");
    }

    // build own C lib
    {
        println!("cargo:rerun-if-changed=src/cmach.c");
        let mut config = cc::Build::new();
        for dir in &include_dirs {
            config.include(dir);
        }
        config.file("src/cmach.c");
        config.compile("libffi_cmach.a");
    }

    // link with Lua
    for dir in &lib_dirs {
        println!("cargo:rustc-link-search=native={}", dir);
    }
    for name in &lib_names {
        println!("cargo:rustc-link-lib={}", name);
    }
}