//! Cubin compilation for the LOB simulator CUDA kernels. //! Mirrors crates/ml-alpha/build.rs. //! Per feedback_no_nvrtc.md: cubins only, no runtime nvrtc. use std::path::PathBuf; use std::process::Command; fn main() -> Result<(), String> { let cuda_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("cuda"); let out_dir = PathBuf::from(std::env::var("OUT_DIR").map_err(|e| e.to_string())?); // Per pearl_build_rs_rerun_if_env_changed.md: pair every env::var // with rerun-if-env-changed. // Default sm_86 covers RTX 3050 (local dev). Production Argo workflows // override via FOXHUNT_CUDA_ARCH=sm_89 (L40S Ada) or sm_90 (H100). let arch = std::env::var("FOXHUNT_CUDA_ARCH").unwrap_or_else(|_| "sm_86".into()); println!("cargo:rerun-if-env-changed=FOXHUNT_CUDA_ARCH"); println!("cargo:rerun-if-env-changed=CUDA_PATH"); println!("cargo:rerun-if-env-changed=NVCC"); let nvcc = std::env::var("NVCC") .map(PathBuf::from) .unwrap_or_else(|_| PathBuf::from("nvcc")); if !cuda_dir.exists() { // No kernels yet (e.g. before C4 lands them) — quietly noop. return Ok(()); } println!("cargo:rerun-if-changed={}", cuda_dir.display()); let entries = std::fs::read_dir(&cuda_dir).map_err(|e| format!("read cuda dir: {e}"))?; for entry in entries { let entry = entry.map_err(|e| e.to_string())?; let path = entry.path(); if path.extension().and_then(|s| s.to_str()) != Some("cu") { continue; } let stem = path .file_stem() .ok_or("cu file has no stem")? .to_string_lossy() .to_string(); let cubin = out_dir.join(format!("{stem}.cubin")); println!("cargo:rerun-if-changed={}", path.display()); let mut cmd = Command::new(&nvcc); cmd.args(["--cubin", "-O3", "-std=c++17"]) .arg(format!("-arch={arch}")) .arg("-I") .arg(&cuda_dir) .arg("-o") .arg(&cubin) .arg(&path); let status = cmd .status() .map_err(|e| format!("spawn nvcc for {}: {e}", path.display()))?; if !status.success() { return Err(format!("nvcc failed for {}", path.display())); } } Ok(()) }