Files
zed/crates/gpui_apple/build.rs
T
Conrad IrwinandBen Kunkle 72dc742101 Support building with corgi (#63396)
[corgi](https://github.com/ConradIrwin/corgi) is a cargo-compatible
build tool that runs `build.rs` scripts and proc-macros in a sandbox (no
network, no ambient env, reads limited to a package's own `.rs` sources
unless declared). This makes Zed buildable under it. Cargo builds (dev
and release) are unaffected.

### Changes

- **`scratch` patch**: patch `scratch` to a small local copy
(`corgi-patches/scratch`) that reads `OUT_DIR` at runtime instead of
baking it in at compile time. `cxx-build` writes its shared cxxbridge
headers into `scratch::path(...)`; upstream returns a single global dir
(outside any action's `OUT_DIR`) that the sandbox can't grant, while
this patch gives each build script a private, writable dir in its own
entry. webrtc-sys is self-contained, so per-crate scratch dirs are
sufficient. Kept local rather than a cxx git fork because a git checkout
of cxx needs symlink support that Windows cargo CI lacks.
- **Dev asset loading**: add `util::dev_fs_embed!` and switch dev-mode
`rust_embed` asset sources (`assets`, `settings`, `grammars`, `agent`,
`edit_prediction_cli`) to read from the checkout at runtime, locating
the repo root by walking from the executable up to the enclosing `.git`.
This avoids baking `CARGO_MANIFEST_DIR` into the binary (corgi rejects
artifacts that embed the build-time checkout path, and it's wrong in any
other worktree). Release builds still embed via `#[derive(RustEmbed)]`.
- **Stop baking checkout paths into artifacts**: drop
`gpui::GPUI_MANIFEST_DIR`; resolve `../gpui` from `gpui_apple`'s build
script; stage the metal shader into `OUT_DIR` before compiling so the
`.metallib` records the output dir, not the checkout; resolve the repo
root at runtime in `remote` and `inspector_ui` (removing
`inspector_ui`'s `build.rs` / `ZED_REPO_DIR`).
- **corgi.toml**: declare the pinned tools (`cmake`, prebuilt libwebrtc
via `LK_CUSTOM_WEBRTC`), a release-only `ZED_COMMIT_SHA` probe, and the
non-`.rs` / cross-package reads each package performs.

### Validation

- `corgi check` + `corgi build`, dev and release, for `zed` (plus `cli`,
`remote_server`, `ep`) with a warm C++ cache — clean; built binaries
embed no checkout path.
- `corgi fmt --check`, `corgi clippy`, and `corgi test` on the changed
crates (922 tests pass).
- `cargo check -p util` locally; relying on CI for the full cargo
build/test matrix.

### Notes for the reviewer

- Per the repo rule, the first two lines of `README.md` are the
review-confirmation marker; remove them before merging (this is what
Danger is failing on).
- `remote_server` cross-compilation (linux-musl via zigbuild) is not yet
covered by `corgi.toml` — host (macOS arm64) only for now.

### Suggested .rules additions

Only if the team wants corgi guidance in `.rules` (it recurred several
times here): dev builds must not bake the checkout path into artifacts —
no `env!("CARGO_MANIFEST_DIR")` or `file!()`-derived absolute paths in
library/binary output. Resolve the repo root at runtime via
`util::dev_repo_root()` (walks to `.git`), and use `util::dev_fs_embed!`
for dev-mode `rust_embed` sources.

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
2026-08-31 17:37:00 +00:00

188 lines
6.7 KiB
Rust

#![allow(clippy::disallowed_methods, reason = "build scripts are exempt")]
fn main() {
#[cfg(target_os = "macos")]
macos_build::run();
}
#[cfg(target_os = "macos")]
mod macos_build {
use std::{
env,
path::{Path, PathBuf},
};
use cbindgen::Config;
pub fn run() {
let header_path = generate_shader_bindings();
#[cfg(feature = "runtime_shaders")]
emit_stitched_shaders(&header_path);
#[cfg(not(feature = "runtime_shaders"))]
compile_metal_shaders(&header_path);
}
fn generate_shader_bindings() -> PathBuf {
let output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("scene.h");
let gpui_dir = find_gpui_crate_dir();
let mut config = Config {
include_guard: Some("SCENE_H".into()),
language: cbindgen::Language::C,
no_includes: true,
..Default::default()
};
config.export.include.extend([
"Bounds".into(),
"Corners".into(),
"Edges".into(),
"Size".into(),
"Pixels".into(),
"PointF".into(),
"Hsla".into(),
"ContentMask".into(),
"Uniforms".into(),
"AtlasTile".into(),
"PathRasterizationInputIndex".into(),
"PathVertex_ScaledPixels".into(),
"PathRasterizationVertex".into(),
"ShadowInputIndex".into(),
"Shadow".into(),
"QuadInputIndex".into(),
"Underline".into(),
"UnderlineInputIndex".into(),
"Quad".into(),
"BorderStyle".into(),
"SpriteInputIndex".into(),
"MonochromeSprite".into(),
"PolychromeSprite".into(),
"PathSprite".into(),
"SurfaceInputIndex".into(),
"SurfaceBounds".into(),
"TransformationMatrix".into(),
]);
config.no_includes = true;
config.enumeration.prefix_with_name = true;
let mut builder = cbindgen::Builder::new();
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
// Source files from gpui that define types used in shaders
let gpui_src_paths = [
gpui_dir.join("src/scene.rs"),
gpui_dir.join("src/geometry.rs"),
gpui_dir.join("src/color.rs"),
gpui_dir.join("src/window.rs"),
gpui_dir.join("src/platform.rs"),
];
// Source files from this crate
let local_src_paths = [crate_dir.join("src/metal_renderer.rs")];
for src_path in gpui_src_paths.iter().chain(local_src_paths.iter()) {
println!("cargo:rerun-if-changed={}", src_path.display());
builder = builder.with_src(src_path);
}
builder
.with_config(config)
.generate()
.expect("Unable to generate bindings")
.write_to_file(&output_path);
output_path
}
/// Locate the gpui crate directory relative to this crate. Resolved at
/// build-script runtime against this crate's manifest dir, so no checkout
/// path is baked into a compiled artifact (which corgi rejects).
fn find_gpui_crate_dir() -> PathBuf {
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../gpui")
}
/// To enable runtime compilation, we need to "stitch" the shaders file with the generated header
/// so that it is self-contained.
#[cfg(feature = "runtime_shaders")]
fn emit_stitched_shaders(header_path: &Path) {
fn stitch_header(header: &Path, shader_path: &Path) -> std::io::Result<PathBuf> {
let header_contents = std::fs::read_to_string(header)?;
let shader_contents = std::fs::read_to_string(shader_path)?;
let stitched_contents = format!("{header_contents}\n{shader_contents}");
let out_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal");
std::fs::write(&out_path, stitched_contents)?;
Ok(out_path)
}
let shader_source_path = "./src/shaders.metal";
let shader_path = PathBuf::from(shader_source_path);
stitch_header(header_path, &shader_path).unwrap();
println!("cargo:rerun-if-changed={shader_source_path}");
}
#[cfg(not(feature = "runtime_shaders"))]
fn compile_metal_shaders(header_path: &Path) {
use std::process::{self, Command};
let shader_path = "./src/shaders.metal";
let air_output_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.air");
let metallib_output_path =
PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metallib");
println!("cargo:rerun-if-changed={}", shader_path);
// The metal compiler records the resolved absolute path of its input
// unconditionally. Compile a copy staged in OUT_DIR so the recorded
// location is the build's canonical output directory, never the
// checkout (corgi rejects artifacts that embed the build path).
let staged_shader_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("shaders.metal");
std::fs::copy(shader_path, &staged_shader_path).unwrap();
let output = Command::new("xcrun")
.args([
"-sdk",
"macosx",
"metal",
"-gline-tables-only",
"-mmacosx-version-min=10.15.7",
"-MO",
"-c",
])
.arg(&staged_shader_path)
.args(["-include", header_path.to_str().unwrap(), "-o"])
.arg(&air_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metal shader compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
let output = Command::new("xcrun")
.args(["-sdk", "macosx", "metallib"])
.arg(&air_output_path)
.arg("-o")
.arg(metallib_output_path)
.output()
.unwrap();
if !output.status.success() {
println!(
"cargo::error=metallib compilation failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
process::exit(1);
}
// The .air intermediate records the compiler's working directory in
// its debug info; the metallib built from it does not. Nothing reads
// the .air after this point, so drop it rather than leave a
// checkout-path-bearing file in OUT_DIR.
std::fs::remove_file(&air_output_path).unwrap();
}
}