Files
DarkRoom/docker/android/README.md
T
dtourolleandClaude Opus 5 b044a8c067 Build the Android CI image in CI, not on a laptop
The Android job ran in gitea.tourolle.paris/dtourolle/darkroom-android:latest,
a tag that had never been pushed. The image existed only as a local
darkroom-android:latest on one machine, so every Android job died at docker
pull with "manifest unknown" before reaching a step. The registry API confirms
it: that manifest is a 404 while the other builder images answer 200.

android-image.yml now builds and pushes it, following KPN's docker.yaml — host
runner rather than a container, so it has the Docker daemon and the host's
cached registry credentials, and a plain-git checkout because that host has no
Node for actions/checkout.

Where it diverges from KPN: that workflow gates on dorny/paths-filter running
inside the builder image, which here would need the very image that is missing.
The tag is the git tree hash of docker/android instead, which changes when and
only when a file there changes. An unrelated push reuses the image, a Dockerfile
edit cannot keep serving a stale latest, and a missing tag rebuilds itself
without a manual step.

The presence probe is curl against the registry API, not `docker manifest
inspect`. The latter exits 1 on this registry even for tags that are plainly
there — jellytau-builder:latest answers HTTP 200 while docker reports "manifest
unknown" for it — and trusting it would have rebuilt 7 GB on every push. The
HEAD request also yields Docker-Content-Digest, so latest is repointed only when
the digests actually disagree, without pulling any layers.

A probe that cannot authenticate falls through to building. Rebuilding when it
was unnecessary costs minutes; skipping a build that was needed is the failure
this commit exists to remove.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 14:59:57 +02:00

3.3 KiB

Android build environment

Reproducible container for cross-compiling DarkRoom to Android. CI and local builds use the same image, so a break appears in one place rather than two.

Use

# Cross-compile for a device ABI
./docker/android/build.sh cargo ndk -t arm64-v8a build --release

# All four ABIs
./docker/android/build.sh cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 -t x86 build --release

# Type-check without linking
./docker/android/build.sh cargo check --target aarch64-linux-android

# Interactive shell
./docker/android/build.sh

# After editing the Dockerfile
./docker/android/build.sh --rebuild

Prefers podman, falls back to docker. First run builds the image, which takes several minutes; after that it is cached.

In CI

.gitea/workflows/android-image.yml builds this image and pushes it to gitea.tourolle.paris/dtourolle/darkroom-android, which the Android job then runs inside. It is called on every push and finishes in seconds unless something here changed — the image is tagged with the git tree hash of docker/android/, so a rebuild happens when and only when a file in this directory does.

Nothing needs pushing by hand. Editing the Dockerfile is the trigger; latest follows automatically. To force a rebuild without a content change, run the workflow from the Gitea UI with force set to true.

The job runs on the host runner rather than in a container, because it needs the Docker daemon and the runner host's cached registry credentials.

Pinned versions

Component Version Why this one
JDK 17 AGP's stable target. The host's JDK 25 breaks Gradle.
Compile SDK API 36 Required for Play distribution
Min API 28 (Android 9) Floor where Vulkan support is dependable (NFR-COMPAT-1)
Build tools 36.0.0 Matches compile SDK
NDK 27.2.12479018 (r27c) Current LTS
Rust 1.92.0 Slint 1.17 requires it; cargo-ndk 4.1.x needs ≥ 1.86
cargo-ndk 4.1.2

Bumping any of these is a reviewable change, not silent drift.

Compile SDK versus min API

These are different and both matter. ANDROID_API (36) is what the SDK compiles against — the newest APIs available. MIN_API (28) is what native code links against, and determines the oldest device that can run the result.

cargo-ndk defaults to API 21 if not told otherwise, which is far below what this app needs. CARGO_NDK_PLATFORM and the per-target linker paths both pin it to MIN_API. Verify with:

file target/aarch64-linux-android/release/*.so
# → "... for Android 28, built by NDK r27c"

Caching

The cargo registry and target directory persist in ~/.cache/darkroom-android, so rebuilds do not re-download the crate index. Clear with:

rm -rf ~/.cache/darkroom-android

The Android target dir is kept separate from the host's target/ — sharing one directory between host and container builds causes constant rebuilds as they invalidate each other's fingerprints.

What is not here

No emulator, no adb device access. This image cross-compiles; it does not run or deploy. Device testing (spikes S2 and S10, which need two GPU vendors) happens on real hardware — the emulator's GPU behaviour is not representative of Adreno or Mali, which is precisely what those spikes exist to test.