fix(os): find in-repo resources from release builds run in the workspace

Testing with a real user config showed that `target/release/ghostty` and
`target/production/ghostty` (also `cargo run --profile production`, which
the README recommends) silently ran without resources: the configured
theme was not applied, bash shell integration was disabled ("no resources
dir set") so tab titles, the prompt cursor and cwd inheritance for new
tabs/splits broke, and terminfo was missing.

Zig never hits this because `zig build` installs `zig-out/share/ghostty`
next to `zig-out/bin`, which the exe-relative sentinel search finds. Cargo
has no such tree; the Rust-specific fallback to `<workspace>/resources`
was limited to the dev profile. It now applies to every profile. It only
triggers when the exe lives inside the workspace it was built from, after
GHOSTTY_RESOURCES_DIR and the share/ sentinel search, so installed
binaries are unaffected.

Test Plan:
- cargo test -p ghostty-os (dev_fallback_finds_workspace_resources now
  covers release builds and env precedence)
- run target/production/ghostty with `theme = Catppuccin Mocha` and
  `shell-integration = bash`: theme applied, titles show the cwd
This commit is contained in:
ddidderr committed 2026-10-12 21:11:39 +02:00
1 parent 908d5b105a
commit 733df84eaf
2 files changed
+15 -11

No files matched your search

+3 -3
View File
@@ -48,9 +48,9 @@ cargo run -- +list-themes # CLI actions work like Ghostty's
cargo run --profile production # optimized build (LTO, no debug assertions) cargo run --profile production # optimized build (LTO, no debug assertions)
``` ```
Dev-profile builds (`cargo run`) find `resources/` (shell integration, Binaries run from this checkout's `target/` (any profile) find `resources/`
themes, terminfo) automatically; other profiles use the installed (shell integration, themes, terminfo) automatically; installed binaries use
resources or `GHOSTTY_RESOURCES_DIR`. Your existing Ghostty config the installed resources or `GHOSTTY_RESOURCES_DIR`. Your existing Ghostty config
(`~/.config/ghostty/config[.ghostty]`) is used as-is. (`~/.config/ghostty/config[.ghostty]`) is used as-is.
## Installing ## Installing
+12 -8
View File
@@ -143,12 +143,12 @@ fn resources_dir_impl(debug: bool, exe: Option<&Path>, env: Option<OsString>) ->
}; };
} }
// Rust-specific: development builds run out of `<workspace>/target`, where // Rust-specific: builds of any profile run out of `<workspace>/target`,
// there is no installed `share/` tree. Fall back to the in-repo // where there is no `share/` tree (Zig installs one into `zig-out/share`
// `<workspace>/resources` directory (it carries the same sentinel, // next to `zig-out/bin`). Fall back to the in-repo `<workspace>/resources`
// `terminfo/x/xterm-ghostty`, see scripts/gen-terminfo.sh) if the binary // directory (it carries the same sentinel, `terminfo/x/xterm-ghostty`, see
// lives inside this workspace. // scripts/gen-terminfo.sh) if the binary lives inside this workspace.
if debug && let Some(dir) = dev_resources_dir(exe_start) { if let Some(dir) = dev_resources_dir(exe_start) {
return ResourcesDir { return ResourcesDir {
app_path: Some(enc(&dir)), app_path: Some(enc(&dir)),
host_path: None, host_path: None,
@@ -261,9 +261,13 @@ mod tests {
// Outside the workspace there is no fallback. // Outside the workspace there is no fallback.
let r = resources_dir_impl(true, Some(Path::new("/nonexistent/bin/x")), None); let r = resources_dir_impl(true, Some(Path::new("/nonexistent/bin/x")), None);
assert_eq!(r.app().map(EncodedOs::as_encoded), None); assert_eq!(r.app().map(EncodedOs::as_encoded), None);
// Release builds never use it. // Release builds run from the workspace use it too...
let exe = workspace_root().join("target/production/ghostty");
let r = resources_dir_impl(false, Some(&exe), None); let r = resources_dir_impl(false, Some(&exe), None);
assert_eq!(r.app().map(EncodedOs::as_encoded), None); assert!(r.app().is_some_and(|d| d.ends_with_plain("/resources")));
// ...but GHOSTTY_RESOURCES_DIR still takes precedence there.
let r = resources_dir_impl(false, Some(&exe), Some("/env/res".into()));
assert_eq!(r.app_os().unwrap(), Path::new("/env/res"));
} }
#[test] #[test]