Add a cross-compiled linux-armv7 leg to the CI matrix - #47
Merged
chrisuthe merged 3 commits intoAug 30, 2026
Merged
Conversation
32-bit Raspberry Pi hardware had no release archive: `get_started_linux.sh` refused every 32-bit ARM userland and told the operator to reinstall a 64-bit OS. This adds a `linux-armv7` publishing leg so a Pi 2, Pi 3, Pi 4 or Pi Zero 2 running a 32-bit Raspberry Pi OS has something to install, and so the tree is proven to still build for that target on every push. It is cross-compiled because nothing else can reach the target: GitHub offers no armv7 runner, and its arm64 runners are Neoverse N1 with no AArch32 at EL0. `scripts/build_arm32.sh` owns the cross configure -- a script rather than lines of YAML, following the precedent build.yml states for `build_macos_pkg.sh`, so it is reproducible without a runner and gated by ci.yml's shellcheck job. The suite is run rather than skipped: `qemu-user-static` through binfmt executes the ctest binaries and `smoke_test.sh` on the host, so the leg keeps the rule every other leg holds. The target flags go in `CMAKE_C_FLAGS`/`CMAKE_CXX_FLAGS` globally rather than on our own targets, because every dependency arrives through FetchContent in the one cmake invocation and a per-target flag would leave them compiling for the host default. A new step reads the linked binary's merged ELF build attributes back and fails the leg unless they say ARMv7, hard-float EABI -- the linker merges attributes across every object in the link, so that one read covers the whole dependency tree. Two steps gated on `runner.os == 'Linux'` rather than on a matrix key would have fired against a cross-built binary, so the matrix gains a `systemd:` key, spelled out on every leg beside `publish` and `avahi`, and an `arm32:` key naming the target. The arm32 leg installs the `:armhf` build dependencies instead of the runner's own, which is what leaves `find_library` nothing of the wrong architecture to answer with; its `expect_mdns` is anchored on the `arm-linux-gnueabihf` path so a host-tree `libdns_sd` fails the leg rather than satisfying the pattern the native legs use. ARMv6 is deliberately not here. Debian and Ubuntu armhf are an ARMv7-A port, and that is where the toolchain's own `crt1.o`, `crtbegin.o` and `libgcc.a` come from -- all ARMv7, all linked in -- so compiling our sources `-march=armv6` does not yield an ARMv6 archive, and the merged `Tag_CPU_arch` says so. It needs a Raspberry Pi OS sysroot. `build_arm32.sh` refuses `armv6` rather than building something that would trap on a Pi Zero, `get_started_linux.sh` refuses an `armv6l` host, and ROADMAP item 12 records what it would take. Downstream: both `for leg in ...` lists in release.yml and its notes prose, the installer's architecture mapping, README, ROADMAP item 12, four wiki pages and a Troubleshooting entry for the two ways a Pi install can fail to start. The archives need glibc 2.38 and so will not start on Raspberry Pi OS bookworm (2.36); they do on trixie (2.41), which is what Raspberry Pi OS ships. That floor is not introduced here -- the existing linux-x86_64 and linux-arm64 archives are built on the same image and carry it too -- but this is the leg that aims a Pi audience at it, so BUILD-INFO.txt now states it where that reader will meet it. Verified in containers: cross configure finds all six backends and resolves mDNS to the armhf path with both dns_sd probes Failed; the build is clean under -Werror on gcc 13; ctest is 426/426 and smoke_test.sh fully green under qemu; Package and the payload assertion pass unchanged, unit and sysusers fragment included. The new gates were checked in both directions -- the ELF assertion rejects an armv8 build, a soft-float build and a native host binary, and the anchored expect_mdns refuses a configure that resolved libdns_sd from the host tree. The native branch still builds and passes 426/426.
Closed
There was a problem hiding this comment.
🟡 Changes recommended
Architecture detection can select incompatible binaries, and the ARMv7 build requires CPU extensions not guaranteed by advertised hosts.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a publishable 32-bit ARM build for supported Raspberry Pi systems.
Changes:
- Adds ARMv7 cross-compilation, emulated testing, and ELF validation.
- Publishes the new archive and updates installation guidance.
- Documents ARMv6 and older-glibc limitations.
File summaries
| File | Description |
|---|---|
.github/workflows/build.yml |
Adds the ARMv7 CI leg. |
.github/workflows/ci.yml |
Updates script execution notes. |
.github/workflows/release.yml |
Publishes the ARMv7 archive. |
scripts/build_arm32.sh |
Configures ARM cross-builds. |
scripts/get_started_linux.sh |
Selects the ARMv7 archive. |
packaging/sendspin-cli.service.in |
Clarifies native syscall ABI handling. |
README.md |
Documents the expanded matrix and releases. |
docs/ROADMAP.md |
Records ARMv7 delivery and remaining ARMv6 work. |
docs/wiki/Home.md |
Advertises ARMv7 availability. |
docs/wiki/Installation.md |
Adds ARMv7 installation instructions. |
docs/wiki/Getting-Started-on-Linux.md |
Expands Linux prerequisites. |
docs/wiki/Getting-Started-on-a-Raspberry-Pi.md |
Documents Pi architecture selection. |
docs/wiki/Troubleshooting.md |
Adds ARM and glibc troubleshooting. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Two ways the armv7 leg could hand somebody a binary their machine cannot run. `uname -m` names the kernel, not the userland, and on a Raspberry Pi the two routinely disagree: `arm_64bit` defaults to 1 on a Pi 4, a Pi 400 and a CM4, so a 32-bit armhf install on one of those boots a 64-bit kernel and reports `aarch64` with no 64-bit library on the disk. `get_started_linux.sh` chose on that answer and installed `linux-arm64`, whose loader is not there. It now asks the userland -- `dpkg --print-architecture`, falling back to `getconf LONG_BIT` paired with the kernel's family -- and keeps `uname -m` only for the question the userland cannot answer: an ARMv6 board cannot run a 64-bit kernel, so `armv6l` there is the CPU speaking. A soft-float `armel` userland is refused rather than handed a hard-float archive, and an unknown architecture is refused rather than falling through to x86-64. The build targeted the Pi's FPU rather than the ABI's. `armv7l` guarantees neither NEON nor VFPv4 -- a Cortex-A8 or A9 is ARMv7-A with VFPv3 and optional NEON -- while the installer maps every ARMv7 host to this archive and the docs advertise generic Linux `armv7`. `-mfpu` is now the armhf baseline of `vfpv3-d16`, so the archive runs on what its name promises. The decoders are fixed-point, so the baseline costs nothing on the path that matters. Verified: the selection was driven through eleven host shapes, including the Pi 4 32-bit-userland-on-64-bit-kernel case that was broken, armel, i386 and s390x. The rebuilt binary reports Tag_CPU_arch v7, Tag_FP_arch VFPv3-D16 and no Tag_Advanced_SIMD_arch at all, still passes the leg's ELF assertion, and is 426/426 on ctest with the smoke test green under qemu.
There was a problem hiding this comment.
🟡 Changes recommended
Two architecture-selection paths can still install binaries that the target userland or CPU cannot execute.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 2
- Review effort level: Balanced
…ppet Two more ways to reach an archive the machine cannot run. The `getconf` fallback passed `$MACHINE` through when it recognised neither the width nor the kernel, which is the mistake the whole block exists to prevent made a second time: a 32-bit x86 userland under an x86-64 kernel reports `x86_64`, and passing that on as the userland hands it the 64-bit archive. It now leaves the userland unset and refuses, so what cannot be positively identified is never guessed at -- which closes the class rather than the one instance of it. The manual-install snippet in the wiki carried its ARMv6 caveat as a trailing comment, and a comment does not stop a Pi Zero downloading the ARMv7 archive and meeting an illegal instruction. It now runs the same `uname -m` guard the script does, for the same reason the script keeps one: an ARMv6 board reports `armhf` like any other 32-bit ARM machine, and cannot run a 64-bit kernel, so `armv6l` there is the CPU speaking. Verified: the selection was driven through thirteen host shapes, the new x86 case and the ten from the previous commit included, with no regression; and the doc snippet was extracted from the page itself and run against six, refusing the Pi Zero and choosing correctly for the rest.
chrisuthe
marked this pull request as ready for review
August 30, 2026 19:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This adds a
linux-armv7publishing leg so a Pi 2, Pi 3, Pi 4 or Pi Zero 2 running a 32-bit Raspberry Pi OS has something to install