Compare commits
3 Commits
7a77c4a07d
..
v0.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e437277f | |||
| ff2b43f1e0 | |||
| 4e83ebbf50 |
@@ -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
|
||||
|
||||
+10
@@ -7,6 +7,12 @@
|
||||
/watch/resources/img
|
||||
/watch/resources/app_icon.png
|
||||
|
||||
# waf's own build lock file - regenerated every `pebble build`, holds
|
||||
# absolute machine paths and a full dump of the invoking shell's
|
||||
# environment variables. Was tracked accidentally; see the commit that
|
||||
# added this .gitignore line for the git rm --cached that untracked it.
|
||||
/watch/.lock-waf_linux_build
|
||||
|
||||
# Durable backup of the same real birth data, outside dist/ so a dist/
|
||||
# wipe doesn't lose it — see the Makefile's `scripts` target. Never
|
||||
# commit this either.
|
||||
@@ -17,3 +23,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
|
||||
|
||||
@@ -20,6 +20,9 @@ make # builds dist/{deck-engine,interpreter-cli}, dist/img/, dist/i18n/
|
||||
make test # builds and runs engine/tests/smoke_test.c and interpreter/tests/*_test.c
|
||||
make clean # removes build/ and the generated binaries/img/i18n (never touches dist/user.properties)
|
||||
make package # builds a versioned Linux CLI tarball at dist/ - see "Packaging" below
|
||||
make watchapp # builds the Pebble watchapp and copies a versioned .pbw to
|
||||
# dist/ - see "Packaging" below. Requires the Pebble SDK
|
||||
# (not needed for anything else above); not run by CI.
|
||||
```
|
||||
|
||||
A single `Makefile` at the repo root builds both `engine/` and
|
||||
@@ -101,6 +104,50 @@ published over SFTP to `dl.ladkau.de` under `files/deck-in-a-dash/` -
|
||||
requires a `DL_SFTP_KEY` secret configured on this repo (or inherited
|
||||
from the org/instance level).
|
||||
|
||||
### Watch app packaging (`make watchapp`)
|
||||
|
||||
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.
|
||||
|
||||
`.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
|
||||
|
||||
```
|
||||
|
||||
@@ -87,7 +87,12 @@ GUIDANCE_TEST_BIN := $(BUILD_DIR)/guidance_test
|
||||
ARCH := $(shell uname -m)
|
||||
PACKAGE_DIR := $(BUILD_DIR)/package
|
||||
|
||||
.PHONY: all test clean images i18n scripts package
|
||||
# ---- watch app (Pebble) ----
|
||||
|
||||
WATCH_DIR := watch
|
||||
WATCH_PBW := $(WATCH_DIR)/build/watch.pbw
|
||||
|
||||
.PHONY: all test clean images i18n scripts package watchapp
|
||||
|
||||
all: $(ENGINE_BINARY) $(INTERP_BINARY) images i18n scripts
|
||||
|
||||
@@ -208,3 +213,32 @@ clean:
|
||||
rm -rf $(BUILD_DIR) $(ENGINE_BINARY) $(INTERP_BINARY) $(DIST_IMG_DIR) $(DIST_I18N_DIR) \
|
||||
$(DIST_DIR)/run-engine.sh $(DIST_DIR)/run-interpreter.sh
|
||||
# user.properties holds the user's own data - never removed by clean.
|
||||
|
||||
# Builds the Pebble watchapp (`pebble build` in watch/, via its own
|
||||
# wscript/pebble_sdk waf tooling - not a plain C toolchain, so this is a
|
||||
# separate target from `all` rather than one of its prerequisites) and
|
||||
# copies the resulting .pbw into dist/, versioned the same way `package`
|
||||
# above versions the CLI tarball (current git tag, or
|
||||
# `make watchapp VERSION=1.2.3` to override). Requires the Pebble SDK
|
||||
# (`pebble` on PATH, plus Python 3 + Pillow for watch/scripts/*.py - see
|
||||
# watch/wscript) - this is NOT checked by the root `make`/`make test`,
|
||||
# so a machine without the Pebble SDK can still build/test the CLI.
|
||||
watchapp:
|
||||
cd $(WATCH_DIR) && pebble build
|
||||
@mkdir -p $(DIST_DIR)
|
||||
@V="$(VERSION)"; \
|
||||
if [ -z "$$V" ]; then \
|
||||
if TAG=$$(git describe --tags --exact-match --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null); then \
|
||||
V=$${TAG#v}; \
|
||||
else \
|
||||
V="0.0.0-dev+$$(git rev-parse --short HEAD)"; \
|
||||
echo "WARNING: HEAD is not on a vX.Y.Z tag - building placeholder version $$V (push a tag to drive a real release version)" >&2; \
|
||||
fi; \
|
||||
fi; \
|
||||
case "$$V" in \
|
||||
[0-9]*.[0-9]*.[0-9]*) ;; \
|
||||
*) echo "PREFLIGHT FAIL: VERSION '$$V' is not a semantic version (expected X.Y.Z, optionally with a -pre+meta suffix)" >&2; exit 1;; \
|
||||
esac; \
|
||||
PBW_NAME="deck-in-a-dash-$$V.pbw"; \
|
||||
cp $(WATCH_PBW) "$(DIST_DIR)/$$PBW_NAME"; \
|
||||
echo "Wrote $(DIST_DIR)/$$PBW_NAME"
|
||||
|
||||
@@ -52,7 +52,18 @@ interpreter/ Separate module + binary (dist/interpreter-cli)
|
||||
deck-engine's --format json output. Supports
|
||||
--format text|html|json and --lang en|de for
|
||||
its own output too (see CLAUDE.md).
|
||||
watch/ The actual Pebble watchapp - links engine/src/
|
||||
and interpreter/src/ in directly (see CLAUDE.md's
|
||||
"Portability to the watch"), plus its own
|
||||
persistence, reading recompute, UI, daily
|
||||
notification, and config page. Built separately
|
||||
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`).
|
||||
```
|
||||
@@ -78,6 +89,29 @@ make package # -> dist/deck-in-a-dash-<version>-linux-<arch>.tar.gz, a
|
||||
|
||||
Requires only a C99 compiler and `make` — no other dependencies.
|
||||
|
||||
**Building the watch app itself** (`watch/`, the actual Pebble app)
|
||||
needs the separate Pebble SDK (`pebble` on `PATH`), so it's a separate
|
||||
target, not part of `make`/`make test`/`make package` above:
|
||||
|
||||
```bash
|
||||
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"
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "app_message.h"
|
||||
#include "config.h"
|
||||
#include "i18n_tables.h"
|
||||
#include "notify.h"
|
||||
#include "reading_state.h"
|
||||
#include "ui_report_window.h"
|
||||
#include "ui_text_window.h"
|
||||
@@ -26,6 +27,7 @@ static void on_config_updated(const WatchConfig *cfg) {
|
||||
|
||||
config_log(&s_config); /* the values this newly-recomputed reading is based on */
|
||||
reading_state_recompute(&s_config.birth, s_config.gender);
|
||||
notify_reschedule(&s_config); /* settings may have changed notify_enabled/hour/minute */
|
||||
if (was_configured) {
|
||||
report_window_reload();
|
||||
} else {
|
||||
@@ -33,16 +35,36 @@ static void on_config_updated(const WatchConfig *cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Fires only if the scheduled wakeup goes off while this app is already
|
||||
* open (notify_was_wakeup_launch() below covers the far more common
|
||||
* case: the wakeup launching the app fresh). Refreshes the same way a
|
||||
* new day's config submission would, since the report window is already
|
||||
* on screen and the reading may now be for a new day. */
|
||||
static void on_wakeup(WakeupId wakeup_id, int32_t cookie) {
|
||||
(void)wakeup_id;
|
||||
(void)cookie;
|
||||
vibes_double_pulse();
|
||||
if (!s_config.configured) return;
|
||||
reading_state_recompute(&s_config.birth, s_config.gender);
|
||||
report_window_reload();
|
||||
notify_reschedule(&s_config); /* wakeup_schedule() is one-shot - re-arm for tomorrow */
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
config_load(&s_config);
|
||||
watch_i18n_load(s_config.lang);
|
||||
app_message_init(on_config_updated);
|
||||
notify_subscribe(on_wakeup);
|
||||
|
||||
config_log(&s_config); /* the values this session's reading (if any) is based on */
|
||||
|
||||
if (s_config.configured) {
|
||||
reading_state_recompute(&s_config.birth, s_config.gender);
|
||||
report_window_push();
|
||||
notify_reschedule(&s_config); /* wakeup_schedule() is one-shot - re-arm on every launch */
|
||||
if (notify_was_wakeup_launch()) {
|
||||
vibes_double_pulse();
|
||||
}
|
||||
} else {
|
||||
show_setup_required();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include "notify.h"
|
||||
|
||||
/* Only one kind of wakeup event exists in this app, so the cookie value
|
||||
* itself carries no information - it exists only because
|
||||
* wakeup_schedule()/wakeup_get_launch_event() require one. */
|
||||
#define NOTIFY_WAKEUP_COOKIE 0
|
||||
|
||||
void notify_reschedule(const WatchConfig *cfg) {
|
||||
wakeup_cancel_all();
|
||||
if (!cfg->notify_enabled) return;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm target_tm = *localtime(&now);
|
||||
target_tm.tm_hour = cfg->notify_hour;
|
||||
target_tm.tm_min = cfg->notify_minute;
|
||||
target_tm.tm_sec = 0;
|
||||
|
||||
time_t target = mktime(&target_tm);
|
||||
if (target <= now) target += SECONDS_PER_DAY; /* today's time already passed */
|
||||
|
||||
WakeupId id = wakeup_schedule(target, NOTIFY_WAKEUP_COOKIE, true);
|
||||
if (id < 0) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "notify: wakeup_schedule failed: %d", (int)id);
|
||||
}
|
||||
}
|
||||
|
||||
bool notify_was_wakeup_launch(void) {
|
||||
return launch_reason() == APP_LAUNCH_WAKEUP;
|
||||
}
|
||||
|
||||
void notify_subscribe(WakeupHandler handler) {
|
||||
wakeup_service_subscribe(handler);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef WATCH_NOTIFY_H
|
||||
#define WATCH_NOTIFY_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* (Re)schedules the daily notification wakeup from `cfg`'s notify_*
|
||||
* fields: cancels any wakeup this app previously scheduled, then - if
|
||||
* cfg->notify_enabled - schedules a new one-shot wakeup_schedule() for
|
||||
* the next occurrence of notify_hour:notify_minute in the watch's own
|
||||
* local time (today if that time hasn't passed yet, tomorrow otherwise).
|
||||
* If !cfg->notify_enabled, this only cancels - no new wakeup is
|
||||
* scheduled.
|
||||
*
|
||||
* Safe to call any time, and needs to be: once per app launch (Pebble's
|
||||
* wakeup_schedule() is one-shot - each firing consumes itself, so the
|
||||
* next day's has to be scheduled again after it fires or after a wakeup
|
||||
* that arrives while the app happens to already be open - see
|
||||
* notify_subscribe()) and on every config submission (settings may have
|
||||
* changed notify_enabled/notify_hour/notify_minute). */
|
||||
void notify_reschedule(const WatchConfig *cfg);
|
||||
|
||||
/* True if this app launch was triggered by the daily notification wakeup
|
||||
* firing rather than the user/phone opening it normally - main.c uses
|
||||
* this to vibrate on launch, since the report window it pushes either
|
||||
* way already leads with day significance and the top transit (the
|
||||
* "quick digest" this notification promises), so no separate screen is
|
||||
* needed for it. */
|
||||
bool notify_was_wakeup_launch(void);
|
||||
|
||||
/* Subscribes `handler` to fire if the scheduled wakeup goes off while
|
||||
* this app is already running in the foreground - the only case
|
||||
* notify_was_wakeup_launch() can't see, since no fresh launch happens
|
||||
* then (see wakeup_service_subscribe()'s own doc comment in pebble.h).
|
||||
* Call once, early in main(). */
|
||||
void notify_subscribe(WakeupHandler handler);
|
||||
|
||||
#endif
|
||||
+10
-7
@@ -61,21 +61,24 @@ def configure(ctx):
|
||||
|
||||
|
||||
def build(ctx):
|
||||
ctx.load('pebble_sdk')
|
||||
|
||||
# Regenerate src/c/i18n_tables.auto.c from engine/i18n/*.lang,
|
||||
# resources/img/*.png from res/img/*.jpeg, and resources/app_icon.png
|
||||
# from res/app_icon_50x50_64_color.png, before compiling/bundling
|
||||
# anything - see each script's own doc comment. All three run eagerly
|
||||
# (not as waf Tasks) since the bundle step below depends on their
|
||||
# output existing; gen_card_images.py/gen_app_icon.py both skip
|
||||
# regenerating anything already up to date, so this stays cheap on
|
||||
# repeat builds. Requires Pillow (`pip install Pillow`) - see
|
||||
# watch/README.md.
|
||||
# (not as waf Tasks) and must run *before* ctx.load('pebble_sdk')
|
||||
# below, since loading that tool in a build context immediately packs
|
||||
# resources/ into the app bundle - on a fresh checkout with no
|
||||
# generated resources/img/ yet, that packing step would crash before
|
||||
# these scripts ever got a chance to create it. gen_card_images.py/
|
||||
# gen_app_icon.py both skip regenerating anything already up to date,
|
||||
# so this stays cheap on repeat builds. Requires Pillow (`pip install
|
||||
# Pillow`) - see watch/README.md.
|
||||
subprocess.check_call([sys.executable, 'scripts/gen_i18n_tables.py'])
|
||||
subprocess.check_call([sys.executable, 'scripts/gen_card_images.py'])
|
||||
subprocess.check_call([sys.executable, 'scripts/gen_app_icon.py'])
|
||||
|
||||
ctx.load('pebble_sdk')
|
||||
|
||||
engine_dir = ctx.path.parent.find_dir('engine/src')
|
||||
engine_nodes = [engine_dir.find_node(name) for name in ENGINE_SRCS]
|
||||
interp_dir = ctx.path.parent.find_dir('interpreter/src')
|
||||
|
||||
Reference in New Issue
Block a user