Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions scripts/build-ghostty-cli-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Options:
--target <triple>
Build a single target, e.g. `aarch64-macos` or `x86_64-macos`.
--output <path> Destination path for the built helper.

Environment:
CMUX_GHOSTTY_HELPER_CACHE_DIR
Override the local helper cache directory.
CMUX_DISABLE_GHOSTTY_HELPER_CACHE=1
Disable cache reads and writes for this invocation.
EOF
}

Expand All @@ -24,6 +30,14 @@ ZIG_REQUIRED="${ZIG_REQUIRED:-$(ghostty_minimum_zig_version "$REPO_ROOT")}"
OUTPUT_PATH=""
TARGET_TRIPLE=""
UNIVERSAL="false"
if [[ -n "${CMUX_GHOSTTY_HELPER_CACHE_DIR:-}" ]]; then
CACHE_ROOT="$CMUX_GHOSTTY_HELPER_CACHE_DIR"
elif [[ -n "${HOME:-}" ]]; then
CACHE_ROOT="$HOME/Library/Caches/cmux/ghostty-cli-helper"
else
CACHE_ROOT=""
fi
CACHE_SCHEMA="ghostty-cli-helper-cache-v1"

zig_binary_arch() {
local zig_path="$1"
Expand All @@ -37,6 +51,78 @@ target_arch_for_triple() {
esac
}

ghostty_cache_is_safe() {
[[ -n "$CACHE_ROOT" ]] || return 1
[[ "${CMUX_DISABLE_GHOSTTY_HELPER_CACHE:-0}" != "1" ]] || return 1
[[ -d "$GHOSTTY_DIR/.git" || -f "$GHOSTTY_DIR/.git" ]] || return 1
# A dirty or caller-owned Ghostty tree must never be substituted by a
# revision-only cache entry. Ignored Zig build output is intentionally fine.
git -C "$GHOSTTY_DIR" diff --quiet HEAD -- . || return 1
[[ -z "$(git -C "$GHOSTTY_DIR" ls-files --others --exclude-standard)" ]] || return 1
return 0
}

ghostty_cache_metadata() {
local zig_bin="$1"
local target="$2"
local effective_target="$3"
local zig_version zig_fingerprint ghostty_sha script_sha sdk_version host_version host_arch
zig_version="$($zig_bin version 2>/dev/null || true)"
zig_fingerprint="$(shasum -a 256 "$zig_bin" 2>/dev/null | awk '{print $1}')"
ghostty_sha="$(git -C "$GHOSTTY_DIR" rev-parse HEAD)"
script_sha="$(shasum -a 256 "$SCRIPT_DIR/build-ghostty-cli-helper.sh" | awk '{print $1}')"
sdk_version="$(xcrun --sdk macosx --show-sdk-version 2>/dev/null || echo unknown)"
host_version="$(sw_vers -productVersion 2>/dev/null || echo unknown)"
host_arch="$(uname -m)"
cat <<EOF
$CACHE_SCHEMA
ghostty=$ghostty_sha
script=$script_sha
zig_version=$zig_version
zig_sha256=$zig_fingerprint
requested_target=${target:-native}
effective_target=${effective_target:-native}
sdk=$sdk_version
macos=$host_version
host_arch=$host_arch
EOF
}

ghostty_cache_install_if_valid() {
local cache_bin="$1"
local cache_manifest="$2"
local metadata="$3"
local prefix="$4"
local cached_metadata cached_sha expected_sha
[[ -x "$cache_bin" && -f "$cache_manifest" ]] || return 1
cached_metadata="$(sed '/^binary_sha256=/d' "$cache_manifest" 2>/dev/null || true)"
[[ "$cached_metadata" == "$metadata" ]] || return 1
cached_sha="$(sed -n 's/^binary_sha256=//p' "$cache_manifest" 2>/dev/null || true)"
expected_sha="$(shasum -a 256 "$cache_bin" 2>/dev/null | awk '{print $1}')"
[[ -n "$cached_sha" && "$cached_sha" == "$expected_sha" ]] || return 1
mkdir -p "$prefix/bin"
install -m 755 "$cache_bin" "$prefix/bin/ghostty"
echo "Reusing cached Ghostty CLI helper"
return 0
}

ghostty_cache_publish() {
local cache_dir="$1"
local metadata="$2"
local binary="$3"
local cache_bin="$cache_dir/ghostty"
local cache_manifest="$cache_dir/manifest"
local tmp_binary="$cache_dir/.ghostty.tmp.$$"
local tmp_manifest="$cache_dir/.manifest.tmp.$$"
local binary_sha
mkdir -p "$cache_dir"
install -m 755 "$binary" "$tmp_binary"
mv -f "$tmp_binary" "$cache_bin"
binary_sha="$(shasum -a 256 "$cache_bin" | awk '{print $1}')"
printf '%s\nbinary_sha256=%s' "$metadata" "$binary_sha" > "$tmp_manifest"
mv -f "$tmp_manifest" "$cache_manifest"
}

# Real host arch, accounting for Rosetta where `uname -m` reports x86_64 on
# Apple Silicon. Used so the default single-arch stub targets the true host.
detected_host_arch() {
Expand Down Expand Up @@ -253,6 +339,21 @@ build_helper() {
effective_target=""
fi

local cache_enabled=0
local cache_dir=""
local cache_metadata=""
if ghostty_cache_is_safe; then
cache_enabled=1
cache_metadata="$(ghostty_cache_metadata "$zig_bin" "$target" "$effective_target")"
local cache_key
cache_key="$(printf '%s' "$cache_metadata" | shasum -a 256 | awk '{print $1}')"
cache_dir="$CACHE_ROOT/$cache_key"
if ghostty_cache_install_if_valid \
"$cache_dir/ghostty" "$cache_dir/manifest" "$cache_metadata" "$prefix"; then
return 0
fi
fi

local args=(
"$zig_bin"
build
Expand All @@ -278,6 +379,16 @@ build_helper() {
# leaves build-runner binaries unlinked against libSystem on a cold cache.
env -u SDKROOT "${args[@]}"
)

[[ -x "$prefix/bin/ghostty" ]] || {
echo "error: Zig did not produce a Ghostty CLI helper at $prefix/bin/ghostty" >&2
return 1
}
if [[ "$cache_enabled" -eq 1 ]]; then
if ! ghostty_cache_publish "$cache_dir" "$cache_metadata" "$prefix/bin/ghostty"; then
echo "warning: unable to publish Ghostty CLI helper cache; continuing with built helper" >&2
fi
fi
}

TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-ghostty-helper.XXXXXX")"
Expand Down
88 changes: 88 additions & 0 deletions tests/test_ghostty_cli_helper_cache.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cmux-ghostty-helper-cache-test.XXXXXX")"
trap 'rm -rf "$TMP_DIR"' EXIT

FAKE_ZIG="$TMP_DIR/zig"
cat > "$FAKE_ZIG" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" == "version" ]]; then
echo "0.16.0"
exit 0
fi
if [[ "${1:-}" == "build" ]]; then
prefix=""
previous=""
for arg in "$@"; do
if [[ "$previous" == "--prefix" ]]; then
prefix="$arg"
break
fi
previous="$arg"
done
[[ -n "$prefix" ]] || { echo "missing --prefix" >&2; exit 1; }
mkdir -p "$prefix/bin"
printf '#!/usr/bin/env bash\necho fake ghostty helper\n' > "$prefix/bin/ghostty"
chmod +x "$prefix/bin/ghostty"
exit 0
fi
echo "unsupported fake zig invocation" >&2
exit 1
EOF
chmod +x "$FAKE_ZIG"

CACHE_DIR="$TMP_DIR/cache"
FIRST="$TMP_DIR/first"
SECOND="$TMP_DIR/second"
THIRD="$TMP_DIR/third"
FOURTH="$TMP_DIR/fourth"
FIFTH="$TMP_DIR/fifth"
SIXTH="$TMP_DIR/sixth"

CMUX_ZIG="$FAKE_ZIG" \
CMUX_GHOSTTY_HELPER_CACHE_DIR="$CACHE_DIR" \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$FIRST" >"$TMP_DIR/first.log"
CMUX_ZIG="$FAKE_ZIG" \
CMUX_GHOSTTY_HELPER_CACHE_DIR="$CACHE_DIR" \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$SECOND" >"$TMP_DIR/second.log"

grep -q 'Building Ghostty CLI helper' "$TMP_DIR/first.log"
grep -q 'Reusing cached Ghostty CLI helper' "$TMP_DIR/second.log"
cmp -s "$FIRST" "$SECOND"

cached_helper="$(find "$CACHE_DIR" -type f -name ghostty -print -quit)"
[[ -n "$cached_helper" ]]
printf 'tampered\n' >> "$cached_helper"
CMUX_ZIG="$FAKE_ZIG" \
CMUX_GHOSTTY_HELPER_CACHE_DIR="$CACHE_DIR" \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$FOURTH" >"$TMP_DIR/fourth.log"
grep -q 'Building Ghostty CLI helper' "$TMP_DIR/fourth.log"
cmp -s "$FIRST" "$FOURTH"

CMUX_ZIG="$FAKE_ZIG" \
CMUX_GHOSTTY_HELPER_CACHE_DIR="$CACHE_DIR" \
CMUX_DISABLE_GHOSTTY_HELPER_CACHE=1 \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$THIRD" >"$TMP_DIR/third.log"
grep -q 'Building Ghostty CLI helper' "$TMP_DIR/third.log"
cmp -s "$FIRST" "$THIRD"

env -u HOME -u CMUX_GHOSTTY_HELPER_CACHE_DIR \
CMUX_ZIG="$FAKE_ZIG" \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$FIFTH" >"$TMP_DIR/fifth.log"
env -u HOME -u CMUX_GHOSTTY_HELPER_CACHE_DIR \
CMUX_ZIG="$FAKE_ZIG" \
"$ROOT_DIR/scripts/build-ghostty-cli-helper.sh" \
--target aarch64-macos --output "$SIXTH" >"$TMP_DIR/sixth.log"
grep -q 'Building Ghostty CLI helper' "$TMP_DIR/fifth.log"
grep -q 'Building Ghostty CLI helper' "$TMP_DIR/sixth.log"
cmp -s "$FIFTH" "$SIXTH"

echo "PASS: Ghostty CLI helper cache reuses matching builds, rejects tampering, and disables safely"
2 changes: 2 additions & 0 deletions tests/test_ghostty_zig_version_sync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ python3 \
--require-setup-zig \
"$ROOT_DIR/.github/workflows"

"$ROOT_DIR/tests/test_ghostty_cli_helper_cache.sh"

if ! grep -Fq 'source "$SCRIPT_DIR/ghostty-zig-version.sh"' "$ROOT_DIR/scripts/setup.sh" ||
! grep -Fq 'ghostty_minimum_zig_version "$PROJECT_DIR"' "$ROOT_DIR/scripts/setup.sh" ||
! grep -Fq 'ZIG_ACTUAL="$(zig version)"' "$ROOT_DIR/scripts/setup.sh" ||
Expand Down
Loading