hydra-rs 0.0.4

Rust bindings to OpenUSD's Hydra rendering layer: scene-index ingestion, render-delegate enumeration, headless render to RGBA via Storm.
use std::env;
use std::path::PathBuf;
use std::process;

fn main() {
    let (include_dir, lib_dir) = resolve_usd_paths();
    let lib_prefix = env::var("USD_LIB_PREFIX").unwrap_or_default();
    let monolithic = env::var("USD_MONOLITHIC")
        .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
        .unwrap_or(false);
    let link_python = env::var("USD_LINK_PYTHON").unwrap_or_else(|_| "framework".to_string());

    // hydra-rs needs USD core + Hydra (hd, hf, hdsi, usdImaging). hdSt /
    // hdx aren't linked at the cxx-bridge level — render delegates load
    // lazily through the plug registry — but we keep core USD libs the
    // same as rust-usd so transitive symbol resolution stays sane.
    let mut usd_libs: Vec<&str> = if monolithic {
        vec!["usd_ms"]
    } else {
        vec![
            "usd", "usdGeom", "usdLux", "usdShade", "sdf", "ar", "pcp", "plug", "kind",
            "vt", "gf", "tf", "work", "trace", "js", "arch", "ts",
            // Hydra core + scene index utilities + USD imaging delegate
            "hd", "hf", "hdsi", "usdImaging",
            // Render task graph + UsdImagingGL high-level engine (HGI-abstracted)
            "hdx", "hgi", "usdImagingGL", "glf",
        ]
    };

    if link_python != "none" && !monolithic {
        usd_libs.push("python");
        usd_libs.push("boost");
    }

    for lib in &usd_libs {
        println!("cargo:rustc-link-lib=dylib={}{}", lib_prefix, lib);
    }

    println!("cargo:rustc-link-search=native={}", lib_dir.display());
    println!("cargo:rustc-link-arg=-Wl,-rpath,{}", lib_dir.display());
    if let Some(parent) = lib_dir.parent() {
        println!("cargo:rustc-link-arg=-Wl,-rpath,{}", parent.display());
    }

    match link_python.as_str() {
        "framework" => {
            let fw_name = env::var("USD_PYTHON_FRAMEWORK").unwrap_or_else(|_| "Python".into());
            if let Ok(fw_dir) = env::var("USD_PYTHON_FRAMEWORK_DIR") {
                println!("cargo:rustc-link-search=framework={}", fw_dir);
                let inside_fw = PathBuf::from(&fw_dir).join(format!("{}.framework", fw_name));
                println!("cargo:rustc-link-arg=-Wl,-rpath,{}", inside_fw.display());
            }
            println!("cargo:rustc-link-lib=framework={}", fw_name);
        }
        "lib" => {
            if let Ok(py_lib_dir) = env::var("USD_PYTHON_LIB_DIR") {
                println!("cargo:rustc-link-search=native={}", py_lib_dir);
                println!("cargo:rustc-link-arg=-Wl,-rpath,{}", py_lib_dir);
            }
            let py_lib = env::var("USD_PYTHON_LIB_NAME").unwrap_or_else(|_| "python3.11".into());
            println!("cargo:rustc-link-lib=dylib={}", py_lib);
        }
        "none" => {}
        other => {
            eprintln!("hydra-rs build.rs: USD_LINK_PYTHON={:?} not understood", other);
            process::exit(1);
        }
    }

    let mut build = cxx_build::bridge("src/lib.rs");
    build
        .file("cpp/hydra_bridge.cpp")
        .include(&include_dir)
        .include("cpp")
        .flag_if_supported("-std=c++17")
        .flag_if_supported("-Wno-deprecated-declarations");

    if let Ok(py_inc) = env::var("USD_PYTHON_INCLUDE_DIR") {
        build.include(py_inc);
    }
    build.compile("hydra_rs_bridge");

    println!("cargo:rerun-if-changed=cpp/hydra_bridge.h");
    println!("cargo:rerun-if-changed=cpp/hydra_bridge.cpp");
    println!("cargo:rerun-if-changed=src/lib.rs");
    println!("cargo:rerun-if-changed=build.rs");
    for var in [
        "USD_INSTALL_DIR", "USD_INCLUDE_DIR", "USD_LIB_DIR", "USD_LIB_PREFIX",
        "USD_MONOLITHIC", "USD_PYTHON_INCLUDE_DIR", "USD_LINK_PYTHON",
        "USD_PYTHON_FRAMEWORK_DIR", "USD_PYTHON_FRAMEWORK", "USD_PYTHON_LIB_DIR",
        "USD_PYTHON_LIB_NAME", "PXR_CMAKE_PREFIX",
    ] {
        println!("cargo:rerun-if-env-changed={}", var);
    }
}

fn resolve_usd_paths() -> (PathBuf, PathBuf) {
    let explicit_include = env::var("USD_INCLUDE_DIR").ok().map(PathBuf::from);
    let explicit_lib = env::var("USD_LIB_DIR").ok().map(PathBuf::from);

    if let (Some(inc), Some(lib)) = (explicit_include.clone(), explicit_lib.clone()) {
        return validate(inc, lib);
    }

    let install = env::var("USD_INSTALL_DIR")
        .ok()
        .or_else(|| env::var("PXR_CMAKE_PREFIX").ok());

    if let Some(install) = install {
        let install = PathBuf::from(install);
        let inc = explicit_include.unwrap_or_else(|| install.join("include"));
        let lib = explicit_lib.unwrap_or_else(|| install.join("lib"));
        return validate(inc, lib);
    }

    eprintln!(
        "hydra-rs build.rs: no USD install configured.\n\
         Set USD_INSTALL_DIR (with include/+lib/), or USD_INCLUDE_DIR+USD_LIB_DIR."
    );
    process::exit(1);
}

fn validate(include_dir: PathBuf, lib_dir: PathBuf) -> (PathBuf, PathBuf) {
    if !include_dir
        .join("pxr/imaging/hd/rendererPluginRegistry.h")
        .exists()
    {
        eprintln!(
            "hydra-rs build.rs: Hydra headers not found.\n\
             Expected pxr/imaging/hd/rendererPluginRegistry.h under: {}",
            include_dir.display()
        );
        process::exit(1);
    }
    if !lib_dir.is_dir() {
        eprintln!("hydra-rs build.rs: lib dir does not exist: {}", lib_dir.display());
        process::exit(1);
    }
    (include_dir, lib_dir)
}