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