From 92f4f521abbb016ac848db3f5ed545e32da5f8d5 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 12 Oct 2026 22:29:05 +0200 Subject: [PATCH] fix(build): avoid perpetually invalidating build script inputs Repeated production builds recompiled build-info, GTK and their dependents because build scripts watched missing optional files and generated outputs. Watch packed-refs only when present and the prebuilt GTK bundle only when consumed. Loose refs remain watched so packing them refreshes Git inputs. Invoke glib-compile-resources directly instead of using a helper that also watches generated XML and UI files. The existing resources directory watch covers their source inputs. Remove the unused helper dependency and document the build input rules. Test Plan: - Run cargo build --profile production three times; repeats compile nothing. - Run cargo clippy, cargo clippy --benches and cargo clippy --tests. - Run cargo +nightly fmt and repeat all three Clippy checks. Documentation: docs/rewrite/ARCHITECTURE.md --- Cargo.lock | 7 ------- crates/ghostty-apprt-gtk/Cargo.toml | 3 --- crates/ghostty-apprt-gtk/build.rs | 27 ++++++++++++++++++++++----- crates/ghostty-build-info/build.rs | 11 +++++++---- docs/rewrite/ARCHITECTURE.md | 10 ++++++++++ 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd755e8..8296b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1379,7 +1379,6 @@ dependencies = [ "ghostty-inspector", "ghostty-os", "ghostty-vt", - "glib-build-tools", "glow", "gtk4", "gtk4-layer-shell", @@ -1856,12 +1855,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "glib-build-tools" -version = "0.22.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9871f38b67853c358b8190f77b9f878eb27d933a950f1045b244c4559a9f5f0" - [[package]] name = "glib-macros" version = "0.22.9" diff --git a/crates/ghostty-apprt-gtk/Cargo.toml b/crates/ghostty-apprt-gtk/Cargo.toml index 288fbd7..21e7c2a 100644 --- a/crates/ghostty-apprt-gtk/Cargo.toml +++ b/crates/ghostty-apprt-gtk/Cargo.toml @@ -46,9 +46,6 @@ dear-imgui-glow = "0.19" glow = "0.18" wayland-sys = { version = "0.31.11", features = ["client", "dlopen"] } -[build-dependencies] -glib-build-tools = "0.22" - [dev-dependencies] env_logger = "0.11" diff --git a/crates/ghostty-apprt-gtk/build.rs b/crates/ghostty-apprt-gtk/build.rs index d8a41fe..13c310e 100644 --- a/crates/ghostty-apprt-gtk/build.rs +++ b/crates/ghostty-apprt-gtk/build.rs @@ -73,9 +73,12 @@ fn main() { // neither blueprint-compiler nor glib-compile-resources (Zig: // `GhosttyDist.Resource` for `ghostty_resources.{c,h}`). As in Zig, it is // never used in a git checkout. - println!("cargo:rerun-if-changed=dist/ghostty.gresource"); let prebuilt = manifest.join("dist/ghostty.gresource"); if prebuilt.is_file() && !manifest.join("../../.git").exists() { + // Watch the bundle only when it is an input. In a checkout it is + // normally absent, and watching a missing file forces every build + // to recompile the resources and all dependent crates. + println!("cargo:rerun-if-changed=dist/ghostty.gresource"); std::fs::copy(&prebuilt, out.join("ghostty.gresource")).unwrap(); return; } @@ -119,10 +122,24 @@ fn main() { let xml_path = out.join("ghostty.gresource.xml"); std::fs::write(&xml_path, xml).unwrap(); - glib_build_tools::compile_resources( - &[out.as_path(), res.as_path()], - xml_path.to_str().unwrap(), - "ghostty.gresource", + // glib-build-tools also watches the generated XML and UI files. Their + // timestamps change during this script, invalidating the next build. + // All actual inputs are already covered by the `resources` watch above. + let compiled = Command::new("glib-compile-resources") + .arg("--sourcedir") + .arg(&out) + .arg("--sourcedir") + .arg(&res) + .arg("--target") + .arg(out.join("ghostty.gresource")) + .arg(&xml_path) + .output() + .expect("`glib-compile-resources` is required to build ghostty-apprt-gtk"); + assert!( + compiled.status.success(), + "glib-compile-resources failed with {}:\n{}", + compiled.status, + String::from_utf8_lossy(&compiled.stderr) ); } diff --git a/crates/ghostty-build-info/build.rs b/crates/ghostty-build-info/build.rs index 93a89c0..d13471f 100644 --- a/crates/ghostty-build-info/build.rs +++ b/crates/ghostty-build-info/build.rs @@ -61,10 +61,13 @@ fn detect_version(manifest_dir: &Path) -> (String, Option) { let git_dir = PathBuf::from(git_dir); println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display()); println!("cargo:rerun-if-changed={}", git_dir.join("refs").display()); - println!( - "cargo:rerun-if-changed={}", - git_dir.join("packed-refs").display() - ); + // Missing watched paths make Cargo rerun this script on every build. + // Packing loose refs also changes `refs`, so it will register this + // optional file once Git creates it. + let packed_refs = git_dir.join("packed-refs"); + if packed_refs.is_file() { + println!("cargo:rerun-if-changed={}", packed_refs.display()); + } } let hash = run( "git", diff --git a/docs/rewrite/ARCHITECTURE.md b/docs/rewrite/ARCHITECTURE.md index 72faeea..5862419 100644 --- a/docs/rewrite/ARCHITECTURE.md +++ b/docs/rewrite/ARCHITECTURE.md @@ -70,6 +70,16 @@ ghostty-bench (benchmarks), ghostty-build-data (completions, man pages, syntax). Non-code: `resources/` (shell-integration, themes, terminfo), `dist/` (desktop integration), `po/` (translations), crate-local `res/`/`shaders/`/`resources/`. +Build inputs: `ghostty-build-info/build.rs` derives version metadata from the +workspace manifest and Git HEAD/refs, watching `packed-refs` only when it exists +(packing loose refs also changes the watched `refs` directory). The GTK build +script compiles `resources/` in checkouts and watches the optional +`dist/ghostty.gresource` bundle only when consuming it from a source tarball. +Missing optional files must not be emitted as Cargo `rerun-if-changed` inputs: +they invalidate every subsequent build and rebuild downstream crates. +GTK invokes `glib-compile-resources` directly so generated XML/UI files in +`OUT_DIR` are not watched as inputs; the `resources/` watch covers their sources. + ## Key design decisions 1. **Safe Rust by default.** Workspace lint `unsafe_code = "forbid"`. Crates