#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
fn main() {
if cross_checking_apple() {
write_shader_stub();
return;
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
apple_build::run();
}
fn cross_checking_apple() -> bool {
cfg!(not(target_os = "macos"))
&& matches!(
std::env::var("CARGO_CFG_TARGET_OS").as_deref(),
Ok("macos") | Ok("ios")
)
}
fn write_shader_stub() {
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is set"));
let name = if std::env::var_os("CARGO_FEATURE_RUNTIME_SHADERS").is_some() {
"stitched_shaders.metal"
} else {
"shaders.metallib"
};
std::fs::write(out_dir.join(name), []).expect("write the shader artifact");
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod apple_build {
use std::{
env,
path::{Path, PathBuf},
};
use cbindgen::Config;
pub fn run() {
let header_path = generate_shader_bindings();
#[cfg(feature = "runtime_shaders")]
emit_stitched_shaders(&header_path);
#[cfg(not(feature = "runtime_shaders"))]
compile_metal_shaders(&header_path);
}
fn generate_shader_bindings() -> PathBuf {
let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
let mut config = Config {
include_guard: Some("SCENE_H".into()),
language: cbindgen::Language::C,
no_includes: true,
..Default::default()
};
config.export.include.extend([
"Bounds".into(),
"Corners".into(),
"Edges".into(),
"Size".into(),
"Pixels".into(),
"PointF".into(),
"Hsla".into(),
"ContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
"PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
"PathRasterizationVertex".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(),
"Underline".into(),
"UnderlineInputIndex".into(),
"Quad".into(),
"BorderStyle".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
"PathSprite".into(),
"SurfaceInputIndex".into(),
"SurfaceBounds".into(),
"ImportedTextureInputIndex".into(),
"TransformationMatrix".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;
let mut builder = cbindgen::Builder::new();
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let shader_src_paths = [
find_sibling_crate_dir("gpui_engine").join("src/scene.rs"),
find_sibling_crate_dir("gpui_engine").join("src/atlas.rs"),
find_sibling_crate_dir("gpui_types").join("src/geometry.rs"),
find_sibling_crate_dir("gpui_types").join("src/color.rs"),
find_sibling_crate_dir("gpui_types").join("src/scene.rs"),
];
let local_src_paths = [crate_dir.join("src/metal_renderer.rs")];
for src_path in shader_src_paths.iter().chain(local_src_paths.iter()) {
println!("cargo:rerun-if-changed={}", src_path.display());
builder = builder.with_src(src_path);
}
builder
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file(&output_path);
output_path
}
fn find_sibling_crate_dir(name: &str) -> PathBuf {
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
.join("vendor")
.join(name)
}
#[cfg(feature = "runtime_shaders")]
fn emit_stitched_shaders(header_path: &Path) {
fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
let header_contents = std::fs::read_to_string(header)?;
let shader_contents = std::fs::read_to_string(shader_path)?;
let stitched_contents = format!("{header_contents}\n{shader_contents}");
let out_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
std::fs::write(&out_path, stitched_contents)?;
Ok(out_path)
}
let shader_source_path = "./src/shaders.metal";
let shader_path = PathBuf::from(shader_source_path);
stitch_header(header_path, &shader_path).unwrap();
println!("cargo:rerun-if-changed={shader_source_path}");
}
#[cfg(not(feature = "runtime_shaders"))]
fn compile_metal_shaders(header_path: &Path) {
use std::process::{self, Command};
let target_os = env::var("CARGO_CFG_TARGET_OS")
.expect("Cargo sets CARGO_CFG_TARGET_OS for build scripts");
let target_env = env::var("CARGO_CFG_TARGET_ENV")
.expect("Cargo sets CARGO_CFG_TARGET_ENV for build scripts");
let (sdk, minimum_version_argument) = match (target_os.as_str(), target_env.as_str()) {
("macos", _) => ("macosx", "-mmacosx-version-min=10.15.7"),
("ios", "sim") => ("iphonesimulator", "-mios-simulator-version-min=15.0"),
("ios", "") => ("iphoneos", "-mios-version-min=15.0"),
_ => {
println!(
"cargo::error=unsupported Metal shader target: target_os={target_os}, target_env={target_env}"
);
process::exit(1);
}
};
let shader_path = "./src/shaders.metal";
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
let metallib_output_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
println!("cargo:rerun-if-changed={}", shader_path);
let staged_shader_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metal");
std::fs::copy(shader_path, &staged_shader_path).unwrap();
let output = Command::new("xcrun")
.args([
"-sdk",
sdk,
"metal",
"-gline-tables-only",
minimum_version_argument,
"-MO",
"-c",
])
.arg(&staged_shader_path)
.args(["-include", header_path.to_str().unwrap(), "-o"])
.arg(&air_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metal shader compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
let output = Command::new("xcrun")
.args(["-sdk", sdk, "metallib"])
.arg(&air_output_path)
.arg("-o")
.arg(metallib_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metallib compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
std::fs::remove_file(&air_output_path).unwrap();
}
}