Files
BikeControl/Dockerfile.builder
T
dtourolleandClaude Opus 5 06b4635470 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>
2026-08-05 19:52:45 +02:00

105 lines
4.2 KiB
Docker

# BikeControl builder image.
#
# One image that can do every job CI asks of it:
# - `cargo test` / `clippy` on the host target, which means the *Linux BLE*
# stack must be present: btleplug talks to BlueZ over D-Bus, so libdbus is a
# hard build dependency, not an optional extra.
# - the Tauri desktop bundle (deb / AppImage), which needs the webkit2gtk set.
# - the Android APK: SDK, NDK, JDK, and the three Android Rust targets.
#
# Build and push:
# docker build -f Dockerfile.builder -t gitea.tourolle.paris/dtourolle/bikecontrol-builder:latest .
# docker push gitea.tourolle.paris/dtourolle/bikecontrol-builder:latest
# or run scripts/build-builder-image.sh.
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
ANDROID_HOME=/opt/android-sdk \
ANDROID_SDK_ROOT=/opt/android-sdk \
NDK_VERSION=27.0.11902837 \
SDK_VERSION=36 \
BUILD_TOOLS_VERSION=35.0.0 \
RUST_BACKTRACE=1 \
CARGO_HOME=/root/.cargo \
PATH="/root/.cargo/bin:$PATH"
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
curl \
wget \
git \
ca-certificates \
unzip \
jq \
file \
pkg-config \
openjdk-17-jdk-headless \
libssl-dev \
libclang-dev \
llvm-dev \
# BLE on the host target: btleplug's Linux backend is bluez-async over the
# `dbus` crate, which links against libdbus-1. Without this the workspace
# does not build at all — `crates/ble` is not optional.
libdbus-1-dev \
# Tauri v2 desktop (needed for `cargo test`/`clippy` on the host target and
# for the deb/AppImage bundle).
libglib2.0-dev \
libgtk-3-dev \
libwebkit2gtk-4.1-dev \
libjavascriptcoregtk-4.1-dev \
libsoup-3.0-dev \
librsvg2-dev \
libayatana-appindicator3-dev \
# AppImage bundling reaches for these at bundle time.
fuse3 \
desktop-file-utils \
&& rm -rf /var/lib/apt/lists/*
# Node 20 for the Vite/Svelte frontend. The UI is plain npm (ui/package-lock.json
# is the lockfile CI installs from), so no bun here.
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
# Rust, with every target the release job builds for.
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \
. "$CARGO_HOME/env" && \
rustup component add rustfmt clippy && \
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
# The Tauri CLI as a cargo subcommand. The frontend package.json deliberately
# does not carry @tauri-apps/cli — the shell is a cargo workspace member, so the
# CLI belongs to the toolchain, not to the UI's dependency tree.
RUN . "$CARGO_HOME/env" && cargo install tauri-cli --locked --version "^2"
# ---------------------------------------------------------------------------
# Android SDK / NDK.
RUN mkdir -p "$ANDROID_HOME" /root/.android && \
printf '### User Sources for `android` cmd line tool ###\ncount=0\n' > /root/.android/repositories.cfg && \
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip && \
unzip -q /tmp/cmdline-tools.zip -d "$ANDROID_HOME" && \
rm /tmp/cmdline-tools.zip && \
mkdir -p "$ANDROID_HOME/cmdline-tools/latest" && \
mv "$ANDROID_HOME/cmdline-tools/cmdline-tools/"* "$ANDROID_HOME/cmdline-tools/latest/" && \
rmdir "$ANDROID_HOME/cmdline-tools/cmdline-tools"
# Licences up front so Gradle never blocks on a prompt in CI.
RUN yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" --licenses > /dev/null
RUN "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" \
"platform-tools" \
"platforms;android-$SDK_VERSION" \
"build-tools;$BUILD_TOOLS_VERSION" \
"ndk;$NDK_VERSION" \
--channel=0 2>&1 | grep -v "Warning" || true
ENV NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION \
ANDROID_NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION \
ANDROID_NDK_ROOT=$ANDROID_HOME/ndk/$NDK_VERSION \
PATH="/opt/android-sdk/platform-tools:/opt/android-sdk/cmdline-tools/latest/bin:$PATH"
WORKDIR /app
ENTRYPOINT ["/bin/bash"]