| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | set -euo pipefail |
| 4 | |
| 5 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 6 | MODULE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 7 | VENDOR_DIR="${MODULE_DIR}/../../../../native/libghostty-vt" |
| 8 | PATCH_DIR="${SCRIPT_DIR}/libghostty-android-patches" |
| 9 | |
| 10 | GHOSTTY_REVISION="${GHOSTTY_REVISION:-9f62873bf195e4d8a762d768a1405a5f2f7b1697}" |
| 11 | GHOSTTY_SOURCE_DIR="${GHOSTTY_SOURCE_DIR:-${HOME}/.cache/t3code/ghostty-${GHOSTTY_REVISION:0:8}}" |
| 12 | GHOSTTY_ZIG_VERSION="${GHOSTTY_ZIG_VERSION:-0.15.2}" |
| 13 | GHOSTTY_ZIG="${GHOSTTY_ZIG:-}" |
| 14 | ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-}" |
| 15 | |
| 16 | log() { |
| 17 | printf '[libghostty-vt-android] %s\n' "$*" |
| 18 | } |
| 19 | |
| 20 | die() { |
| 21 | printf '[libghostty-vt-android] error: %s\n' "$*" >&2 |
| 22 | exit 1 |
| 23 | } |
| 24 | |
| 25 | require_cmd() { |
| 26 | command -v "$1" >/dev/null 2>&1 || die "missing required command: $1" |
| 27 | } |
| 28 | |
| 29 | ensure_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 | |
| 68 | ensure_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 | |
| 84 | apply_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 | |
| 101 | if [[ -z "${ANDROID_NDK_HOME}" ]]; then |
| 102 | die "ANDROID_NDK_HOME must point to an installed Android NDK" |
| 103 | fi |
| 104 | [[ -d "${ANDROID_NDK_HOME}" ]] || die "Android NDK not found: ${ANDROID_NDK_HOME}" |
| 105 | |
| 106 | ensure_zig |
| 107 | ensure_ghostty_source |
| 108 | apply_ghostty_patches |
| 109 | |
| 110 | strip_tool="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt" |
| 111 | strip_tool="$(find "${strip_tool}" -path '*/bin/llvm-strip' -print -quit)" |
| 112 | [[ -x "${strip_tool}" ]] || die "llvm-strip not found under ${ANDROID_NDK_HOME}" |
| 113 | |
| 114 | targets=( |
| 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 | |
| 121 | build_root="$(mktemp -d)" |
| 122 | trap 'rm -rf "${build_root}"' EXIT |
| 123 | mkdir -p "${VENDOR_DIR}/include" |
| 124 | |
| 125 | for 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" |
| 146 | done |
| 147 | |
| 148 | rm -rf "${VENDOR_DIR}/include/ghostty" |
| 149 | cp -R "${build_root}/arm64-v8a/include/ghostty" "${VENDOR_DIR}/include/ghostty" |
| 150 | cp "${GHOSTTY_SOURCE_DIR}/LICENSE" "${VENDOR_DIR}/LICENSE" |
| 151 | printf '%s\n' "${GHOSTTY_REVISION}" > "${VENDOR_DIR}/VERSION" |
| 152 | log "done" |