APK build: persistent ccache + Gradle cache + export-only target
The Android CI job recompiled all native C++ (whisper.cpp, reanimated, worklets, gesture-handler) from scratch every push — ~41 min on the shared runner — because nothing survived the `COPY . .` layer invalidation, and `--load` exported the whole ~15GB image just to copy one APK out. Dockerfile (docker/android.Dockerfile): - Install ccache and patch the NDK CMake toolchain file so every module's externalNativeBuild routes C/C++ compiles + links through ccache (the third-party modules don't honor ccache on their own). ccache dir is a persistent BuildKit cache mount, so objects survive across builds. - Mount GRADLE_USER_HOME as a cache and pass --build-cache --parallel, so Kotlin/Java/resource/dex tasks and resolved deps persist too. - Cache-mount the bun install dir; move the nodejs install into the cached toolchain layer (was reinstalling on every source change). - New `apk` stage (FROM scratch) holding just the signed APK, for --output type=local extraction without loading the 15GB image. CI (.gitea/workflows/ci.yml): - build-apk now builds `--target apk --output type=local,dest=./out` and streams ./out/app-release.apk to /srv/wisp/wisp.apk — no --load, no docker create/cp, and the build image no longer piles up on the host. Validated locally (arm64, real sign+assemble path): cold gradle 5m5s -> warm gradle 57s; 61/61 C++ compiles served from ccache (100% hit); 226/578 Gradle tasks from cache; export-only build 60s wall, APK valid + v2/v3 signed. The first server build after this is still cold (~41 min) to populate the caches; pushes after that are much faster. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+17
-12
@@ -68,9 +68,11 @@ jobs:
|
||||
# ---- build-apk: signed release APK, served at /wisp.apk ------------------
|
||||
# Socket-safe: the whole Android build runs inside `docker buildx build`
|
||||
# (context streamed to the host BuildKit — no host bind-mounts), signed via
|
||||
# BuildKit --secret from the ANDROID_* repo secrets. We then extract the APK
|
||||
# with docker create/cp and copy it into the running `wisp` web container so
|
||||
# it is downloadable at https://wisp.briggen.dev/wisp.apk.
|
||||
# BuildKit --secret from the ANDROID_* repo secrets. The `apk` target +
|
||||
# `--output type=local` write just the APK to ./out (no image load), which we
|
||||
# stream into /srv/wisp/wisp.apk so it's downloadable at
|
||||
# https://wisp.briggen.dev/wisp.apk. Speed comes from persistent BuildKit
|
||||
# cache mounts (ccache for C++, Gradle build cache) declared in the Dockerfile.
|
||||
build-apk:
|
||||
# Auto-build the APK on every push to master (arm64-only keeps it ~minutes).
|
||||
# Runs after deploy-web so the web updates promptly, then the APK follows and
|
||||
@@ -89,23 +91,26 @@ jobs:
|
||||
printf '%s' "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" > "$RUNNER_TEMP/sec/ks_pass"
|
||||
printf '%s' "${{ secrets.ANDROID_KEY_ALIAS }}" > "$RUNNER_TEMP/sec/ks_alias"
|
||||
printf '%s' "${{ secrets.ANDROID_KEY_PASSWORD }}" > "$RUNNER_TEMP/sec/ks_keypass"
|
||||
- name: Build signed APK image
|
||||
- name: Build + extract the signed APK (export-only target, no image load)
|
||||
run: |
|
||||
# `--target apk --output type=local` writes ONLY app-release.apk to
|
||||
# ./out — no `--load` of the ~15GB build image (saves the multi-minute
|
||||
# export/unpack each run and stops that image filling the host disk).
|
||||
# The persistent ccache + Gradle build cache (BuildKit cache mounts in
|
||||
# the Dockerfile) make C++ recompiles near-instant on warm runs.
|
||||
docker buildx build \
|
||||
--secret id=ks_b64,src="$RUNNER_TEMP/sec/ks_b64" \
|
||||
--secret id=ks_pass,src="$RUNNER_TEMP/sec/ks_pass" \
|
||||
--secret id=ks_alias,src="$RUNNER_TEMP/sec/ks_alias" \
|
||||
--secret id=ks_keypass,src="$RUNNER_TEMP/sec/ks_keypass" \
|
||||
-f docker/android.Dockerfile -t wisp-android:ci --load .
|
||||
- name: Extract APK + publish to the host (served at /wisp.apk via the compose volume)
|
||||
--target apk --output type=local,dest=./out \
|
||||
-f docker/android.Dockerfile .
|
||||
ls -lh ./out/app-release.apk
|
||||
- name: Publish APK to the host (served at /wisp.apk via the compose volume)
|
||||
run: |
|
||||
cid=$(docker create wisp-android:ci)
|
||||
docker cp "$cid:/workspace/android/app/build/outputs/apk/release/app-release.apk" ./app-release.apk
|
||||
docker rm "$cid"
|
||||
ls -lh app-release.apk
|
||||
# Stream the APK into a host-mounted dir so it survives web container
|
||||
# recreation (the wisp container mounts /srv/wisp/wisp.apk read-only).
|
||||
docker run --rm -i -v /srv/wisp:/out alpine:3 sh -c 'mkdir -p /out && cat > /out/wisp.apk' < ./app-release.apk
|
||||
docker run --rm -i -v /srv/wisp:/out alpine:3 sh -c 'mkdir -p /out && cat > /out/wisp.apk' < ./out/app-release.apk
|
||||
- name: Cleanup staged secrets
|
||||
if: always()
|
||||
run: rm -rf "$RUNNER_TEMP/sec"
|
||||
@@ -114,4 +119,4 @@ jobs:
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: wisp-release-apk
|
||||
path: app-release.apk
|
||||
path: ./out/app-release.apk
|
||||
|
||||
Reference in New Issue
Block a user