Benchmarks / CPU and I/O (per commit) (push) Successful in 2m52s
Benchmarks / Frame budget (on demand) (push) Skipped
Build and test / Desktop (Linux) (push) Successful in 44m13s
Build and test / Layer separation (push) Successful in 56s
🐳 Android image / Build and push (push) Successful in 17m16s
Build and test / android-image (push) Successful in 17m17s
Traceability / Requirement traces (push) Successful in 1m6s
Build and test / Android (aarch64) (push) Successful in 23m37s
The release keystore now exists and its four secrets are loaded into Gitea, so CI produces an APK a device can update in place. Until now every build, CI and local alike, was signed with a throwaway debug key -- CI's fresh per run, the local one exactly as durable as the cache directory it lived in -- and the night that cache was cleared, no build anywhere could install over the tablet's copy. package.sh forwards KEYSTORE_PASS, KEY_PASS and KEY_ALIAS into the container and copies the keystore under the mounted target directory for the build, so a local release-signed build is one environment line. The doc records where the local copy of the key lives.
115 lines
5.3 KiB
Bash
Executable File
115 lines
5.3 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.
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Release signing, when asked for. assemble-apk.sh selects it by the presence
|
|
# of KEYSTORE_PASS (see its header), and the keystore has to be reachable from
|
|
# inside the container, so a host path in KEYSTORE is copied under the mounted
|
|
# target directory for the duration of the build and removed after. The
|
|
# passwords travel as environment, never as arguments -- docs/android-signing.md
|
|
# has the incantation.
|
|
# ---------------------------------------------------------------------------
|
|
echo "==> packaging APK"
|
|
SIGNING_ENV=()
|
|
CONTAINER_KEYSTORE=""
|
|
if [[ -n "${KEYSTORE_PASS:-}" ]]; then
|
|
[[ -f "${KEYSTORE:-}" ]] || { echo "error: KEYSTORE_PASS is set but KEYSTORE is not a file" >&2; exit 1; }
|
|
install -m 600 "${KEYSTORE}" "${CACHE}/target/release.keystore"
|
|
CONTAINER_KEYSTORE="${CACHE}/target/release.keystore"
|
|
SIGNING_ENV=(
|
|
KEYSTORE=/work/target-android/release.keystore
|
|
KEYSTORE_PASS="${KEYSTORE_PASS}"
|
|
KEY_PASS="${KEY_PASS:-${KEYSTORE_PASS}}"
|
|
KEY_ALIAS="${KEY_ALIAS:?KEY_ALIAS is required when KEYSTORE_PASS is set}"
|
|
)
|
|
fi
|
|
"${HERE}/build.sh" env \
|
|
ABI="${ABI}" RUST_TARGET="${RUST_TARGET}" \
|
|
DARKROOM_DEBUGGABLE="${DARKROOM_DEBUGGABLE:-}" \
|
|
"${SIGNING_ENV[@]}" \
|
|
/work/docker/android/assemble-apk.sh
|
|
if [[ -n "${CONTAINER_KEYSTORE}" ]]; then
|
|
rm -f "${CONTAINER_KEYSTORE}"
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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
|