Build every artifact in a pinned image, not on my laptop
Two builder images, because the jobs genuinely need two distributions: Ubuntu for the tests, the Linux bundle and the Android APK, and Arch for the .pkg.tar.zst, since makepkg is Arch-specific. Both are built from this repo (NFR-12) and pinned by tag in the workflows, so a Dockerfile change only reaches CI once it has been pushed. libdbus-1-dev is not incidental in the Ubuntu image: btleplug's Linux backend is bluez-async over the dbus crate, so without it the workspace does not build at all. The per-commit Android job is a cargo check, not an APK. The full signed build is ~15 minutes and runs only on tags; a one-minute check catches what actually breaks — the JNI shim, droidplug, and any desktop-only API that has crept into a shared crate. Two invariants a compiler cannot see are checked there too, because both fail silently: the app builds, installs, launches, and finds no trainer. The JNI symbol in android.rs is matched by the runtime by name, so renaming the Kotlin package compiles fine and simply never initialises btleplug; and gen/ must stay untracked or the sync script quietly becomes optional. The Android versionCode carries a 1000 floor. `tauri android init` writes 1000 for 0.1.0 today, so anyone holding a locally built APK already has that number installed, and a bare major/minor/patch code would be 100 — a downgrade, which Android refuses outright. The fmt check is advisory for now. The tree predates this workflow and `cargo fmt --all` currently rewrites ~2000 lines across 28 files; making it a gate here would mean landing a repo-wide reformat as a side effect of adding CI. Run fmt in its own commit, then drop the continue-on-error. The two clippy warnings that stood between the tree and a real `-D warnings` gate are fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build (and optionally push) the CI builder images.
|
||||
#
|
||||
# scripts/build-builder-image.sh # build both, no push
|
||||
# scripts/build-builder-image.sh --push # build both and push
|
||||
# scripts/build-builder-image.sh --only arch # just the Arch image
|
||||
# scripts/build-builder-image.sh --only main # just the Ubuntu image
|
||||
#
|
||||
# Two images, because the jobs genuinely need different distributions:
|
||||
# main — Ubuntu: tests, Linux desktop bundle, Android APK (Dockerfile.builder)
|
||||
# arch — Arch: the .pkg.tar.zst, which needs makepkg (Dockerfile.arch)
|
||||
#
|
||||
# The workflows in .gitea/workflows pin these by tag, so a change to either
|
||||
# Dockerfile only reaches CI once this has run with --push.
|
||||
set -euo pipefail
|
||||
|
||||
REGISTRY="${REGISTRY:-gitea.tourolle.paris}"
|
||||
NAMESPACE="${NAMESPACE:-$REGISTRY/dtourolle}"
|
||||
TAG="${TAG:-latest}"
|
||||
|
||||
PUSH=0
|
||||
ONLY=both
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--push) PUSH=1 ;;
|
||||
--only) shift; ONLY="${1:-}" ;;
|
||||
-h|--help) sed -n '2,16p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
||||
*) echo "❌ Unknown option: $1 (try --help)" >&2; exit 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
build_one() {
|
||||
local dockerfile="$1" image="$NAMESPACE/$2:$TAG"
|
||||
echo "🐳 Building $image from $dockerfile"
|
||||
docker build -f "$dockerfile" -t "$image" .
|
||||
if [ "$PUSH" = 1 ]; then
|
||||
echo "⬆️ Pushing $image"
|
||||
docker push "$image"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$ONLY" in
|
||||
main) build_one Dockerfile.builder bikecontrol-builder ;;
|
||||
arch) build_one Dockerfile.arch bikecontrol-arch-builder ;;
|
||||
both)
|
||||
build_one Dockerfile.builder bikecontrol-builder
|
||||
build_one Dockerfile.arch bikecontrol-arch-builder
|
||||
;;
|
||||
*) echo "❌ --only takes main, arch or both" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
[ "$PUSH" = 1 ] || echo "ℹ️ Not pushed. Re-run with --push when you are happy with it."
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env sh
|
||||
# Pin an explicit, monotonic Android versionCode.
|
||||
#
|
||||
# `tauri android init` derives one from the semver in a way that is not
|
||||
# monotonic across a version series: 0.1.0 and 0.0.10 can collide, and Android
|
||||
# refuses to install an APK whose versionCode is not greater than the installed
|
||||
# one. A rider who cannot update is a rider who stops updating.
|
||||
#
|
||||
# code = 1000 + major * 10000 + minor * 100 + patch
|
||||
#
|
||||
# so 0.1.0 -> 1100, 0.1.3 -> 1103, 0.2.0 -> 1200, 1.0.0 -> 11000. Monotonic in
|
||||
# semver order for any minor/patch below 100, which is well past where this
|
||||
# project will ever get.
|
||||
#
|
||||
# The 1000 floor is not decoration: `tauri android init` writes versionCode=1000
|
||||
# for 0.1.0 today, so anyone carrying a locally-built APK already has that
|
||||
# number installed. A bare major/minor/patch code would be 100 — a *downgrade*,
|
||||
# which Android refuses outright.
|
||||
#
|
||||
# POSIX sh: the runner's /bin/sh is dash. No here-strings, no \s in sed.
|
||||
set -e
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PROPS="$ROOT/src-tauri/gen/android/app/tauri.properties"
|
||||
CONF="$ROOT/src-tauri/tauri.conf.json"
|
||||
|
||||
[ -f "$PROPS" ] || { echo "❌ $PROPS not found — run 'cargo tauri android init' first" >&2; exit 1; }
|
||||
|
||||
VERSION=$(grep '"version"' "$CONF" | head -1 | sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/')
|
||||
MAJ=$(echo "$VERSION" | cut -d. -f1)
|
||||
MIN=$(echo "$VERSION" | cut -d. -f2)
|
||||
PAT=$(echo "$VERSION" | cut -d. -f3)
|
||||
# Guard a malformed or short version so we can never emit code 0, which Android
|
||||
# treats as "older than everything".
|
||||
: "${MAJ:=0}" "${MIN:=0}" "${PAT:=0}"
|
||||
CODE=$(( 1000 + MAJ * 10000 + MIN * 100 + PAT ))
|
||||
|
||||
echo "version=$VERSION -> versionCode=$CODE"
|
||||
if grep -q '^tauri.android.versionCode=' "$PROPS"; then
|
||||
sed -i "s/^tauri.android.versionCode=.*/tauri.android.versionCode=$CODE/" "$PROPS"
|
||||
else
|
||||
echo "tauri.android.versionCode=$CODE" >> "$PROPS"
|
||||
fi
|
||||
cat "$PROPS"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
# On a tag build, the tag is the single source of truth for the app version.
|
||||
#
|
||||
# Only src-tauri/tauri.conf.json is rewritten. That is what the Tauri bundler
|
||||
# stamps onto the .deb/.AppImage and what `tauri android init` turns into the
|
||||
# Android versionName, so it is the version a user ever sees. The workspace
|
||||
# Cargo.toml is deliberately left alone: editing it would invalidate Cargo.lock
|
||||
# and break every `--locked` build in the same run, to change a number that
|
||||
# appears nowhere but the binary's own metadata.
|
||||
#
|
||||
# On a non-tag run this is a no-op, so it is safe to call unconditionally.
|
||||
#
|
||||
# POSIX sh: the runner's /bin/sh is dash.
|
||||
set -e
|
||||
|
||||
CONF="$(dirname "$0")/../src-tauri/tauri.conf.json"
|
||||
|
||||
case "${GITHUB_REF:-}" in
|
||||
refs/tags/v*)
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
echo "Setting app version to $VERSION (from $GITHUB_REF)"
|
||||
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" "$CONF"
|
||||
;;
|
||||
*)
|
||||
echo "Not a tag build — keeping the version in tauri.conf.json"
|
||||
;;
|
||||
esac
|
||||
|
||||
grep '"version"' "$CONF"
|
||||
Reference in New Issue
Block a user