Files
roro9stack/.gitea/workflows/ci.yml
T
twislaandClaude Opus 5.5 07ac7ba457
CI / build (pull_request) Successful in 7m11s
Site / build (pull_request) Successful in 9s
CI: stop rebuilding the framework at every run; a build cache, ccache, and the version out of the compiler flags (#74)
A pull request's firmware step took 358 s: 260 of them rebuilding the
framework that was already in the volume, because the platform decides by
sdkconfig.defaults in the project folder, which is generated and not in git,
so no fresh checkout had it. It is now kept in the volume, inside the
libraries it describes, and copied into the checkout; the platform still
checks its hash against platformio.ini.

The version was a -D on every command line: each commit recompiled
everything, and no cache could help. scripts/version.py now writes one
generated header, read by one file.

PlatformIO's build cache in the volume for pull requests' firmware, ccache
for the host tests (built for coverage, which the build cache can't keep),
the tools in a venv in the volume, and a tag builds its firmware once.
A release still compiles its own sources from nothing.

Measured on fresh copies of the tree: the firmware step 358 s to 27 s (a new
version and one changed file), tests and coverage 49 s to 33 s, a local
rebuild with nothing changed 77 s to 13 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
2026-10-07 02:10:34 +02:00

155 lines
8.2 KiB
YAML

# CI and releases (docs/milestones/R1.md).
# A push to main: the host tests, with their coverage of lib/, and the README's badges
# published to the branch `badges`.
# A pull request: the same tests and coverage, then the firmware (from the build cache).
# A branch's pushes run nothing by themselves: its pull request runs, once.
# A tag v*: the tests, then the firmware built once, clean, signed and published as a
# Gitea release.
# Run by hand: the release of a tag that exists already (the ones from before CI).
#
# The job runs in a plain Python image, as scripts/ci.sh does on a developer's machine, with the
# toolchains in a Docker volume the runner allows (container.valid_volumes: roro9stack-pio): that
# volume is the cache. No JavaScript actions, so the image needs no Node: the checkout is git.
#
# What the volume keeps between runs, and what makes each go stale (issue #74, R1.md):
# /pio/packages, /pio/platforms toolchains and the framework: by their versions in platformio.ini
# .../framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
# the mark that the framework is already rebuilt with our SDK settings: the
# project's sdkconfig.defaults, which isn't in git, so that every fresh
# checkout rebuilt the framework (260 s). The platform checks its hash
# against platformio.ini's settings, and rebuilds if they differ. It is kept
# inside the libraries it describes, so it goes when they are reinstalled.
# /pio/ci/build-cache PlatformIO's build cache (SCons): objects by the signature of their
# sources and command line. For pull requests only: a release compiles
# its own sources from nothing.
# /pio/ci/ccache the host tests' objects (they're built for coverage, which the build
# cache can't keep: it would lose the .gcno files)
# /pio/ci/venv PlatformIO and gcovr: delete the folder to upgrade them
name: CI
on:
push:
branches: [main] # other branches are tested by their pull request: one run, not two
tags: ['v*']
# A change that touches nothing but the site and the documents it is built from runs the Site
# workflow only (a tag always runs this one: Gitea doesn't apply path filters to tags).
paths-ignore: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md']
pull_request:
paths-ignore: ['site/**', 'docs/**', 'README.md', 'CONTEXT.md']
workflow_dispatch:
inputs:
tag:
description: An existing tag to build and publish as a release
required: true
jobs:
build:
runs-on: ubuntu
# A pull request from a fork would run someone else's code on our runner: not without us (Q154).
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
container:
image: python:3.12-slim
volumes:
- roro9stack-pio:/pio
env:
PLATFORMIO_CORE_DIR: /pio
RORO_NO_DOCKER: 1
SDK_MARK: /pio/packages/framework-arduinoespressif32-libs/.roro-sdkconfig.defaults
CCACHE_DIR: /pio/ci/ccache
CCACHE_MAXSIZE: 1G
steps:
- name: Tools
run: |
apt-get update -qq
apt-get install -y -qq --no-install-recommends git build-essential openssl ccache >/dev/null
mkdir -p /pio/ci
if [ ! -x /pio/ci/venv/bin/pio ]; then
python -m venv /pio/ci/venv
/pio/ci/venv/bin/pip install -q --no-cache-dir platformio gcovr
fi
ln -sf /pio/ci/venv/bin/pio /pio/ci/venv/bin/gcovr /usr/local/bin/
pio --version; df -h /pio | tail -1; du -sh /pio/ci/* 2>/dev/null || true
# The build cache only grows: start it again past 3 GB (a full set of objects is 160 MB, and each run adds about 40).
if [ "$(du -sm /pio/ci/build-cache 2>/dev/null | cut -f1)" -gt 3072 ] 2>/dev/null; then rm -rf /pio/ci/build-cache; fi
- name: Check out
run: |
find . -mindepth 1 -maxdepth 1 -exec rm -rf {} +
git config --global --add safe.directory '*'
git init -q .
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git fetch -q --tags origin '+refs/heads/*:refs/remotes/origin/*' '+refs/pull/*/head:refs/remotes/pull/*'
git checkout -q --detach "${{ github.sha }}"
git describe --tags --always
- name: Host tests, and their coverage of lib/
if: github.event_name != 'workflow_dispatch'
run: |
export PATH="/usr/lib/ccache:$PATH" # gcc and g++ through ccache
scripts/coverage.sh
ccache -s | grep -E 'Hits|Misses' | head -2
# A pull request only: a tag's firmware is built once, by the release step below.
- name: The firmware
if: github.event_name == 'pull_request'
env:
PLATFORMIO_BUILD_CACHE_DIR: /pio/ci/build-cache
run: |
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" sdkconfig.defaults
scripts/ci.sh builds
cp sdkconfig.defaults "$SDK_MARK" # what the framework in the volume is rebuilt with, now
# The README's badges are files on a branch of their own, replaced at each push to main and
# at each tag (the release badge says which tag is the latest)
- name: Publish the badges
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag')
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
rm -rf /tmp/badges && mkdir /tmp/badges
cp .pio/coverage/coverage.svg .pio/coverage/summary.json /tmp/badges/
scripts/coverage_badge.py --plain release "$(git describe --tags --abbrev=0)" /tmp/badges/release.svg
cd /tmp/badges
git init -q -b badges .
git add .
git -c user.name="roro9stack CI" -c user.email="ci@git.twis.la" commit -q -m "Coverage of ${{ github.ref_name }} at ${{ github.sha }}"
git push -q --force "$(echo "${{ github.server_url }}" | sed "s#://#://ci:${GITEA_TOKEN}@#")/${{ github.repository }}.git" badges
- name: Which release
id: release
run: |
if [ "${{ github.event_name }}" = workflow_dispatch ]; then
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
elif [ "${{ github.ref_type }}" = tag ]; then
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Build and sign the release
if: steps.release.outputs.tag != ''
env:
OTA_SIGNING_KEY: ${{ secrets.OTA_SIGNING_KEY }}
run: |
# The sources of the tag in a clone of their own; the tools are this commit's.
rm -rf /tmp/release-src dist
git clone -q . /tmp/release-src
git -C /tmp/release-src checkout -q --detach "refs/tags/${{ steps.release.outputs.tag }}"
# The key exists as a file only while this step runs, in a container that goes with the job.
umask 077
export RORO_OTA_KEY="$(mktemp)"
trap 'rm -f "$RORO_OTA_KEY"' EXIT
printf '%s\n' "$OTA_SIGNING_KEY" > "$RORO_OTA_KEY"
umask 022
# The framework rebuilt with our settings is reused if it matches (the platform checks);
# the release's own sources are compiled from nothing, with no build cache.
[ ! -f "$SDK_MARK" ] || cp "$SDK_MARK" /tmp/release-src/sdkconfig.defaults
scripts/release_build.sh /tmp/release-src dist
# An old tag has no SDK settings of its own, and no sdkconfig.defaults afterwards: nothing to mark.
[ ! -f /tmp/release-src/sdkconfig.defaults ] || [ ! -d "$(dirname "$SDK_MARK")" ] || cp /tmp/release-src/sdkconfig.defaults "$SDK_MARK"
- name: Publish the release
if: steps.release.outputs.tag != ''
env:
GITEA_API: ${{ github.server_url }}/api/v1
GITEA_REPO: ${{ github.repository }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: scripts/release_publish.py dist