Skip to content

Move the ARMv6 leg out of core CI into a workflow of its own - #51

Merged
chrisuthe merged 2 commits into
mainfrom
chrisuthe/task/move-the-armv6-leg-out-of-core-ci-into-a-workflow
Aug 31, 2026
Merged

Move the ARMv6 leg out of core CI into a workflow of its own#51
chrisuthe merged 2 commits into
mainfrom
chrisuthe/task/move-the-armv6-leg-out-of-core-ci-into-a-workflow

Conversation

@chrisuthe

Copy link
Copy Markdown
Member

linux-armv6 emulates every compile under qemu-user inside a Raspbian container: 23 minutes measured, where every other leg in the matrix finishes in under three and a half. As a matrix leg it ran whenever the matrix ran — every branch push, and again on every pull request — so every push waited half an hour on the leg that almost never had anything new to say.

The split

.github/workflows/build-armv6.yml is a new one-job workflow holding that leg with every matrix.* branch collapsed to the container-only side. It carries its own triggers:

  • workflow_call, so release.yml calls it
  • push: branches: [main] — post-merge coverage
  • pull_request filtered to .github/workflows/build-armv6.yml, scripts/build_armv6_container.sh and CMakeLists.txt
  • workflow_dispatch

No unfiltered branch push, which is the cost being removed.

build.yml loses the leg and everything only it used: the container matrix key, the emulator-registration and container start/stop steps, and every if [ -n "\$IMAGE" ] branch in Configure, Build, Test, Smoke test, Package and the architecture assertion. matrix.arm32 stays — linux-armv7 still uses it.

release.yml calls both workflows and its release job needs both, so a red ARMv6 build fails the release rather than publishing without its archive. The two expected-asset lists are untouched and still name sendspin-cli-\$VERSION-linux-armv6.tar.gz.

Nothing the leg proved is lost

Every assertion survives: QEMU_CPU=arm1176 throughout (configure included), the configure-output expectations for backends and mDNS, Tag_CPU_arch: v6 and Tag_ABI_VFP_args: VFP registers read off the linked binary, the GLIBC_2.36 bookworm ceiling, the EMULATED BUILD-INFO.txt block with its glibc floor read off the binary, the payload file-list diff against both the staged tree and the tarball, -Wno-error=restrict as the single warning exemption, --init with a non-root build user, and if: always() on the container stop. The cache key is the same literal deps-linux-armv6-<hash>, so existing caches still hit.

The concurrency guard

build-armv6.yml keeps a concurrency block — cancelling a superseded run is worth more on a 25-minute job than anywhere else here — but cancel-in-progress is conditioned on the ref rather than the workflow name:

cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }}

Two runs share a group only if they share a ref, a release run's ref is always a tag, and no tag-ref run cancels anything — so the guard holds whatever github.workflow resolves to inside a called workflow, which is a fact this repo cannot test without cutting a release. release.yml's no-concurrency comment now says that its tag-only trigger is load-bearing for this, so a later workflow_dispatch there cannot silently re-open the window.

The trade, written down

A tag now builds one thing a push does not. build.yml's header no longer claims a release is gated on exactly what a push is gated on, and neither does release.yml or ROADMAP item 10. What bounds the cost: the tip of main gets an ARMv6 build within half an hour of a merge, and the release path still blocks on a green ARMv6 build. The pull_request path filter is deliberately honest about its reach — it catches infrastructure changes, not the ARMv6-only source breaks this project has actually met (the gcc 12 -Wrestrict false positive and the -latomic class), which are caught after the merge instead.

Also in here

The matrix timeout-minutes comes down from 40 to 20. That 40 was sized for ARMv6 alone; 20 is sized off recent runs — linux-armv7 is the slowest remaining leg at ~3m09s and macos-arm64 comes in under two minutes, so it leaves better than five times the slowest measurement.

