Files
Gathin23 3cacdd57c7 tools: docker-build builds and runs the image as x86-64, so an Apple Silicon Mac gets an APK (#2941)
On an arm64 host the image came out arm64, and eight minutes in :app:mergeReleaseResources failed: the
Linux AAPT2 the Android Gradle plugin downloads is published for x86-64 only. Both twins now pass
--platform linux/amd64 to docker build and docker run; on an x86-64 computer that changes nothing. The
reporter's run under Rosetta built in 7m20s cold and installed on an Android TV 9 set.

docker-build.md's Apple Silicon section says so, names the Rosetta and Colima settings, and gives the one
command that removes an arm64 image an older copy left behind. The first-run tests expect the flag on the
build, the shell twin is run once more to see it on the run, and a source check covers the PowerShell twin
where pwsh is missing.
2026-11-02 11:56:31 +05:30

89 lines
4.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build the launcher or an add-on inside Docker, so this computer needs no JDK or Android SDK.
# Usage: tools/docker-build.sh [launcher | addons/<name>] [gradle task ...]
# tools/docker-build.sh the launcher, assembleRelease
# tools/docker-build.sh addons/weather an add-on
# tools/docker-build.sh launcher testReleaseUnitTest lintRelease assembleRelease
# The APK lands where a native build puts it (<project>/app/build/outputs/apk/release/).
# Install the launcher with: SKIP_BUILD=1 TV_HOST=<tv ip> launcher/build-and-install.sh
# Guide, signing key included: tools/docker-build.md
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
PROJECT="${1:-launcher}"
[ $# -gt 0 ] && shift
TASKS=("$@")
[ ${#TASKS[@]} -gt 0 ] || TASKS=(assembleRelease)
IMAGE="${IMAGE:-cleanyourtv-android-build}"
GRADLE_VOLUME="${GRADLE_VOLUME:-cleanyourtv-gradle}"
# Where the debug signing key lives inside the container. The default is the image's own, kept in the Gradle volume;
# KEY_DIR picks another place in it, HOST_KEY_DIR mounts a folder from this computer (your ~/.android, say).
KEY_DIR="${KEY_DIR:-/gradle/.android}"
PROJECT="${PROJECT%/}"
if [ ! -f "$ROOT/$PROJECT/settings.gradle" ] && [ ! -f "$ROOT/$PROJECT/settings.gradle.kts" ]; then
echo "ERROR: $PROJECT is not a Gradle project in this repository (try launcher or addons/<name>)." >&2
exit 1
fi
command -v docker >/dev/null || { echo "ERROR: docker is not installed or not on PATH." >&2; exit 1; }
KEY_ARGS=()
if [ -n "${HOST_KEY_DIR:-}" ]; then
# Docker bind-mounts an absolute path that is there, and nothing else: given a folder that is not there it
# creates it (empty, and owned by root on Linux), and given a relative path it reads the name as a volume. So
# /key would be an empty folder or volume, the build would sign with a new debug key, and the next install over
# the app already on the TV would fail with INSTALL_FAILED_UPDATE_INCOMPATIBLE - the failure "The signing key"
# in tools/docker-build.md explains, reached without a word. A quoted '~/.android' lands here too: no shell
# expands a tilde inside a variable. A drive-letter path is absolute as well, which is the form a Windows
# reader's HOST_KEY_DIR takes under Git Bash - tools/theme.sh reads C:/Users/... the same way (#1796).
case "$HOST_KEY_DIR" in
/*|[A-Za-z]:[\\/]*) ;;
*)
echo "ERROR: HOST_KEY_DIR=$HOST_KEY_DIR is not an absolute path." >&2
echo "Docker reads a relative one as the name of a volume, so the build would make a new debug key." >&2
echo "Pass the folder your native build uses: \"\$HOME/.android\" ('~/.android' in quotes is not expanded)." >&2
exit 1
;;
esac
if [ ! -d "$HOST_KEY_DIR" ]; then
echo "ERROR: HOST_KEY_DIR=$HOST_KEY_DIR is not a folder on this computer." >&2
echo "Docker would not mount your native key there, the build would make a new debug key, and an install" >&2
echo "over the app already on the TV would fail with INSTALL_FAILED_UPDATE_INCOMPATIBLE (tools/docker-build.md)." >&2
exit 1
fi
KEY_ARGS=(-v "$HOST_KEY_DIR:/key")
KEY_DIR=/key
fi
# Always x86-64, on an Apple Silicon Mac or an ARM board too: the Linux AAPT2 the Android Gradle plugin downloads
# is published for x86-64 only, so an arm64 image stops at :app:mergeReleaseResources minutes in (#2941). Docker
# Desktop runs it under Rosetta or QEMU; on an x86-64 computer the flag changes nothing.
PLATFORM=linux/amd64
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
echo "Building the $IMAGE image once (JDK 17, Android SDK 34, build-tools 34.0.0; a few minutes)..."
docker build --platform "$PLATFORM" -t "$IMAGE" "$ROOT/tools/docker"
fi
# Git Bash on Windows rewrites /paths in arguments and gives POSIX paths Docker can't mount.
SRC="$ROOT"
USER_ARGS=()
case "$(uname -s)" in
MINGW* | MSYS* | CYGWIN*)
export MSYS_NO_PATHCONV=1
SRC="$(cd "$ROOT" && pwd -W)"
;;
*)
# The caller's uid, so the build output is not owned by root.
USER_ARGS=(--user "$(id -u):$(id -g)")
;;
esac
# The whole repository is mounted: an add-on's gradlew may call ../../launcher/gradlew.
docker run --rm --platform "$PLATFORM" ${USER_ARGS[@]+"${USER_ARGS[@]}"} ${KEY_ARGS[@]+"${KEY_ARGS[@]}"} \
-v "$SRC:/repo" -v "$GRADLE_VOLUME:/gradle" -w "/repo/$PROJECT" \
-e ANDROID_USER_HOME="$KEY_DIR" \
"$IMAGE" sh ./gradlew --no-daemon --console=plain "${TASKS[@]}"
for apk in "$ROOT/$PROJECT"/app/build/outputs/apk/*/*.apk; do
[ -f "$apk" ] && echo "APK: ${apk#"$ROOT"/} ($(wc -c < "$apk" | tr -d ' ') B)"
done
exit 0