build.rs

  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}