stripack-sys 0.1.1

Raw Rust FFI bindings to STRIPACK for Delaunay triangulation on the unit sphere
Documentation
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
use which::which;

fn main() {
    let target = env::var("TARGET").unwrap();
    let host = env::var("HOST").unwrap();
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());

    let lib_dir = manifest_dir.join("lib").join(&target);
    let lib_file = if target.contains("windows-msvc") {
        lib_dir.join("stripack.lib")
    } else {
        lib_dir.join("libstripack.a")
    };

    if lib_file.exists() {
        println!("cargo:warning=Using pre-built library for {target}");
        println!("cargo:rustc-link-search=native={}", lib_dir.display());
        println!("cargo:rustc-link-lib=static=stripack");
    } else if target == host {
        println!("cargo:warning=Compiling from source for {target}");
        compile_from_source(&out_dir);
        create_static_lib(&out_dir, &target);
    } else {
        panic!("No pre-built library for target: {target}\nHost: {host}");
    }

    if target.contains("windows") {
        println!("cargo:rustc-link-search=native=C:/msys64/mingw64/lib");
        println!("cargo:rustc-link-search=native=C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/14");
        println!("cargo:rustc-link-lib=dylib=gfortran");
        println!("cargo:rustc-link-lib=dylib=quadmath");
    } else if target.contains("darwin") {
        println!("cargo:rustc-link-search=native=/opt/homebrew/lib");
        println!("cargo:rustc-link-search=native=/opt/homebrew/lib/gcc/14");
        println!("cargo:rustc-link-search=native=/usr/local/lib");
        println!("cargo:rustc-link-lib=gfortran");
    } else {
        println!("cargo:rustc-link-lib=gfortran");
    }
}

fn compile_from_source(out_dir: &Path) {
    let fortran_src = "fortran/stripack.f90";

    let obj_path = out_dir.join("stripack.o");

    let Some(compiler) = detect_fortran_compiler() else {
        panic!("No fortran compiler found.");
    };

    let status = Command::new(compiler)
        .args(["-c", "-O2", "-fPIC", fortran_src, "-o"])
        .arg(&obj_path)
        .status()
        .expect("Failed to compile Fortran source");

    assert!(status.success(), "gfortran compilation failed");
}

fn create_static_lib(out_dir: &Path, target: &str) {
    let fortran_src = "fortran/stripack.f90";
    let Some(archiver) = detect_archiver() else {
        panic!("No archiver found.");
    };
    let obj_path = out_dir.join("stripack.o");
    let lib_path = if target.contains("windows-msvc") {
        out_dir.join("stripack.lib")
    } else {
        out_dir.join("libstripack.a")
    };

    let args = match archiver.as_str() {
        "lib" => vec![],
        _ => vec!["rcs"],
    };

    let lib_path = match archiver.as_str() {
        "lib" => format!("/OUT:{}", lib_path.to_string_lossy()),
        _ => format!("{}", lib_path.to_string_lossy()),
    };

    let status = std::process::Command::new(&archiver)
        .args(args)
        .arg(&lib_path)
        .arg(&obj_path)
        .status()
        .expect("Failed to create static library");

    assert!(status.success(), "{archiver} failed");

    println!(
        "cargo:rustc-link-search=native={}",
        out_dir.to_string_lossy()
    );
    println!("cargo:rustc-link-lib=static=stripack");
    println!("cargo:rerun-if-changed={fortran_src}");
}

fn detect_fortran_compiler() -> Option<String> {
    if let Ok(fc) = env::var("FC") {
        return Some(fc);
    }

    let candidates = vec![
        "gfortran",
        "gfortran-14",
        "gfortran-13",
        "gfortran-12",
        "ifort",
        "ifx",
        "flang",
    ];

    for name in candidates {
        if let Ok(path) = which(name) {
            return Some(path.to_string_lossy().to_string());
        }
    }

    None
}

fn detect_archiver() -> Option<String> {
    if let Ok(ar) = env::var("AR") {
        return Some(ar);
    }

    let candidates = vec!["ar", "x86_64-w64-mingw32-ar", "lib"];
    for name in candidates {
        if let Ok(path) = which(name) {
            return Some(path.to_string_lossy().to_string());
        }
    }

    None
}