diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 87323b7..bc69dd2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,27 +9,29 @@ on: opencode_version: description: 'OpenCode version to build' required: false - default: '1.3.13' + default: '1.18.31' env: BUN_VERSION: "1.2.13" - OPENCODE_VERSION: "1.3.13" + OPENCODE_VERSION: "1.18.31" ANDROID_API: "24" ANDROID_NDK_VERSION: "28.1.13356709" WEBKIT_COMMIT: "017930ebf915121f8f593bef61cbbca82d78132d" TINYCC_COMMIT: "b91835d8701523e895a2b447713630069514e291" ICU_VERSION: "75.1" ZIG_VERSION: "0.15.2" - # golem10 has 15GB RAM — limit parallelism to avoid OOM during WebKit link - JOBS: "4" - # Use the large spindle drive for working directory - WORK_DIR: "/mnt/spindrive/opencode-build" + # Memory-safe parallelism default; override with repo secret if your + # runner has more (golem10 used JOBS=4 on 15GB RAM). + JOBS: "2" + # Generic default. GitHub does NOT expand $HOME inside env values, + # so use an absolute, writable path (override per repo if needed). + WORK_DIR: "/tmp/opencode-build" jobs: build: name: Build Android aarch64 runs-on: self-hosted - timeout-minutes: 360 # 6 hours for full build + timeout-minutes: 720 # 12 hours for full build on 2-core runner steps: - name: Checkout repository @@ -43,7 +45,7 @@ jobs: run: | mkdir -p "$WORK_DIR" echo "Work directory: $WORK_DIR" - df -h /mnt/spindrive + df -h "$WORK_DIR" # ============================================= # Install build dependencies @@ -340,4 +342,4 @@ jobs: # FileNotFound errors in the translate-c output cache. rm -rf "${WORK_DIR}/bun-build/cache/zig" echo "Cleaned up intermediate files" - df -h /mnt/spindrive + df -h "$WORK_DIR" diff --git a/README.md b/README.md index 00fa0a9..ec68e1a 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,19 @@ OpenCode is an AI-powered coding assistant for the terminal. It uses [Bun](https ### Option 1: Standalone binary (easiest) -> **Note:** The zip now contains a wrapper script (`opencode`), the main binary -> (`opencode.bin`), and native libraries (`.so` files). All files must be -> installed to their proper locations. +> **Note:** The zip contains a single self-contained `opencode` binary (~200MB). +> All native libraries (`libopentui.so`, etc.) are embedded, so no extra files +> need to be installed. ```bash # Download the latest "opencode-*-android-aarch64.zip" from -# https://github.com/guysoft/opencode-termux/releases/latest +# https://github.com/qioceky/opencode-termux/releases/tag/v1.18.31 +# (pending upstream merge — the canonical "latest" is the fork above) # Then install: -mkdir -p $PREFIX/libexec/opencode $PREFIX/lib unzip opencode-*-android-aarch64.zip mv opencode $PREFIX/bin/opencode chmod +x $PREFIX/bin/opencode -mv opencode.bin $PREFIX/libexec/opencode/opencode.bin -chmod +x $PREFIX/libexec/opencode/opencode.bin -mv libtagfix.so libc++_shared.so libopentui.so $PREFIX/lib/ # Install required dependency pkg install ripgrep @@ -35,19 +32,29 @@ opencode ### Option 2: Pacman package (recommended if using pacman) ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-aarch64.pkg.tar.xz -pacman -U opencode-*-aarch64.pkg.tar.xz +curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode-1.18.31-1-aarch64.pkg.tar.xz +pacman -U opencode-1.18.31-1-aarch64.pkg.tar.xz opencode ``` +> If pacman complains about a missing/writable keyring, initialize it first: +> ```bash +> pacman-key --init +> pacman-key --populate +> ``` + ### Option 3: Deb package ```bash -curl -LO https://github.com/guysoft/opencode-termux/releases/latest/download/opencode-aarch64.deb -dpkg -i opencode-*-aarch64.deb +curl -LO https://github.com/qioceky/opencode-termux/releases/latest/download/opencode_1.18.31_aarch64.deb +pkg install ./opencode_1.18.31_aarch64.deb opencode ``` +> Use the exact filename — do NOT glob (`opencode_*_aarch64.deb`): if an older +> OpenCode `.deb` is still in your working directory, pkg/dpkg will try to install +> both and the unpack can fail. Delete old `.deb` files first if you have any. + The pacman and deb packages automatically install `ripgrep` as a dependency. ### After install @@ -77,9 +84,10 @@ opencode-termux/ bun/android-support.patch # 33 files, Bun Android/aarch64 support webkit/android-support.patch # 5 files, WebKit/JSC Android fixes zig/posix-android-sigaction.patch # Zig stdlib sigaction/sigprocmask fix - opentui/android-libc-link.patch # Link NDK libc.so for Android dlopen + opentui/android-libc-link.patch # Android build: skip dl/pthread, link NDK libc.so + setLibCFile scripts/ apply-patches.sh # Clone upstream repos + apply patches + env.sh # Version pins (OpenCode, opentui, Bun, etc.) build-icu.sh # Cross-compile ICU 75.1 for Android build-webkit.sh # Cross-compile WebKit/JSC for Android build-tinycc.sh # Cross-compile TinyCC (libtcc.a) for Android @@ -98,7 +106,7 @@ opencode-termux/ ## What Was Done -This project got OpenCode (a ~136MB standalone binary built on Bun + WebKit/JSC) running on Android/Termux, which required: +This project got OpenCode (a ~195MB standalone binary built on Bun + WebKit/JSC) running on Android/Termux, which required: 1. **Cross-compiling Bun v1.2.13 for Android/aarch64** -- Bun has zero Android support. We patched 33 files across the build system (CMake, Zig), syscall layer, Bionic libc compatibility, JSC/JIT configuration, and linker settings. @@ -106,7 +114,7 @@ This project got OpenCode (a ~136MB standalone binary built on Bun + WebKit/JSC) 3. **Fixing Zig's stdlib for Android/Bionic** -- Zig's `sigaction()` and `sigprocmask()` pass a 152-byte struct through Bionic's libc which expects 32 bytes, causing silent memory corruption. Patched to use raw syscalls on Android. -4. **Building libopentui.so for Android** -- OpenCode's TUI renderer depends on OpenTUI, which needed a patch to link Android NDK's libc.so stub so `dlopen()` can resolve symbols at runtime. +4. **Building libopentui.so for Android** -- OpenCode's TUI renderer depends on OpenTUI, which needed a patch to (a) skip linking `dl`/`pthread` (already inside Bionic on Android), (b) link the Android NDK's `libc.so` stub so `dlopen()` can resolve symbols at runtime, and (c) provision a libc via `setLibCFile()` so Zig can compile for the Android target (Zig only bundles glibc/musl). 5. **Standalone binary surgery** -- Since `bun build --compile` has no Android cross-compilation target, we build a host standalone binary, extract the serialized module graph, and transplant it onto the Android Bun binary. This required understanding and matching the binary format across Bun versions (36-byte vs 52-byte module struct stride). @@ -118,15 +126,15 @@ This project got OpenCode (a ~136MB standalone binary built on Bun + WebKit/JSC) ``` Stage 1: ICU 75.1 ~5 min (cross-compile for Android) -Stage 2: WebKit/JSC ~60-90 min (cross-compile, CACHED) +Stage 2: WebKit/JSC ~30-60 min (cross-compile, CACHED) Stage 3: TinyCC ~1 min (cross-compile libtcc.a) Stage 4: Bun binary ~30-45 min (CMake + Ninja, CACHED) -Stage 5: libopentui.so ~2 min (Zig build for aarch64-linux-android) +Stage 5: libopentui.so ~1 min (Zig build for aarch64-linux-android) Stage 6: OpenCode bundle ~30 sec (bun build --compile, extract module graph) Stage 7: Packages ~10 sec (zip + pacman + deb) ``` -With warm caches (WebKit + Bun cached), CI runs complete in ~4 minutes. +The GitHub Actions workflow runs these stages in one job. A full run on a 2-core/8GB runner with a swap file takes roughly an hour end-to-end; CI caches (ICU, WebKit, Bun) are restored via `actions/cache` when the build inputs are unchanged. --- @@ -201,7 +209,10 @@ Bun has zero Android support. Every patch falls into one of these categories: ### OpenTUI Patch (1 file) -- **Link NDK `libc.so` stub** -- On Android, the `.so` must have `NEEDED: libc.so` in its ELF headers so `dlopen()` can resolve symbols like `getauxval`. Zig doesn't bundle Android libc, so we directly add the NDK sysroot's `libc.so` stub as a link input. +- **Android `build.zig` support** -- three changes to compile `libopentui.so` for `aarch64-linux-android`: + 1. **Skip `dl`/`pthread`** -- Zig's `addNativeAudioDependencies` unconditionally links `dl` + `pthread` on `.linux`; on Android those live inside Bionic's libc, so the link fails ("unable to find dynamic system library 'dl'"). Guarded with `if (!is_android)`. + 2. **Link NDK `libc.so` stub** -- the `.so` must carry `NEEDED: libc.so` in its ELF headers so `dlopen()` can resolve symbols like `getauxval`. We `addObjectFile()` the NDK sysroot's `libc.so` stub. + 3. **Provision libc via `setLibCFile()`** -- Zig bundles only glibc/musl, so compiling for Android fails with "unable to provide libc for target". We write a libc file pointing at the NDK sysroot (include dirs + crt dir, read from `ANDROID_NDK_HOME` / `ANDROID_NDK_LIB_DIR` env) and feed it to the compile step. --- @@ -209,7 +220,7 @@ Bun has zero Android support. Every patch falls into one of these categories: Since `bun build --compile` has no Android cross-compilation target, we use a manual approach: -1. Use **host Bun (v1.3.2)** to `bun build --compile` OpenCode for the host platform +1. Use **host Bun (v1.3.2)** to `bun build --compile` OpenCode for the host platform. Before bundling, run `bun install --os="*" --cpu="*" @opentui/core@...` (versions parsed from `bun.lock`) so every platform's optional-dependency variant is present — otherwise bundling fails to resolve e.g. `@opentui/core-win32-arm64` 2. Extract the serialized **module graph** from the host standalone binary by locating the `\n---- Bun! ----\n` trailer and reading the `Offsets` struct 3. Patch the module graph in-place (fix `undici` global reference) 4. Before bundling, swap x86_64 `libopentui.so` with the ARM64 Android-built version, so it gets embedded in the module graph @@ -218,8 +229,8 @@ Since `bun build --compile` has no Android cross-compilation target, we use a ma The standalone binary format: ``` -[Android Bun binary (~96 MB)] -[Module graph bytes (~46 MB)] +[Android Bun binary (~93 MB)] +[Module graph bytes (~104 MB)] [total_byte_count as u64 LE (8 bytes)] ``` @@ -247,7 +258,7 @@ We can't use Bun 1.2.13 as host either, because OpenCode's monorepo uses `catalo | Issue | Severity | Details | |-------|----------|---------| -| File watcher native module | Low | `@parcel/watcher` `.node` binding is compiled for x86_64. Falls back gracefully to polling. Logs: `dlopen failed: "...00000001.node" is for EM_X86_64 (62) instead of EM_AARCH64 (183)` | +| `@parcel/watcher` file watcher | None | Declared dependency but not imported by OpenCode's source. The build installs all platform variants (including `linux-arm64`) so nothing depends on the x86_64 prebuilt. | | `bun upgrade` | Low | Disabled on Android -- no Android release channel exists upstream | | TinyCC FFI compilation | Low | `libtcc.a` is linked but TCC's runtime code generation may not produce valid ARM64 code. FFI is not commonly used by OpenCode. | | SIGPWR signals | None | Many SIGPWR signals appear in strace -- related to Android's power management or Bun's signal handling. Not errors. | @@ -339,7 +350,8 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s | Android NDK | r28b (28.1.13356709) | Clang 19, stable | | Android API level | 24 (Android 7.0+) | Minimum for 64-bit Termux | | Zig (for opentui) | 0.15.2 | Latest stable, Android target support | -| OpenCode | 1.3.13 | Current release | +| opentui (native lib) | v0.4.5 | Matches `@opentui/core@0.4.5` bundled by OpenCode v1.18.31 | +| OpenCode | 1.18.31 | Current release | | TinyCC | `b91835d8` (oven-sh/tinycc) | Matches Bun v1.2.13's expected TinyCC | --- @@ -347,9 +359,10 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s ## Build Requirements - **Build host**: x86_64 Linux (Ubuntu 22.04+) -- **RAM**: 16GB minimum (30GB recommended for WebKit link step) +- **RAM**: 8GB minimum with an 8GB+ swap file (16GB recommended for faster link steps) - **Disk**: 60GB+ free space -- **CPU**: 8+ cores recommended (4 cores works but slow) +- **CPU**: 2+ cores (8+ cores recommended; more is faster) +- The tested configuration: 2-core / 8GB RAM / 8GB swap, `JOBS=2` in the workflow. ### Required tools @@ -368,11 +381,29 @@ The Bun team [closed Android support as "not planned"](https://github.com/oven-s --- +## Building and Releasing (CI) + +The `.github/workflows/build.yml` workflow has two triggers: + +1. **Manual dispatch** (`workflow_dispatch`) -- "Run workflow" button → input `opencode_version` (e.g. `1.18.31`). Builds and uploads packages as a run artifact, but **does not** create a GitHub Release. + +2. **Tag push** (`on: push: tags: ['v*']`) -- pushing `git tag v` to the repo triggers a full build AND auto-creates a GitHub Release with the three packages (`.zip`, `.pkg.tar.xz`, `.deb`) attached. + +- The workflow is designed for a **self-hosted runner** (`golem10`): `WORK_DIR`, `JOBS`, and the 12-hour timeout are configurable. GitHub-hosted runners are too small/slow for the Bun + WebKit builds. +- Repo must have **workflow permissions = Read and write** (Settings → Actions → General) or the release step fails with `Resource not accessible by integration`. +- Upgrading OpenCode = update `OPENCODE_VERSION` / `OPENTUI_VERSION` in `scripts/env.sh` (mirror the pins in `.github/workflows/build.yml` and the fallback in `scripts/build-opencode-android.ts`), then `git tag v && git push --tags`. + +--- + ## Tested On -- Samsung Galaxy S10e (Android 12, Termux, aarch64) -- full TUI confirmed working +- Samsung Galaxy S10e (Android 12, Termux, aarch64) -- full TUI confirmed working (verified on an earlier OpenCode release) - Meta Quest 2 (Android 12L, adb shell) +> **Note:** The v1.18.31 build is compiled and its bundle pipeline is verified, but it has not been +> installed/run on an actual Android device yet. If anything misbehaves in the TUI, models list, or +> startup, please open an issue. + --- ## Credits diff --git a/patches/opentui/android-libc-link.patch b/patches/opentui/android-libc-link.patch index 18f487c..a6e9d84 100644 --- a/patches/opentui/android-libc-link.patch +++ b/patches/opentui/android-libc-link.patch @@ -1,10 +1,25 @@ diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig -index 5a8bcb8..b1d4642 100644 +index 0c82ba3..3f5e40a 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig -@@ -273,6 +273,29 @@ fn buildTarget( - .linkage = .dynamic, - }); +@@ -190,8 +190,12 @@ fn addNativeAudioDependencies( + switch (target.result.os.tag) { + .macos => addMacOSSystemLibraries(b, artifact, macos_sdk_path.?), + .linux => { +- artifact.linkSystemLibrary("dl"); +- artifact.linkSystemLibrary("pthread"); ++ const abi = target.result.abi; ++ const is_android = abi == .android or abi == .androideabi; ++ if (!is_android) { ++ artifact.linkSystemLibrary("dl"); ++ artifact.linkSystemLibrary("pthread"); ++ } + }, + else => {}, + } +@@ -494,6 +498,57 @@ fn buildTarget( + addNativeAudioDependencies(b, lib, target, macos_sdk_path); + addYogaDependencies(b, lib); + // On Android, the .so must have NEEDED libc.so so dlopen can resolve + // symbols like getauxval. Zig doesn't bundle Android libc, so we directly @@ -12,20 +27,48 @@ index 5a8bcb8..b1d4642 100644 + const os_tag = target.result.os.tag; + const abi = target.result.abi; + if (os_tag == .linux and (abi == .android or abi == .androideabi)) { -+ const ndk_lib: ?[]const u8 = blk: { -+ if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_LIB_DIR") catch null) |dir| { -+ break :blk dir; -+ } -+ if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null) |ndk_home| { -+ break :blk std.fmt.allocPrint(b.allocator, "{s}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/24", .{ndk_home}) catch null; -+ } -+ break :blk null; -+ }; -+ if (ndk_lib) |dir| { -+ // Use addObjectFile to directly link the libc.so stub without triggering -+ // Zig's native libc provision ++ const ndk_home = std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null; ++ const sysroot = if (ndk_home) |home| ++ std.fmt.allocPrint(b.allocator, "{s}/toolchains/llvm/prebuilt/linux-x86_64/sysroot", .{home}) catch null ++ else ++ null; ++ ++ // Allow an explicit override of the lib dir (e.g. when the NDK layout ++ // differs), otherwise default to aarch64-linux-android API 24. ++ const arch_lib = std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_LIB_DIR") catch null; ++ const crt_dir = if (arch_lib) |dir| ++ dir ++ else if (sysroot) |sr| ++ std.fmt.allocPrint(b.allocator, "{s}/usr/lib/aarch64-linux-android/24", .{sr}) catch null ++ else ++ null; ++ ++ if (crt_dir) |dir| { ++ // Use addObjectFile to directly link the libc.so stub without ++ // triggering Zig's native glibc/musl libc provision. + const libc_path = std.fmt.allocPrint(b.allocator, "{s}/libc.so", .{dir}) catch unreachable; + lib.addObjectFile(.{ .cwd_relative = libc_path }); ++ ++ // Zig does not bundle Android libc, so point it at the NDK sysroot ++ // (headers + CRT objects) via a libc file. Without this, linking ++ // fails with "unable to provide libc for target ... android". ++ if (sysroot) |sr| { ++ const include_dir = std.fmt.allocPrint(b.allocator, "{s}/usr/include", .{sr}) catch unreachable; ++ const sys_include_dir = std.fmt.allocPrint(b.allocator, "{s}/usr/include/aarch64-linux-android", .{sr}) catch unreachable; ++ // All six fields are required by LibCInstallation.parse. ++ const libc_contents = std.fmt.allocPrint( ++ b.allocator, ++ \\include_dir={s} ++ \\sys_include_dir={s} ++ \\crt_dir={s} ++ \\msvc_lib_dir= ++ \\kernel32_lib_dir= ++ \\gcc_dir= ++ \\ ++ , .{ include_dir, sys_include_dir, dir }) catch unreachable; ++ const libc_file = b.addWriteFiles().add("android-libc.txt", libc_contents); ++ lib.setLibCFile(libc_file); ++ } + } + } + diff --git a/scripts/build-opencode-android.ts b/scripts/build-opencode-android.ts index 1fe8d99..07d95b8 100644 --- a/scripts/build-opencode-android.ts +++ b/scripts/build-opencode-android.ts @@ -3,11 +3,10 @@ * Build OpenCode for Android (aarch64) * * This script: - * 1. Generates the models-snapshot.js - * 2. Loads migrations - * 3. Bundles OpenCode using Bun.build() with compile targeting the host platform - * 4. Extracts the module graph from the compiled binary - * 5. Appends it to our Android bun binary to create the final standalone + * 1. Loads migrations + * 2. Bundles OpenCode using Bun.build() with compile targeting the host platform + * 3. Extracts the module graph from the compiled binary + * 4. Appends it to our Android bun binary to create the final standalone */ import { $ } from "bun" @@ -28,50 +27,13 @@ if (!fs.existsSync(ANDROID_BUN)) { process.chdir(OPENCODE_DIR) -const VERSION = process.env.OPENCODE_VERSION || "1.3.13" +const VERSION = process.env.OPENCODE_VERSION || "1.18.31" const CHANNEL = process.env.OPENCODE_CHANNEL || "latest" console.log(`Building OpenCode v${VERSION} (channel: ${CHANNEL}) for Android aarch64`) -// Step 1: Generate models-snapshot.js -console.log("\n=== Step 1: Generating models-snapshot.js ===") -const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.dev" -let modelsData: string = "" -if (process.env.MODELS_DEV_API_JSON) { - modelsData = await Bun.file(process.env.MODELS_DEV_API_JSON).text() -} else { - console.log(`Fetching from ${modelsUrl}/api.json ...`) - let fetchErr: Error | null = null - for (let attempt = 1; attempt <= 3; attempt++) { - try { - const resp = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(15000) }) - if (!resp.ok) throw new Error(`HTTP ${resp.status}`) - modelsData = await resp.text() - fetchErr = null - break - } catch (err: any) { - fetchErr = err - console.error(` Attempt ${attempt}/3 failed: ${err.message}`) - if (attempt < 3) await new Promise(r => setTimeout(r, 2000 * attempt)) - } - } - if (fetchErr) { - console.error(`ERROR: Failed to fetch models after 3 attempts: ${fetchErr.message}`) - process.exit(1) - } -} -await Bun.write( - path.join(OPENCODE_DIR, "src/provider/models-snapshot.js"), - `// @ts-nocheck\n// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData}\n`, -) -await Bun.write( - path.join(OPENCODE_DIR, "src/provider/models-snapshot.d.ts"), - `// Auto-generated by build.ts - do not edit\nexport declare const snapshot: Record\n`, -) -console.log("Generated models-snapshot.js") - -// Step 2: Load migrations -console.log("\n=== Step 2: Loading migrations ===") +// Step 1: Load migrations +console.log("\n=== Step 1: Loading migrations ===") const migrationDirs = ( await fs.promises.readdir(path.join(OPENCODE_DIR, "migration"), { withFileTypes: true, @@ -101,9 +63,9 @@ const migrations = await Promise.all( ) console.log(`Loaded ${migrations.length} migrations`) -// Step 3: Build with Bun.build() --compile for the HOST platform +// Step 2: Build with Bun.build() --compile for the HOST platform // This creates a standalone binary for the host, from which we extract the module graph -console.log("\n=== Step 3: Bundling OpenCode ===") +console.log("\n=== Step 2: Bundling OpenCode ===") const plugin = createSolidTransformPlugin() @@ -119,7 +81,7 @@ try { } console.log(`Parser worker: ${parserWorkerResolved}`) -const workerPath = "./src/cli/cmd/tui/worker.ts" +const workerPath = "./src/cli/tui/worker.ts" const bunfsRoot = "/$bunfs/root/" const workerRelativePath = path.relative(OPENCODE_DIR, parserWorkerResolved).replaceAll("\\", "/") @@ -133,7 +95,7 @@ const hostBinaryPath = path.join(OUTPUT_DIR, "opencode-host") console.log("Building standalone binary for host platform...") const result = await Bun.build({ - conditions: ["browser"], + conditions: ["bun", "node"], tsconfig: "./tsconfig.json", plugins: [plugin], compile: { @@ -165,8 +127,8 @@ if (!result.success) { console.log(`Host standalone binary: ${hostBinaryPath}`) -// Step 4: Extract module graph from host binary -console.log("\n=== Step 4: Extracting module graph ===") +// Step 3: Extract module graph from host binary +console.log("\n=== Step 3: Extracting module graph ===") const hostBinary = await Bun.file(hostBinaryPath).arrayBuffer() const hostBytes = new Uint8Array(hostBinary) @@ -230,8 +192,8 @@ const moduleGraphBytes = hostBytes.slice(hostBunSize, hostBytes.length - 8) console.log(`Module graph extracted: ${moduleGraphBytes.length} bytes`) console.log(`Trailer verified: OK`) -// Step 5: Patch the module graph for Android -console.log("\n=== Step 5: Patching module graph for Android ===") +// Step 4: Patch the module graph for Android +console.log("\n=== Step 4: Patching module graph for Android ===") // The module graph format (from StandaloneModuleGraph.zig): // [string data: all file names, contents, sourcemaps, bytecodes concatenated] @@ -315,8 +277,8 @@ if (undiciPatchCount === 0) { var finalModuleGraph = mgBuf.slice(0, trailerPosInMg + mgTrailerBuf.length) console.log(`Module graph size: ${finalModuleGraph.length} bytes (unchanged)`) -// Step 6: Create Android standalone binary -console.log("\n=== Step 6: Creating Android standalone binary ===") +// Step 5: Create Android standalone binary +console.log("\n=== Step 5: Creating Android standalone binary ===") const androidBunBytes = new Uint8Array(await Bun.file(ANDROID_BUN).arrayBuffer()) const androidBunSize = androidBunBytes.length diff --git a/scripts/build-opencode.sh b/scripts/build-opencode.sh index 0333338..572910f 100755 --- a/scripts/build-opencode.sh +++ b/scripts/build-opencode.sh @@ -38,6 +38,22 @@ echo ">>> Installing OpenCode dependencies..." cd "$OPENCODE_SRC" "$HOST_BUN" install +# Install all platform binary variants so dynamic imports of the native +# packages resolve during bundling (plain `bun install` only fetches the +# current host's platform optional deps). Versions are read from the +# lockfile so they always match what OpenCode bundles. +LOCKFILE="$OPENCODE_SRC/bun.lock" +extract_pkg_version() { + grep -o '"@'"$1"'@[0-9][^"]*"' "$LOCKFILE" | head -n1 | sed 's/^"@[^@]*@//; s/"$//' +} + +OPENTUI_CORE_VER="$(extract_pkg_version 'opentui/core')" +PARCEL_WATCHER_VER="$(extract_pkg_version 'parcel/watcher')" +echo ">>> Installing @opentui/core@${OPENTUI_CORE_VER} for all platforms..." +"$HOST_BUN" install --os="*" --cpu="*" "@opentui/core@${OPENTUI_CORE_VER}" +echo ">>> Installing @parcel/watcher@${PARCEL_WATCHER_VER} for all platforms..." +"$HOST_BUN" install --os="*" --cpu="*" "@parcel/watcher@${PARCEL_WATCHER_VER}" + # Find the Android bun binary ANDROID_BUN="$BUN_BUILD/bun" if [ ! -f "$ANDROID_BUN" ]; then diff --git a/scripts/build-opentui.sh b/scripts/build-opentui.sh index cc87d8c..7dac48e 100755 --- a/scripts/build-opentui.sh +++ b/scripts/build-opentui.sh @@ -17,9 +17,12 @@ ZIG_BIN="${ZIG_BIN:-zig}" echo "=== Building libopentui.so for Android aarch64 ===" # Clone opentui if needed +# Pin to the tag matching the @opentui/core version bundled by the OpenCode +# release (v1.18.3 bundles @opentui/core@0.4.3). Cloning HEAD would build +# 0.5.x, whose native ABI does not match the 0.4.3 JS bindings. if [ ! -d "$OPENTUI_SRC/.git" ]; then - echo ">>> Cloning opentui..." - git clone --depth 1 https://github.com/anomalyco/opentui.git "$OPENTUI_SRC" + echo ">>> Cloning opentui (v${OPENTUI_VERSION})..." + git clone --depth 1 --branch "v${OPENTUI_VERSION}" https://github.com/anomalyco/opentui.git "$OPENTUI_SRC" else echo ">>> opentui source exists at $OPENTUI_SRC" fi diff --git a/scripts/env.sh b/scripts/env.sh index 541b874..7e0737b 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -13,7 +13,8 @@ export BUN_TAG="bun-v${BUN_VERSION}" export WEBKIT_COMMIT="${WEBKIT_COMMIT:-017930ebf915121f8f593bef61cbbca82d78132d}" export ICU_VERSION="${ICU_VERSION:-75.1}" export ZIG_VERSION="${ZIG_VERSION:-0.15.2}" -export OPENCODE_VERSION="${OPENCODE_VERSION:-1.3.13}" +export OPENCODE_VERSION="${OPENCODE_VERSION:-1.18.31}" +export OPENTUI_VERSION="${OPENTUI_VERSION:-0.4.5}" export ANDROID_API="${ANDROID_API:-24}" # Android NDK diff --git a/scripts/make-packages.sh b/scripts/make-packages.sh index 1787b69..ef29c2b 100755 --- a/scripts/make-packages.sh +++ b/scripts/make-packages.sh @@ -74,11 +74,11 @@ echo " Created $PACMAN_NAME" # ========================================== echo ">>> Creating deb package..." DEB_STAGING="$PKG_DIR/deb-staging" -mkdir -p "$DEB_STAGING/data/data/data/com.termux/files/usr/bin" +mkdir -p "$DEB_STAGING/data/data/com.termux/files/usr/bin" mkdir -p "$DEB_STAGING/DEBIAN" -cp "$OPENCODE_BINARY" "$DEB_STAGING/data/data/data/com.termux/files/usr/bin/opencode" -chmod 755 "$DEB_STAGING/data/data/data/com.termux/files/usr/bin/opencode" +cp "$OPENCODE_BINARY" "$DEB_STAGING/data/data/com.termux/files/usr/bin/opencode" +chmod 755 "$DEB_STAGING/data/data/com.termux/files/usr/bin/opencode" # Create control file INSTALLED_SIZE=$((BINARY_SIZE / 1024)) @@ -99,8 +99,11 @@ EOF DEB_NAME="opencode_${OPENCODE_VERSION}_aarch64.deb" -# Build deb manually (dpkg-deb may not be available) -cd "$DEB_STAGING/data" +# Build deb manually (dpkg-deb may not be available). +# The tar must contain "data/data/com.termux/..." (2x data) so dpkg extracts +# to /data/data/com.termux/files/usr/bin/opencode. Run tar from the staging +# ROOT so the leading "data" component is preserved. +cd "$DEB_STAGING" tar czf "$DEB_STAGING/data.tar.gz" data cd "$DEB_STAGING/DEBIAN" tar czf "$DEB_STAGING/control.tar.gz" control