Public Access
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
155 lines
8.2 KiB
YAML
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
|