StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | // We can use `std::process:Command` here because this is invoked within a build script, |
| 2 | // _not_ within the Warp binary (where it could cause a terminal to temporarily flash on |
| 3 | // Windows). |
| 4 | #![allow(clippy::disallowed_types)] |
| 5 | |
| 6 | use std::{env, fs, path::PathBuf, process::Command}; |
| 7 | |
| 8 | use cfg_aliases::cfg_aliases; |
| 9 | |
| 10 | fn main() { |
| 11 | cfg_aliases! { |
| 12 | macos: { target_os = "macos" }, |
| 13 | // We use winit on all platforms other than mac, where we have a custom |
| 14 | // AppKit-based platform implementation. |
| 15 | winit: { not(macos) }, |
| 16 | // We use wgpu for rendering on all platforms where we use winit, but |
| 17 | // we can also use it on macOS, if enabled. |
| 18 | wgpu: { any(winit, feature = "experimental-wgpu-renderer") }, |
| 19 | native: { not(target_family = "wasm") }, |
| 20 | } |
| 21 | |
| 22 | if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") { |
| 23 | bindgen_shader_types(); |
| 24 | compile_metal_shaders(); |
| 25 | compile_objc_lib(); |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | fn bindgen_shader_types() { |
| 30 | let header_path = "src/platform/mac/rendering/metal/shaders/shader_types.h"; |
| 31 | println!("cargo:rerun-if-changed={header_path}"); |
| 32 | let bindings = bindgen::Builder::default() |
| 33 | .header(header_path) |
| 34 | .allowlist_type("vector_float2") |
| 35 | .allowlist_type("Uniforms") |
| 36 | .allowlist_type("PerRectUniforms") |
| 37 | .allowlist_type("PerGlyphUniforms") |
| 38 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) |
| 39 | // Disable intrinsic headers that define types containing 16-bit floats (`_Float16`, |
| 40 | // `__m512h`, etc.) via preprocessor directive. `bindgen` doesn't know how to process |
| 41 | // these types and panics at compile time. The types aren't used by our shader headers, |
| 42 | // so suppressing the declarations is safe. |
| 43 | // |
| 44 | // - Xcode 15+: avx512fp16intrin.h, avx512vlfp16intrin.h |
| 45 | // - Xcode 26+ (clang 21): amxavx512intrin.h, avx10_2convertintrin.h, |
| 46 | // avx10_2_512convertintrin.h |
| 47 | // |
| 48 | // TODO(charlespierce): Remove once https://github.com/rust-lang/rust-bindgen/issues/2500 |
| 49 | // is resolved. |
| 50 | .clang_args([ |
| 51 | "-D__AVX512VLFP16INTRIN_H", |
| 52 | "-D__AVX512FP16INTRIN_H", |
| 53 | "-D__AMX_AVX512INTRIN_H", |
| 54 | "-D__AVX10_2CONVERTINTRIN_H", |
| 55 | "-D__AVX10_2_512CONVERTINTRIN_H", |
| 56 | "-D__AVX10_2_512MINMAXINTRIN_H", |
| 57 | "-D__AVX10_2_512NIINTRIN_H", |
| 58 | "-D__AVX10_2_512SATCVTINTRIN_H", |
| 59 | ]) |
| 60 | .generate() |
| 61 | .unwrap_or_else(|_| panic!("unable to generate bindings for {header_path}")); |
| 62 | |
| 63 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); |
| 64 | bindings |
| 65 | .write_to_file(out_path.join("shader_types.rs")) |
| 66 | .expect("Couldn't write shader type bindings!"); |
| 67 | } |
| 68 | |
| 69 | fn compile_metal_shaders() { |
| 70 | let header_path = "src/platform/mac/rendering/metal/shaders/shader_types.h"; |
| 71 | let metal_path = "src/platform/mac/rendering/metal/shaders/shaders.metal"; |
| 72 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); |
| 73 | |
| 74 | let air_path = out_path.join("shaders.air"); |
| 75 | let air_path = air_path.to_str().unwrap(); |
| 76 | |
| 77 | let lib_path = out_path.join("shaders.metallib"); |
| 78 | let lib_path = lib_path.to_str().unwrap(); |
| 79 | |
| 80 | println!("cargo:rerun-if-changed={header_path}"); |
| 81 | println!("cargo:rerun-if-changed={metal_path}"); |
| 82 | |
| 83 | let metal_available = Command::new("xcrun") |
| 84 | .args(["-sdk", "macosx", "-find", "metal"]) |
| 85 | .output() |
| 86 | .is_ok_and(|output| output.status.success()); |
| 87 | |
| 88 | if !metal_available { |
| 89 | println!( |
| 90 | "cargo:warning=Metal toolchain not found; writing placeholder shaders.metallib for cargo check" |
| 91 | ); |
| 92 | fs::write(lib_path, []).expect("could not write placeholder Metal library"); |
| 93 | return; |
| 94 | } |
| 95 | |
| 96 | let mut compile_args = vec!["-sdk", "macosx", "metal", "-c", metal_path, "-o", air_path]; |
| 97 | if cfg!(feature = "enable-metal-frame-capture") { |
| 98 | compile_args.push("-frecord-sources"); |
| 99 | compile_args.push("-gline-tables-only"); |
| 100 | } |
| 101 | let result = Command::new("xcrun") |
| 102 | .args(&compile_args) |
| 103 | .output() |
| 104 | .expect("error compiling metal shaders to .air"); |
| 105 | if !result.status.success() { |
| 106 | println!( |
| 107 | "cargo:warning=Metal shader compilation failed; writing placeholder shaders.metallib for cargo check: {}", |
| 108 | std::str::from_utf8(&result.stderr).unwrap_or("<non-utf8 stderr>") |
| 109 | ); |
| 110 | fs::write(lib_path, []).expect("could not write placeholder Metal library"); |
| 111 | return; |
| 112 | } |
| 113 | |
| 114 | let result = Command::new("xcrun") |
| 115 | .args(["-sdk", "macosx", "metallib", air_path, "-o", lib_path]) |
| 116 | .output() |
| 117 | .expect("error compiling metal shaders to .metallib"); |
| 118 | assert!( |
| 119 | result.status.success(), |
| 120 | "error compling metal shaders to .metallib; {}", |
| 121 | std::str::from_utf8(&result.stderr).unwrap(), |
| 122 | ); |
| 123 | } |
| 124 | |
| 125 | fn compile_objc_lib() { |
| 126 | println!("cargo:rustc-link-lib=framework=UserNotifications"); |
| 127 | println!("cargo:rustc-link-lib=framework=Carbon"); |
| 128 | println!("cargo:rustc-link-lib=framework=SystemConfiguration"); |
| 129 | println!("cargo:rustc-link-lib=framework=UniformTypeIdentifiers"); |
| 130 | println!("cargo:rustc-link-lib=framework=AVFoundation"); |
| 131 | println!("cargo:rustc-link-lib=framework=ServiceManagement"); |
| 132 | println!("cargo:rerun-if-changed=src/platform/mac/objc/app.h"); |
| 133 | println!("cargo:rerun-if-changed=src/platform/mac/objc/app.m"); |
| 134 | println!("cargo:rerun-if-changed=src/platform/mac/objc/keycode.m"); |
| 135 | println!("cargo:rerun-if-changed=src/platform/mac/objc/host_view.m"); |
| 136 | println!("cargo:rerun-if-changed=src/platform/mac/objc/host_view.h"); |
| 137 | println!("cargo:rerun-if-changed=src/platform/mac/objc/hotkey.h"); |
| 138 | println!("cargo:rerun-if-changed=src/platform/mac/objc/hotkey.m"); |
| 139 | println!("cargo:rerun-if-changed=src/platform/mac/objc/menus.h"); |
| 140 | println!("cargo:rerun-if-changed=src/platform/mac/objc/menus.m"); |
| 141 | println!("cargo:rerun-if-changed=src/platform/mac/objc/notifications/notifications.h"); |
| 142 | println!("cargo:rerun-if-changed=src/platform/mac/objc/notifications/notifications.m"); |
| 143 | println!("cargo:rerun-if-changed=src/platform/mac/objc/window.m"); |
| 144 | println!("cargo:rerun-if-changed=src/platform/mac/objc/window_blur.m"); |
| 145 | println!("cargo:rerun-if-changed=src/platform/mac/objc/window_blur.h"); |
| 146 | // Referenced from https://github.com/tonymillion/Reachability |
| 147 | println!("cargo:rerun-if-changed=src/platform/mac/objc/reachability.h"); |
| 148 | println!("cargo:rerun-if-changed=src/platform/mac/objc/reachability.m"); |
| 149 | println!("cargo:rerun-if-changed=src/platform/mac/objc/alert.m"); |
| 150 | println!("cargo:rerun-if-changed=src/platform/mac/objc/alert.h"); |
| 151 | println!("cargo:rerun-if-changed=src/platform/mac/objc/fullscreen_queue.h"); |
| 152 | println!("cargo:rerun-if-changed=src/platform/mac/objc/fullscreen_queue.m"); |
| 153 | |
| 154 | // Link against the clang_rt library so that the @available keyword |
| 155 | // doesn't produce linker errors. |
| 156 | // |
| 157 | // See: https://github.com/alexcrichton/curl-rust/issues/279 |
| 158 | if let Some(path) = macos_link_search_path() { |
| 159 | println!("cargo:rustc-link-lib=clang_rt.osx"); |
| 160 | println!("cargo:rustc-link-search={path}"); |
| 161 | } |
| 162 | |
| 163 | cc::Build::new() |
| 164 | .file("src/platform/mac/objc/app.m") |
| 165 | .file("src/platform/mac/objc/host_view.m") |
| 166 | .file("src/platform/mac/objc/hotkey.m") |
| 167 | .file("src/platform/mac/objc/reachability.m") |
| 168 | .file("src/platform/mac/objc/keycode.m") |
| 169 | .file("src/platform/mac/objc/menus.m") |
| 170 | .file("src/platform/mac/objc/notifications/notifications.m") |
| 171 | .file("src/platform/mac/objc/window.m") |
| 172 | .file("src/platform/mac/objc/fullscreen_queue.m") |
| 173 | .file("src/platform/mac/objc/window_blur.m") |
| 174 | .file("src/platform/mac/objc/alert.m") |
| 175 | .compile("warp_objc"); |
| 176 | } |
| 177 | |
| 178 | /// Determine the path containing the macOS standard libraries by querying |
| 179 | /// clang's library search paths. |
| 180 | fn macos_link_search_path() -> Option<String> { |
| 181 | let output = Command::new("clang") |
| 182 | .arg("--print-search-dirs") |
| 183 | .output() |
| 184 | .ok()?; |
| 185 | if !output.status.success() { |
| 186 | println!( |
| 187 | "failed to run 'clang --print-search-dirs', continuing without a link search path" |
| 188 | ); |
| 189 | return None; |
| 190 | } |
| 191 | |
| 192 | let stdout = String::from_utf8_lossy(&output.stdout); |
| 193 | for line in stdout.lines() { |
| 194 | if line.contains("libraries: =") { |
| 195 | let path = line.split('=').nth(1)?; |
| 196 | return Some(format!("{path}/lib/darwin")); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | println!("failed to determine link search path, continuing without it"); |
| 201 | None |
| 202 | } |
| 203 |