Split the monolithic build-image.sh into three scripts: build-image.sh now only builds locally, run-image.sh
runs dist.sh inside that local image via a bind mount, andcupload-image.sh pushes the already-built image. The Dockerfile also warms the Gradle dependency cache in a throwaway build stage, and pre-installs the pinned CMake version, so `run-image.sh` doesn't re-download the same Maven dependencies on every run. dist.sh now strips the -pre+meta suffix before writing package.json's version, since Pebble's build tooling parses it strictly as X.Y.Z integers and was rejecting suffixed versions.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Build context for build-image/Dockerfile. The context is the repo root
|
||||
# (see build-image.sh) so the Gradle-cache-warming stage can COPY in the
|
||||
# companion app's real project files — everything else is excluded to keep
|
||||
# the context small and to make sure secrets never reach the Docker daemon.
|
||||
.git
|
||||
dist
|
||||
docs
|
||||
versions
|
||||
SchwertUndMagieOnPebbleWatchApp
|
||||
|
||||
# Companion app: only the Gradle project files are needed (see the
|
||||
# gradle-cache-warm stage) — not generated build output or the VICE/nibtools
|
||||
# source tarballs (buildVice/buildNibtools are excluded from that stage's
|
||||
# gradle invocation, so they're never unpacked there).
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/build
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/.cxx
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/vice-src
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/vice-libs
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/nibtools-src
|
||||
SchwertUndMagieOnPebbleCompanionApp/app/src/main/jni/nibtools-libs
|
||||
SchwertUndMagieOnPebbleCompanionApp/res/*.tar.gz
|
||||
SchwertUndMagieOnPebbleCompanionApp/.gradle
|
||||
SchwertUndMagieOnPebbleCompanionApp/.idea
|
||||
SchwertUndMagieOnPebbleCompanionApp/local.properties
|
||||
SchwertUndMagieOnPebbleCompanionApp/build
|
||||
SchwertUndMagieOnPebbleCompanionApp/captures
|
||||
|
||||
# Secrets — must never reach the Docker daemon, even unused.
|
||||
registry.env
|
||||
**/keystore.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -32,6 +32,11 @@ jobs:
|
||||
credentials:
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
# Without this, the runner's Docker daemon reuses whatever it already
|
||||
# has cached locally under the `:latest` tag and never notices a newer
|
||||
# image was pushed — options: is passed straight through to
|
||||
# `docker create`, which supports --pull since Docker 20.10.
|
||||
options: --pull=always
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -150,8 +150,10 @@ Pushing a tag `vX.Y.Z` builds both apps in a containerized runner and
|
||||
publishes a Gitea Release with the versioned artifacts attached — see
|
||||
`docs/publish.md` §4. One-time setup:
|
||||
|
||||
1. Build and push the build environment image (`./build-image.sh`, needs
|
||||
`registry.env` — copy from `registry.env.example`).
|
||||
1. Build the build environment image locally and push it (`./build-image.sh`
|
||||
then `./upload-image.sh`, needs `registry.env` — copy from
|
||||
`registry.env.example`). Use `./run-image.sh` in between to sanity-check
|
||||
the image before pushing.
|
||||
2. Register a self-hosted `act_runner` with a Docker executor.
|
||||
3. Add repo secrets under **Settings → Actions → Secrets**:
|
||||
|
||||
|
||||
+9
-16
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds and pushes the release build environment image (build-image/Dockerfile)
|
||||
# to the container registry. The Gitea Actions release workflow pulls this
|
||||
# image to run dist.sh. Bump build-image/VERSION whenever the Dockerfile
|
||||
# changes so the workflow can pin a stable tag.
|
||||
# Builds the release build environment image (build-image/Dockerfile)
|
||||
# locally, tagged with build-image/VERSION and :latest. Does not push —
|
||||
# test it with ./run-image.sh first, then publish with ./upload-image.sh.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
@@ -19,27 +18,21 @@ command -v docker >/dev/null 2>&1 \
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/registry.env"
|
||||
|
||||
: "${REGISTRY:?registry.env must set REGISTRY}"
|
||||
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
|
||||
: "${REGISTRY_USER:?registry.env must set REGISTRY_USER}"
|
||||
: "${REGISTRY_PASSWORD:?registry.env must set REGISTRY_PASSWORD}"
|
||||
|
||||
VERSION="$(<"$ROOT/build-image/VERSION")"
|
||||
[ -n "$VERSION" ] || fail "build-image/VERSION is empty"
|
||||
|
||||
echo "== Logging in to $REGISTRY =="
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
|
||||
|
||||
echo "== Building $REGISTRY_IMAGE:$VERSION =="
|
||||
# Context is the repo root (not build-image/) so the Dockerfile's
|
||||
# gradle-cache-warm stage can COPY in the companion app's real Gradle
|
||||
# project files — see .dockerignore for what's excluded from that context.
|
||||
docker build \
|
||||
-t "$REGISTRY_IMAGE:$VERSION" \
|
||||
-t "$REGISTRY_IMAGE:latest" \
|
||||
-f "$ROOT/build-image/Dockerfile" \
|
||||
"$ROOT/build-image"
|
||||
|
||||
echo "== Pushing $REGISTRY_IMAGE:$VERSION and :latest =="
|
||||
docker push "$REGISTRY_IMAGE:$VERSION"
|
||||
docker push "$REGISTRY_IMAGE:latest"
|
||||
"$ROOT"
|
||||
|
||||
echo "== Done =="
|
||||
echo "Image: $REGISTRY_IMAGE:$VERSION"
|
||||
echo "Image: $REGISTRY_IMAGE:$VERSION (and :latest)"
|
||||
echo "Test it with ./run-image.sh, then publish with ./upload-image.sh"
|
||||
|
||||
+36
-4
@@ -1,17 +1,18 @@
|
||||
# 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 and push with ../build-image.sh whenever a version
|
||||
# below, or the companion app's ndkVersion/compileSdk, changes.
|
||||
# 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
|
||||
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
|
||||
@@ -35,7 +36,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
dos2unix autoconf automake pkg-config xa65 build-essential gettext flex \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# --- Android SDK: cmdline-tools, platform, build-tools, NDK ---
|
||||
# --- 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" \
|
||||
@@ -48,6 +51,7 @@ RUN mkdir -p "$ANDROID_HOME/cmdline-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
|
||||
@@ -64,4 +68,32 @@ 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.
|
||||
#
|
||||
# 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 --continue || true)
|
||||
|
||||
FROM base
|
||||
COPY --from=gradle-cache-warm /root/.gradle /root/.gradle
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
@@ -87,7 +87,11 @@ sed -i \
|
||||
-e "s/versionCode = [0-9]\+/versionCode = $VERSION_CODE/" \
|
||||
-e "s/versionName = \"[^\"]*\"/versionName = \"$VERSION\"/" \
|
||||
"$GRADLE_KTS"
|
||||
sed -i -e "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" "$WATCH_PKG_JSON"
|
||||
# Pebble's own build tooling parses package.json's version strictly as
|
||||
# X.Y.Z integers — it rejects the -pre+meta suffix dist.sh otherwise allows
|
||||
# (including the default "0.0.0-dev+<sha>" placeholder), so strip it here.
|
||||
PEBBLE_VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}"
|
||||
sed -i -e "s/\"version\": \"[^\"]*\"/\"version\": \"$PEBBLE_VERSION\"/" "$WATCH_PKG_JSON"
|
||||
|
||||
mkdir -p "$DIST"
|
||||
|
||||
|
||||
+19
-7
@@ -249,11 +249,11 @@ be bumped in source files beforehand (`dist.sh` patches `versionCode`/
|
||||
running the same thing locally).
|
||||
|
||||
The whole toolchain (Android SDK/NDK, Pebble SDK) lives in
|
||||
`build-image/Dockerfile`, built and pushed to a container registry by
|
||||
`build-image.sh`. This keeps the setup portable: the runner just needs Docker
|
||||
and pulls that image, so it isn't tied to any one machine's local toolchain
|
||||
install and can be moved or re-registered elsewhere without touching this
|
||||
repo's build scripts.
|
||||
`build-image/Dockerfile`, built locally and pushed to a container registry by
|
||||
three separate scripts (below). This keeps the setup portable: the runner
|
||||
just needs Docker and pulls that image, so it isn't tied to any one machine's
|
||||
local toolchain install and can be moved or re-registered elsewhere without
|
||||
touching this repo's build scripts.
|
||||
|
||||
### 4.1 Build environment image
|
||||
|
||||
@@ -262,9 +262,14 @@ pinned in `app/build.gradle.kts`, matching what's validated for local builds.
|
||||
It does **not** contain the release keystore — that's injected at job runtime
|
||||
from Actions secrets (§4.3), never baked into the image.
|
||||
|
||||
Three scripts, kept separate so a Dockerfile change can be built and tested
|
||||
locally before anything is pushed to the registry:
|
||||
|
||||
```bash
|
||||
cp registry.env.example registry.env # fill in your registry credentials
|
||||
./build-image.sh # builds + pushes :latest and the pinned VERSION tag
|
||||
./build-image.sh # builds :latest and the pinned VERSION tag, locally only
|
||||
./run-image.sh # runs dist.sh inside that local image — sanity-check before publishing
|
||||
./upload-image.sh # pushes the already-built :latest and VERSION tag to cr.ladkau.de
|
||||
```
|
||||
|
||||
Rebuild and push whenever `build-image/Dockerfile` changes (e.g. a Pebble SDK
|
||||
@@ -329,12 +334,19 @@ Watch the run under the repo's Actions tab. On success, the release appears
|
||||
under the repo's Releases page with the three versioned artifacts attached.
|
||||
|
||||
To build the same versioned artifacts locally without pushing a tag (e.g. to
|
||||
test before releasing):
|
||||
test before releasing), either run `dist.sh` directly with the host toolchain:
|
||||
|
||||
```bash
|
||||
VERSION=1.2.3 ./dist.sh
|
||||
```
|
||||
|
||||
or run it inside the build-image container (same environment the runner
|
||||
uses — see §4.1):
|
||||
|
||||
```bash
|
||||
./run-image.sh 1.2.3
|
||||
```
|
||||
|
||||
## 5. Pebble watch app → Rebble app store / direct distribution
|
||||
|
||||
The official Pebble app store shut down years ago; the community-run
|
||||
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs dist.sh inside the local build-image container, mirroring what the
|
||||
# Gitea Actions release workflow does — useful for testing build-image
|
||||
# changes (or dist.sh/build_vice.sh/build_nibtools.sh changes) locally
|
||||
# before pushing anything to cr.ladkau.de.
|
||||
#
|
||||
# Usage: ./run-image.sh [VERSION]
|
||||
# VERSION is passed through to dist.sh; omit it for dist.sh's own
|
||||
# git-tag-based default (see dist.sh's header comment).
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ROOT="$(pwd)"
|
||||
|
||||
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
|
||||
|
||||
command -v docker >/dev/null 2>&1 \
|
||||
|| fail "docker not found in PATH"
|
||||
|
||||
[ -f "$ROOT/registry.env" ] \
|
||||
|| fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/registry.env"
|
||||
|
||||
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
|
||||
|
||||
IMAGE_TAG="$(<"$ROOT/build-image/VERSION")"
|
||||
[ -n "$IMAGE_TAG" ] || fail "build-image/VERSION is empty"
|
||||
IMAGE="$REGISTRY_IMAGE:$IMAGE_TAG"
|
||||
|
||||
docker image inspect "$IMAGE" >/dev/null 2>&1 \
|
||||
|| fail "$IMAGE not found locally — run ./build-image.sh first"
|
||||
|
||||
VERSION="${1:-${VERSION:-}}"
|
||||
|
||||
echo "== Running dist.sh inside $IMAGE =="
|
||||
docker run --rm \
|
||||
-v "$ROOT:/workspace" \
|
||||
-w /workspace \
|
||||
-e VERSION="$VERSION" \
|
||||
"$IMAGE" \
|
||||
bash -c 'git config --global --add safe.directory /workspace && ./dist.sh'
|
||||
|
||||
echo "== Done — artifacts in dist/ =="
|
||||
ls -la "$ROOT/dist"
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Pushes the build environment image — already built locally with
|
||||
# ./build-image.sh, and ideally verified with ./run-image.sh — to
|
||||
# cr.ladkau.de. The Gitea Actions release workflow pulls this image to
|
||||
# run dist.sh.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
ROOT="$(pwd)"
|
||||
|
||||
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
|
||||
|
||||
command -v docker >/dev/null 2>&1 \
|
||||
|| fail "docker not found in PATH"
|
||||
|
||||
[ -f "$ROOT/registry.env" ] \
|
||||
|| fail "registry.env not found — copy registry.env.example to registry.env and fill in your cr.ladkau.de credentials"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$ROOT/registry.env"
|
||||
|
||||
: "${REGISTRY:?registry.env must set REGISTRY}"
|
||||
: "${REGISTRY_IMAGE:?registry.env must set REGISTRY_IMAGE}"
|
||||
: "${REGISTRY_USER:?registry.env must set REGISTRY_USER}"
|
||||
: "${REGISTRY_PASSWORD:?registry.env must set REGISTRY_PASSWORD}"
|
||||
|
||||
VERSION="$(<"$ROOT/build-image/VERSION")"
|
||||
[ -n "$VERSION" ] || fail "build-image/VERSION is empty"
|
||||
|
||||
docker image inspect "$REGISTRY_IMAGE:$VERSION" >/dev/null 2>&1 \
|
||||
|| fail "$REGISTRY_IMAGE:$VERSION not found locally — run ./build-image.sh first"
|
||||
|
||||
echo "== Logging in to $REGISTRY =="
|
||||
echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin
|
||||
|
||||
echo "== Pushing $REGISTRY_IMAGE:$VERSION and :latest =="
|
||||
docker push "$REGISTRY_IMAGE:$VERSION"
|
||||
docker push "$REGISTRY_IMAGE:latest"
|
||||
|
||||
echo "== Done =="
|
||||
echo "Image: $REGISTRY_IMAGE:$VERSION"
|
||||
Reference in New Issue
Block a user