use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let shader_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("shaders");
println!("cargo::rerun-if-changed={}", shader_dir.display());
let mut metal_sources: Vec<PathBuf> = Vec::new();
for entry in std::fs::read_dir(&shader_dir).expect("shaders/ directory must exist") {
let path = entry.expect("readable shaders/ entry").path();
match path.extension().and_then(|e| e.to_str()) {
Some("metal") => {
println!("cargo::rerun-if-changed={}", path.display());
metal_sources.push(path);
}
Some("h") => {
println!("cargo::rerun-if-changed={}", path.display());
}
_ => {}
}
}
metal_sources.sort();
if env::var_os("CARGO_FEATURE_RUNTIME_SHADERS").is_some() {
return;
}
let probe = Command::new("xcrun")
.args(["-sdk", "macosx", "metal", "--version"])
.output();
let toolchain_ok = matches!(probe, Ok(ref out) if out.status.success());
if !toolchain_ok {
panic!(
"metal-json: the Metal shader toolchain was not found \
(`xcrun -sdk macosx metal --version` failed).\n\
Fix one of:\n\
1. Install full Xcode, then run: xcodebuild -downloadComponent MetalToolchain\n\
2. Build with runtime shader compilation instead: \
cargo build --features runtime-shaders"
);
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let debug_profile = env::var("PROFILE").as_deref() == Ok("debug");
let std_flag = pick_std_flag(&out_dir);
let mut air_files: Vec<PathBuf> = Vec::new();
for src in &metal_sources {
let stem = src.file_stem().unwrap().to_str().unwrap();
let air = out_dir.join(format!("{stem}.air"));
let mut cmd = Command::new("xcrun");
cmd.args(["-sdk", "macosx", "metal"]);
if let Some(flag) = std_flag {
cmd.arg(flag);
}
if debug_profile {
cmd.args(["-frecord-sources", "-gline-tables-only"]);
}
cmd.arg("-c").arg(src).arg("-o").arg(&air);
run(cmd, &format!("compile {}", src.display()));
air_files.push(air);
}
if air_files.is_empty() {
panic!("metal-json: no .metal sources found in {}", shader_dir.display());
}
let metallib = out_dir.join("metal_json.metallib");
let mut cmd = Command::new("xcrun");
cmd.args(["-sdk", "macosx", "metallib"]);
cmd.args(&air_files);
cmd.arg("-o").arg(&metallib);
run(cmd, "link metal_json.metallib");
}
fn pick_std_flag(out_dir: &Path) -> Option<&'static str> {
let probe_src = out_dir.join("mj_std_probe.metal");
let probe_air = out_dir.join("mj_std_probe.air");
std::fs::write(&probe_src, "kernel void mj_std_probe() {}\n").unwrap();
for flag in ["-std=metal3.1", "-std=metal3.0"] {
let ok = Command::new("xcrun")
.args(["-sdk", "macosx", "metal", flag, "-c"])
.arg(&probe_src)
.arg("-o")
.arg(&probe_air)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if ok {
return Some(flag);
}
}
None
}
fn run(mut cmd: Command, what: &str) {
let out = cmd
.output()
.unwrap_or_else(|e| panic!("metal-json build.rs: failed to spawn `{cmd:?}` ({what}): {e}"));
if !out.status.success() {
panic!(
"metal-json build.rs: {what} failed ({}):\n--- stdout ---\n{}\n--- stderr ---\n{}",
out.status,
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr),
);
}
}