Add a Docker-based build image and wire it into CI for the watch app
build / build (push) Failing after 0s
build / build (push) Failing after 0s
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
name: build
|
||||
|
||||
# Build a versioned Linux CLI release tarball (see `make package`) on
|
||||
# every push/PR, plus on-demand via the Gitea "Run workflow" button.
|
||||
# Build a versioned Linux CLI release tarball (see `make package`) and the
|
||||
# Pebble watchapp (see `make watchapp`) on every push/PR, plus on-demand
|
||||
# via the Gitea "Run workflow" button. Runs inside build-image (see
|
||||
# ../../build-image/Dockerfile, built/pushed via ../../build-image.sh and
|
||||
# ../../upload-image.sh), which bundles the Pebble SDK alongside the C
|
||||
# toolchain.
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
@@ -17,12 +21,18 @@ jobs:
|
||||
# Must match a label your act_runner is registered with.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: cr.ladkau.de/deck-in-a-dash/builder:latest
|
||||
credentials:
|
||||
username: ${{ secrets.REGISTRY_USER }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# `make package`'s version comes from `git describe --tags` (see
|
||||
# the Makefile's `package` target) - needs full history/tags,
|
||||
# not actions/checkout's default shallow single-commit clone.
|
||||
# `make package`/`make watchapp`'s version comes from `git
|
||||
# describe --tags` - needs full history/tags, not
|
||||
# actions/checkout's default shallow single-commit clone.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Preflight
|
||||
@@ -30,12 +40,14 @@ jobs:
|
||||
set -euo pipefail
|
||||
command -v cc >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: no C compiler (cc) in PATH" >&2; exit 1; }
|
||||
command -v make >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: make not in PATH" >&2; exit 1; }
|
||||
command -v pebble >/dev/null 2>&1 || { echo "PREFLIGHT FAIL: pebble not in PATH" >&2; exit 1; }
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
set -euo pipefail
|
||||
make test
|
||||
make package
|
||||
make watchapp
|
||||
|
||||
- name: Upload build artifact
|
||||
# v4 uses the newer @actions/artifact backend, which this Gitea
|
||||
@@ -45,14 +57,21 @@ jobs:
|
||||
name: deck-in-a-dash
|
||||
path: dist/deck-in-a-dash-*-linux-*.tar.gz
|
||||
|
||||
- name: Upload watchapp artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deck-in-a-dash-watchapp
|
||||
path: dist/deck-in-a-dash-*.pbw
|
||||
|
||||
- name: Publish to dl.ladkau.de
|
||||
# Uploads the tarball over SFTP instead of using
|
||||
# Uploads the tarball and .pbw over SFTP instead of using
|
||||
# actions/upload-artifact (whose zip wrapping can't be disabled).
|
||||
# Only runs on push so PR builds don't publish.
|
||||
if: gitea.event_name == 'push'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
FILE="$(ls dist/deck-in-a-dash-*-linux-*.tar.gz)"
|
||||
TARBALL="$(ls dist/deck-in-a-dash-*-linux-*.tar.gz)"
|
||||
PBW="$(ls dist/deck-in-a-dash-*.pbw)"
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.DL_SFTP_KEY }}" > ~/.ssh/dl_sftp_key
|
||||
chmod 600 ~/.ssh/dl_sftp_key
|
||||
@@ -60,5 +79,6 @@ jobs:
|
||||
-o StrictHostKeyChecking=accept-new \
|
||||
uploader@dl.ladkau.de <<EOF
|
||||
-mkdir files/deck-in-a-dash
|
||||
put $FILE files/deck-in-a-dash/$(basename "$FILE")
|
||||
put $TARBALL files/deck-in-a-dash/$(basename "$TARBALL")
|
||||
put $PBW files/deck-in-a-dash/$(basename "$PBW")
|
||||
EOF
|
||||
|
||||
@@ -17,3 +17,7 @@ core
|
||||
core.*
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Container registry credentials for build-image.sh/run-image.sh/
|
||||
# upload-image.sh — never commit; copy registry.env.example instead.
|
||||
/registry.env
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,19 +106,47 @@ from the org/instance level).
|
||||
|
||||
### Watch app packaging (`make watchapp`)
|
||||
|
||||
A separate target, deliberately **not** a prerequisite of `all` and
|
||||
**not** run by CI - it shells out to `pebble build` inside `watch/`
|
||||
(via `watch/wscript`'s own Pebble SDK/waf tooling, not this Makefile's
|
||||
plain-`cc` rules) and copies the resulting `watch/build/watch.pbw` to
|
||||
A separate target, deliberately **not** a prerequisite of `all` - it
|
||||
shells out to `pebble build` inside `watch/` (via `watch/wscript`'s own
|
||||
Pebble SDK/waf tooling, not this Makefile's plain-`cc` rules) and copies
|
||||
the resulting `watch/build/watch.pbw` to
|
||||
`dist/deck-in-a-dash-<version>.pbw`, using the exact same version
|
||||
resolution as `make package` above (current git tag, or `make watchapp
|
||||
VERSION=1.2.3` to override - see that section, not duplicated here).
|
||||
Requires the Pebble SDK (`pebble` on `PATH`, plus the Python 3 + Pillow
|
||||
`watch/wscript` needs for its resource-generation scripts) - a machine
|
||||
without it can still build/test/package the CLI via every other target
|
||||
in this file. Not wired into `.gitea/workflows/build.yml`: doing so
|
||||
would need the Pebble SDK toolchain installed on the CI runner, which
|
||||
this project doesn't currently provision.
|
||||
in this file.
|
||||
|
||||
`.gitea/workflows/build.yml` **does** run `make watchapp` on every
|
||||
push/PR, alongside `make test`/`make package` - the whole job runs
|
||||
inside a container image (`build-image/`, built and pushed with
|
||||
`build-image.sh`/`upload-image.sh`, see "Build image" below) that has
|
||||
the Pebble SDK baked in, so the CI runner itself needs no toolchain
|
||||
beyond Docker access to pull that image. Both the CLI tarball and the
|
||||
`.pbw` are uploaded as build artifacts and published to
|
||||
`dl.ladkau.de/files/deck-in-a-dash/` on push.
|
||||
|
||||
### Build image
|
||||
|
||||
`build-image/Dockerfile` pins the exact toolchain versions (Pebble
|
||||
Tool, Pebble SDK core, Node.js) needed for `make test`, `make package`,
|
||||
and `make watchapp`, so builds are reproducible independent of whatever
|
||||
happens to be installed on a given machine. Three root-level scripts
|
||||
drive it, each reading registry settings from a gitignored
|
||||
`registry.env` (copy `registry.env.example` to create it - never commit
|
||||
the real file, since it holds registry credentials) and the image tag
|
||||
from `build-image/VERSION`:
|
||||
|
||||
- `build-image.sh` - builds and locally tags the image (`:VERSION` and
|
||||
`:latest`), no push.
|
||||
- `run-image.sh [VERSION]` - runs `make test package watchapp` inside
|
||||
the locally-built image against a live bind-mount of the repo, wiping
|
||||
generated build artifacts first for CI parity. Use this to validate a
|
||||
`build-image/Dockerfile` change before pushing the image anywhere.
|
||||
- `upload-image.sh` - logs in and pushes the image (`:VERSION` and
|
||||
`:latest`) to the registry; `.gitea/workflows/build.yml` pulls
|
||||
`:latest` to run its build job (see "Watch app packaging" above).
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ watch/ The actual Pebble watchapp - links engine/src/
|
||||
from the rest of this repo (`make watchapp`,
|
||||
below) - requires the Pebble SDK.
|
||||
docs/ Architecture/dataflow diagrams, input/output format reference.
|
||||
build-image/ Dockerfile for the reproducible build environment
|
||||
(C toolchain + Pebble SDK) used by CI and by the
|
||||
build-image.sh/run-image.sh/upload-image.sh scripts
|
||||
at the repo root (see "Building" below).
|
||||
Makefile Single root Makefile, builds both engine/ and
|
||||
interpreter/, and packages a release (`make package`).
|
||||
```
|
||||
@@ -94,6 +98,20 @@ make watchapp # -> dist/deck-in-a-dash-<version>.pbw, same version
|
||||
# resolution as `make package`
|
||||
```
|
||||
|
||||
**CI runs `make watchapp` too** — `.gitea/workflows/build.yml` builds
|
||||
and tests everything inside a container built from `build-image/`,
|
||||
which bundles the Pebble SDK alongside the C toolchain so the runner
|
||||
itself doesn't need either installed. To build/test that image
|
||||
yourself, copy `registry.env.example` to `registry.env` and fill in
|
||||
your registry credentials, then:
|
||||
|
||||
```bash
|
||||
./build-image.sh # build and locally tag the image
|
||||
./run-image.sh # run `make test package watchapp` inside it, against
|
||||
# this checkout, for a CI-equivalent local build
|
||||
./upload-image.sh # push the image so CI can pull it
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
**Daily use**, via `dist/run-engine.sh`: pulls today's date and the
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# 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]}")"
|
||||
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}"
|
||||
|
||||
VERSION="$(<"$ROOT/build-image/VERSION")"
|
||||
[ -n "$VERSION" ] || fail "build-image/VERSION is empty"
|
||||
|
||||
echo "== Building $REGISTRY_IMAGE:$VERSION =="
|
||||
docker build \
|
||||
-t "$REGISTRY_IMAGE:$VERSION" \
|
||||
-t "$REGISTRY_IMAGE:latest" \
|
||||
"$ROOT/build-image"
|
||||
|
||||
echo "== Done =="
|
||||
echo "Image: $REGISTRY_IMAGE:$VERSION (and :latest)"
|
||||
echo "Test it with ./run-image.sh, then publish with ./upload-image.sh"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Build environment for deck_in_a_dash release artifacts: a plain C
|
||||
# toolchain for the desktop deck-engine/interpreter-cli binaries (`make
|
||||
# test`, `make package`) plus the Pebble SDK for the watch app (`make
|
||||
# watchapp`), pinned to the versions validated on the maintainer's dev
|
||||
# machine. Rebuild with ../build-image.sh whenever a version below
|
||||
# changes.
|
||||
#
|
||||
# Does NOT contain any secrets - registry/SFTP credentials are injected
|
||||
# at job runtime from Gitea Actions secrets, never baked into this image.
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG PEBBLE_TOOL_VERSION=5.0.39
|
||||
ARG PEBBLE_SDK_CORE_VERSION=4.17
|
||||
ARG NODE_VERSION=24.16.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/root/.local/bin:${PATH}
|
||||
|
||||
# build-essential: gcc/make for the desktop deck-engine/interpreter-cli
|
||||
# binaries (`make test`, `make package`).
|
||||
# python3/python3-venv/python3-pip: `pebble sdk install` below creates a
|
||||
# venv per SDK version; Pillow (installed below) is needed at `pebble
|
||||
# build` time by watch/scripts/gen_card_images.py and gen_app_icon.py.
|
||||
# git/curl/ca-certificates/unzip/tar/xz-utils: checkout, tool downloads,
|
||||
# and `make package`/`make watchapp`'s `git describe --tags` versioning.
|
||||
# openssh-client: the build workflow's `sftp` publish to dl.ladkau.de.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential python3 python3-venv python3-pip \
|
||||
git curl ca-certificates unzip tar xz-utils openssh-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip3 install --no-cache-dir Pillow
|
||||
|
||||
# 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.
|
||||
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 (via uv, pipx-style isolated install) + the
|
||||
# sdk-core toolchain/webpack deps for PEBBLE_SDK_CORE_VERSION, both
|
||||
# fully resolved here so no `docker run` of this image needs network
|
||||
# access to install anything before `pebble build` works.
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh \
|
||||
&& uv tool install "pebble-tool==${PEBBLE_TOOL_VERSION}" \
|
||||
&& pebble sdk install "${PEBBLE_SDK_CORE_VERSION}"
|
||||
|
||||
WORKDIR /workspace
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copy to registry.env (gitignored, never commit the real values) and
|
||||
# fill in your cr.ladkau.de credentials. Used by build-image.sh,
|
||||
# run-image.sh, and upload-image.sh.
|
||||
REGISTRY=cr.ladkau.de
|
||||
REGISTRY_IMAGE=cr.ladkau.de/deck-in-a-dash/builder
|
||||
REGISTRY_USER=
|
||||
REGISTRY_PASSWORD=
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs `make test package watchapp` inside the local build-image
|
||||
# container, mirroring what the Gitea Actions build workflow does -
|
||||
# useful for testing build-image changes locally before pushing
|
||||
# anything to cr.ladkau.de.
|
||||
#
|
||||
# Usage: ./run-image.sh [VERSION]
|
||||
# VERSION is passed through to `make` (see the Makefile's `package`/
|
||||
# `watchapp` targets); omit it for their own git-tag-based default.
|
||||
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:-}}"
|
||||
|
||||
# CI always starts from a fresh checkout, but this script bind-mounts the
|
||||
# live host repo — so build output left over from a previous local run
|
||||
# (build/, dist/, watch/build, and the generated watch resources below)
|
||||
# would make this run skip work a real fresh checkout wouldn't, silently
|
||||
# hiding bugs that only show up in CI. Wipe them first so every run
|
||||
# exercises a true from-scratch build, same as CI.
|
||||
echo "== Cleaning generated build artifacts for a fresh build =="
|
||||
rm -rf \
|
||||
"$ROOT/build" "$ROOT/dist" "$ROOT/watch/build" \
|
||||
"$ROOT/watch/resources/img" "$ROOT/watch/resources/app_icon.png" \
|
||||
"$ROOT/watch/src/c/i18n_tables.auto.c"
|
||||
|
||||
echo "== Running make test package watchapp inside $IMAGE =="
|
||||
# The container runs as root (needed for the toolchain baked into the
|
||||
# image), so anything it writes into this bind mount — dist/, build/,
|
||||
# watch/build, etc. — would otherwise come back owned by root, leaving
|
||||
# the host repo unusable without sudo. Chown everything back to the host
|
||||
# user on exit, whether the build succeeds or fails.
|
||||
docker run --rm \
|
||||
-v "$ROOT:/workspace" \
|
||||
-w /workspace \
|
||||
-e VERSION="$VERSION" \
|
||||
-e HOST_UID="$(id -u)" \
|
||||
-e HOST_GID="$(id -g)" \
|
||||
"$IMAGE" \
|
||||
bash -c '
|
||||
git config --global --add safe.directory /workspace
|
||||
trap "chown -R \"$HOST_UID:$HOST_GID\" /workspace" EXIT
|
||||
make test package watchapp
|
||||
'
|
||||
|
||||
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 build workflow pulls this image to run
|
||||
# `make test package watchapp`.
|
||||
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