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>
This commit is contained in:
1 parent
87222d1d29
commit
72dc742101
25 files changed
+422
-95
No files matched your search
Generated
+2
-6
@@ -298,7 +298,6 @@ dependencies = [
|
||||
"regex",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"rust-embed",
|
||||
"sandbox",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
@@ -946,7 +945,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gpui",
|
||||
"rust-embed",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5473,7 +5472,6 @@ dependencies = [
|
||||
"rand 0.9.4",
|
||||
"release_channel",
|
||||
"reqwest_client",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
@@ -7848,6 +7846,7 @@ dependencies = [
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"tree-sitter-yaml",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15937,8 +15936,6 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
|
||||
|
||||
[[package]]
|
||||
name = "screencapturekit"
|
||||
@@ -16469,7 +16466,6 @@ dependencies = [
|
||||
"paths",
|
||||
"pretty_assertions",
|
||||
"release_channel",
|
||||
"rust-embed",
|
||||
"schemars 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
+12
@@ -988,6 +988,18 @@ notify = { git = "https://github.com/zed-industries/notify", rev = "0890bbb8ca40
|
||||
notify-types = { git = "https://github.com/zed-industries/notify", rev = "0890bbb8ca40a4b5d1f67031698dd7918b37d991" }
|
||||
webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "0a1c519cfce9b365229026b55de9b9dbdb6fed3c" }
|
||||
|
||||
# corgi runs build.rs/proc-macros in a sandbox with no write access outside the
|
||||
# action's own OUT_DIR. Upstream `scratch` bakes its build-time OUT_DIR in via
|
||||
# `env!` and hands every crate one shared directory, which the sandbox can't
|
||||
# grant and a content-addressed build can't share. This patch reads OUT_DIR at
|
||||
# runtime, so each build script gets a private, writable dir in its own entry —
|
||||
# where cxx-build writes its shared cxxbridge headers. Kept local rather than a
|
||||
# cxx git fork so Windows cargo builds are unaffected (a git checkout of cxx
|
||||
# needs symlink support that Windows CI lacks). webrtc-sys is self-contained, so
|
||||
# per-crate scratch dirs are sufficient. Upstream issue:
|
||||
# https://github.com/dtolnay/cxx/issues/1752
|
||||
scratch = { path = "corgi-patches/scratch" }
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
incremental = true
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "scratch"
|
||||
version = "1.0.9"
|
||||
edition = "2021"
|
||||
description = "corgi-patched scratch: uses a keyed, per-action shared directory"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
@@ -0,0 +1,8 @@
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
// Same as upstream: the fallback dir is wiped by `cargo clean`.
|
||||
let out_dir = env::var_os("OUT_DIR").unwrap();
|
||||
let _ = fs::remove_dir_all(&out_dir);
|
||||
let _ = fs::create_dir(&out_dir);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//! A corgi-compatible `scratch` implementation.
|
||||
//!
|
||||
//! Upstream bakes its own OUT_DIR in at compile time (`env!`), making one
|
||||
//! global directory that every crate's build script writes into --
|
||||
//! cross-action shared mutable state, which a content-addressed build
|
||||
//! cannot allow. This patch reads OUT_DIR at *runtime*, so each build
|
||||
//! script gets a private dir inside its own (published, deterministic)
|
||||
//! entry. Self-contained cxx crates are unaffected; genuine cross-crate
|
||||
//! header sharing would fail loudly with a missing include, which is the
|
||||
//! signal to build the union-of-exports view.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn path(suffix: &str) -> PathBuf {
|
||||
let p = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join(suffix);
|
||||
let _ = fs::create_dir_all(&p);
|
||||
p
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# corgi build configuration for Zed.
|
||||
# Corgi source and documentation: https://github.com/ConradIrwin/corgi
|
||||
#
|
||||
# corgi runs build.rs scripts and proc-macros in a sandbox with no network,
|
||||
# no ambient environment, and reads limited to each package's own source plus
|
||||
# what is declared here. Every setting names the packages it applies to, so
|
||||
# only those packages' actions key on it — comment or command-text edits
|
||||
# rebuild nothing.
|
||||
#
|
||||
# See the top-level Cargo.toml `[patch.crates-io]` for the local `scratch` patch
|
||||
# that lets cxx/webrtc-sys build under the sandbox, and `util::fs_embed!` for
|
||||
# how dev builds read embedded assets from the checkout instead of baking the
|
||||
# build path into the binary.
|
||||
|
||||
# sha256-pinned tools, fetched into the corgi store and exposed to the named
|
||||
# packages' build scripts via the given environment variable. URLs and hashes
|
||||
# are for the macOS arm64 build host.
|
||||
#
|
||||
# Note: no protoc tool is needed. `proto` and `livekit_api` compile their
|
||||
# protobufs with `protox` (a pure-Rust protoc) and `prost_build::compile_fds`,
|
||||
# so nothing shells out to a protoc binary.
|
||||
|
||||
[tools.cmake]
|
||||
version = "4.1.2"
|
||||
url = "https://github.com/Kitware/CMake/releases/download/v4.1.2/cmake-4.1.2-macos-universal.tar.gz"
|
||||
sha256 = "3be85f5b999e327b1ac7d804cbc9acd767059e9f603c42ec2765f6ab68fbd367"
|
||||
bin = "cmake-4.1.2-macos-universal/CMake.app/Contents/bin/cmake"
|
||||
env = "CMAKE"
|
||||
packages = ["webrtc-sys", "wasmtime-c-api-impl"]
|
||||
|
||||
# Prebuilt libwebrtc, so webrtc-sys-build does not have to download it over the
|
||||
# network (which the sandbox forbids). The tag must match WEBRTC_TAG in the
|
||||
# pinned livekit-rust-sdks revision; webrtc-sys-build reads LK_CUSTOM_WEBRTC and
|
||||
# skips the download when it is set.
|
||||
[tools.webrtc-prebuilt]
|
||||
version = "0001d84-4"
|
||||
url = "https://github.com/zed-industries/livekit-rust-sdks/releases/download/webrtc-0001d84-4/webrtc-mac-arm64-release.zip"
|
||||
sha256 = "09ba71bfbc15c6aacced7fe20451a9648dfe648c24295f28d75fc386dbd7281a"
|
||||
path = "mac-arm64-release"
|
||||
env = "LK_CUSTOM_WEBRTC"
|
||||
packages = ["webrtc-sys"]
|
||||
|
||||
# Plan-time probe: the git SHA is read here (outside the sandbox) and frozen
|
||||
# into the keyed value. zed/build.rs prefers $ZED_COMMIT_SHA over running git,
|
||||
# which the sandbox blocks. Release-only; dev builds embed no SHA (the source
|
||||
# reads it via option_env!). cli/remote_server build.rs run git directly rather
|
||||
# than reading this var, so they are not listed here.
|
||||
[env.ZED_COMMIT_SHA]
|
||||
command = "git rev-parse HEAD"
|
||||
packages = ["zed"]
|
||||
profiles = ["release"]
|
||||
|
||||
# Reads a package's actions perform that corgi's sandbox does not grant by
|
||||
# default. By default only a package's own `.rs` files (plus its manifest) are
|
||||
# readable, so non-`.rs` files a build script or macro reads must be listed even
|
||||
# when they live inside the package. Paths are relative to the named package's
|
||||
# root and are granted and hashed as inputs.
|
||||
[extra-inputs]
|
||||
# Own non-.rs files read at build time (protobufs, bindgen headers, doc/data).
|
||||
proto = ["proto"]
|
||||
livekit_api = ["vendored/protocol"]
|
||||
media = ["src/bindings.h"]
|
||||
gpui = ["README.md"]
|
||||
git = ["src/checkpoint.gitignore"]
|
||||
git_ui = ["src/commit_message_prompt.txt"]
|
||||
editor = ["../grammars/src"]
|
||||
edit_prediction = ["license_patterns"]
|
||||
agent_skills = ["builtin"]
|
||||
agent_settings = ["src/prompts"]
|
||||
json_schema_store = ["src/schemas"]
|
||||
prettier = ["src/prettier_server.js"]
|
||||
zed = ["resources"]
|
||||
# Cross-package reads (and, for gpui_apple, its own metal shader source).
|
||||
extension_host = ["../extension_api/wit"]
|
||||
gpui_apple = ["../gpui/src", "src/shaders.metal"]
|
||||
prompt_store = ["../git_ui/src/commit_message_prompt.txt"]
|
||||
release_channel = ["../zed/RELEASE_CHANNEL"]
|
||||
cli = ["../../script/uninstall.sh"]
|
||||
remote_server = ["../zed/Cargo.toml"]
|
||||
# `../zed/Cargo.toml`: build.rs reads it for ZED_PKG_VERSION. `../grammars/src`
|
||||
# and `src/prompts` are embedded in release builds; dev builds read them at
|
||||
# runtime via fs_embed.
|
||||
edit_prediction_cli = ["../zed/Cargo.toml", "../grammars/src", "src/prompts"]
|
||||
@@ -55,7 +55,6 @@ prompt_store.workspace = true
|
||||
quick-xml.workspace = true
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
rust-embed.workspace = true
|
||||
sandbox.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use gpui::SharedString;
|
||||
use handlebars::Handlebars;
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/templates"]
|
||||
#[include = "*.hbs"]
|
||||
struct Assets;
|
||||
// Dev builds read the checkout's templates at runtime instead of embedding
|
||||
// them; see the `assets` crate for the rationale.
|
||||
util::fs_embed! {
|
||||
struct Assets,
|
||||
crate_relative = "src/templates",
|
||||
root_relative = "crates/agent/src/templates",
|
||||
include = ["*.hbs"],
|
||||
}
|
||||
|
||||
pub struct Templates(Handlebars<'static>);
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
rust-embed.workspace = true
|
||||
util.workspace = true
|
||||
+19
-13
@@ -2,20 +2,26 @@
|
||||
|
||||
use anyhow::Context as _;
|
||||
use gpui::{App, AssetSource, Result, SharedString};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[include = "images/**/*"]
|
||||
#[include = "themes/**/*"]
|
||||
#[exclude = "themes/src/*"]
|
||||
#[include = "sounds/**/*"]
|
||||
#[include = "prompts/**/*"]
|
||||
#[include = "*.md"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
// Release builds embed the assets; dev builds read them from the checkout at
|
||||
// runtime so edits show up on the next launch without a rebuild and no
|
||||
// build-time path is baked in (which corgi's sandbox rejects). See
|
||||
// `util::fs_embed!`.
|
||||
util::fs_embed! {
|
||||
pub struct Assets,
|
||||
crate_relative = "../../assets",
|
||||
root_relative = "assets",
|
||||
include = [
|
||||
"fonts/**/*",
|
||||
"icons/**/*",
|
||||
"images/**/*",
|
||||
"themes/**/*",
|
||||
"sounds/**/*",
|
||||
"prompts/**/*",
|
||||
"*.md",
|
||||
],
|
||||
exclude = ["themes/src/*", "*.DS_Store"],
|
||||
}
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<Option<std::borrow::Cow<'static, [u8]>>> {
|
||||
|
||||
@@ -70,7 +70,6 @@ rand.workspace = true
|
||||
similar = "2.7.0"
|
||||
flate2 = "1.1.8"
|
||||
toml.workspace = true
|
||||
rust-embed.workspace = true
|
||||
gaoya = "0.2.0"
|
||||
|
||||
# Wasmtime is included as a dependency in order to enable the same
|
||||
|
||||
@@ -24,19 +24,16 @@ use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader, BufWriter, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
// Dev builds read grammars' language configs from the checkout at runtime
|
||||
// instead of embedding them, so no build-time path is baked into the binary.
|
||||
#[cfg(not(feature = "dynamic_prompts"))]
|
||||
mod language_configs_embedded {
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../grammars/src/"]
|
||||
#[include = "*/config.toml"]
|
||||
pub struct LanguageConfigs;
|
||||
util::fs_embed! {
|
||||
struct LanguageConfigs,
|
||||
crate_relative = "../grammars/src/",
|
||||
root_relative = "crates/grammars/src",
|
||||
include = ["*/config.toml"],
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "dynamic_prompts"))]
|
||||
use language_configs_embedded::LanguageConfigs;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LanguageConfig {
|
||||
name: String,
|
||||
@@ -100,7 +97,7 @@ pub struct FilterLanguagesArgs {
|
||||
fn build_extension_to_language_map() -> HashMap<String, String> {
|
||||
let mut map = HashMap::default();
|
||||
|
||||
for file_path in LanguageConfigs::iter() {
|
||||
for file_path in LanguageConfigs::iter().filter(|path| path.ends_with("config.toml")) {
|
||||
if let Some(content) = LanguageConfigs::get(&file_path) {
|
||||
let content_str = match std::str::from_utf8(&content.data) {
|
||||
Ok(s) => s,
|
||||
|
||||
@@ -26,11 +26,12 @@ pub fn get_prompt(name: &'static str) -> Cow<'static, str> {
|
||||
|
||||
#[cfg(not(feature = "dynamic_prompts"))]
|
||||
pub fn get_prompt(name: &'static str) -> Cow<'static, str> {
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/prompts"]
|
||||
struct EmbeddedPrompts;
|
||||
// Dev builds read prompts from the checkout at runtime (no baked path).
|
||||
util::fs_embed! {
|
||||
struct EmbeddedPrompts,
|
||||
crate_relative = "src/prompts",
|
||||
root_relative = "crates/edit_prediction_cli/src/prompts",
|
||||
}
|
||||
|
||||
match EmbeddedPrompts::get(name) {
|
||||
Some(file) => match file.data {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#![allow(unused_mut)] // False positives in platform specific code
|
||||
|
||||
extern crate self as gpui;
|
||||
#[doc(hidden)]
|
||||
pub static GPUI_MANIFEST_DIR: &'static str = env!("CARGO_MANIFEST_DIR");
|
||||
#[macro_use]
|
||||
mod action;
|
||||
mod app;
|
||||
|
||||
@@ -38,7 +38,6 @@ parking_lot.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||
cbindgen.workspace = true
|
||||
gpui.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
@@ -96,9 +96,11 @@ mod macos_build {
|
||||
output_path
|
||||
}
|
||||
|
||||
/// Locate the gpui crate directory relative to this crate.
|
||||
/// 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 {
|
||||
gpui::GPUI_MANIFEST_DIR.into()
|
||||
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
|
||||
@@ -129,6 +131,13 @@ mod macos_build {
|
||||
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",
|
||||
@@ -138,11 +147,9 @@ mod macos_build {
|
||||
"-mmacosx-version-min=10.15.7",
|
||||
"-MO",
|
||||
"-c",
|
||||
shader_path,
|
||||
"-include",
|
||||
(header_path.to_str().unwrap()),
|
||||
"-o",
|
||||
])
|
||||
.arg(&staged_shader_path)
|
||||
.args(["-include", header_path.to_str().unwrap(), "-o"])
|
||||
.arg(&air_output_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -157,7 +164,7 @@ mod macos_build {
|
||||
|
||||
let output = Command::new("xcrun")
|
||||
.args(["-sdk", "macosx", "metallib"])
|
||||
.arg(air_output_path)
|
||||
.arg(&air_output_path)
|
||||
.arg("-o")
|
||||
.arg(metallib_output_path)
|
||||
.output()
|
||||
@@ -170,5 +177,11 @@ mod macos_build {
|
||||
);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ anyhow.workspace = true
|
||||
language_core.workspace = true
|
||||
rust-embed.workspace = true
|
||||
toml.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
tree-sitter = { workspace = true, optional = true }
|
||||
tree-sitter-bash = { workspace = true, optional = true }
|
||||
|
||||
@@ -2,12 +2,15 @@ use std::borrow::Cow;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use language_core::{LanguageConfig, LanguageQueries, QueryFile, QueryFileContents};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/"]
|
||||
#[exclude = "*.rs"]
|
||||
struct GrammarDir;
|
||||
// Dev builds read the checkout's query files at runtime instead of embedding
|
||||
// them; see the `assets` crate for the rationale.
|
||||
util::fs_embed! {
|
||||
struct GrammarDir,
|
||||
crate_relative = "src/",
|
||||
root_relative = "crates/grammars/src",
|
||||
exclude = ["*.rs"],
|
||||
}
|
||||
|
||||
/// Register all built-in native tree-sitter grammars with the provided registration function.
|
||||
///
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
fn main() {
|
||||
let cargo_manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let mut path = std::path::PathBuf::from(&cargo_manifest_dir);
|
||||
|
||||
if path.file_name().as_ref().and_then(|name| name.to_str()) != Some("inspector_ui") {
|
||||
panic!(
|
||||
"expected CARGO_MANIFEST_DIR to end with crates/inspector_ui, but got {cargo_manifest_dir}"
|
||||
);
|
||||
}
|
||||
path.pop();
|
||||
|
||||
if path.file_name().as_ref().and_then(|name| name.to_str()) != Some("crates") {
|
||||
panic!(
|
||||
"expected CARGO_MANIFEST_DIR to end with crates/inspector_ui, but got {cargo_manifest_dir}"
|
||||
);
|
||||
}
|
||||
path.pop();
|
||||
|
||||
println!("cargo:rustc-env=ZED_REPO_DIR={}", path.display());
|
||||
}
|
||||
@@ -109,10 +109,11 @@ fn render_inspector_id(inspector_id: &InspectorElementId, cx: &App) -> Div {
|
||||
let source_location = inspector_id.path.source_location;
|
||||
// For unknown reasons, for some elements the path is absolute.
|
||||
let source_location_string = source_location.to_string();
|
||||
let source_location_string = source_location_string
|
||||
.strip_prefix(env!("ZED_REPO_DIR"))
|
||||
.and_then(|s| s.strip_prefix("/"))
|
||||
.map(|s| s.to_string())
|
||||
// Resolve the checkout at runtime rather than baking it in at build time
|
||||
// (which corgi rejects, and which would be wrong in any other worktree).
|
||||
let source_location_string = util::dev_repo_root()
|
||||
.and_then(|root| Path::new(&source_location_string).strip_prefix(root).ok())
|
||||
.map(|path| path.display().to_string())
|
||||
.unwrap_or(source_location_string);
|
||||
|
||||
v_flex()
|
||||
@@ -160,7 +161,9 @@ fn render_inspector_id(inspector_id: &InspectorElementId, cx: &App) -> Div {
|
||||
async fn open_zed_source_location(
|
||||
location: &'static std::panic::Location<'static>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut path = Path::new(env!("ZED_REPO_DIR")).to_path_buf();
|
||||
let mut path = util::dev_repo_root()
|
||||
.context("locating the zed checkout to open sources from")?
|
||||
.to_path_buf();
|
||||
path.push(Path::new(location.file()));
|
||||
let path_arg = format!(
|
||||
"{}:{}:{}",
|
||||
|
||||
@@ -246,7 +246,6 @@ async fn build_remote_server_from_source(
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Option<std::path::PathBuf>> {
|
||||
use std::env::VarError;
|
||||
use std::path::Path;
|
||||
use util::command::{Command, Stdio, new_command};
|
||||
|
||||
if let Ok(path) = std::env::var("ZED_COPY_REMOTE_SERVER") {
|
||||
@@ -328,7 +327,10 @@ async fn build_remote_server_from_source(
|
||||
log::info!("building remote server binary from source");
|
||||
run_cmd(
|
||||
new_command("cargo")
|
||||
.current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
|
||||
.current_dir(
|
||||
util::dev_repo_root()
|
||||
.context("locating the zed checkout to build remote_server from source")?,
|
||||
)
|
||||
.args([
|
||||
"build",
|
||||
"--package",
|
||||
@@ -374,7 +376,10 @@ async fn build_remote_server_from_source(
|
||||
log::info!("building remote binary from source for {triple} with Zig");
|
||||
run_cmd(
|
||||
new_command("cargo")
|
||||
.current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
|
||||
.current_dir(
|
||||
util::dev_repo_root()
|
||||
.context("locating the zed checkout to build remote_server from source")?,
|
||||
)
|
||||
.args([
|
||||
"zigbuild",
|
||||
"--package",
|
||||
@@ -390,7 +395,8 @@ async fn build_remote_server_from_source(
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
let bin_path = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../.."))
|
||||
let bin_path = util::dev_repo_root()
|
||||
.context("locating the zed checkout that built remote_server from source")?
|
||||
.join("target")
|
||||
.join("remote_server")
|
||||
.join(&triple)
|
||||
|
||||
@@ -27,7 +27,6 @@ log.workspace = true
|
||||
migrator.workspace = true
|
||||
paths.workspace = true
|
||||
release_channel.workspace = true
|
||||
rust-embed.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -26,7 +26,6 @@ pub mod private {
|
||||
|
||||
use gpui::{App, Global};
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
use std::env;
|
||||
use std::{borrow::Cow, fmt, str};
|
||||
use util::asset_str;
|
||||
@@ -117,12 +116,15 @@ impl fmt::Display for WorktreeId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "settings/*"]
|
||||
#[include = "keymaps/*"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct SettingsAssets;
|
||||
// Dev builds read the checkout's files at runtime instead of embedding them;
|
||||
// see the `assets` crate for the rationale.
|
||||
util::fs_embed! {
|
||||
pub struct SettingsAssets,
|
||||
crate_relative = "../../assets",
|
||||
root_relative = "assets",
|
||||
include = ["settings/*", "keymaps/*"],
|
||||
exclude = ["*.DS_Store"],
|
||||
}
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
let settings = SettingsStore::new(cx, &default_settings());
|
||||
|
||||
@@ -634,6 +634,196 @@ pub fn asset_str<A: rust_embed::RustEmbed>(path: &str) -> Cow<'static, str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The checkout that produced this binary, resolved at runtime by walking up to
|
||||
/// the first ancestor that contains a `.git` entry (a directory in a normal
|
||||
/// clone, a file in a git worktree or submodule). Cargo and corgi both place
|
||||
/// built binaries under `<repo>/target/<profile>/`, so the repository root is
|
||||
/// always an ancestor of the executable.
|
||||
///
|
||||
/// Dev-only affordances use this instead of baking a build-time path into the
|
||||
/// artifact: such a path points at the wrong checkout from any other worktree
|
||||
/// and, under corgi, is rejected because artifacts must be checkout-independent
|
||||
/// to be shared across worktrees.
|
||||
///
|
||||
/// The executable's launch path is tried first, then its canonical form, then
|
||||
/// the working directory. In CI, `target/` (or the checkout root) can be a
|
||||
/// symlink onto another volume, so canonicalizing the executable alone can walk
|
||||
/// off the checkout and miss `.git`; the launch path and the test runner's cwd
|
||||
/// (a crate dir under the checkout) stay inside it.
|
||||
pub fn dev_repo_root() -> Option<&'static std::path::Path> {
|
||||
use std::path::PathBuf;
|
||||
static ROOT: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
|
||||
ROOT.get_or_init(|| {
|
||||
let exe = std::env::current_exe().ok();
|
||||
let candidates = [
|
||||
exe.clone(),
|
||||
exe.and_then(|exe| exe.canonicalize().ok()),
|
||||
std::env::current_dir().ok(),
|
||||
];
|
||||
candidates.into_iter().flatten().find_map(|start| {
|
||||
Some(
|
||||
start
|
||||
.ancestors()
|
||||
.find(|dir| dir.join(".git").exists())?
|
||||
.to_path_buf(),
|
||||
)
|
||||
})
|
||||
})
|
||||
.as_deref()
|
||||
}
|
||||
|
||||
/// Re-exports that back [`fs_embed!`] so a caller only needs to depend on `util`,
|
||||
/// not on `rust_embed` directly (the macro's dependency is an implementation
|
||||
/// detail). Hidden from the public API.
|
||||
#[doc(hidden)]
|
||||
pub mod __rust_embed {
|
||||
pub use rust_embed::{EmbeddedFile, Filenames, Metadata, RustEmbed, utils};
|
||||
}
|
||||
|
||||
/// Backs the dev arm of [`fs_embed!`]'s `iter`: every file under the root-relative
|
||||
/// directory that passes the same rust_embed include/exclude globs the
|
||||
/// release derive uses, so the dev and release file sets are identical. Reuses
|
||||
/// rust_embed's own matcher rather than reimplementing glob semantics.
|
||||
#[cfg(debug_assertions)]
|
||||
#[doc(hidden)]
|
||||
pub fn __fs_embed_iter(
|
||||
root_relative: &str,
|
||||
includes: &[&str],
|
||||
excludes: &[&str],
|
||||
) -> rust_embed::Filenames {
|
||||
let Some(root) = dev_repo_root().map(|root| root.join(root_relative)) else {
|
||||
return rust_embed::Filenames::Dynamic(Box::new(std::iter::empty::<
|
||||
std::borrow::Cow<'static, str>,
|
||||
>()));
|
||||
};
|
||||
let matcher = rust_embed::utils::PathMatcher::new(includes, excludes);
|
||||
let names: Vec<std::borrow::Cow<'static, str>> =
|
||||
rust_embed::utils::get_files(root.to_string_lossy().into_owned(), matcher)
|
||||
.map(|entry| std::borrow::Cow::Owned(entry.rel_path))
|
||||
.collect();
|
||||
rust_embed::Filenames::Dynamic(Box::new(names.into_iter()))
|
||||
}
|
||||
|
||||
/// Backs the dev arm of [`fs_embed!`]'s `get`: reads a single file from the
|
||||
/// checkout, returning `None` when the include/exclude globs filter it out so
|
||||
/// `get` matches release's embedded set exactly.
|
||||
#[cfg(debug_assertions)]
|
||||
#[doc(hidden)]
|
||||
pub fn __fs_embed_get(
|
||||
root_relative: &str,
|
||||
file_path: &str,
|
||||
includes: &[&str],
|
||||
excludes: &[&str],
|
||||
) -> Option<rust_embed::EmbeddedFile> {
|
||||
let matcher = rust_embed::utils::PathMatcher::new(includes, excludes);
|
||||
if !matcher.is_path_included(file_path) {
|
||||
return None;
|
||||
}
|
||||
let root = dev_repo_root()
|
||||
.expect("dev asset loading requires running from within the checkout")
|
||||
.join(root_relative);
|
||||
rust_embed::utils::read_file_from_fs(&root.join(file_path)).ok()
|
||||
}
|
||||
|
||||
/// A `rust_embed` asset source that embeds files in release builds and reads them
|
||||
/// from the checkout at runtime in dev builds (edits show up on the next launch
|
||||
/// with no rebuild). One invocation replaces the previous pairing of a
|
||||
/// `#[cfg(not(debug_assertions))] #[derive(RustEmbed)]` struct with a separate
|
||||
/// dev macro, and keeps a single source of truth for the include/exclude globs.
|
||||
///
|
||||
/// It expands to both arms:
|
||||
/// * Release (`not(debug_assertions)`): `#[derive(RustEmbed)]` embedding
|
||||
/// `crate_relative` at build time, with the given `include`/`exclude` globs.
|
||||
/// * Dev (`debug_assertions`): a runtime filesystem source rooted at
|
||||
/// `root_relative`, applying those same globs through rust_embed's own matcher.
|
||||
///
|
||||
/// Two paths are required because the arms resolve from different bases: the
|
||||
/// release derive reads `crate_relative` relative to the crate's `Cargo.toml`
|
||||
/// at build time (rust_embed's rule), while the dev arm resolves `root_relative`
|
||||
/// relative to the repository root at runtime via [`dev_repo_root`]. Baking the
|
||||
/// build-time path into the dev artifact would point at the wrong checkout from
|
||||
/// another worktree and is rejected by corgi, whose sandbox requires
|
||||
/// checkout-independent output.
|
||||
///
|
||||
/// ```ignore
|
||||
/// util::fs_embed! {
|
||||
/// pub struct Assets,
|
||||
/// crate_relative = "../../assets",
|
||||
/// root_relative = "assets",
|
||||
/// include = ["fonts/**/*", "themes/**/*", "*.md"],
|
||||
/// exclude = ["themes/src/*", "*.DS_Store"],
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! fs_embed {
|
||||
(
|
||||
$vis:vis struct $name:ident,
|
||||
crate_relative = $crate_relative:literal,
|
||||
root_relative = $root_relative:literal
|
||||
$(, include = [$($include:literal),* $(,)?])?
|
||||
$(, exclude = [$($exclude:literal),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
// `crate_path` points the derive's generated code at util's re-export so
|
||||
// the caller needs no direct `rust_embed` dependency.
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[derive($crate::__rust_embed::RustEmbed)]
|
||||
#[crate_path = "::util::__rust_embed"]
|
||||
#[folder = $crate_relative]
|
||||
$($(#[include = $include])*)?
|
||||
$($(#[exclude = $exclude])*)?
|
||||
$vis struct $name;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
$vis struct $name;
|
||||
|
||||
// Mirror the derive's public surface: inherent `get`/`iter` (callable
|
||||
// without the trait in scope) plus the trait impl (for generic bounds
|
||||
// like `util::asset_str` and `handlebars::register_embed_templates`), so
|
||||
// the two arms are interchangeable at call sites.
|
||||
#[cfg(debug_assertions)]
|
||||
impl $name {
|
||||
pub fn get(
|
||||
file_path: &str,
|
||||
) -> ::core::option::Option<$crate::__rust_embed::EmbeddedFile> {
|
||||
$crate::__fs_embed_get(
|
||||
$root_relative,
|
||||
file_path,
|
||||
&[$($($include),*)?],
|
||||
&[$($($exclude),*)?],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn iter(
|
||||
) -> impl ::core::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> + 'static
|
||||
{
|
||||
$crate::__fs_embed_iter(
|
||||
$root_relative,
|
||||
&[$($($include),*)?],
|
||||
&[$($($exclude),*)?],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl $crate::__rust_embed::RustEmbed for $name {
|
||||
fn get(
|
||||
file_path: &str,
|
||||
) -> ::core::option::Option<$crate::__rust_embed::EmbeddedFile> {
|
||||
<$name>::get(file_path)
|
||||
}
|
||||
|
||||
fn iter() -> $crate::__rust_embed::Filenames {
|
||||
$crate::__fs_embed_iter(
|
||||
$root_relative,
|
||||
&[$($($include),*)?],
|
||||
&[$($($exclude),*)?],
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait RangeExt<T> {
|
||||
fn sorted(&self) -> Self;
|
||||
fn to_inclusive(&self) -> RangeInclusive<T>;
|
||||
|
||||
Reference in new issue
Block a user