CI: cache Android SDK/NDK, Gradle, and npm across APK builds
Persist three Docker volumes into the build job (android-sdk, gradle home, npm cache) and enable the Gradle build cache, so repeat builds skip the ~2GB NDK download, Maven dependency resolution, and unchanged native/Kotlin compilation — cutting builds from ~1h to a few minutes after the first run. The runner must whitelist these volumes via config.yaml (container.valid_volumes); deploy-runner.sh now writes that config, pre-creates the volumes, and starts the daemon with --config. Requires re-running deploy-runner.sh on the runner host before the next tag. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1ca2c38fbf
commit
3583c2e15f
5 changed files with 89 additions and 12 deletions
|
|
@ -11,14 +11,27 @@ jobs:
|
|||
runs-on: docker
|
||||
container:
|
||||
image: node:22-bookworm
|
||||
# Persistent caches across runs (Docker named volumes). These must be
|
||||
# allowed in the runner's config.yaml `container.valid_volumes` — the
|
||||
# deploy-runner.sh script sets that up. First run populates them (~1h);
|
||||
# later runs reuse the SDK/NDK, Gradle deps + build cache, and npm cache,
|
||||
# dropping the build to a few minutes.
|
||||
volumes:
|
||||
- camptickets-android-sdk:/opt/android-sdk
|
||||
- camptickets-gradle:/root/.gradle
|
||||
- camptickets-npm:/root/.npm
|
||||
env:
|
||||
ANDROID_HOME: /opt/android-sdk
|
||||
ANDROID_SDK_ROOT: /opt/android-sdk
|
||||
GRADLE_USER_HOME: /root/.gradle
|
||||
# Force IPv4 for all JVMs (Gradle launcher, daemon, Kotlin/CMake workers).
|
||||
# The runner host has no working IPv6 route, so Maven Central (which has
|
||||
# AAAA records) was unreachable — this makes Java ignore AAAA and use IPv4.
|
||||
JAVA_TOOL_OPTIONS: -Djava.net.preferIPv4Stack=true
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false
|
||||
# org.gradle.caching enables the local build cache (persisted in
|
||||
# GRADLE_USER_HOME), so unchanged native/Kotlin tasks are restored instead
|
||||
# of recompiled.
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx4g -Dorg.gradle.daemon=false -Dorg.gradle.caching=true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -36,16 +49,25 @@ jobs:
|
|||
apt-get install -y --no-install-recommends openjdk-17-jdk-headless unzip wget git
|
||||
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install Android SDK
|
||||
- name: Install Android SDK (cached)
|
||||
run: |
|
||||
set -eux
|
||||
set -eu
|
||||
# Skip the whole install when the cache volume already has the SDK.
|
||||
# (NDK + CMake are auto-installed by Gradle into the same volume on
|
||||
# the first build, so they persist too.)
|
||||
if [ ! -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then
|
||||
echo "Installing Android command-line tools..."
|
||||
mkdir -p "$ANDROID_HOME/cmdline-tools"
|
||||
cd /tmp
|
||||
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdtools.zip
|
||||
unzip -q cmdtools.zip -d "$ANDROID_HOME/cmdline-tools"
|
||||
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
|
||||
else
|
||||
echo "Android SDK found in cache volume — skipping download."
|
||||
fi
|
||||
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"
|
||||
yes | sdkmanager --licenses >/dev/null || true
|
||||
yes | sdkmanager --licenses >/dev/null 2>&1 || true
|
||||
# sdkmanager is a no-op for packages already present in the volume.
|
||||
sdkmanager --install "platform-tools" \
|
||||
"platforms;android-36" "platforms;android-35" \
|
||||
"build-tools;36.0.0" "build-tools;35.0.0" >/dev/null
|
||||
|
|
@ -73,7 +95,7 @@ jobs:
|
|||
CAMPSCAN_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease --init-script ../../ci/signing.gradle --no-daemon
|
||||
./gradlew assembleRelease --init-script ../../ci/signing.gradle --no-daemon --build-cache
|
||||
mkdir -p "$GITHUB_WORKSPACE/artifacts"
|
||||
cp app/build/outputs/apk/release/app-release.apk \
|
||||
"$GITHUB_WORKSPACE/artifacts/camp-scan-${{ steps.ver.outputs.tag }}.apk"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,27 @@ The runner advertises the `docker` label; the build workflow (`.forgejo/workflow
|
|||
2. Push a tag: `git tag v0.1.0 && git push origin v0.1.0`.
|
||||
3. The runner builds `camp-scan-v0.1.0.apk` and attaches it to a Forgejo release; Obtainium picks it up.
|
||||
|
||||
## Build caching (fast repeat builds)
|
||||
|
||||
The first APK build takes ~1h (downloads Gradle + the ~2GB Android NDK, resolves
|
||||
all Maven deps, compiles native modules). After that it should drop to a few
|
||||
minutes because three persistent Docker volumes are reused across runs:
|
||||
|
||||
| Volume | Holds |
|
||||
|---|---|
|
||||
| `camptickets-android-sdk` → `/opt/android-sdk` | SDK, NDK, CMake |
|
||||
| `camptickets-gradle` → `/root/.gradle` | Gradle dist, Maven deps, local build cache |
|
||||
| `camptickets-npm` → `/root/.npm` | npm download cache |
|
||||
|
||||
For this to work the runner must **allow** these volumes via `config.yaml`
|
||||
(`container.valid_volumes`) — `deploy-runner.sh` writes that config, pre-creates
|
||||
the volumes, and starts the daemon with `--config /data/config.yaml`. If you set
|
||||
the runner up by hand, copy `config.yaml` next to the compose file and add
|
||||
`--config /data/config.yaml` to the daemon command.
|
||||
|
||||
To force a clean rebuild, remove the volumes:
|
||||
`docker volume rm camptickets-android-sdk camptickets-gradle camptickets-npm`.
|
||||
|
||||
## Notes
|
||||
|
||||
- The runner I initially registered on the app host has been removed. If Forgejo
|
||||
|
|
@ -37,3 +58,6 @@ The runner advertises the `docker` label; the build workflow (`.forgejo/workflow
|
|||
*Settings → Actions → Runners*.
|
||||
- `DOCKER_GID` must match the roomy server's docker socket group, or the runner
|
||||
can't reach the Docker daemon.
|
||||
- After updating to a caching-enabled runner, re-run `deploy-runner.sh` (or
|
||||
`docker compose up -d` in the runner dir) so the new `config.yaml` + volumes
|
||||
take effect, then push a fresh tag.
|
||||
|
|
|
|||
9
runner/config.yaml
Normal file
9
runner/config.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Forgejo runner config. The important bit is valid_volumes: it whitelists the
|
||||
# named Docker volumes the build-apk workflow mounts as persistent caches
|
||||
# (Android SDK/NDK, Gradle home, npm cache). Without these listed, the runner
|
||||
# rejects the workflow's `volumes:` and the job fails.
|
||||
container:
|
||||
valid_volumes:
|
||||
- camptickets-android-sdk
|
||||
- camptickets-gradle
|
||||
- camptickets-npm
|
||||
|
|
@ -11,6 +11,9 @@ services:
|
|||
- "${DOCKER_GID:-988}"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
# config.yaml allows the build workflow to mount the persistent cache
|
||||
# volumes (valid_volumes); without it those mounts are rejected.
|
||||
- ./config.yaml:/data/config.yaml:ro
|
||||
# Runner spawns job containers via the host Docker daemon.
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file: .env
|
||||
|
|
@ -31,4 +34,4 @@ services:
|
|||
--name "${RUNNER_NAME:-camptickets-runner}" \
|
||||
--labels "docker:docker://node:22-bookworm"
|
||||
fi
|
||||
exec forgejo-runner daemon
|
||||
exec forgejo-runner daemon --config /data/config.yaml
|
||||
|
|
|
|||
|
|
@ -61,6 +61,24 @@ mkdir -p "$INSTALL_DIR/data"
|
|||
# The runner image runs as uid/gid 1000 and must own its config dir.
|
||||
chown -R 1000:1000 "$INSTALL_DIR/data"
|
||||
|
||||
# Runner config: allow the build workflow to mount the persistent cache volumes
|
||||
# (Android SDK/NDK, Gradle home, npm cache) so APK builds don't redownload and
|
||||
# recompile everything each run. Without valid_volumes listed, the runner
|
||||
# rejects the workflow's `volumes:` and the job fails.
|
||||
cat > "$INSTALL_DIR/config.yaml" <<'CONFIG'
|
||||
container:
|
||||
valid_volumes:
|
||||
- camptickets-android-sdk
|
||||
- camptickets-gradle
|
||||
- camptickets-npm
|
||||
CONFIG
|
||||
chown 1000:1000 "$INSTALL_DIR/config.yaml"
|
||||
|
||||
# Pre-create the named cache volumes (idempotent).
|
||||
docker volume create camptickets-android-sdk >/dev/null
|
||||
docker volume create camptickets-gradle >/dev/null
|
||||
docker volume create camptickets-npm >/dev/null
|
||||
|
||||
cat > "$INSTALL_DIR/docker-compose.yml" <<COMPOSE
|
||||
services:
|
||||
runner:
|
||||
|
|
@ -72,6 +90,7 @@ services:
|
|||
- "\${DOCKER_GID}"
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./config.yaml:/data/config.yaml:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
env_file: .env
|
||||
command:
|
||||
|
|
@ -86,7 +105,7 @@ services:
|
|||
--name "\${RUNNER_NAME}" \\
|
||||
--labels "${JOB_LABEL}"
|
||||
fi
|
||||
exec forgejo-runner daemon
|
||||
exec forgejo-runner daemon --config /data/config.yaml
|
||||
COMPOSE
|
||||
|
||||
# Preserve an existing token line if re-running without a new token.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue