# Build environment for schwert_und_magie_on_pebble release artifacts: # Android SDK/NDK (companion app) + Pebble SDK (watch app), matching the # versions pinned in app/build.gradle.kts and validated on the maintainer's # dev machine. Rebuild with ../build-image.sh whenever a version below, or # the companion app's ndkVersion/compileSdk, changes. # # Does NOT contain the release keystore or any secrets — those are injected # at job runtime from Gitea Actions secrets, never baked into this image. FROM eclipse-temurin:21-jdk-jammy AS base ARG ANDROID_CMDLINE_TOOLS_VERSION=11076708 ARG ANDROID_PLATFORM=android-36 ARG ANDROID_BUILD_TOOLS=36.1.0 ARG ANDROID_NDK=30.0.14904198 ARG ANDROID_CMAKE=3.22.1 ARG PEBBLE_TOOL_VERSION=5.0.35 ARG PEBBLE_SDK_CORE_VERSION=4.9.169 ARG NODE_VERSION=24.16.0 ENV DEBIAN_FRONTEND=noninteractive \ ANDROID_HOME=/opt/android-sdk \ ANDROID_SDK_ROOT=/opt/android-sdk \ ANDROID_NDK_HOME=/opt/android-sdk/ndk/30.0.14904198 \ PATH=/root/.local/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:${PATH} # git/unzip/curl/jq: checkout, SDK downloads, and the release workflow's # calls to the Gitea API (create release, upload assets). # openssh-client: the release workflow's `sftp` upload to dl.ladkau.de. # python3-venv: pebble-tool's `sdk install` creates a venv per SDK version. # dos2unix/autoconf/automake/pkg-config/xa65/build-essential/gettext/flex/bison: # host tools required by the companion app's build_vice.sh (see that file's # own preflight check, plus flex/bison for VICE's AC_PROG_LEX/AC_PROG_YACC- # based configure) to cross-compile VICE via autotools before NDK clang takes # over for the actual target compilation. RUN apt-get update && apt-get install -y --no-install-recommends \ curl ca-certificates git unzip tar xz-utils python3 python3-venv file jq openssh-client \ dos2unix autoconf automake pkg-config xa65 build-essential gettext flex bison \ && rm -rf /var/lib/apt/lists/* # --- Android SDK: cmdline-tools, platform, build-tools, NDK, CMake --- # CMake version must match app/build.gradle.kts's externalNativeBuild.cmake.version # — baking it in here avoids AGP installing it on first `docker run` instead. RUN mkdir -p "$ANDROID_HOME/cmdline-tools" \ && curl -sSL -o /tmp/cmdline-tools.zip \ "https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_TOOLS_VERSION}_latest.zip" \ && unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME/cmdline-tools" \ && mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" \ && rm /tmp/cmdline-tools.zip \ && yes | sdkmanager --licenses >/dev/null \ && sdkmanager --install \ "platform-tools" \ "platforms;${ANDROID_PLATFORM}" \ "build-tools;${ANDROID_BUILD_TOOLS}" \ "ndk;${ANDROID_NDK}" \ "cmake;${ANDROID_CMAKE}" \ >/dev/null # Node.js: `pebble sdk install` below runs `npm install` for the SDK-core's # bundled webpack tooling — it does not bring its own node/npm, only the JS # deps themselves. Version matches what's validated on the maintainer's # machine; sdk-core's own bundled arm-none-eabi toolchain needs nothing extra. RUN curl -sSL -o /tmp/node.tar.xz \ "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \ && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \ && rm /tmp/node.tar.xz # --- Pebble SDK: pebble-tool + sdk-core --- RUN curl -LsSf https://astral.sh/uv/install.sh | sh \ && /root/.local/bin/uv tool install "pebble-tool==${PEBBLE_TOOL_VERSION}" \ && /root/.local/bin/pebble sdk install "${PEBBLE_SDK_CORE_VERSION}" # --- Warm the Gradle dependency cache --- # The companion app's real Gradle project files (not the generated build/ # output, and not the VICE/nibtools source tarballs — see .dockerignore) are # COPYed into a throwaway location and built once here, so every actual # `docker run` of this image (which bind-mounts a fresh checkout over # /workspace) hits a warm ~/.gradle cache instead of re-downloading the same # Maven dependencies from dl.google.com/mavenCentral every single run. # # buildVice/buildNibtools are excluded (their source tarballs aren't in the # build context) — CMake already handles that gracefully, falling back to a # placeholder (see vice_jni.c / build_vice.sh's own header comment) — so this # stage only ever warms the Gradle/Maven dependency cache, never bakes in # compiled VICE/nibtools output. # # packageRelease/signReleaseBundle are also excluded — they're the only tasks # that need the release keystore, which never exists here (secrets are # injected at job runtime, never baked into the image). Excluding them lets # everything upstream (dependency resolution, Kotlin/native compilation, # resource merging, dexing) still run and get cached, without two guaranteed, # noisy "missing storeFile" failures cluttering every image build. # # This is a pure optimization: if the app's dependencies change after this # image was built, Gradle just downloads the delta against the warm cache at # `docker run` time — same as it would without this stage, just slower for # that one run, never broken. `|| true` means a transient network failure # here only costs a slower first `docker run`, never breaks the image build. FROM base AS gradle-cache-warm COPY SchwertUndMagieOnPebbleCompanionApp /tmp/warm/SchwertUndMagieOnPebbleCompanionApp WORKDIR /tmp/warm/SchwertUndMagieOnPebbleCompanionApp RUN chmod +x gradlew \ && (./gradlew bundleRelease assembleRelease \ -x buildVice -x buildNibtools \ -x packageRelease -x signReleaseBundle \ --continue || true) FROM base COPY --from=gradle-cache-warm /root/.gradle /root/.gradle WORKDIR /workspace