Comment debt swept where the old arrangement was named: ci.yml, scripts/build_armv6_container.sh (including the message it prints to a developer whose container died), scripts/build_arm32.sh, scripts/get_started_linux.sh, README's "## CI", ROADMAP items 10 and 12, and docs/wiki/Installation.md.

Checks

actionlint is clean on all five workflows and shellcheck scripts/*.sh passes. This PR touches its own workflow path, so it runs linux-armv6 through the new file's path filter — which is what stands in for a workflow_dispatch that is not available until the workflow is on the default branch.

linux-armv6 emulates every compile under qemu-user inside a Raspbian
container: 23 minutes measured, against the two or three every other leg
in the matrix takes. As a matrix leg it ran whenever the matrix ran --
every branch push, and again on every pull request -- so every push
waited half an hour on the one leg that almost never had anything new to
say.

It is now .github/workflows/build-armv6.yml, one container-only job
carrying its own triggers: workflow_call so release.yml can call it,
push on main, a pull_request filtered to the workflow itself,
scripts/build_armv6_container.sh and CMakeLists.txt, and
workflow_dispatch. No unfiltered branch push, which is the cost being
removed. Its concurrency guard is conditioned on the ref rather than on
the workflow name, so it cannot cancel a run release.yml started
whatever github.workflow resolves to inside a called workflow.

Every assertion the matrix leg carried survives: QEMU_CPU=arm1176
throughout, the configure-output expectations, Tag_CPU_arch v6 and the
hard-float EABI read off the linked binary, the GLIBC_2.36 bookworm
ceiling, the EMULATED BUILD-INFO.txt block, the payload diff against
both the staged tree and the tarball, and if: always() on the container
stop.

build.yml loses the leg and everything only it used: the container
matrix key, the emulator registration and container start/stop steps,
and every [ -n "$IMAGE" ] branch. Its matrix timeout comes down from 40
to 20, that 40 having been sized for ARMv6 alone.

release.yml calls both workflows and its release job needs both, so a
red ARMv6 build fails the release rather than publishing without its
archive. The two expected-asset lists are untouched and still name
sendspin-cli-$VERSION-linux-armv6.tar.gz.

The trade this buys -- a tag builds one thing a push does not -- is
written down rather than left to be discovered: in both workflows'
comments, in README's CI section, and in ROADMAP item 12.
…out off a real run

- build.yml: drop a stray comment fragment left where the !matrix.container
  paragraph was, and justify timeout-minutes: 20 against measured job
  durations rather than an inherited sentence. The slowest remaining leg is
  linux-armv7 at ~3m09s and macos-arm64 comes in under two minutes, so 20
  leaves better than five times the slowest measurement.
- build.yml: note that build-armv6.yml holds a third copy of the shared
  cmake options array, the payload assertion already carrying that kind of
  cross-reference both ways.
- release.yml: say in the no-concurrency comment that the tag-only trigger
  is also what makes build-armv6.yml's conditional cancel-in-progress hold,
  so a later workflow_dispatch here cannot silently re-open that window.
- scripts/get_started_linux.sh: build_armv6_container.sh is no longer run on
  every build, and 'the containerised leg' names nothing.
- scripts/build_armv6_container.sh: the caller is one job, not a matrix leg.
- docs/ROADMAP.md item 10: drop the echo of the retired claim that a release
  is gated on what a push is gated on.
- README, ROADMAP, build-armv6.yml: the tip of main is covered within half an
  hour of a merge, not every merge -- cancel-in-progress means a superseded
  run does not finish.
- docs/wiki/Installation.md: 'anything not in the matrix' no longer names the
  set of architectures with no release.
- Comments that narrated the split rather than describing the current
  arrangement, and the overstated claim that the job name is load-bearing.
@chrisuthe
chrisuthe marked this pull request as ready for review August 31, 2026 02:47
@chrisuthe
chrisuthe merged commit b5c69c6 into main Aug 31, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant