use std::env; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); let shader_dir = Path::new("shaders"); let metallib_path = out_dir.join("default.metallib"); println!("cargo:rerun-if-changed=shaders/"); // Only compile Metal shaders on macOS if !cfg!(target_os = "macos") { // Write empty dummy metallib for non-macOS builds fs::write(&metallib_path, &[]).unwrap(); println!( "cargo:warning=Not on macOS; Metal shaders will not be compiled. \ Using empty metallib stub." ); return; } // Check if xcrun is available let xcrun_check = Command::new("xcrun").arg("--version").output(); if xcrun_check.is_err() || !xcrun_check.unwrap().status.success() { fs::write(&metallib_path, &[]).unwrap(); println!( "cargo:warning=xcrun not found; Metal shaders will not be compiled. \ Using empty metallib stub." ); return; } // Find all .metal shader files let metal_files: Vec = if shader_dir.exists() { fs::read_dir(shader_dir) .unwrap() .filter_map(|entry| { let entry = entry.ok()?; let path = entry.path(); if path.extension().and_then(|e| e.to_str()) == Some("metal") { Some(path) } else { None } }) .collect() } else { Vec::new() }; if metal_files.is_empty() { fs::write(&metallib_path, &[]).unwrap(); println!("cargo:warning=No .metal shader files found in shaders/"); return; } // Compile each .metal file to .air let mut air_files = Vec::new(); for metal_file in &metal_files { let stem = metal_file.file_stem().unwrap().to_str().unwrap(); let air_path = out_dir.join(format!("{}.air", stem)); println!("cargo:rerun-if-changed={}", metal_file.display()); let status = Command::new("xcrun") .args([ "metal", "-c", "-target", "air64-apple-macos14.0", "-o", ]) .arg(&air_path) .arg(metal_file) .status(); match status { Ok(s) if s.success() => { air_files.push(air_path); } Ok(s) => { println!( "cargo:warning=Failed to compile {} (exit code: {:?})", metal_file.display(), s.code() ); } Err(e) => { println!( "cargo:warning=Failed to run xcrun metal for {}: {}", metal_file.display(), e ); } } } if air_files.is_empty() { fs::write(&metallib_path, &[]).unwrap(); println!("cargo:warning=No .air files produced; using empty metallib stub."); return; } // Link all .air files into a single .metallib let mut cmd = Command::new("xcrun"); cmd.arg("metallib").arg("-o").arg(&metallib_path); for air in &air_files { cmd.arg(air); } match cmd.status() { Ok(s) if s.success() => { println!("cargo:note=Successfully compiled Metal shaders into default.metallib"); } Ok(s) => { fs::write(&metallib_path, &[]).unwrap(); println!( "cargo:warning=metallib linking failed (exit code: {:?}); using empty stub.", s.code() ); } Err(e) => { fs::write(&metallib_path, &[]).unwrap(); println!( "cargo:warning=Failed to run xcrun metallib: {}; using empty stub.", e ); } } }