1#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
2
3fn main() {
4 #[cfg(target_os = "macos")]
5 macos_build::run();
6}
7
8#[cfg(target_os = "macos")]
9mod macos_build {
10 use std::{
11 env,
12 path::{Path, PathBuf},
13 };
14
15 use cbindgen::Config;
16
17 pub fn run() {
18 generate_dispatch_bindings();
19
20 let header_path = generate_shader_bindings();
21
22 #[cfg(feature = "runtime_shaders")]
23 emit_stitched_shaders(&header_path);
24 #[cfg(not(feature = "runtime_shaders"))]
25 compile_metal_shaders(&header_path);
26 }
27
28 fn generate_dispatch_bindings() {
29 println!("cargo:rustc-link-lib=framework=System");
30
31 let bindings = bindgen::Builder::default()
32 .header("src/dispatch.h")
33 .allowlist_var("_dispatch_main_q")
34 .allowlist_var("_dispatch_source_type_data_add")
35 .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
36 .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
37 .allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
38 .allowlist_var("DISPATCH_TIME_NOW")
39 .allowlist_function("dispatch_get_global_queue")
40 .allowlist_function("dispatch_async_f")
41 .allowlist_function("dispatch_after_f")
42 .allowlist_function("dispatch_time")
43 .allowlist_function("dispatch_source_merge_data")
44 .allowlist_function("dispatch_source_create")
45 .allowlist_function("dispatch_source_set_event_handler_f")
46 .allowlist_function("dispatch_resume")
47 .allowlist_function("dispatch_suspend")
48 .allowlist_function("dispatch_source_cancel")
49 .allowlist_function("dispatch_set_context")
50 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
51 .layout_tests(false)
52 .generate()
53 .expect("unable to generate bindings");
54
55 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
56 bindings
57 .write_to_file(out_path.join("dispatch_sys.rs"))
58 .expect("couldn't write dispatch bindings");
59 }
60
61 fn generate_shader_bindings() -> PathBuf {
62 let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
63
64 let gpui_dir = find_gpui_crate_dir();
65
66 let mut config = Config {
67 include_guard: Some("SCENE_H".into()),
68 language: cbindgen::Language::C,
69 no_includes: true,
70 ..Default::default()
71 };
72 config.export.include.extend([
73 "Bounds".into(),
74 "Corners".into(),
75 "Edges".into(),
76 "Size".into(),
77 "Pixels".into(),
78 "PointF".into(),
79 "Hsla".into(),
80 "ContentMask".into(),
81 "Uniforms".into(),
82 "AtlasTile".into(),
83 "PathRasterizationInputIndex".into(),
84 "PathVertex_ScaledPixels".into(),
85 "PathRasterizationVertex".into(),
86 "ShadowInputIndex".into(),
87 "Shadow".into(),
88 "QuadInputIndex".into(),
89 "Underline".into(),
90 "UnderlineInputIndex".into(),
91 "Quad".into(),
92 "BorderStyle".into(),
93 "SpriteInputIndex".into(),
94 "MonochromeSprite".into(),
95 "PolychromeSprite".into(),
96 "PathSprite".into(),
97 "SurfaceInputIndex".into(),
98 "SurfaceBounds".into(),
99 "TransformationMatrix".into(),
100 ]);
101 config.no_includes = true;
102 config.enumeration.prefix_with_name = true;
103
104 let mut builder = cbindgen::Builder::new();
105
106 let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
107
108 // Source files from gpui that define types used in shaders
109 let gpui_src_paths = [
110 gpui_dir.join("src/scene.rs"),
111 gpui_dir.join("src/geometry.rs"),
112 gpui_dir.join("src/color.rs"),
113 gpui_dir.join("src/window.rs"),
114 gpui_dir.join("src/platform.rs"),
115 ];
116
117 // Source files from this crate
118 let local_src_paths = [crate_dir.join("src/metal_renderer.rs")];
119
120 for src_path in gpui_src_paths.iter().chain(local_src_paths.iter()) {
121 println!("cargo:rerun-if-changed={}", src_path.display());
122 builder = builder.with_src(src_path);
123 }
124
125 builder
126 .with_config(config)
127 .generate()
128 .expect("Unable to generate bindings")
129 .write_to_file(&output_path);
130
131 output_path
132 }
133
134 /// Locate the gpui crate directory relative to this crate.
135 fn find_gpui_crate_dir() -> PathBuf {
136 gpui::GPUI_MANIFEST_DIR.into()
137 }
138
139 /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
140 /// so that it is self-contained.
141 #[cfg(feature = "runtime_shaders")]
142 fn emit_stitched_shaders(header_path: &Path) {
143 fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
144 let header_contents = std::fs::read_to_string(header)?;
145 let shader_contents = std::fs::read_to_string(shader_path)?;
146 let stitched_contents = format!("{header_contents}\n{shader_contents}");
147 let out_path =
148 PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
149 std::fs::write(&out_path, stitched_contents)?;
150 Ok(out_path)
151 }
152 let shader_source_path = "./src/shaders.metal";
153 let shader_path = PathBuf::from(shader_source_path);
154 stitch_header(header_path, &shader_path).unwrap();
155 println!("cargo:rerun-if-changed={}", &shader_source_path);
156 }
157
158 #[cfg(not(feature = "runtime_shaders"))]
159 fn compile_metal_shaders(header_path: &Path) {
160 use std::process::{self, Command};
161 let shader_path = "./src/shaders.metal";
162 let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
163 let metallib_output_path =
164 PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
165 println!("cargo:rerun-if-changed={}", shader_path);
166
167 let output = Command::new("xcrun")
168 .args([
169 "-sdk",
170 "macosx",
171 "metal",
172 "-gline-tables-only",
173 "-mmacosx-version-min=10.15.7",
174 "-MO",
175 "-c",
176 shader_path,
177 "-include",
178 (header_path.to_str().unwrap()),
179 "-o",
180 ])
181 .arg(&air_output_path)
182 .output()
183 .unwrap();
184
185 if !output.status.success() {
186 println!(
187 "cargo::error=metal shader compilation failed:\n{}",
188 String::from_utf8_lossy(&output.stderr)
189 );
190 process::exit(1);
191 }
192
193 let output = Command::new("xcrun")
194 .args(["-sdk", "macosx", "metallib"])
195 .arg(air_output_path)
196 .arg("-o")
197 .arg(metallib_output_path)
198 .output()
199 .unwrap();
200
201 if !output.status.success() {
202 println!(
203 "cargo::error=metallib compilation failed:\n{}",
204 String::from_utf8_lossy(&output.stderr)
205 );
206 process::exit(1);
207 }
208 }
209}