sfcgal-sys 0.8.0

Low-level FFI bindings to SFCGAL.
extern crate bindgen;
extern crate cc;

use std::{
    env::{self, VarError},
    ffi::OsStr,
    iter,
    path::PathBuf,
};

fn main() {
    println!("cargo:rustc-link-lib=SFCGAL");

    let mut cargo_metadata = Vec::with_capacity(64);
    let link_libs_opt = env_var("SFCGAL_LINK_LIBS");
    let link_paths_opt = env_var("SFCGAL_LINK_PATHS");
    let include_paths_opt = env_var("SFCGAL_INCLUDE_PATHS");

    if let Some(link_paths) = link_paths_opt {
        let meta = link_paths
            .split(',')
            .map(str::trim)
            .filter(|x| !x.is_empty())
            .flat_map(|path| {
                let out = iter::once(format!("cargo:rustc-link-search={}", path));
                #[cfg(target_os = "macos")]
                {
                    out.chain(iter::once(format!(
                        "cargo:rustc-link-search=framework={}",
                        path
                    )))
                }
                #[cfg(not(target_os = "macos"))]
                {
                    out
                }
            });

        cargo_metadata.extend(meta);
    }

    if let Some(link_libs) = link_libs_opt {
        cargo_metadata.extend(
            process_library_list(
                link_libs
                    .split(',')
                    .map(str::trim)
                    .filter(|x| !x.is_empty()),
            )
            .map(|l| format!("cargo:rustc-link-lib={}", l)),
        );
    }

    let include_paths: Vec<_> = match include_paths_opt {
        Some(include_paths) => include_paths
            .split(',')
            .map(str::trim)
            .filter(|x| !x.is_empty())
            .map(PathBuf::from)
            .collect(),
        None => vec![PathBuf::from("/usr/include")],
    };

    {
        let mut build = cc::Build::new();
        build.file("src/wrapper.c");

        for include_path in include_paths.iter() {
            build.include(include_path);
        }

        build.compile("sfcgalwrapper");
    }

    let bindings = bindgen::Builder::default()
        .rust_target(bindgen::RustTarget::Stable_1_33)
        .header("src/wrapper.h")
        .clang_args(
            include_paths
                .iter()
                .map(|path| format!("-I{}", path.display())),
        )
        .allowlist_type("sfcgal_.*$")
        .allowlist_var("sfcgal_.*$")
        .allowlist_function("sfcgal_.*$")
        .allowlist_type("w_sfcgal_.*$")
        .allowlist_var("w_sfcgal_.*$")
        .allowlist_function("w_sfcgal_.*$")
        .size_t_is_usize(true)
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");

    for meta in cargo_metadata.into_iter() {
        println!("{}", meta);
    }
}

fn process_library_list(
    libs: impl IntoIterator<Item = impl Into<PathBuf>>,
) -> impl Iterator<Item = String> {
    libs.into_iter().map(|x| {
        let mut path: PathBuf = x.into();
        let extension = path.extension().and_then(OsStr::to_str).unwrap_or_default();
        let is_framework = extension.eq_ignore_ascii_case("framework");
        const LIB_EXTS: [&str; 7] = ["so", "a", "dll", "lib", "dylib", "framework", "tbd"];
        if is_framework || LIB_EXTS.iter().any(|e| e.eq_ignore_ascii_case(extension)) {
            path.set_extension("");
        }
        path.file_name()
            .and_then(|f| {
                f.to_str().map(|f| {
                    if is_framework {
                        format!("framework={}", f)
                    } else {
                        f.to_owned()
                    }
                })
            })
            .expect("Invalid library name")
    })
}

fn env_var<K: AsRef<OsStr>>(key: K) -> Option<String> {
    match env::var(key) {
        Ok(val) => Some(val),
        Err(VarError::NotPresent) => None,
        Err(VarError::NotUnicode(_)) => panic!("the value of environment variable is not Unicode"),
    }
}