1#!/usr/bin/env bash
2
3set -euo pipefail
4
5SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6MODULE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
7VENDOR_DIR="${MODULE_DIR}/../../../../native/libghostty-vt"
8PATCH_DIR="${SCRIPT_DIR}/libghostty-android-patches"
9
10GHOSTTY_REVISION="${GHOSTTY_REVISION:-9f62873bf195e4d8a762d768a1405a5f2f7b1697}"
11GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-${HOME}/.cache/t3code/ghostty-${GHOSTTY_REVISION:0:8}}"
12GHOSTTY_ZIG_VERSION="${GHOSTTY_ZIG_VERSION:-0.15.2}"
13GHOSTTY_ZIG="${GHOSTTY_ZIG:-}"
14ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-}"
15
16log() {
17 printf '[libghostty-vt-android] %s\n' "$*"
18}
19
20die() {
21 printf '[libghostty-vt-android] error: %s\n' "$*" >&2
22 exit 1
23}
24
25require_cmd() {
26 command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
27}
28
29ensure_zig() {
30 if [[ -n "${GHOSTTY_ZIG}" ]]; then
31 [[ -x "${GHOSTTY_ZIG}" ]] || die "GHOSTTY_ZIG is not executable: ${GHOSTTY_ZIG}"
32 return
33 fi
34 if command -v zig >/dev/null 2>&1 && [[ "$(zig version)" == "${GHOSTTY_ZIG_VERSION}" ]]; then
35 GHOSTTY_ZIG="$(command -v zig)"
36 return
37 fi
38
39 local host_os host_arch cache_dir
40 host_os="$(uname -s | tr '[:upper:]' '[:lower:]')"
41 host_arch="$(uname -m)"
42 case "${host_os}" in
43 darwin) host_os="macos" ;;
44 linux) ;;
45 *) die "unsupported host OS for Zig download: ${host_os}" ;;
46 esac
47 case "${host_arch}" in
48 arm64) host_arch="aarch64" ;;
49 aarch64 | x86_64) ;;
50 *) die "unsupported host architecture for Zig download: ${host_arch}" ;;
51 esac
52
53 cache_dir="${HOME}/.cache/t3code/zig-${GHOSTTY_ZIG_VERSION}"
54 GHOSTTY_ZIG="${cache_dir}/zig"
55 if [[ -x "${GHOSTTY_ZIG}" ]]; then
56 return
57 fi
58
59 require_cmd curl
60 require_cmd tar
61 mkdir -p "${cache_dir}"
62 log "downloading Zig ${GHOSTTY_ZIG_VERSION}"
63 curl -fsSL \
64 "https://ziglang.org/download/${GHOSTTY_ZIG_VERSION}/zig-${host_arch}-${host_os}-${GHOSTTY_ZIG_VERSION}.tar.xz" \
65 | tar -xJ --strip-components=1 -C "${cache_dir}"
66}
67
68ensure_ghostty_source() {
69 if [[ ! -d "${GHOSTTY_SOURCE_DIR}/.git" ]]; then
70 require_cmd git
71 log "cloning Ghostty ${GHOSTTY_REVISION}"
72 git clone --filter=blob:none --no-checkout https://github.com/ghostty-org/ghostty.git \
73 "${GHOSTTY_SOURCE_DIR}"
74 git -C "${GHOSTTY_SOURCE_DIR}" fetch --depth=1 origin "${GHOSTTY_REVISION}"
75 git -C "${GHOSTTY_SOURCE_DIR}" checkout --detach "${GHOSTTY_REVISION}"
76 fi
77
78 local actual_revision
79 actual_revision="$(git -C "${GHOSTTY_SOURCE_DIR}" rev-parse HEAD)"
80 [[ "${actual_revision}" == "${GHOSTTY_REVISION}" ]] || \
81 die "expected Ghostty ${GHOSTTY_REVISION}, found ${actual_revision}"
82}
83
84apply_ghostty_patches() {
85 [[ -d "${PATCH_DIR}" ]] || return
86
87 local patch_file patch_name
88 for patch_file in "${PATCH_DIR}"/*.patch; do
89 [[ -e "${patch_file}" ]] || continue
90 patch_name="$(basename "${patch_file}")"
91 if git -C "${GHOSTTY_SOURCE_DIR}" apply --reverse --check "${patch_file}" >/dev/null 2>&1; then
92 log "patch already applied: ${patch_name}"
93 continue
94 fi
95 log "applying patch: ${patch_name}"
96 git -C "${GHOSTTY_SOURCE_DIR}" apply --check "${patch_file}"
97 git -C "${GHOSTTY_SOURCE_DIR}" apply "${patch_file}"
98 done
99}
100
101if [[ -z "${ANDROID_NDK_HOME}" ]]; then
102 die "ANDROID_NDK_HOME must point to an installed Android NDK"
103fi
104[[ -d "${ANDROID_NDK_HOME}" ]] || die "Android NDK not found: ${ANDROID_NDK_HOME}"
105
106ensure_zig
107ensure_ghostty_source
108apply_ghostty_patches
109
110strip_tool="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt"
111strip_tool="$(find "${strip_tool}" -path '*/bin/llvm-strip' -print -quit)"
112[[ -x "${strip_tool}" ]] || die "llvm-strip not found under ${ANDROID_NDK_HOME}"
113
114targets=(
115 "arm64-v8a:aarch64-linux-android"
116 "armeabi-v7a:arm-linux-androideabi"
117 "x86:x86-linux-android"
118 "x86_64:x86_64-linux-android"
119)
120
121build_root="$(mktemp -d)"
122trap 'rm -rf "${build_root}"' EXIT
123mkdir -p "${VENDOR_DIR}/include"
124
125for entry in "${targets[@]}"; do
126 abi="${entry%%:*}"
127 target="${entry#*:}"
128 prefix="${build_root}/${abi}"
129 log "building ${abi} (${target})"
130 (
131 cd "${GHOSTTY_SOURCE_DIR}"
132 ANDROID_NDK_HOME="${ANDROID_NDK_HOME}" "${GHOSTTY_ZIG}" build \
133 -Demit-lib-vt \
134 -Dtarget="${target}" \
135 -Doptimize=ReleaseFast \
136 -Dstrip=true \
137 -Dsimd=false \
138 -p "${prefix}"
139 )
140
141 mkdir -p "${MODULE_DIR}/android/src/main/jniLibs/${abi}"
142 cp "${prefix}/lib/libghostty-vt.so.0.1.0" \
143 "${MODULE_DIR}/android/src/main/jniLibs/${abi}/libghostty-vt.so"
144 "${strip_tool}" --strip-unneeded \
145 "${MODULE_DIR}/android/src/main/jniLibs/${abi}/libghostty-vt.so"
146done
147
148rm -rf "${VENDOR_DIR}/include/ghostty"
149cp -R "${build_root}/arm64-v8a/include/ghostty" "${VENDOR_DIR}/include/ghostty"
150cp "${GHOSTTY_SOURCE_DIR}/LICENSE" "${VENDOR_DIR}/LICENSE"
151printf '%s\n' "${GHOSTTY_REVISION}" > "${VENDOR_DIR}/VERSION"
152log "done"