1#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
2#![cfg_attr(not(target_os = "macos"), allow(unused))]
3
4use std::env;
5
6fn main() {
7 let target = env::var("CARGO_CFG_TARGET_OS");
8 println!("cargo::rustc-check-cfg=cfg(gles)");
9
10 match target.as_deref() {
11 Ok("macos") => {
12 #[cfg(target_os = "macos")]
13 macos::build();
14 }
15 Ok("windows") => {
16 #[cfg(target_os = "windows")]
17 windows::build();
18 }
19 _ => (),
20 };
21}
22#[cfg(target_os = "macos")]
23mod macos {
24 use std::{
25 env,
26 path::{Path, PathBuf},
27 };
28
29 use cbindgen::Config;
30
31 pub(super) fn build() {
32 generate_dispatch_bindings();
33
34 let header_path = generate_shader_bindings();
35
36 #[cfg(feature = "runtime_shaders")]
37 emit_stitched_shaders(&header_path);
38 #[cfg(not(feature = "runtime_shaders"))]
39 compile_metal_shaders(&header_path);
40 }
41
42 fn generate_dispatch_bindings() {
43 println!("cargo:rustc-link-lib=framework=System");
44
45 let bindings = bindgen::Builder::default()
46 .header("src/platform/mac/dispatch.h")
47 .allowlist_var("_dispatch_main_q")
48 .allowlist_var("_dispatch_source_type_data_add")
49 .allowlist_var("DISPATCH_QUEUE_PRIORITY_HIGH")
50 .allowlist_var("DISPATCH_QUEUE_PRIORITY_DEFAULT")
51 .allowlist_var("DISPATCH_QUEUE_PRIORITY_LOW")
52 .allowlist_var("DISPATCH_TIME_NOW")
53 .allowlist_function("dispatch_get_global_queue")
54 .allowlist_function("dispatch_async_f")
55 .allowlist_function("dispatch_after_f")
56 .allowlist_function("dispatch_time")
57 .allowlist_function("dispatch_source_merge_data")
58 .allowlist_function("dispatch_source_create")
59 .allowlist_function("dispatch_source_set_event_handler_f")
60 .allowlist_function("dispatch_resume")
61 .allowlist_function("dispatch_suspend")
62 .allowlist_function("dispatch_source_cancel")
63 .allowlist_function("dispatch_set_context")
64 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
65 .layout_tests(false)
66 .generate()
67 .expect("unable to generate bindings");
68
69 let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
70 bindings
71 .write_to_file(out_path.join("dispatch_sys.rs"))
72 .expect("couldn't write dispatch bindings");
73 }
74
75 fn generate_shader_bindings() -> PathBuf {
76 let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
77 let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
78 let mut config = Config {
79 include_guard: Some("SCENE_H".into()),
80 language: cbindgen::Language::C,
81 no_includes: true,
82 ..Default::default()
83 };
84 config.export.include.extend([
85 "Bounds".into(),
86 "Corners".into(),
87 "Edges".into(),
88 "Size".into(),
89 "Pixels".into(),
90 "PointF".into(),
91 "Hsla".into(),
92 "ContentMask".into(),
93 "Uniforms".into(),
94 "AtlasTile".into(),
95 "PathRasterizationInputIndex".into(),
96 "PathVertex_ScaledPixels".into(),
97 "PathRasterizationVertex".into(),
98 "ShadowInputIndex".into(),
99 "Shadow".into(),
100 "QuadInputIndex".into(),
101 "Underline".into(),
102 "UnderlineInputIndex".into(),
103 "Quad".into(),
104 "BorderStyle".into(),
105 "SpriteInputIndex".into(),
106 "MonochromeSprite".into(),
107 "PolychromeSprite".into(),
108 "PathSprite".into(),
109 "SurfaceInputIndex".into(),
110 "SurfaceBounds".into(),
111 "TransformationMatrix".into(),
112 ]);
113 config.no_includes = true;
114 config.enumeration.prefix_with_name = true;
115
116 let mut builder = cbindgen::Builder::new();
117
118 let src_paths = [
119 crate_dir.join("src/scene.rs"),
120 crate_dir.join("src/geometry.rs"),
121 crate_dir.join("src/color.rs"),
122 crate_dir.join("src/window.rs"),
123 crate_dir.join("src/platform.rs"),
124 crate_dir.join("src/platform/mac/metal_renderer.rs"),
125 ];
126 for src_path in src_paths {
127 println!("cargo:rerun-if-changed={}", src_path.display());
128 builder = builder.with_src(src_path);
129 }
130
131 builder
132 .with_config(config)
133 .generate()
134 .expect("Unable to generate bindings")
135 .write_to_file(&output_path);
136
137 output_path
138 }
139
140 /// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
141 /// so that it is self-contained.
142 #[cfg(feature = "runtime_shaders")]
143 fn emit_stitched_shaders(header_path: &Path) {
144 use std::str::FromStr;
145 fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
146 let header_contents = std::fs::read_to_string(header)?;
147 let shader_contents = std::fs::read_to_string(shader_path)?;
148 let stitched_contents = format!("{header_contents}\n{shader_contents}");
149 let out_path =
150 PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
151 std::fs::write(&out_path, stitched_contents)?;
152 Ok(out_path)
153 }
154 let shader_source_path = "./src/platform/mac/shaders.metal";
155 let shader_path = PathBuf::from_str(shader_source_path).unwrap();
156 stitch_header(header_path, &shader_path).unwrap();
157 println!("cargo:rerun-if-changed={}", &shader_source_path);
158 }
159
160 #[cfg(not(feature = "runtime_shaders"))]
161 fn compile_metal_shaders(header_path: &Path) {
162 use std::process::{self, Command};
163 let shader_path = "./src/platform/mac/shaders.metal";
164 let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
165 let metallib_output_path =
166 PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
167 println!("cargo:rerun-if-changed={}", shader_path);
168
169 let output = Command::new("xcrun")
170 .args([
171 "-sdk",
172 "macosx",
173 "metal",
174 "-gline-tables-only",
175 "-mmacosx-version-min=10.15.7",
176 "-MO",
177 "-c",
178 shader_path,
179 "-include",
180 (header_path.to_str().unwrap()),
181 "-o",
182 ])
183 .arg(&air_output_path)
184 .output()
185 .unwrap();
186
187 if !output.status.success() {
188 println!(
189 "cargo::error=metal shader compilation failed:\n{}",
190 String::from_utf8_lossy(&output.stderr)
191 );
192 process::exit(1);
193 }
194
195 let output = Command::new("xcrun")
196 .args(["-sdk", "macosx", "metallib"])
197 .arg(air_output_path)
198 .arg("-o")
199 .arg(metallib_output_path)
200 .output()
201 .unwrap();
202
203 if !output.status.success() {
204 println!(
205 "cargo::error=metallib compilation failed:\n{}",
206 String::from_utf8_lossy(&output.stderr)
207 );
208 process::exit(1);
209 }
210 }
211}
212
213#[cfg(target_os = "windows")]
214mod windows {
215 use std::{
216 ffi::OsString,
217 fs,
218 io::Write,
219 path::{Path, PathBuf},
220 process::{self, Command},
221 };
222
223 pub(super) fn build() {
224 // Compile HLSL shaders
225 #[cfg(not(debug_assertions))]
226 compile_shaders();
227
228 // Embed the Windows manifest and resource file
229 #[cfg(feature = "windows-manifest")]
230 embed_resource();
231 }
232
233 #[cfg(feature = "windows-manifest")]
234 fn embed_resource() {
235 let manifest = std::path::Path::new("resources/windows/gpui.manifest.xml");
236 let rc_file = std::path::Path::new("resources/windows/gpui.rc");
237 println!("cargo:rerun-if-changed={}", manifest.display());
238 println!("cargo:rerun-if-changed={}", rc_file.display());
239 embed_resource::compile(rc_file, embed_resource::NONE)
240 .manifest_required()
241 .unwrap();
242 }
243
244 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
245 fn compile_shaders() {
246 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
247 .join("src/platform/windows/shaders.hlsl");
248 let out_dir = std::env::var("OUT_DIR").unwrap();
249
250 println!("cargo:rerun-if-changed={}", shader_path.display());
251
252 // Check if fxc.exe is available
253 let fxc_path = find_fxc_compiler();
254
255 // Define all modules
256 let modules = [
257 "quad",
258 "shadow",
259 "path_rasterization",
260 "path_sprite",
261 "underline",
262 "monochrome_sprite",
263 "subpixel_sprite",
264 "polychrome_sprite",
265 ];
266
267 let rust_binding_path = format!("{}/shaders_bytes.rs", out_dir);
268 if Path::new(&rust_binding_path).exists() {
269 fs::remove_file(&rust_binding_path)
270 .expect("Failed to remove existing Rust binding file");
271 }
272 for module in modules {
273 compile_shader_for_module(
274 module,
275 &out_dir,
276 &fxc_path,
277 shader_path.to_str().unwrap(),
278 &rust_binding_path,
279 );
280 }
281
282 {
283 let shader_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
284 .join("src/platform/windows/color_text_raster.hlsl");
285 compile_shader_for_module(
286 "emoji_rasterization",
287 &out_dir,
288 &fxc_path,
289 shader_path.to_str().unwrap(),
290 &rust_binding_path,
291 );
292 }
293 }
294
295 /// Locate `binary` in the newest installed Windows SDK.
296 pub fn find_latest_windows_sdk_binary(
297 binary: &str,
298 ) -> Result<Option<PathBuf>, Box<dyn std::error::Error>> {
299 let key = windows_registry::LOCAL_MACHINE
300 .open("SOFTWARE\\WOW6432Node\\Microsoft\\Microsoft SDKs\\Windows\\v10.0")?;
301
302 let install_folder: String = key.get_string("InstallationFolder")?; // "C:\Program Files (x86)\Windows Kits\10\"
303 let install_folder_bin = Path::new(&install_folder).join("bin");
304
305 let mut versions: Vec<_> = std::fs::read_dir(&install_folder_bin)?
306 .flatten()
307 .filter(|entry| entry.path().is_dir())
308 .filter_map(|entry| entry.file_name().into_string().ok())
309 .collect();
310
311 versions.sort_by_key(|s| {
312 s.split('.')
313 .filter_map(|p| p.parse().ok())
314 .collect::<Vec<u32>>()
315 });
316
317 let arch = match std::env::consts::ARCH {
318 "x86_64" => "x64",
319 "aarch64" => "arm64",
320 _ => Err(format!(
321 "Unsupported architecture: {}",
322 std::env::consts::ARCH
323 ))?,
324 };
325
326 if let Some(highest_version) = versions.last() {
327 return Ok(Some(
328 install_folder_bin
329 .join(highest_version)
330 .join(arch)
331 .join(binary),
332 ));
333 }
334
335 Ok(None)
336 }
337
338 /// You can set the `GPUI_FXC_PATH` environment variable to specify the path to the fxc.exe compiler.
339 fn find_fxc_compiler() -> String {
340 // Check environment variable
341 if let Ok(path) = std::env::var("GPUI_FXC_PATH")
342 && Path::new(&path).exists()
343 {
344 return path;
345 }
346
347 // Try to find in PATH
348 // NOTE: This has to be `where.exe` on Windows, not `where`, it must be ended with `.exe`
349 if let Ok(output) = std::process::Command::new("where.exe")
350 .arg("fxc.exe")
351 .output()
352 && output.status.success()
353 {
354 let path = String::from_utf8_lossy(&output.stdout);
355 return path.trim().to_string();
356 }
357
358 if let Ok(Some(path)) = find_latest_windows_sdk_binary("fxc.exe") {
359 return path.to_string_lossy().into_owned();
360 }
361
362 panic!("Failed to find fxc.exe");
363 }
364
365 fn compile_shader_for_module(
366 module: &str,
367 out_dir: &str,
368 fxc_path: &str,
369 shader_path: &str,
370 rust_binding_path: &str,
371 ) {
372 // Compile vertex shader
373 let output_file = format!("{}/{}_vs.h", out_dir, module);
374 let const_name = format!("{}_VERTEX_BYTES", module.to_uppercase());
375 compile_shader_impl(
376 fxc_path,
377 &format!("{module}_vertex"),
378 &output_file,
379 &const_name,
380 shader_path,
381 "vs_4_1",
382 );
383 generate_rust_binding(&const_name, &output_file, rust_binding_path);
384
385 // Compile fragment shader
386 let output_file = format!("{}/{}_ps.h", out_dir, module);
387 let const_name = format!("{}_FRAGMENT_BYTES", module.to_uppercase());
388 compile_shader_impl(
389 fxc_path,
390 &format!("{module}_fragment"),
391 &output_file,
392 &const_name,
393 shader_path,
394 "ps_4_1",
395 );
396 generate_rust_binding(&const_name, &output_file, rust_binding_path);
397 }
398
399 fn compile_shader_impl(
400 fxc_path: &str,
401 entry_point: &str,
402 output_path: &str,
403 var_name: &str,
404 shader_path: &str,
405 target: &str,
406 ) {
407 let output = Command::new(fxc_path)
408 .args([
409 "/T",
410 target,
411 "/E",
412 entry_point,
413 "/Fh",
414 output_path,
415 "/Vn",
416 var_name,
417 "/O3",
418 shader_path,
419 ])
420 .output();
421
422 match output {
423 Ok(result) => {
424 if result.status.success() {
425 return;
426 }
427 println!(
428 "cargo::error=Shader compilation failed for {}:\n{}",
429 entry_point,
430 String::from_utf8_lossy(&result.stderr)
431 );
432 process::exit(1);
433 }
434 Err(e) => {
435 println!("cargo::error=Failed to run fxc for {}: {}", entry_point, e);
436 process::exit(1);
437 }
438 }
439 }
440
441 fn generate_rust_binding(const_name: &str, head_file: &str, output_path: &str) {
442 let header_content = fs::read_to_string(head_file).expect("Failed to read header file");
443 let const_definition = {
444 let global_var_start = header_content.find("const BYTE").unwrap();
445 let global_var = &header_content[global_var_start..];
446 let equal = global_var.find('=').unwrap();
447 global_var[equal + 1..].trim()
448 };
449 let rust_binding = format!(
450 "const {}: &[u8] = &{}\n",
451 const_name,
452 const_definition.replace('{', "[").replace('}', "]")
453 );
454 let mut options = fs::OpenOptions::new()
455 .create(true)
456 .append(true)
457 .open(output_path)
458 .expect("Failed to open Rust binding file");
459 options
460 .write_all(rust_binding.as_bytes())
461 .expect("Failed to write Rust binding file");
462 }
463}