package.sh does two things: it decides how the host reaches the image, and it assembles an APK once inside it. Only the first half is host-specific. CI already runs in that image, so the second half was about to be copied into a workflow step -- two copies of aapt2/zipalign/apksigner ordering, drifting apart at whatever rate the toolchain moves. So it moves to docker/android/assemble-apk.sh, which assumes it is inside the image and takes its paths from the environment, because the callers disagree about them: the container mounts the repo at /work, the runner checks it out wherever it likes. Every default reproduces what package.sh did, so the host path is unchanged. Two things stop being hard-coded on the way. The build-tools version and the compile SDK are resolved from what is installed rather than written out as 36.0.0 and android-36 -- the versions are Dockerfile ARGs, and a second copy is a second thing to miss when they move. --min-sdk-version now comes from that same ARG instead of a literal 28, which is the number the API-level check in CI already reads. The intermediates are removed at the end. They were harmless in a cache directory nobody looks at; beside a published artefact they are four more files for a glob to pick up by mistake. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
4.1 KiB
Bash
Executable File
89 lines
4.1 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 four tools it would have driven — aapt2, d8, zipalign,
|
|
# apksigner — are in build-tools already and are enough on their own, because
|
|
# the app has no Java of its own: android-activity's glue calls android_main
|
|
# directly, and the only classes in the APK are the ones Slint's build script
|
|
# compiles for its own helper.
|
|
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}" \
|
|
/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
|