初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)
- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连 - M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire - M1.5: tmux -CC 原生 tab(MVP) - 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/ - 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口) - 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名 - 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
106
vendor/libghostty-spm/Script/apply-patches.sh
vendored
Executable file
106
vendor/libghostty-spm/Script/apply-patches.sh
vendored
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/bin/zsh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[-] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR=${1:-}
|
||||
PATCH_DIR=${2:-"$(pwd)/Patches/ghostty"}
|
||||
|
||||
if [ -z "$SOURCE_DIR" ]; then
|
||||
echo "Usage: $0 <source_dir> [patch_dir]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "[-] ghostty source directory not found: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$PATCH_DIR" ]; then
|
||||
echo "[+] no patches directory found: $PATCH_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
apply_unified_patch() {
|
||||
local patch_file="$1"
|
||||
|
||||
if [ -d "$SOURCE_DIR/.git" ] && command -v git >/dev/null 2>&1; then
|
||||
if git -C "$SOURCE_DIR" apply --check --reverse "$patch_file" >/dev/null 2>&1; then
|
||||
echo "[+] patch already applied: $(basename "$patch_file")"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! git -C "$SOURCE_DIR" apply --check "$patch_file" >/dev/null 2>&1; then
|
||||
echo "[-] failed to validate patch: $patch_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git -C "$SOURCE_DIR" apply "$patch_file"
|
||||
echo "[+] applied patch: $(basename "$patch_file")"
|
||||
return
|
||||
fi
|
||||
|
||||
if patch -p1 -R --dry-run -d "$SOURCE_DIR" <"$patch_file" >/dev/null 2>&1; then
|
||||
echo "[+] patch already applied: $(basename "$patch_file")"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! patch -p1 --dry-run -d "$SOURCE_DIR" <"$patch_file" >/dev/null 2>&1; then
|
||||
echo "[-] failed to validate patch: $patch_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
patch -p1 -d "$SOURCE_DIR" <"$patch_file" >/dev/null
|
||||
echo "[+] applied patch: $(basename "$patch_file")"
|
||||
}
|
||||
|
||||
modern_host_io=false
|
||||
if grep -q "ghostty_surface_foreground_pid" "$SOURCE_DIR/include/ghostty.h"; then
|
||||
modern_host_io=true
|
||||
fi
|
||||
|
||||
host_io_applied=false
|
||||
if grep -q "GHOSTTY_SURFACE_IO_BACKEND_HOST_MANAGED" "$SOURCE_DIR/include/ghostty.h"; then
|
||||
host_io_applied=true
|
||||
fi
|
||||
|
||||
for patch_file in "$PATCH_DIR"/*; do
|
||||
[ -e "$patch_file" ] || continue
|
||||
|
||||
patch_name=$(basename "$patch_file")
|
||||
case "$patch_name" in
|
||||
0002-host-managed-io.patch)
|
||||
[ "$modern_host_io" = false ] || continue
|
||||
if [ "$host_io_applied" = true ]; then
|
||||
echo "[+] patch already applied: $patch_name"
|
||||
continue
|
||||
fi
|
||||
apply_unified_patch "$patch_file"
|
||||
;;
|
||||
0002-host-managed-io-modern.patch)
|
||||
[ "$modern_host_io" = true ] || continue
|
||||
if [ "$host_io_applied" = true ]; then
|
||||
echo "[+] patch already applied: $patch_name"
|
||||
continue
|
||||
fi
|
||||
apply_unified_patch "$patch_file"
|
||||
;;
|
||||
*.md) ;;
|
||||
*.patch)
|
||||
apply_unified_patch "$patch_file"
|
||||
;;
|
||||
*.sh)
|
||||
"$patch_file" "$SOURCE_DIR"
|
||||
;;
|
||||
*)
|
||||
echo "[-] unsupported patch file: $patch_file"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
273
vendor/libghostty-spm/Script/build-ghostty.sh
vendored
Executable file
273
vendor/libghostty-spm/Script/build-ghostty.sh
vendored
Executable file
@@ -0,0 +1,273 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ROOT_DIR=$(pwd)
|
||||
SOURCE_DIR=${1:-}
|
||||
ZIG_TARGET=${2:-}
|
||||
OUTPUT_DIR=${3:-}
|
||||
ZIG_CPU=${ZIG_CPU:-}
|
||||
ZIG_BUILD_EXTRA_ARGS=${ZIG_BUILD_EXTRA_ARGS:-}
|
||||
|
||||
if [ -z "$SOURCE_DIR" ] || [ -z "$ZIG_TARGET" ] || [ -z "$OUTPUT_DIR" ]; then
|
||||
echo "Usage: $0 <source_dir> <zig_target> <output_dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "[!] ghostty source directory not found: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$SOURCE_DIR/include/ghostty.h" ]; then
|
||||
echo "[!] ghostty header not found: $SOURCE_DIR/include/ghostty.h"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v zig >/dev/null 2>&1; then
|
||||
echo "[!] zig not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REQUESTED_TARGET="$ZIG_TARGET"
|
||||
IS_ARM64E=0
|
||||
APPLE_SDK=
|
||||
APPLE_TARGET=
|
||||
|
||||
case "$REQUESTED_TARGET" in
|
||||
arm64e-macos)
|
||||
IS_ARM64E=1
|
||||
ZIG_TARGET="aarch64-macos"
|
||||
APPLE_SDK="macosx"
|
||||
APPLE_TARGET="arm64e-apple-macosx13.0"
|
||||
;;
|
||||
arm64e-ios)
|
||||
IS_ARM64E=1
|
||||
ZIG_TARGET="aarch64-ios"
|
||||
APPLE_SDK="iphoneos"
|
||||
APPLE_TARGET="arm64e-apple-ios15.0"
|
||||
;;
|
||||
arm64e-ios-macabi)
|
||||
IS_ARM64E=1
|
||||
ZIG_TARGET="aarch64-ios-macabi"
|
||||
APPLE_SDK="macosx"
|
||||
APPLE_TARGET="arm64e-apple-ios15.0-macabi"
|
||||
;;
|
||||
esac
|
||||
|
||||
./Script/apply-patches.sh "$SOURCE_DIR"
|
||||
|
||||
CACHE_ROOT="${BUILD_CACHE_ROOT:-$ROOT_DIR/build/cache}"
|
||||
GLOBAL_CACHE_DIR="${ZIG_GLOBAL_CACHE_DIR:-$CACHE_ROOT/zig-global}"
|
||||
LOCAL_CACHE_DIR="$CACHE_ROOT/$REQUESTED_TARGET/zig-local"
|
||||
MODULE_CACHE_DIR="${CLANG_MODULE_CACHE_ROOT:-$CACHE_ROOT/clang-module-cache}/$REQUESTED_TARGET"
|
||||
|
||||
if [ "$IS_ARM64E" -eq 1 ]; then
|
||||
if ! command -v xcrun >/dev/null 2>&1; then
|
||||
echo "[!] xcrun not found for arm64e build"
|
||||
exit 1
|
||||
fi
|
||||
./Script/prepare-arm64e-source.sh "$SOURCE_DIR" "$GLOBAL_CACHE_DIR"
|
||||
fi
|
||||
|
||||
echo "[*] building ghostty static library"
|
||||
echo " target: $REQUESTED_TARGET"
|
||||
echo " source: $SOURCE_DIR"
|
||||
echo " output: $OUTPUT_DIR"
|
||||
|
||||
rm -rf "$OUTPUT_DIR" "$LOCAL_CACHE_DIR" "$MODULE_CACHE_DIR"
|
||||
mkdir -p \
|
||||
"$OUTPUT_DIR/lib" \
|
||||
"$OUTPUT_DIR/include" \
|
||||
"$GLOBAL_CACHE_DIR" \
|
||||
"$LOCAL_CACHE_DIR" \
|
||||
"$MODULE_CACHE_DIR"
|
||||
|
||||
rm -rf "$SOURCE_DIR/zig-out"
|
||||
|
||||
ZIG_BUILD_COMMAND=(
|
||||
zig build
|
||||
-Doptimize=${ZIG_OPTIMIZE:-ReleaseFast}
|
||||
-Dapp-runtime=none
|
||||
-Demit-exe=false
|
||||
-Demit-xcframework=false
|
||||
-Demit-macos-app=false
|
||||
-Demit-docs=false
|
||||
-Dsentry=false
|
||||
-Dcustom-shaders=false
|
||||
-Dinspector=false
|
||||
-Dtarget="$ZIG_TARGET"
|
||||
)
|
||||
|
||||
if [ -n "$ZIG_CPU" ]; then
|
||||
ZIG_BUILD_COMMAND+=("-Dcpu=$ZIG_CPU")
|
||||
fi
|
||||
|
||||
if [ -n "$ZIG_BUILD_EXTRA_ARGS" ]; then
|
||||
# shellcheck disable=SC2206
|
||||
EXTRA_ARGS=($ZIG_BUILD_EXTRA_ARGS)
|
||||
ZIG_BUILD_COMMAND+=("${EXTRA_ARGS[@]}")
|
||||
fi
|
||||
|
||||
(
|
||||
cd "$SOURCE_DIR"
|
||||
env \
|
||||
CLANG_MODULE_CACHE_PATH="$MODULE_CACHE_DIR" \
|
||||
ZIG_GLOBAL_CACHE_DIR="$GLOBAL_CACHE_DIR" \
|
||||
ZIG_LOCAL_CACHE_DIR="$LOCAL_CACHE_DIR" \
|
||||
"${ZIG_BUILD_COMMAND[@]}"
|
||||
)
|
||||
|
||||
find_built_library() {
|
||||
local preferred_name="$1"
|
||||
find "$LOCAL_CACHE_DIR/o" -type f -name "$preferred_name" -print 2>/dev/null | sort | tail -n 1
|
||||
}
|
||||
|
||||
LIBRARY_PATH=
|
||||
|
||||
if [ -f "$SOURCE_DIR/zig-out/lib/libghostty.a" ]; then
|
||||
LIBRARY_PATH="$SOURCE_DIR/zig-out/lib/libghostty.a"
|
||||
fi
|
||||
|
||||
if [ -z "$LIBRARY_PATH" ]; then
|
||||
LIBRARY_PATH=$(find_built_library "libghostty-fat.a")
|
||||
fi
|
||||
|
||||
if [ -z "$LIBRARY_PATH" ]; then
|
||||
LIBRARY_PATH=$(find_built_library "libghostty.a")
|
||||
fi
|
||||
|
||||
if [ -z "$LIBRARY_PATH" ]; then
|
||||
echo "[!] failed to locate built libghostty archive in $LOCAL_CACHE_DIR"
|
||||
if [[ "$ZIG_TARGET" == *macos* || "$ZIG_TARGET" == *ios* || "$ZIG_TARGET" == *tvos* || "$ZIG_TARGET" == *visionos* || "$ZIG_TARGET" == *watchos* ]]; then
|
||||
echo "[!] note: upstream Ghostty does not install Darwin libghostty for app-runtime=none unless extra build wiring is triggered"
|
||||
echo "[!] try again with ZIG_BUILD_EXTRA_ARGS='-Demit-xcframework=true' if you want to force Darwin libghostty build graph execution"
|
||||
fi
|
||||
find "$LOCAL_CACHE_DIR" -maxdepth 3 -type f | sort | tail -n 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resolve std::__1::__libcpp_verbose_abort inside the archive: the Apple
|
||||
# system libc++ only exports it since iOS 16.3 / macOS 13.3 / tvOS 16.3, and
|
||||
# Zig's bundled libc++ headers reference it strongly (no availability markup),
|
||||
# which crashes consumers at launch on older OS versions.
|
||||
COMPAT_SOURCE="$ROOT_DIR/Script/support/libcxx-verbose-abort-compat.c"
|
||||
COMPAT_OBJECT="$LOCAL_CACHE_DIR/libcxx-verbose-abort-compat.o"
|
||||
zig cc -target "$ZIG_TARGET" -Os -fno-sanitize=undefined -c "$COMPAT_SOURCE" -o "$COMPAT_OBJECT"
|
||||
|
||||
if [ "$IS_ARM64E" -eq 1 ]; then
|
||||
RAW_ARCHIVE="$LOCAL_CACHE_DIR/libghostty-arm64.a"
|
||||
CONVERTED_ARCHIVE="$LOCAL_CACHE_DIR/libghostty-arm64e.a"
|
||||
CORE_OBJECT="$LOCAL_CACHE_DIR/libghostty-arm64e-core.o"
|
||||
ARM64E_COMPAT_OBJECT="$LOCAL_CACHE_DIR/arm64e-compat.o"
|
||||
|
||||
xcrun libtool -static -no_warning_for_no_symbols -o "$RAW_ARCHIVE" "$LIBRARY_PATH" "$COMPAT_OBJECT"
|
||||
python3 "$ROOT_DIR/Script/convert-archive-arm64e.py" "$RAW_ARCHIVE" "$CONVERTED_ARCHIVE"
|
||||
|
||||
xcrun --sdk "$APPLE_SDK" clang \
|
||||
-target "$APPLE_TARGET" \
|
||||
-r \
|
||||
-nostdlib \
|
||||
"-Wl,-force_load,$CONVERTED_ARCHIVE" \
|
||||
-Wl,-alias,_ghostty_app_new,_ghostty_arm64e_core_app_new \
|
||||
-Wl,-unexported_symbol,_ghostty_app_new \
|
||||
-Wl,-alias,_ghostty_surface_new,_ghostty_arm64e_core_surface_new \
|
||||
-Wl,-unexported_symbol,_ghostty_surface_new \
|
||||
-o "$CORE_OBJECT"
|
||||
|
||||
xcrun --sdk "$APPLE_SDK" clang \
|
||||
-target "$APPLE_TARGET" \
|
||||
-Os \
|
||||
-fno-sanitize=undefined \
|
||||
-I "$SOURCE_DIR/include" \
|
||||
-c "$ROOT_DIR/Script/support/arm64e-compat.c" \
|
||||
-o "$ARM64E_COMPAT_OBJECT"
|
||||
|
||||
xcrun libtool -static -no_warning_for_no_symbols \
|
||||
-o "$OUTPUT_DIR/lib/libghostty.a" \
|
||||
"$CORE_OBJECT" \
|
||||
"$ARM64E_COMPAT_OBJECT"
|
||||
|
||||
if [ "$(lipo -archs "$OUTPUT_DIR/lib/libghostty.a")" != "arm64e" ]; then
|
||||
echo "[!] built archive is not a single arm64e slice"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SYMBOLS=$(nm -gjU "$OUTPUT_DIR/lib/libghostty.a" | sort -u)
|
||||
for symbol in \
|
||||
_ghostty_app_new \
|
||||
_ghostty_surface_new \
|
||||
_ghostty_arm64e_core_app_new \
|
||||
_ghostty_arm64e_core_surface_new \
|
||||
_pa_pthread_cr \
|
||||
_pa_qs; do
|
||||
if ! grep -Fxq "$symbol" <<<"$SYMBOLS"; then
|
||||
echo "[!] arm64e archive missing symbol: $symbol"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CORE_IMPORTS=$(nm -u "$CORE_OBJECT" | sort -u)
|
||||
for symbol in _pa_pthread_cr _pa_qs; do
|
||||
if ! grep -Fxq "$symbol" <<<"$CORE_IMPORTS"; then
|
||||
echo "[!] arm64e core missing redirected import: $symbol"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
for symbol in _pthread_create _qsort; do
|
||||
if grep -Fxq "$symbol" <<<"$CORE_IMPORTS"; then
|
||||
echo "[!] arm64e core contains unsigned callback import: $symbol"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "[*] built pointer-authenticated arm64e archive"
|
||||
else
|
||||
ARM64E_COMPAT_OBJECT="$LOCAL_CACHE_DIR/arm64e-compat.o"
|
||||
case "$ZIG_TARGET" in
|
||||
*-ios-simulator) COMPAT_SDK="iphonesimulator" ;;
|
||||
*-ios-macabi | *-macos) COMPAT_SDK="macosx" ;;
|
||||
*-ios) COMPAT_SDK="iphoneos" ;;
|
||||
*-tvos-simulator) COMPAT_SDK="appletvsimulator" ;;
|
||||
*-tvos) COMPAT_SDK="appletvos" ;;
|
||||
*-visionos-simulator) COMPAT_SDK="xrsimulator" ;;
|
||||
*-visionos) COMPAT_SDK="xros" ;;
|
||||
*-watchos-simulator) COMPAT_SDK="watchsimulator" ;;
|
||||
*-watchos) COMPAT_SDK="watchos" ;;
|
||||
*)
|
||||
echo "[!] unsupported compatibility target: $ZIG_TARGET"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
COMPAT_SDK_PATH=$(xcrun --sdk "$COMPAT_SDK" --show-sdk-path)
|
||||
zig cc \
|
||||
-target "$ZIG_TARGET" \
|
||||
-Os \
|
||||
-fno-sanitize=undefined \
|
||||
-isystem "$COMPAT_SDK_PATH/usr/include" \
|
||||
-I "$SOURCE_DIR/include" \
|
||||
-c "$ROOT_DIR/Script/support/arm64e-compat.c" \
|
||||
-o "$ARM64E_COMPAT_OBJECT"
|
||||
xcrun libtool -static -no_warning_for_no_symbols \
|
||||
-o "$OUTPUT_DIR/lib/libghostty.a" \
|
||||
"$LIBRARY_PATH" \
|
||||
"$COMPAT_OBJECT" \
|
||||
"$ARM64E_COMPAT_OBJECT"
|
||||
echo "[*] appended compatibility objects"
|
||||
fi
|
||||
|
||||
cp "$SOURCE_DIR/include/ghostty.h" "$OUTPUT_DIR/include/ghostty.h"
|
||||
cat >"$OUTPUT_DIR/include/module.modulemap" <<'EOF'
|
||||
module libghostty {
|
||||
umbrella header "ghostty.h"
|
||||
export *
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[*] built archive: $OUTPUT_DIR/lib/libghostty.a"
|
||||
32
vendor/libghostty-spm/Script/build-manifest.sh
vendored
Executable file
32
vendor/libghostty-spm/Script/build-manifest.sh
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCFRAMEWORK_PATH_ZIP=${1:-}
|
||||
DOWNLOAD_URL=${2:-}
|
||||
|
||||
if [ -z "$XCFRAMEWORK_PATH_ZIP" ] || [ -z "$DOWNLOAD_URL" ]; then
|
||||
echo "Usage: $0 <xcframework_zip> <download_url>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$XCFRAMEWORK_PATH_ZIP" ]; then
|
||||
echo "[!] $XCFRAMEWORK_PATH_ZIP not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SHA256SUM=$(shasum -a 256 "$XCFRAMEWORK_PATH_ZIP" | awk '{print $1}')
|
||||
echo "[*] SHA256: $SHA256SUM"
|
||||
|
||||
PACKAGE_MANIFEST=$(cat Package.swift.template)
|
||||
PACKAGE_MANIFEST=${PACKAGE_MANIFEST/__DOWNLOAD_URL__/$DOWNLOAD_URL}
|
||||
PACKAGE_MANIFEST=${PACKAGE_MANIFEST/__CHECKSUM__/$SHA256SUM}
|
||||
|
||||
echo "$PACKAGE_MANIFEST" >Package.swift
|
||||
126
vendor/libghostty-spm/Script/build-platform.sh
vendored
Executable file
126
vendor/libghostty-spm/Script/build-platform.sh
vendored
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR=${1:-}
|
||||
PLATFORM_GROUP=${2:-}
|
||||
OUTPUT_DIR=${3:-}
|
||||
|
||||
if [ -z "$SOURCE_DIR" ] || [ -z "$PLATFORM_GROUP" ] || [ -z "$OUTPUT_DIR" ]; then
|
||||
echo "Usage: $0 <source_dir> <platform_group> <output_dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "[!] ghostty source directory not found: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_variant() {
|
||||
local variant_name="$1"
|
||||
shift
|
||||
|
||||
local variant_dir="$OUTPUT_DIR/$variant_name"
|
||||
local intermediate_dir="$OUTPUT_DIR/.intermediates/$variant_name"
|
||||
local first_headers=
|
||||
local libraries=()
|
||||
local target_spec=
|
||||
local target=
|
||||
local cpu=
|
||||
|
||||
rm -rf "$variant_dir" "$intermediate_dir"
|
||||
mkdir -p "$variant_dir/lib" "$variant_dir/include" "$intermediate_dir"
|
||||
|
||||
for target_spec in "$@"; do
|
||||
target=${target_spec%%@*}
|
||||
cpu=
|
||||
if [ "$target_spec" != "$target" ]; then
|
||||
cpu=${target_spec#*@}
|
||||
fi
|
||||
|
||||
local target_dir="$intermediate_dir/$target"
|
||||
if [ -n "$cpu" ]; then
|
||||
env ZIG_CPU="$cpu" ./Script/build-ghostty.sh "$SOURCE_DIR" "$target" "$target_dir"
|
||||
else
|
||||
./Script/build-ghostty.sh "$SOURCE_DIR" "$target" "$target_dir"
|
||||
fi
|
||||
libraries+=("$target_dir/lib/libghostty.a")
|
||||
if [ -z "$first_headers" ]; then
|
||||
first_headers="$target_dir/include"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$first_headers" ]; then
|
||||
echo "[!] no libraries were built for variant: $variant_name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -R "$first_headers/." "$variant_dir/include/"
|
||||
|
||||
if [ "${#libraries[@]}" -eq 1 ]; then
|
||||
cp "${libraries[0]}" "$variant_dir/lib/libghostty.a"
|
||||
else
|
||||
lipo -create "${libraries[@]}" -output "$variant_dir/lib/libghostty.a"
|
||||
fi
|
||||
|
||||
echo "[*] assembled variant: $variant_name"
|
||||
}
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
case "$PLATFORM_GROUP" in
|
||||
macos)
|
||||
build_variant "macosx" \
|
||||
"aarch64-macos" \
|
||||
"arm64e-macos@apple_a12" \
|
||||
"x86_64-macos"
|
||||
;;
|
||||
ios)
|
||||
build_variant "iphoneos" \
|
||||
"aarch64-ios" \
|
||||
"arm64e-ios@apple_a12"
|
||||
build_variant "iphonesimulator" \
|
||||
"aarch64-ios-simulator@apple_a17" \
|
||||
"x86_64-ios-simulator"
|
||||
;;
|
||||
maccatalyst)
|
||||
build_variant "maccatalyst" \
|
||||
"aarch64-ios-macabi@apple_a17" \
|
||||
"arm64e-ios-macabi@apple_a17" \
|
||||
"x86_64-ios-macabi"
|
||||
;;
|
||||
tvos)
|
||||
build_variant "appletvos" \
|
||||
"aarch64-tvos"
|
||||
build_variant "appletvsimulator" \
|
||||
"aarch64-tvos-simulator@apple_a17" \
|
||||
"x86_64-tvos-simulator"
|
||||
;;
|
||||
visionos)
|
||||
build_variant "xros" \
|
||||
"aarch64-visionos"
|
||||
build_variant "xrsimulator" \
|
||||
"aarch64-visionos-simulator@apple_a17" \
|
||||
"x86_64-visionos-simulator"
|
||||
;;
|
||||
watchos)
|
||||
build_variant "watchos" \
|
||||
"aarch64-watchos"
|
||||
build_variant "watchsimulator" \
|
||||
"aarch64-watchos-simulator@apple_a17" \
|
||||
"x86_64-watchos-simulator"
|
||||
;;
|
||||
*)
|
||||
echo "[!] unknown platform group: $PLATFORM_GROUP"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "[*] built platform group: $PLATFORM_GROUP"
|
||||
130
vendor/libghostty-spm/Script/build.sh
vendored
Executable file
130
vendor/libghostty-spm/Script/build.sh
vendored
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./build.sh [options]
|
||||
|
||||
Options:
|
||||
--source <path> Use an existing Ghostty checkout.
|
||||
--ref <tag-or-commit> Checkout the given ref in the source checkout.
|
||||
--platforms <csv> Build platform groups. Default:
|
||||
macos,ios,maccatalyst
|
||||
--download-url <url> Generate Package.swift from Package.swift.template.
|
||||
--skip-tests Skip local xcodebuild and swift test verification.
|
||||
-h, --help Show this help.
|
||||
|
||||
Notes:
|
||||
- Default source path is ./References/ghostty-upstream
|
||||
- This builds real per-target static archives, then assembles
|
||||
BinaryTarget/GhosttyKit.xcframework and build/GhosttyKit.xcframework.zip
|
||||
- Upstream Ghostty patches from ./Patches/ghostty are applied automatically
|
||||
- Current verified groups: macos, ios, maccatalyst
|
||||
- Current upstream Ghostty crashes for: tvos, visionos, watchos
|
||||
EOF
|
||||
}
|
||||
|
||||
ROOT_DIR=$(pwd)
|
||||
SOURCE_DIR="$ROOT_DIR/References/ghostty-upstream"
|
||||
PLATFORMS="macos,ios,maccatalyst"
|
||||
DOWNLOAD_URL=${DOWNLOAD_URL:-}
|
||||
GHOSTTY_REF=
|
||||
SKIP_TESTS=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--source)
|
||||
SOURCE_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--ref)
|
||||
GHOSTTY_REF="$2"
|
||||
shift 2
|
||||
;;
|
||||
--platforms)
|
||||
PLATFORMS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--download-url)
|
||||
DOWNLOAD_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--skip-tests)
|
||||
SKIP_TESTS=1
|
||||
shift
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[!] unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ! command -v zig >/dev/null 2>&1; then
|
||||
echo "[!] zig not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "[*] ghostty source not found, cloning into $SOURCE_DIR"
|
||||
mkdir -p "$(dirname "$SOURCE_DIR")"
|
||||
git clone https://github.com/ghostty-org/ghostty "$SOURCE_DIR"
|
||||
fi
|
||||
|
||||
if [ -n "$GHOSTTY_REF" ]; then
|
||||
echo "[*] checking out ghostty ref: $GHOSTTY_REF"
|
||||
git -C "$SOURCE_DIR" fetch --tags origin
|
||||
git -C "$SOURCE_DIR" checkout "$GHOSTTY_REF"
|
||||
fi
|
||||
|
||||
ARTIFACTS_DIR="$ROOT_DIR/build/artifacts"
|
||||
XCFRAMEWORK_PATH="$ROOT_DIR/BinaryTarget/GhosttyKit.xcframework"
|
||||
XCFRAMEWORK_ZIP="$ROOT_DIR/build/GhosttyKit.xcframework.zip"
|
||||
|
||||
rm -rf "$ARTIFACTS_DIR" "$XCFRAMEWORK_PATH" "$XCFRAMEWORK_ZIP"
|
||||
mkdir -p "$ARTIFACTS_DIR" "$(dirname "$XCFRAMEWORK_PATH")"
|
||||
|
||||
echo "[*] zig version: $(zig version)"
|
||||
echo "[*] ghostty source: $SOURCE_DIR"
|
||||
echo "[*] platform groups: $PLATFORMS"
|
||||
|
||||
OLD_IFS=$IFS
|
||||
IFS=','
|
||||
set -- $PLATFORMS
|
||||
IFS=$OLD_IFS
|
||||
|
||||
for platform_group in "$@"; do
|
||||
platform_group=$(echo "$platform_group" | xargs)
|
||||
[ -n "$platform_group" ] || continue
|
||||
./Script/build-platform.sh "$SOURCE_DIR" "$platform_group" "$ARTIFACTS_DIR"
|
||||
done
|
||||
|
||||
./Script/merge-xcframework.sh \
|
||||
"$ARTIFACTS_DIR" \
|
||||
"$XCFRAMEWORK_PATH" \
|
||||
"$XCFRAMEWORK_ZIP"
|
||||
|
||||
if [ -n "$DOWNLOAD_URL" ]; then
|
||||
./Script/build-manifest.sh "$XCFRAMEWORK_ZIP" "$DOWNLOAD_URL"
|
||||
fi
|
||||
|
||||
if [ "$SKIP_TESTS" -eq 0 ]; then
|
||||
./Script/test.sh
|
||||
swift test
|
||||
fi
|
||||
|
||||
echo "[*] xcframework: $XCFRAMEWORK_PATH"
|
||||
echo "[*] zip: $XCFRAMEWORK_ZIP"
|
||||
194
vendor/libghostty-spm/Script/convert-archive-arm64e.py
vendored
Executable file
194
vendor/libghostty-spm/Script/convert-archive-arm64e.py
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
AR_MAGIC = b"!<arch>\n"
|
||||
AR_HEADER_SIZE = 60
|
||||
MACHO64_LITTLE_ENDIAN_MAGIC = b"\xcf\xfa\xed\xfe"
|
||||
CPU_TYPE_ARM64 = 0x0100000C
|
||||
CPU_SUBTYPE_ARM64_ALL = 0
|
||||
CPU_SUBTYPE_ARM64E = 0x80000002
|
||||
LC_SYMTAB = 0x2
|
||||
N_TYPE_MASK = 0x0E
|
||||
N_UNDF = 0x0
|
||||
NLIST64_SIZE = 16
|
||||
UNDEFINED_SYMBOL_RENAMES = {
|
||||
b"_pthread_create": b"_pa_pthread_cr",
|
||||
b"_qsort": b"_pa_qs",
|
||||
}
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
raise SystemExit(f"[-] {message}")
|
||||
|
||||
|
||||
def parse_decimal(raw: bytes, field: str) -> int:
|
||||
try:
|
||||
return int(raw.decode("ascii").strip())
|
||||
except ValueError:
|
||||
fail(f"invalid {field}: {raw!r}")
|
||||
|
||||
|
||||
def rewrite_undefined_symbols(
|
||||
data: bytearray,
|
||||
object_start: int,
|
||||
member_end: int,
|
||||
) -> dict[bytes, int]:
|
||||
object_size = member_end - object_start
|
||||
if object_size < 32:
|
||||
fail(f"truncated Mach-O header at offset {object_start}")
|
||||
|
||||
command_count, command_bytes = struct.unpack_from("<II", data, object_start + 16)
|
||||
command_start = object_start + 32
|
||||
command_end = command_start + command_bytes
|
||||
if command_end > member_end:
|
||||
fail(f"Mach-O load commands cross member boundary at offset {object_start}")
|
||||
|
||||
symtab = None
|
||||
cursor = command_start
|
||||
for _ in range(command_count):
|
||||
if cursor + 8 > command_end:
|
||||
fail(f"truncated Mach-O load command at offset {cursor}")
|
||||
command, command_size = struct.unpack_from("<II", data, cursor)
|
||||
if command_size < 8 or cursor + command_size > command_end:
|
||||
fail(f"invalid Mach-O load command size at offset {cursor}")
|
||||
if command == LC_SYMTAB:
|
||||
if command_size < 24 or symtab is not None:
|
||||
fail(f"invalid Mach-O symbol table command at offset {cursor}")
|
||||
symtab = struct.unpack_from("<IIII", data, cursor + 8)
|
||||
cursor += command_size
|
||||
|
||||
if cursor != command_end:
|
||||
fail(f"Mach-O load command sizes do not match header at offset {object_start}")
|
||||
if symtab is None:
|
||||
return {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
|
||||
|
||||
symbol_offset, symbol_count, string_offset, string_size = symtab
|
||||
symbol_bytes = symbol_count * NLIST64_SIZE
|
||||
if symbol_offset + symbol_bytes > object_size:
|
||||
fail(f"Mach-O symbol table crosses member boundary at offset {object_start}")
|
||||
if string_offset + string_size > object_size:
|
||||
fail(f"Mach-O string table crosses member boundary at offset {object_start}")
|
||||
|
||||
string_start = object_start + string_offset
|
||||
string_end = string_start + string_size
|
||||
renamed = {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
|
||||
|
||||
for index in range(symbol_count):
|
||||
entry = object_start + symbol_offset + index * NLIST64_SIZE
|
||||
string_index = struct.unpack_from("<I", data, entry)[0]
|
||||
symbol_type = data[entry + 4]
|
||||
if string_index == 0 or symbol_type & N_TYPE_MASK != N_UNDF:
|
||||
continue
|
||||
|
||||
name_start = string_start + string_index
|
||||
if name_start >= string_end:
|
||||
fail(f"Mach-O symbol name crosses string table at offset {entry}")
|
||||
name_end = data.find(0, name_start, string_end)
|
||||
if name_end < 0:
|
||||
fail(f"unterminated Mach-O symbol name at offset {name_start}")
|
||||
|
||||
name = bytes(data[name_start:name_end])
|
||||
replacement = UNDEFINED_SYMBOL_RENAMES.get(name)
|
||||
if replacement is None:
|
||||
continue
|
||||
if len(replacement) > len(name):
|
||||
fail(f"replacement symbol is longer than source: {name!r}")
|
||||
|
||||
replacement_size = len(name) + 1
|
||||
data[name_start : name_start + replacement_size] = replacement.ljust(
|
||||
replacement_size,
|
||||
b"\0",
|
||||
)
|
||||
renamed[name] += 1
|
||||
|
||||
return renamed
|
||||
|
||||
|
||||
def convert_archive(source: pathlib.Path, destination: pathlib.Path) -> int:
|
||||
data = bytearray(source.read_bytes())
|
||||
if not data.startswith(AR_MAGIC):
|
||||
fail(f"not a static archive: {source}")
|
||||
|
||||
cursor = len(AR_MAGIC)
|
||||
patched_objects = 0
|
||||
renamed_symbols = {name: 0 for name in UNDEFINED_SYMBOL_RENAMES}
|
||||
|
||||
while cursor < len(data):
|
||||
if cursor + AR_HEADER_SIZE > len(data):
|
||||
fail(f"truncated archive member header at offset {cursor}")
|
||||
|
||||
header = data[cursor : cursor + AR_HEADER_SIZE]
|
||||
if header[58:60] != b"`\n":
|
||||
fail(f"invalid archive member header at offset {cursor}")
|
||||
|
||||
member_size = parse_decimal(header[48:58], "archive member size")
|
||||
member_start = cursor + AR_HEADER_SIZE
|
||||
member_end = member_start + member_size
|
||||
if member_end > len(data):
|
||||
fail(f"archive member crosses file boundary at offset {cursor}")
|
||||
|
||||
object_start = member_start
|
||||
member_name = header[:16].decode("ascii", errors="replace").strip()
|
||||
if member_name.startswith("#1/"):
|
||||
name_size = parse_decimal(member_name[3:].encode("ascii"), "extended member name size")
|
||||
object_start += name_size
|
||||
if object_start > member_end:
|
||||
fail(f"extended member name crosses member boundary at offset {cursor}")
|
||||
|
||||
if data[object_start : object_start + 4] == MACHO64_LITTLE_ENDIAN_MAGIC:
|
||||
if object_start + 12 > member_end:
|
||||
fail(f"truncated Mach-O header at offset {object_start}")
|
||||
|
||||
cpu_type, cpu_subtype = struct.unpack_from("<II", data, object_start + 4)
|
||||
if cpu_type != CPU_TYPE_ARM64:
|
||||
fail(f"unexpected Mach-O CPU type 0x{cpu_type:08x} at offset {object_start}")
|
||||
if cpu_subtype not in {CPU_SUBTYPE_ARM64_ALL, CPU_SUBTYPE_ARM64E}:
|
||||
fail(f"unexpected ARM64 CPU subtype 0x{cpu_subtype:08x} at offset {object_start}")
|
||||
|
||||
struct.pack_into("<I", data, object_start + 8, CPU_SUBTYPE_ARM64E)
|
||||
member_renames = rewrite_undefined_symbols(data, object_start, member_end)
|
||||
for name, count in member_renames.items():
|
||||
renamed_symbols[name] += count
|
||||
patched_objects += 1
|
||||
|
||||
cursor = member_end + (member_size & 1)
|
||||
|
||||
if cursor != len(data):
|
||||
fail(f"archive alignment crosses file boundary: {source}")
|
||||
if patched_objects == 0:
|
||||
fail(f"archive contains no ARM64 Mach-O objects: {source}")
|
||||
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = destination.with_name(f".{destination.name}.{os.getpid()}.tmp")
|
||||
temporary.write_bytes(data)
|
||||
os.replace(temporary, destination)
|
||||
for name, replacement in UNDEFINED_SYMBOL_RENAMES.items():
|
||||
print(
|
||||
f"[+] redirected {renamed_symbols[name]} {name.decode()} imports "
|
||||
f"to {replacement.decode()}"
|
||||
)
|
||||
return patched_objects
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if len(sys.argv) != 3:
|
||||
fail(f"usage: {sys.argv[0]} <arm64_archive> <arm64e_archive>")
|
||||
|
||||
source = pathlib.Path(sys.argv[1])
|
||||
destination = pathlib.Path(sys.argv[2])
|
||||
if not source.is_file():
|
||||
fail(f"archive not found: {source}")
|
||||
if source.resolve() == destination.resolve():
|
||||
fail("source and destination must differ")
|
||||
|
||||
count = convert_archive(source, destination)
|
||||
print(f"[+] converted {count} Mach-O objects to arm64e: {destination}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
195
vendor/libghostty-spm/Script/generate-themes.sh
vendored
Executable file
195
vendor/libghostty-spm/Script/generate-themes.sh
vendored
Executable file
@@ -0,0 +1,195 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
# Downloads all Ghostty theme files from iTerm2-Color-Schemes and generates
|
||||
# Swift source files for the GhosttyTheme library.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
OUTPUT_DIR="$ROOT_DIR/Sources/GhosttyTheme/Themes"
|
||||
TEMP_DIR="$(mktemp -d)"
|
||||
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Fetch the list of theme files from GitHub API
|
||||
echo "[+] fetching theme list from github api"
|
||||
curl -sL "https://api.github.com/repos/mbadolato/iTerm2-Color-Schemes/contents/ghostty" \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
for item in data:
|
||||
if item['type'] == 'file':
|
||||
print(item['name'])
|
||||
" > "$TEMP_DIR/theme_list.txt"
|
||||
|
||||
THEME_COUNT=$(wc -l < "$TEMP_DIR/theme_list.txt" | tr -d ' ')
|
||||
echo "[+] found $THEME_COUNT themes"
|
||||
|
||||
# Download all themes
|
||||
echo "[+] downloading themes"
|
||||
mkdir -p "$TEMP_DIR/themes"
|
||||
while IFS= read -r name; do
|
||||
encoded_name=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$name'))")
|
||||
curl -sL "https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/ghostty/$encoded_name" \
|
||||
-o "$TEMP_DIR/themes/$name" &
|
||||
# Limit concurrent downloads
|
||||
if (( $(jobs -r | wc -l) >= 20 )); then
|
||||
wait -n
|
||||
fi
|
||||
done < "$TEMP_DIR/theme_list.txt"
|
||||
wait
|
||||
echo "[+] downloaded all themes"
|
||||
|
||||
# Generate Swift files using Python
|
||||
echo "[+] generating swift source files"
|
||||
python3 - "$TEMP_DIR/themes" "$OUTPUT_DIR" "$TEMP_DIR/theme_list.txt" << 'PYEOF'
|
||||
import os, sys, re, string
|
||||
|
||||
themes_dir = sys.argv[1]
|
||||
output_dir = sys.argv[2]
|
||||
list_file = sys.argv[3]
|
||||
|
||||
with open(list_file) as f:
|
||||
theme_names = [line.strip() for line in f if line.strip()]
|
||||
|
||||
def parse_theme(path):
|
||||
result = {}
|
||||
palette = {}
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' not in line:
|
||||
continue
|
||||
key, _, value = line.partition('=')
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key == 'palette':
|
||||
# format: N=#RRGGBB or N=RRGGBB
|
||||
idx_str, _, color = value.partition('=')
|
||||
idx = int(idx_str.strip())
|
||||
color = color.strip().lstrip('#')
|
||||
palette[idx] = color
|
||||
else:
|
||||
# strip # from color values
|
||||
clean_val = value.lstrip('#')
|
||||
result[key] = clean_val
|
||||
result['_palette'] = palette
|
||||
return result
|
||||
|
||||
def swift_identifier(name):
|
||||
"""Convert theme name to a valid Swift identifier."""
|
||||
# Replace + with Plus before stripping
|
||||
ident = name.replace('+', 'Plus')
|
||||
# Replace non-alphanumeric with underscore
|
||||
ident = re.sub(r'[^a-zA-Z0-9]', '_', ident)
|
||||
# Collapse multiple underscores
|
||||
ident = re.sub(r'_+', '_', ident)
|
||||
# Strip leading/trailing underscores
|
||||
ident = ident.strip('_')
|
||||
# Prefix with underscore if starts with digit
|
||||
if ident and ident[0].isdigit():
|
||||
ident = '_' + ident
|
||||
# camelCase: lowercase first char
|
||||
if ident:
|
||||
ident = ident[0].lower() + ident[1:]
|
||||
return ident
|
||||
|
||||
def swift_string(s):
|
||||
return '"' + s.replace('\\', '\\\\').replace('"', '\\"') + '"'
|
||||
|
||||
def optional_field(data, ghostty_key, swift_type="String"):
|
||||
val = data.get(ghostty_key)
|
||||
if val:
|
||||
return swift_string(val)
|
||||
return "nil"
|
||||
|
||||
# Group themes by first letter
|
||||
groups = {}
|
||||
all_themes = []
|
||||
|
||||
for name in sorted(theme_names, key=str.lower):
|
||||
path = os.path.join(themes_dir, name)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
try:
|
||||
data = parse_theme(path)
|
||||
except Exception as e:
|
||||
print(f" [-] skipping {name}: {e}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
bg = data.get('background')
|
||||
fg = data.get('foreground')
|
||||
if not bg or not fg:
|
||||
print(f" [-] skipping {name}: missing background/foreground", file=sys.stderr)
|
||||
continue
|
||||
|
||||
ident = swift_identifier(name)
|
||||
first_char = name[0].upper()
|
||||
if first_char not in string.ascii_uppercase:
|
||||
first_char = 'Symbols'
|
||||
|
||||
palette = data.get('_palette', {})
|
||||
palette_str = '['
|
||||
for i in sorted(palette.keys()):
|
||||
palette_str += f'{i}: {swift_string(palette[i])}, '
|
||||
if palette_str.endswith(', '):
|
||||
palette_str = palette_str[:-2]
|
||||
palette_str += ']'
|
||||
|
||||
entry = {
|
||||
'name': name,
|
||||
'ident': ident,
|
||||
'background': swift_string(bg),
|
||||
'foreground': swift_string(fg),
|
||||
'cursorColor': optional_field(data, 'cursor-color'),
|
||||
'cursorText': optional_field(data, 'cursor-text'),
|
||||
'selectionBackground': optional_field(data, 'selection-background'),
|
||||
'selectionForeground': optional_field(data, 'selection-foreground'),
|
||||
'palette': palette_str,
|
||||
'group': first_char,
|
||||
}
|
||||
|
||||
groups.setdefault(first_char, []).append(entry)
|
||||
all_themes.append(entry)
|
||||
|
||||
# Write per-group Swift files
|
||||
for group, entries in sorted(groups.items()):
|
||||
filename = f"Themes_{group}.swift"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
with open(filepath, 'w') as f:
|
||||
f.write("// Auto-generated by Script/generate-themes.sh — do not edit\n\n")
|
||||
f.write("public extension GhosttyThemeDefinition {\n")
|
||||
for e in entries:
|
||||
f.write(f" static let {e['ident']} = GhosttyThemeDefinition(\n")
|
||||
f.write(f" name: {swift_string(e['name'])},\n")
|
||||
f.write(f" background: {e['background']},\n")
|
||||
f.write(f" foreground: {e['foreground']},\n")
|
||||
f.write(f" cursorColor: {e['cursorColor']},\n")
|
||||
f.write(f" cursorText: {e['cursorText']},\n")
|
||||
f.write(f" selectionBackground: {e['selectionBackground']},\n")
|
||||
f.write(f" selectionForeground: {e['selectionForeground']},\n")
|
||||
f.write(f" palette: {e['palette']}\n")
|
||||
f.write(f" )\n\n")
|
||||
f.write("}\n")
|
||||
print(f" [+] wrote {filename} ({len(entries)} themes)")
|
||||
|
||||
# Write catalog file
|
||||
catalog_path = os.path.join(output_dir, "ThemeCatalog_Generated.swift")
|
||||
with open(catalog_path, 'w') as f:
|
||||
f.write("// Auto-generated by Script/generate-themes.sh — do not edit\n\n")
|
||||
f.write("public extension GhosttyThemeCatalog {\n")
|
||||
f.write(" static let allThemes: [GhosttyThemeDefinition] = [\n")
|
||||
for e in all_themes:
|
||||
f.write(f" .{e['ident']},\n")
|
||||
f.write(" ]\n")
|
||||
f.write("}\n")
|
||||
print(f" [+] wrote ThemeCatalog_Generated.swift ({len(all_themes)} themes)")
|
||||
|
||||
print(f"[+] generated {len(all_themes)} themes total")
|
||||
PYEOF
|
||||
|
||||
echo "[+] done"
|
||||
93
vendor/libghostty-spm/Script/merge-xcframework.sh
vendored
Executable file
93
vendor/libghostty-spm/Script/merge-xcframework.sh
vendored
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARTIFACTS_DIR=${1:-}
|
||||
OUTPUT_XCFRAMEWORK=${2:-}
|
||||
OUTPUT_ZIP=${3:-}
|
||||
|
||||
if [ -z "$ARTIFACTS_DIR" ] || [ -z "$OUTPUT_XCFRAMEWORK" ]; then
|
||||
echo "Usage: $0 <artifacts_dir> <output_xcframework> [output_zip]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$ARTIFACTS_DIR" ]; then
|
||||
echo "[!] artifacts directory not found: $ARTIFACTS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCFRAMEWORK_COMMAND=()
|
||||
FOUND_VARIANTS=0
|
||||
|
||||
verify_headers() {
|
||||
local header_dir="$1"
|
||||
|
||||
if [ ! -f "$header_dir/ghostty.h" ]; then
|
||||
echo "[!] missing header: $header_dir/ghostty.h"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$header_dir/module.modulemap" ]; then
|
||||
echo "[!] missing module map: $header_dir/module.modulemap"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
add_library() {
|
||||
local variant_dir="$1"
|
||||
local library_path="$variant_dir/lib/libghostty.a"
|
||||
local header_dir="$variant_dir/include"
|
||||
|
||||
if [ ! -f "$library_path" ]; then
|
||||
echo "[!] missing library: $library_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$header_dir" ]; then
|
||||
echo "[!] missing headers: $header_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_headers "$header_dir"
|
||||
XCFRAMEWORK_COMMAND+=("-library" "$library_path" "-headers" "$header_dir")
|
||||
FOUND_VARIANTS=$((FOUND_VARIANTS + 1))
|
||||
}
|
||||
|
||||
for variant_dir in "$ARTIFACTS_DIR"/*; do
|
||||
[ -d "$variant_dir" ] || continue
|
||||
[ "$(basename "$variant_dir")" = ".intermediates" ] && continue
|
||||
add_library "$variant_dir"
|
||||
done
|
||||
|
||||
if [ "$FOUND_VARIANTS" -eq 0 ]; then
|
||||
echo "[!] no staged variants found in $ARTIFACTS_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$OUTPUT_XCFRAMEWORK")"
|
||||
rm -rf "$OUTPUT_XCFRAMEWORK"
|
||||
|
||||
echo "[*] creating static-library xcframework: $OUTPUT_XCFRAMEWORK"
|
||||
xcodebuild -create-xcframework \
|
||||
-output "$OUTPUT_XCFRAMEWORK" \
|
||||
"${XCFRAMEWORK_COMMAND[@]}"
|
||||
|
||||
./Script/verify-xcframework.sh "$OUTPUT_XCFRAMEWORK"
|
||||
|
||||
if [ -n "$OUTPUT_ZIP" ]; then
|
||||
mkdir -p "$(dirname "$OUTPUT_ZIP")"
|
||||
rm -f "$OUTPUT_ZIP"
|
||||
(
|
||||
cd "$(dirname "$OUTPUT_XCFRAMEWORK")"
|
||||
ditto -c -k --sequesterRsrc --keepParent "$(basename "$OUTPUT_XCFRAMEWORK")" "$(basename "$OUTPUT_ZIP")"
|
||||
)
|
||||
mv "$(dirname "$OUTPUT_XCFRAMEWORK")/$(basename "$OUTPUT_ZIP")" "$OUTPUT_ZIP"
|
||||
./Script/verify-xcframework.sh "$OUTPUT_ZIP"
|
||||
echo "[*] packed xcframework zip: $OUTPUT_ZIP"
|
||||
fi
|
||||
53
vendor/libghostty-spm/Script/prepare-arm64e-source.sh
vendored
Executable file
53
vendor/libghostty-spm/Script/prepare-arm64e-source.sh
vendored
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/zsh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[-] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR=${1:-}
|
||||
GLOBAL_CACHE_DIR=${2:-}
|
||||
|
||||
if [ -z "$SOURCE_DIR" ] || [ -z "$GLOBAL_CACHE_DIR" ]; then
|
||||
echo "Usage: $0 <source_dir> <zig_global_cache_dir>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "[-] ghostty source directory not found: $SOURCE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ZIG_OBJC_URL="https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz"
|
||||
ZIG_OBJC_HASH="zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK"
|
||||
VENDOR_DIR="$SOURCE_DIR/.libghostty-spm/zig_objc"
|
||||
VENDOR_PATCH="$(pwd)/Patches/arm64e/zig-objc/0001-arm64e-boundaries.patch"
|
||||
VENDOR_PATCH_MARKER="$VENDOR_DIR/.libghostty-spm-arm64e-patched"
|
||||
|
||||
./Script/apply-patches.sh "$SOURCE_DIR" "$(pwd)/Patches/arm64e/ghostty"
|
||||
|
||||
if [ ! -f "$VENDOR_DIR/src/block.zig" ]; then
|
||||
fetched_hash=$(zig fetch --global-cache-dir "$GLOBAL_CACHE_DIR" "$ZIG_OBJC_URL")
|
||||
if [ "$fetched_hash" != "$ZIG_OBJC_HASH" ]; then
|
||||
echo "[-] zig-objc hash mismatch: expected $ZIG_OBJC_HASH, got $fetched_hash"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$VENDOR_DIR")"
|
||||
cp -R "$GLOBAL_CACHE_DIR/p/$ZIG_OBJC_HASH" "$VENDOR_DIR"
|
||||
echo "[+] vendored pinned zig-objc for arm64e"
|
||||
fi
|
||||
|
||||
if [ ! -f "$VENDOR_PATCH_MARKER" ]; then
|
||||
patch -p1 -f -d "$VENDOR_DIR" < "$VENDOR_PATCH" >/dev/null
|
||||
touch "$VENDOR_PATCH_MARKER"
|
||||
echo "[+] applied zig-objc arm64e patch"
|
||||
else
|
||||
echo "[+] zig-objc arm64e patch already applied"
|
||||
fi
|
||||
|
||||
echo "[+] prepared arm64e pointer-authentication boundaries"
|
||||
152
vendor/libghostty-spm/Script/support/arm64e-compat.c
vendored
Normal file
152
vendor/libghostty-spm/Script/support/arm64e-compat.c
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "ghostty.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__arm64e__)
|
||||
#include <pthread.h>
|
||||
#include <ptrauth.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
enum {
|
||||
ghostty_block_has_copy_dispose = 1 << 25,
|
||||
ghostty_objc_isa_discriminator = 0x6AE1,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uintptr_t reserved;
|
||||
uintptr_t size;
|
||||
void *copy_helper;
|
||||
void *dispose_helper;
|
||||
} ghostty_block_descriptor_s;
|
||||
|
||||
typedef struct {
|
||||
void *isa;
|
||||
int flags;
|
||||
int reserved;
|
||||
void *invoke;
|
||||
ghostty_block_descriptor_s *descriptor;
|
||||
} ghostty_block_s;
|
||||
|
||||
static atomic_flag ghostty_descriptor_lock = ATOMIC_FLAG_INIT;
|
||||
|
||||
static void *ghostty_sign_function_pointer(void *pointer,
|
||||
uintptr_t discriminator) {
|
||||
void *raw = ptrauth_strip(pointer, ptrauth_key_function_pointer);
|
||||
return ptrauth_sign_unauthenticated(
|
||||
raw, ptrauth_key_function_pointer, discriminator);
|
||||
}
|
||||
|
||||
static void *ghostty_sign_objc_isa_pointer(void *pointer,
|
||||
uintptr_t discriminator) {
|
||||
void *raw = ptrauth_strip(pointer, ptrauth_key_objc_isa_pointer);
|
||||
return ptrauth_sign_unauthenticated(
|
||||
raw, ptrauth_key_objc_isa_pointer, discriminator);
|
||||
}
|
||||
|
||||
static void ghostty_prepare_block_descriptor(ghostty_block_descriptor_s *descriptor) {
|
||||
while (atomic_flag_test_and_set_explicit(&ghostty_descriptor_lock,
|
||||
memory_order_acquire)) {
|
||||
}
|
||||
|
||||
descriptor->copy_helper = ghostty_sign_function_pointer(
|
||||
descriptor->copy_helper,
|
||||
(uintptr_t)&descriptor->copy_helper);
|
||||
descriptor->dispose_helper = ghostty_sign_function_pointer(
|
||||
descriptor->dispose_helper,
|
||||
(uintptr_t)&descriptor->dispose_helper);
|
||||
|
||||
atomic_flag_clear_explicit(&ghostty_descriptor_lock, memory_order_release);
|
||||
}
|
||||
|
||||
const void *ghostty_arm64e_sign_function(const void *pointer) {
|
||||
return ghostty_sign_function_pointer((void *)pointer, 0);
|
||||
}
|
||||
|
||||
void ghostty_arm64e_prepare_block(void *raw_block) {
|
||||
ghostty_block_s *block = raw_block;
|
||||
|
||||
if ((block->flags & ghostty_block_has_copy_dispose) != 0) {
|
||||
ghostty_prepare_block_descriptor(block->descriptor);
|
||||
}
|
||||
|
||||
block->isa = ghostty_sign_objc_isa_pointer(
|
||||
block->isa,
|
||||
ptrauth_blend_discriminator(&block->isa, ghostty_objc_isa_discriminator));
|
||||
block->invoke = ghostty_sign_function_pointer(
|
||||
block->invoke,
|
||||
(uintptr_t)&block->invoke);
|
||||
|
||||
#if __has_feature(ptrauth_signed_block_descriptors)
|
||||
block->descriptor = ptrauth_sign_unauthenticated(
|
||||
ptrauth_strip(block->descriptor, ptrauth_key_block_descriptor_pointer),
|
||||
ptrauth_key_block_descriptor_pointer,
|
||||
ptrauth_blend_discriminator(&block->descriptor, 0xC0BB));
|
||||
#endif
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) int pa_pthread_cr(
|
||||
pthread_t *thread,
|
||||
const pthread_attr_t *attributes,
|
||||
void *(*start_routine)(void *),
|
||||
void *argument) {
|
||||
start_routine = ghostty_sign_function_pointer((void *)start_routine, 0);
|
||||
return pthread_create(thread, attributes, start_routine, argument);
|
||||
}
|
||||
|
||||
__attribute__((visibility("hidden"))) void pa_qs(
|
||||
void *base,
|
||||
size_t count,
|
||||
size_t size,
|
||||
int (*comparator)(const void *, const void *)) {
|
||||
comparator = ghostty_sign_function_pointer((void *)comparator, 0);
|
||||
qsort(base, count, size, comparator);
|
||||
}
|
||||
|
||||
extern ghostty_app_t ghostty_arm64e_core_app_new(
|
||||
const ghostty_runtime_config_s *,
|
||||
ghostty_config_t);
|
||||
extern ghostty_surface_t ghostty_arm64e_core_surface_new(
|
||||
ghostty_app_t,
|
||||
const ghostty_surface_config_s *);
|
||||
|
||||
GHOSTTY_API ghostty_app_t ghostty_app_new(
|
||||
const ghostty_runtime_config_s *config,
|
||||
ghostty_config_t ghostty_config) {
|
||||
ghostty_runtime_config_s raw = *config;
|
||||
raw.wakeup_cb = ptrauth_strip(raw.wakeup_cb, ptrauth_key_function_pointer);
|
||||
raw.action_cb = ptrauth_strip(raw.action_cb, ptrauth_key_function_pointer);
|
||||
raw.read_clipboard_cb =
|
||||
ptrauth_strip(raw.read_clipboard_cb, ptrauth_key_function_pointer);
|
||||
raw.confirm_read_clipboard_cb =
|
||||
ptrauth_strip(raw.confirm_read_clipboard_cb, ptrauth_key_function_pointer);
|
||||
raw.write_clipboard_cb =
|
||||
ptrauth_strip(raw.write_clipboard_cb, ptrauth_key_function_pointer);
|
||||
raw.close_surface_cb =
|
||||
ptrauth_strip(raw.close_surface_cb, ptrauth_key_function_pointer);
|
||||
return ghostty_arm64e_core_app_new(&raw, ghostty_config);
|
||||
}
|
||||
|
||||
GHOSTTY_API ghostty_surface_t ghostty_surface_new(
|
||||
ghostty_app_t app,
|
||||
const ghostty_surface_config_s *config) {
|
||||
ghostty_surface_config_s raw = *config;
|
||||
raw.receive_buffer =
|
||||
ptrauth_strip(raw.receive_buffer, ptrauth_key_function_pointer);
|
||||
raw.receive_resize =
|
||||
ptrauth_strip(raw.receive_resize, ptrauth_key_function_pointer);
|
||||
return ghostty_arm64e_core_surface_new(app, &raw);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
const void *ghostty_arm64e_sign_function(const void *pointer) {
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void ghostty_arm64e_prepare_block(void *block) {
|
||||
(void)block;
|
||||
}
|
||||
|
||||
#endif
|
||||
18
vendor/libghostty-spm/Script/support/libcxx-verbose-abort-compat.c
vendored
Normal file
18
vendor/libghostty-spm/Script/support/libcxx-verbose-abort-compat.c
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// libc++'s std::__1::__libcpp_verbose_abort is only exported by the Apple
|
||||
// system libc++.1.dylib since iOS 16.3 / macOS 13.3 / tvOS 16.3. Zig's
|
||||
// bundled libc++ headers reference it unconditionally (no Apple availability
|
||||
// markup), so apps linking libghostty.a crash at launch on older OS versions
|
||||
// with dyld "Symbol missing". Shipping the definition inside the archive
|
||||
// resolves the reference at static link time instead.
|
||||
//
|
||||
// Header-free on purpose: zig cc has no Apple sysroot in this build.
|
||||
|
||||
__attribute__((noreturn)) void abort(void);
|
||||
|
||||
__attribute__((visibility("hidden"), noreturn))
|
||||
void libghostty_libcpp_verbose_abort(const char *format, ...) __asm__("__ZNSt3__122__libcpp_verbose_abortEPKcz");
|
||||
|
||||
void libghostty_libcpp_verbose_abort(const char *format, ...) {
|
||||
(void)format;
|
||||
abort();
|
||||
}
|
||||
80
vendor/libghostty-spm/Script/tag-release.sh
vendored
Executable file
80
vendor/libghostty-spm/Script/tag-release.sh
vendored
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/zsh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[-] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: ./Script/tag-release.sh [options]
|
||||
|
||||
Options:
|
||||
--push push the new tag to origin after creating it
|
||||
--suffix <value> override the ci-style release suffix
|
||||
-h, --help show this help
|
||||
|
||||
Notes:
|
||||
- release tags follow the ci pattern: 1.0.<suffix>
|
||||
- this script tags swift source releases only
|
||||
- this script does not create storage.* tags or update binaries
|
||||
EOF
|
||||
}
|
||||
|
||||
PUSH_TAG=0
|
||||
RELEASE_SUFFIX=
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--push)
|
||||
PUSH_TAG=1
|
||||
shift
|
||||
;;
|
||||
--suffix)
|
||||
RELEASE_SUFFIX="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "[-] unknown argument: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$RELEASE_SUFFIX" ]; then
|
||||
RELEASE_SUFFIX=$(date -u +%s)
|
||||
fi
|
||||
|
||||
RELEASE_TAG="1.0.$RELEASE_SUFFIX"
|
||||
RELEASE_DATE=$(date -u +%Y-%m-%d)
|
||||
HEAD_SHA=$(git rev-parse --short=12 HEAD)
|
||||
|
||||
if git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
echo "[-] tag already exists: $RELEASE_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_MESSAGE=$(cat <<EOF
|
||||
Swift source release $RELEASE_DATE
|
||||
|
||||
Tag: $RELEASE_TAG
|
||||
Commit: $HEAD_SHA
|
||||
EOF
|
||||
)
|
||||
|
||||
git tag -a "$RELEASE_TAG" -m "$TAG_MESSAGE"
|
||||
echo "[+] created tag $RELEASE_TAG for $HEAD_SHA on $RELEASE_DATE"
|
||||
|
||||
if [ "$PUSH_TAG" -eq 1 ]; then
|
||||
git push origin "$RELEASE_TAG"
|
||||
echo "[+] pushed tag $RELEASE_TAG"
|
||||
fi
|
||||
288
vendor/libghostty-spm/Script/test-xcframework-consumer.sh
vendored
Executable file
288
vendor/libghostty-spm/Script/test-xcframework-consumer.sh
vendored
Executable file
@@ -0,0 +1,288 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCFRAMEWORK_PATH=${1:-BinaryTarget/GhosttyKit.xcframework}
|
||||
|
||||
if [ ! -d "$XCFRAMEWORK_PATH" ]; then
|
||||
echo "[!] xcframework not found: $XCFRAMEWORK_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
format_output() {
|
||||
if command -v xcbeautify >/dev/null 2>&1; then
|
||||
xcbeautify
|
||||
else
|
||||
cat
|
||||
fi
|
||||
}
|
||||
|
||||
WORK_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
|
||||
mkdir -p "$WORK_DIR/Consumer/BinaryTarget" "$WORK_DIR/Consumer/Sources/Consumer"
|
||||
ditto "$XCFRAMEWORK_PATH" "$WORK_DIR/Consumer/BinaryTarget/GhosttyKit.xcframework"
|
||||
|
||||
cat >"$WORK_DIR/Consumer/Package.swift" <<'EOF'
|
||||
// swift-tools-version: 6.0
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Consumer",
|
||||
platforms: [
|
||||
.iOS(.v15),
|
||||
.macOS(.v13),
|
||||
.macCatalyst(.v15),
|
||||
],
|
||||
products: [
|
||||
.library(name: "Consumer", targets: ["Consumer"]),
|
||||
],
|
||||
targets: [
|
||||
.binaryTarget(
|
||||
name: "libghostty",
|
||||
path: "BinaryTarget/GhosttyKit.xcframework"
|
||||
),
|
||||
.target(
|
||||
name: "Consumer",
|
||||
dependencies: ["libghostty"],
|
||||
linkerSettings: [
|
||||
.linkedLibrary("c++"),
|
||||
.linkedFramework("Carbon", .when(platforms: [.macOS])),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
EOF
|
||||
|
||||
cat >"$WORK_DIR/Consumer/Sources/Consumer/Consumer.swift" <<'EOF'
|
||||
import libghostty
|
||||
|
||||
public func passThroughPlatform(_ platform: ghostty_platform_e) -> ghostty_platform_e {
|
||||
platform
|
||||
}
|
||||
EOF
|
||||
|
||||
ARM64E_LINK_SOURCE="$WORK_DIR/arm64e-link.c"
|
||||
cat >"$ARM64E_LINK_SOURCE" <<'EOF'
|
||||
#include "ghostty.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <ptrauth.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern int pa_pthread_cr(
|
||||
pthread_t *,
|
||||
const pthread_attr_t *,
|
||||
void *(*)(void *),
|
||||
void *);
|
||||
extern void pa_qs(
|
||||
void *,
|
||||
size_t,
|
||||
size_t,
|
||||
int (*)(const void *, const void *));
|
||||
|
||||
static void wakeup(void *userdata) {
|
||||
(void)userdata;
|
||||
}
|
||||
|
||||
static void *thread_main(void *context) {
|
||||
int *value = context;
|
||||
*value = 42;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int compare_ints(const void *lhs, const void *rhs) {
|
||||
const int left = *(const int *)lhs;
|
||||
const int right = *(const int *)rhs;
|
||||
return (left > right) - (left < right);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
if (ghostty_init(0, NULL) != GHOSTTY_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ghostty_config_t config = ghostty_config_new();
|
||||
if (config == NULL) {
|
||||
return 2;
|
||||
}
|
||||
ghostty_config_finalize(config);
|
||||
|
||||
ghostty_runtime_config_s runtime = {0};
|
||||
runtime.wakeup_cb = wakeup;
|
||||
ghostty_app_t app = ghostty_app_new(&runtime, config);
|
||||
if (app == NULL) {
|
||||
ghostty_config_free(config);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int thread_value = 0;
|
||||
pthread_t thread;
|
||||
void *(*raw_thread_main)(void *) =
|
||||
ptrauth_strip(&thread_main, ptrauth_key_function_pointer);
|
||||
if (pa_pthread_cr(&thread, NULL, raw_thread_main, &thread_value) != 0) {
|
||||
return 4;
|
||||
}
|
||||
if (pthread_join(thread, NULL) != 0 || thread_value != 42) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
int values[] = {3, 1, 2};
|
||||
int (*raw_compare)(const void *, const void *) =
|
||||
ptrauth_strip(&compare_ints, ptrauth_key_function_pointer);
|
||||
pa_qs(values, 3, sizeof(values[0]), raw_compare);
|
||||
if (values[0] != 1 || values[1] != 2 || values[2] != 3) {
|
||||
return 6;
|
||||
}
|
||||
|
||||
ghostty_app_free(app);
|
||||
ghostty_config_free(config);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
|
||||
find_slice_paths() {
|
||||
local platform="$1"
|
||||
local platform_variant="$2"
|
||||
|
||||
python3 - "$XCFRAMEWORK_PATH" "$platform" "$platform_variant" <<'PY'
|
||||
import os
|
||||
import plistlib
|
||||
import sys
|
||||
|
||||
xcframework, expected_platform, expected_variant = sys.argv[1:]
|
||||
expected_variant = expected_variant or None
|
||||
|
||||
with open(os.path.join(xcframework, "Info.plist"), "rb") as handle:
|
||||
libraries = plistlib.load(handle)["AvailableLibraries"]
|
||||
|
||||
for library in libraries:
|
||||
if library.get("SupportedPlatform") != expected_platform:
|
||||
continue
|
||||
if library.get("SupportedPlatformVariant") != expected_variant:
|
||||
continue
|
||||
|
||||
root = os.path.join(xcframework, library["LibraryIdentifier"])
|
||||
print(os.path.join(root, library["LibraryPath"]))
|
||||
print(os.path.join(root, library["HeadersPath"]))
|
||||
break
|
||||
else:
|
||||
raise SystemExit(
|
||||
f"[!] xcframework slice not found: {expected_platform}/{expected_variant}"
|
||||
)
|
||||
PY
|
||||
}
|
||||
|
||||
link_arm64e() {
|
||||
local name="$1"
|
||||
local sdk="$2"
|
||||
local target="$3"
|
||||
local platform="$4"
|
||||
local platform_variant="$5"
|
||||
local slice_paths
|
||||
local library_path
|
||||
local headers_path
|
||||
local output_path="$WORK_DIR/arm64e-link-$name"
|
||||
local command
|
||||
|
||||
slice_paths=$(find_slice_paths "$platform" "$platform_variant")
|
||||
library_path=${slice_paths%%$'\n'*}
|
||||
headers_path=${slice_paths#*$'\n'}
|
||||
command=(
|
||||
xcrun --sdk "$sdk" clang
|
||||
-target "$target"
|
||||
"$ARM64E_LINK_SOURCE"
|
||||
-I "$headers_path"
|
||||
"-Wl,-force_load,$library_path"
|
||||
-lc++
|
||||
-framework Foundation
|
||||
-framework CoreFoundation
|
||||
-framework CoreGraphics
|
||||
-framework CoreText
|
||||
-framework CoreVideo
|
||||
-framework QuartzCore
|
||||
-framework IOSurface
|
||||
-framework Metal
|
||||
-framework MetalKit
|
||||
)
|
||||
|
||||
case "$name" in
|
||||
macos)
|
||||
command+=(-framework Carbon)
|
||||
;;
|
||||
ios)
|
||||
command+=(-framework UIKit)
|
||||
;;
|
||||
maccatalyst)
|
||||
local sdk_path
|
||||
sdk_path=$(xcrun --sdk macosx --show-sdk-path)
|
||||
command+=(
|
||||
-F "$sdk_path/System/iOSSupport/System/Library/Frameworks"
|
||||
-L "$sdk_path/System/iOSSupport/usr/lib"
|
||||
-framework UIKit
|
||||
)
|
||||
;;
|
||||
esac
|
||||
|
||||
command+=(-o "$output_path")
|
||||
echo "[*] full link platform=$name architecture=arm64e"
|
||||
"${command[@]}"
|
||||
|
||||
if [ "$(lipo -archs "$output_path")" != "arm64e" ]; then
|
||||
echo "[!] linked executable is not a single arm64e slice: $output_path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$name" = "macos" ] && [ "$(uname -m)" = "arm64" ]; then
|
||||
echo "[*] runtime smoke platform=macos architecture=arm64e"
|
||||
"$output_path"
|
||||
fi
|
||||
}
|
||||
|
||||
test_build() {
|
||||
local destination="$1"
|
||||
local architecture=${2:-}
|
||||
local command=(
|
||||
xcodebuild
|
||||
-scheme Consumer
|
||||
-destination "$destination"
|
||||
-derivedDataPath "$WORK_DIR/DerivedData"
|
||||
-packageCachePath "$WORK_DIR/PackageCache"
|
||||
)
|
||||
|
||||
if [ -n "$architecture" ]; then
|
||||
command+=("ARCHS=$architecture" "ONLY_ACTIVE_ARCH=YES")
|
||||
fi
|
||||
command+=(build)
|
||||
|
||||
echo "[*] consumer build destination=$destination architecture=${architecture:-default}"
|
||||
"${command[@]}" 2>&1 | format_output
|
||||
local exit_code=${PIPESTATUS[0]}
|
||||
if [ "$exit_code" -ne 0 ]; then
|
||||
echo "[!] consumer build failed destination=$destination"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
}
|
||||
|
||||
(
|
||||
cd "$WORK_DIR/Consumer"
|
||||
test_build "generic/platform=macOS"
|
||||
test_build "generic/platform=macOS,variant=Mac Catalyst"
|
||||
test_build "generic/platform=iOS"
|
||||
test_build "generic/platform=iOS Simulator"
|
||||
test_build "generic/platform=macOS" "arm64e"
|
||||
test_build "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
|
||||
test_build "generic/platform=iOS" "arm64e"
|
||||
)
|
||||
|
||||
link_arm64e "macos" "macosx" "arm64e-apple-macosx13.0" "macos" ""
|
||||
link_arm64e "ios" "iphoneos" "arm64e-apple-ios15.0" "ios" ""
|
||||
link_arm64e "maccatalyst" "macosx" "arm64e-apple-ios15.0-macabi" "ios" "maccatalyst"
|
||||
|
||||
echo "[*] xcframework consumer tests passed"
|
||||
56
vendor/libghostty-spm/Script/test.sh
vendored
Executable file
56
vendor/libghostty-spm/Script/test.sh
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
export CLANG_MODULE_CACHE_PATH="${CLANG_MODULE_CACHE_PATH:-/tmp/clang-module-cache}"
|
||||
export SWIFTPM_MODULECACHE_OVERRIDE="${SWIFTPM_MODULECACHE_OVERRIDE:-/tmp/swiftpm-module-cache}"
|
||||
|
||||
format_output() {
|
||||
if command -v xcbeautify >/dev/null 2>&1; then
|
||||
xcbeautify
|
||||
else
|
||||
cat
|
||||
fi
|
||||
}
|
||||
|
||||
test_build() {
|
||||
local scheme="$1"
|
||||
local destination="$2"
|
||||
local architecture=${3:-}
|
||||
local command=(
|
||||
xcodebuild
|
||||
-scheme "$scheme"
|
||||
-destination "$destination"
|
||||
)
|
||||
|
||||
if [ -n "$architecture" ]; then
|
||||
command+=("ARCHS=$architecture" "ONLY_ACTIVE_ARCH=YES")
|
||||
fi
|
||||
command+=(build)
|
||||
|
||||
echo "[*] build scheme=$scheme destination=$destination architecture=${architecture:-default}"
|
||||
"${command[@]}" 2>&1 | format_output
|
||||
local exit_code=${PIPESTATUS[0]}
|
||||
if [ "$exit_code" -ne 0 ]; then
|
||||
echo "[!] failed scheme=$scheme destination=$destination"
|
||||
exit "$exit_code"
|
||||
fi
|
||||
}
|
||||
|
||||
test_build "GhosttyKit" "generic/platform=macOS"
|
||||
test_build "GhosttyKit" "generic/platform=iOS"
|
||||
test_build "GhosttyKit" "generic/platform=iOS Simulator"
|
||||
test_build "GhosttyTerminal" "generic/platform=macOS"
|
||||
test_build "GhosttyTerminal" "generic/platform=macOS,variant=Mac Catalyst"
|
||||
test_build "GhosttyTerminal" "generic/platform=iOS"
|
||||
test_build "GhosttyTerminal" "generic/platform=iOS Simulator"
|
||||
test_build "GhosttyKit" "generic/platform=macOS" "arm64e"
|
||||
test_build "GhosttyKit" "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
|
||||
test_build "GhosttyKit" "generic/platform=iOS" "arm64e"
|
||||
test_build "GhosttyTerminal" "generic/platform=macOS" "arm64e"
|
||||
test_build "GhosttyTerminal" "generic/platform=macOS,variant=Mac Catalyst" "arm64e"
|
||||
test_build "GhosttyTerminal" "generic/platform=iOS" "arm64e"
|
||||
|
||||
echo "[*] all tests passed"
|
||||
73
vendor/libghostty-spm/Script/verify-release.sh
vendored
Executable file
73
vendor/libghostty-spm/Script/verify-release.sh
vendored
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PACKAGE_TAG=${1:-}
|
||||
STORAGE_TAG=${2:-}
|
||||
ASSET_NAME=${3:-GhosttyKit.xcframework.zip}
|
||||
|
||||
if [ -z "$PACKAGE_TAG" ] || [ -z "$STORAGE_TAG" ]; then
|
||||
echo "Usage: $0 <package_tag> <storage_tag> [asset_name]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v gh >/dev/null 2>&1; then
|
||||
echo "[!] gh not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --tags origin
|
||||
|
||||
package_commit=$(git rev-parse "refs/tags/$PACKAGE_TAG")
|
||||
storage_commit=$(git rev-parse "refs/tags/$STORAGE_TAG")
|
||||
|
||||
if [ "$package_commit" != "$storage_commit" ]; then
|
||||
echo "[!] package tag and storage tag point at different commits"
|
||||
echo " $PACKAGE_TAG: $package_commit"
|
||||
echo " $STORAGE_TAG: $storage_commit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
manifest=$(git show "$PACKAGE_TAG:Package.swift")
|
||||
download_url=$(printf '%s\n' "$manifest" | python3 -c 'import re, sys; text=sys.stdin.read(); urls=re.findall(r"url:\s*\"([^\"]+)\"", text); matches=[url for url in urls if "/releases/download/" in url and url.endswith("/GhosttyKit.xcframework.zip")]; print(matches[0] if matches else "", end="")')
|
||||
checksum=$(printf '%s\n' "$manifest" | python3 -c 'import re, sys; text=sys.stdin.read(); match=re.search(r"checksum:\s*\"([0-9a-f]{64})\"", text); print(match.group(1) if match else "", end="")')
|
||||
|
||||
if [ -z "$download_url" ] || [ -z "$checksum" ]; then
|
||||
echo "[!] failed to read binary target URL/checksum from Package.swift at $PACKAGE_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expected_url="https://github.com/Lakr233/libghostty-spm/releases/download/$STORAGE_TAG/$ASSET_NAME"
|
||||
if [ "$download_url" != "$expected_url" ]; then
|
||||
echo "[!] Package.swift download URL does not match storage release"
|
||||
echo " expected: $expected_url"
|
||||
echo " actual: $download_url"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
asset_digest=$(
|
||||
gh release view "$STORAGE_TAG" \
|
||||
--json assets \
|
||||
--jq ".assets[] | select(.name == \"$ASSET_NAME\") | .digest"
|
||||
)
|
||||
|
||||
if [ -z "$asset_digest" ]; then
|
||||
echo "[!] release asset not found: $STORAGE_TAG/$ASSET_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$asset_digest" != "sha256:$checksum" ]; then
|
||||
echo "[!] release asset digest does not match Package.swift checksum"
|
||||
echo " asset: $asset_digest"
|
||||
echo " manifest: sha256:$checksum"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] release verified: $PACKAGE_TAG -> $STORAGE_TAG/$ASSET_NAME"
|
||||
125
vendor/libghostty-spm/Script/verify-xcframework.sh
vendored
Executable file
125
vendor/libghostty-spm/Script/verify-xcframework.sh
vendored
Executable file
@@ -0,0 +1,125 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
if [ ! -f .root ]; then
|
||||
echo "[*] malformed project structure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INPUT_PATH=${1:-}
|
||||
|
||||
if [ -z "$INPUT_PATH" ]; then
|
||||
echo "Usage: $0 <xcframework_or_zip>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -e "$INPUT_PATH" ]; then
|
||||
echo "[!] not found: $INPUT_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TEMP_DIR=
|
||||
XCFRAMEWORK_PATH="$INPUT_PATH"
|
||||
|
||||
cleanup() {
|
||||
if [ -n "$TEMP_DIR" ]; then
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ "$INPUT_PATH" == *.zip ]]; then
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
ditto -x -k "$INPUT_PATH" "$TEMP_DIR"
|
||||
XCFRAMEWORK_PATH=$(find "$TEMP_DIR" -maxdepth 1 -name "*.xcframework" -type d | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$XCFRAMEWORK_PATH" ] || [ ! -d "$XCFRAMEWORK_PATH" ]; then
|
||||
echo "[!] xcframework not found in input: $INPUT_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$XCFRAMEWORK_PATH" <<'PY'
|
||||
import os
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
xcframework = sys.argv[1]
|
||||
info_path = os.path.join(xcframework, "Info.plist")
|
||||
|
||||
if not os.path.isfile(info_path):
|
||||
raise SystemExit(f"[!] missing xcframework Info.plist: {info_path}")
|
||||
|
||||
for root, dirs, _ in os.walk(xcframework):
|
||||
for directory in dirs:
|
||||
if directory.endswith(".framework"):
|
||||
raise SystemExit(f"[!] framework wrapper is not allowed in static-library xcframework: {os.path.join(root, directory)}")
|
||||
|
||||
with open(info_path, "rb") as handle:
|
||||
info = plistlib.load(handle)
|
||||
|
||||
libraries = info.get("AvailableLibraries")
|
||||
if not isinstance(libraries, list) or not libraries:
|
||||
raise SystemExit("[!] AvailableLibraries is empty or missing")
|
||||
|
||||
for library in libraries:
|
||||
identifier = library.get("LibraryIdentifier")
|
||||
library_path = library.get("LibraryPath")
|
||||
binary_path = library.get("BinaryPath")
|
||||
headers_path = library.get("HeadersPath")
|
||||
platform = library.get("SupportedPlatform")
|
||||
platform_variant = library.get("SupportedPlatformVariant")
|
||||
declared_architectures = set(library.get("SupportedArchitectures", []))
|
||||
|
||||
if not identifier:
|
||||
raise SystemExit("[!] library entry missing LibraryIdentifier")
|
||||
if platform not in {"ios", "macos", "tvos", "watchos", "xros"}:
|
||||
raise SystemExit(f"[!] unsupported platform in {identifier}: {platform}")
|
||||
if library_path != "libghostty.a" or binary_path != "libghostty.a":
|
||||
raise SystemExit(f"[!] {identifier} must reference libghostty.a, got LibraryPath={library_path!r} BinaryPath={binary_path!r}")
|
||||
if headers_path != "Headers":
|
||||
raise SystemExit(f"[!] {identifier} must use HeadersPath=Headers, got {headers_path!r}")
|
||||
|
||||
variant_dir = os.path.join(xcframework, identifier)
|
||||
archive_path = os.path.join(variant_dir, library_path)
|
||||
headers_dir = os.path.join(variant_dir, headers_path)
|
||||
header_path = os.path.join(headers_dir, "ghostty.h")
|
||||
modulemap_path = os.path.join(headers_dir, "module.modulemap")
|
||||
|
||||
for required_path in (archive_path, header_path, modulemap_path):
|
||||
if not os.path.exists(required_path):
|
||||
raise SystemExit(f"[!] {identifier} missing required file: {required_path}")
|
||||
|
||||
actual_architectures = set(
|
||||
subprocess.check_output(["lipo", "-archs", archive_path], text=True).split()
|
||||
)
|
||||
if actual_architectures != declared_architectures:
|
||||
raise SystemExit(
|
||||
f"[!] {identifier} architecture metadata mismatch: "
|
||||
f"plist={sorted(declared_architectures)} archive={sorted(actual_architectures)}"
|
||||
)
|
||||
|
||||
expected_architectures = {
|
||||
("macos", None): {"arm64", "arm64e", "x86_64"},
|
||||
("ios", None): {"arm64", "arm64e"},
|
||||
("ios", "maccatalyst"): {"arm64", "arm64e", "x86_64"},
|
||||
("ios", "simulator"): {"arm64", "x86_64"},
|
||||
}.get((platform, platform_variant))
|
||||
if expected_architectures is not None and actual_architectures != expected_architectures:
|
||||
raise SystemExit(
|
||||
f"[!] {identifier} architectures must be {sorted(expected_architectures)}, "
|
||||
f"got {sorted(actual_architectures)}"
|
||||
)
|
||||
|
||||
with open(modulemap_path, "r", encoding="utf-8") as handle:
|
||||
modulemap = handle.read()
|
||||
if "framework module libghostty" in modulemap:
|
||||
raise SystemExit(f"[!] {identifier} module map must not declare a framework module")
|
||||
if "module libghostty" not in modulemap:
|
||||
raise SystemExit(f"[!] {identifier} module map does not declare module libghostty")
|
||||
|
||||
print(f"[*] verified static-library xcframework layout and architectures: {xcframework}")
|
||||
PY
|
||||
Reference in New Issue
Block a user