Skip to main content

native/nx_vulkan_native/build.rs

// build.rs — compile the C++ shim + vendored Spirit Vulkan backend,
// link Vulkan.
//
// As of 2026-05-02 the Spirit Vulkan backend is vendored under
// nx_vulkan/c_src/spirit/ (see c_src/spirit/VENDOR.md for the
// pinned upstream commit). This build script no longer requires a
// sibling Spirit checkout — hex.pm installs work out of the box.
//
// SPIRIT_DIR is honored as a development override: when set,
// shaders from $SPIRIT_DIR/shaders/ are copied into priv/shaders/
// at build time, letting mac-248's freshly-compiled shaders flow
// in without an explicit vendor refresh. The C++ backend itself is
// always taken from the vendored copy; refresh it explicitly via
// the procedure documented in c_src/spirit/VENDOR.md.

use std::env;
use std::path::PathBuf;

fn main() {
    let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    // crate is at nx_vulkan/native/nx_vulkan_native/ ; root is two up.
    let nx_vulkan_root = crate_dir.parent().unwrap().parent().unwrap();

    let shim_c = nx_vulkan_root.join("c_src");
    let vendored_spirit = shim_c.join("spirit");

    println!("cargo:rerun-if-changed={}", shim_c.join("nx_vulkan_shim.cpp").display());
    println!("cargo:rerun-if-changed={}", shim_c.join("nx_vulkan_shim.h").display());
    println!(
        "cargo:rerun-if-changed={}",
        vendored_spirit.join("src/engine/Backend_par_vulkan.cpp").display()
    );
    println!(
        "cargo:rerun-if-changed={}",
        vendored_spirit.join("include/engine/Backend_par_vulkan.hpp").display()
    );
    println!("cargo:rerun-if-env-changed=SPIRIT_DIR");

    let mut build = cc::Build::new();
    build
        .cpp(true)
        .std("c++14")
        .flag_if_supported("-Wno-unused-result")
        .flag_if_supported("-Wno-unused-parameter")
        .define("SPIRIT_USE_VULKAN", None)
        .include(shim_c.clone())
        .include(vendored_spirit.join("include"))
        .file(shim_c.join("nx_vulkan_shim.cpp"))
        .file(vendored_spirit.join("src/engine/Backend_par_vulkan.cpp"));

    // FreeBSD + macOS install Vulkan headers under /usr/local/include
    // (FreeBSD: pkg install vulkan-headers; macOS: brew install vulkan-headers).
    // Linux's clang searches /usr/include automatically; FreeBSD's
    // does not search /usr/local/include without being asked.
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
    if target_os == "freebsd" || target_os == "macos" {
        build.include("/usr/local/include");
        println!("cargo:rustc-link-search=native=/usr/local/lib");
    }

    build.compile("nx_vulkan_shim");

    // Link Vulkan loader. On Linux this is libvulkan.so; on FreeBSD
    // the same; on macOS, MoltenVK provides libvulkan.dylib.
    println!("cargo:rustc-link-lib=dylib=vulkan");

    // Shader handling:
    //
    // - Default (hex.pm install, no SPIRIT_DIR): the vendored
    //   priv/shaders/*.spv are already in place; do nothing.
    //
    // - SPIRIT_DIR set (developer with a Spirit checkout, typically
    //   mac-248 iterating on shaders): refresh priv/shaders/*.spv
    //   from $SPIRIT_DIR/shaders/ on every build.
    let priv_shaders = nx_vulkan_root.join("priv").join("shaders");
    let _ = std::fs::create_dir_all(&priv_shaders);

    if let Ok(spirit_dir) = env::var("SPIRIT_DIR") {
        let spirit_shaders = PathBuf::from(spirit_dir).join("shaders");
        if spirit_shaders.exists() {
            // Watch the directory itself so a new .spv appearing triggers a rerun.
            // Per-file rerun-if-changed only fires on modifications to existing
            // files; without the dir watch, fresh shaders need a `cargo clean`.
            println!("cargo:rerun-if-changed={}", spirit_shaders.display());
            for entry in std::fs::read_dir(&spirit_shaders).unwrap() {
                let entry = entry.unwrap();
                let path = entry.path();
                if path.extension().and_then(|s| s.to_str()) == Some("spv") {
                    let dst = priv_shaders.join(path.file_name().unwrap());
                    let _ = std::fs::copy(&path, &dst);
                    println!("cargo:rerun-if-changed={}", path.display());
                }
            }
        }
    }
}