Files
DarkRoom/docker/android/package.sh
T
dtourolleandClaude Opus 5 24bf5be574 Compile our own Java into the APK, so the classes Android constructs can exist
The APK's only dex was Slint's. `assemble-apk.sh` found `classes.dex` under
the android-activity backend's build directory and copied it in, and there was
no `javac` step and no `d8` of anything of ours — package.sh's header said so
outright, on the reasoning that the app has no Java because android-activity
calls `android_main` directly.

That reasoning holds for everything the app *calls* and fails for everything
Android *constructs*. A `ContentProvider` is instantiated by the system from
its manifest entry; nothing in the process ever reaches its constructor, so
there is no JNI route to writing one in Rust. The launch `Intent` is the same
shape of problem from the other end: it arrives through `Activity.getIntent()`,
and the activity android-activity hands out is a stock `NativeActivity` rather
than a subclass with room for code. FR-PLAT-AND-6 needs both, and FR-PLAT-AND-4
needs a foreground `Service`, which is a third.

So: everything under `apps/darkroom-android/android/java/` goes through javac
against `android.jar`, and d8 merges the classes with Slint's finished dex into
one `classes.dex`. Merging rather than emitting a second dex keeps the staging
and zip steps as they are — multidex is native at API 28, but two files to keep
in step buys nothing at this size.

The step is skipped when the tree holds no Java, which is the state this commit
leaves it in. Nothing about the APK changes until a `.java` file appears.

`-source 8 -target 8 -bootclasspath android.jar` is not caution about language
features. It is the last combination in which javac allows the boot class path
to be replaced: from `-target 9` the flag is rejected, the platform classes
come from the JDK instead of from android.jar, and the build stays green while
the device raises `NoClassDefFoundError` for a class Android never shipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 23:18:18 +02:00

90 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build DarkRoom into an installable APK, without Gradle.
#
# ./docker/android/package.sh # build + package, debug-signed
# ./docker/android/package.sh --install # ...and adb install to a device
#
# Gradle would add a second build system, a second dependency tree, and a
# second place for the toolchain versions to drift out of step with the
# Dockerfile. The tools it would have driven — javac, aapt2, d8, zipalign,
# apksigner — are in the image already and are enough on their own, because
# the app is Rust: android-activity's glue calls android_main directly, and
# the only Java in the APK is Slint's helper plus the handful of classes
# Android insists on constructing itself (see assemble-apk.sh's Java step).
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "${HERE}/../.." && pwd)"
ABI="arm64-v8a"
RUST_TARGET="aarch64-linux-android"
PKG_DIR="${REPO}/apps/darkroom-android/android"
# The container writes here (see build.sh); the APK is assembled in the same
# place so both halves of the build agree on one output directory.
CACHE="${XDG_CACHE_HOME:-${HOME}/.cache}/darkroom-android"
# Under the cache's target/ rather than beside it: the container writes to
# /work/target-android/apk, and that is the directory bind-mounted here.
OUT="${CACHE}/target/apk"
APK="${OUT}/darkroom.apk"
# The version, taken from the workspace rather than restated here.
#
# `AndroidManifest.xml` deliberately declares none: a manifest that states a
# version is a second place for one to be wrong, and it was — the APK reported
# `versionName=null` and `versionCode=0` on the device while the binary inside
# it knew perfectly well what it was.
#
# `versionCode` must be a single increasing integer, which a semantic version
# is not, so it is packed: MAJOR*10000 + MINOR*100 + PATCH. That keeps the
# ordering Android needs (it refuses to install an APK whose code is lower than
# the installed one) and stays readable — 0.4.0 is 400. It allows 99 minors and
# 99 patches per major, which is a limit worth knowing about and a long way off.
VERSION_NAME="$(sed -n 's/^version = "\(.*\)"$/\1/p' "${REPO}/Cargo.toml" | head -1)"
if [[ -z "${VERSION_NAME}" ]]; then
echo "error: no version in Cargo.toml" >&2
exit 1
fi
VERSION_CODE="$(awk -F. '{ print $1 * 10000 + $2 * 100 + $3 }' <<< "${VERSION_NAME}")"
echo "==> version ${VERSION_NAME} (code ${VERSION_CODE})"
INSTALL=0
[[ "${1:-}" == "--install" ]] && INSTALL=1
# ---------------------------------------------------------------------------
# 1. Cross-compile the shared library.
# ---------------------------------------------------------------------------
echo "==> building libdarkroom.so (${ABI})"
"${HERE}/build.sh" cargo ndk -t "${ABI}" -o /work/target-android/jniLibs \
build --release -p darkroom-android
SO="${CACHE}/target/jniLibs/${ABI}/libdarkroom.so"
[[ -f "${SO}" ]] || { echo "error: ${SO} not built" >&2; exit 1; }
# ---------------------------------------------------------------------------
# 2. Assemble the APK inside the container, where the SDK lives.
#
# The assembly itself is assemble-apk.sh, which runs in the image and is shared
# with CI — see its header. Only the mount layout is decided here: the repo is
# at /work and the cache's target directory at /work/target-android, so every
# default in that script already points at the right place.
# ---------------------------------------------------------------------------
echo "==> packaging APK"
"${HERE}/build.sh" env \
ABI="${ABI}" RUST_TARGET="${RUST_TARGET}" \
DARKROOM_DEBUGGABLE="${DARKROOM_DEBUGGABLE:-}" \
/work/docker/android/assemble-apk.sh
# ---------------------------------------------------------------------------
# 3. Install from the host.
#
# The container has adb but no device: build.sh mounts no USB and shares no
# network, so the host's already-authorised adb server is the shorter path.
# ---------------------------------------------------------------------------
if [[ "${INSTALL}" == "1" ]]; then
command -v adb >/dev/null || { echo "error: adb not on PATH" >&2; exit 1; }
echo "==> installing"
adb install -r "${APK}"
echo "==> launching"
adb shell am start -n paris.tourolle.darkroom/android.app.NativeActivity
fi