Skip to content

ci: build Linux AppImages for x86_64 and arm64 - #6

Merged
abossard merged 5 commits into
mcp-serverfrom
ci-linux-arm64
Aug 16, 2026
Merged

ci: build Linux AppImages for x86_64 and arm64#6
abossard merged 5 commits into
mcp-serverfrom
ci-linux-arm64

Conversation

@abossard

Copy link
Copy Markdown
Owner

Re-enables the Linux CI build and publishes AppImages for both 64-bit architectures.

build-linux had been disabled with if: false since cc03493 ("Temporarily disable Linux and Windows builds to iterate on macOS signing"). Windows was restored in 018ca0c; Linux never was. As a result mcp-latest has had no Linux asset at all.

What changes

Reworks build-linux into a two-leg architecture matrix:

Leg Runner Output
x86_64 ubuntu-24.04 qlcplus-v5-…-x86_64.AppImage
aarch64 ubuntu-24.04-arm qlcplus-v5-…-aarch64.AppImage

Both build natively, no emulation. The matrix is narrowed to build-v5-mcp, matching what Windows and macOS already do.

ubuntu-22.04 is gone: it and ubuntu-22.04-arm begin deprecation on 2026-09-17, after which workflows on those labels are terminated with an error (runner-images#14254).

release-mcp now needs build-linux, so it cannot publish before the Linux artifacts upload.

Bugs found on the way

Three only surfaced once the job actually ran:

  • cache-apt-pkgs-action reported success after installing zero packages. It hit a 404 on a superseded libudev-dev deb; the build then failed 90 seconds later at configure with a misleading "Neither libftdi-0.X nor libftdi-1.X found!". Replaced with plain apt-get plus a pkg-config assertion so a dependency problem is reported where it happens. Also libfuse2 does not exist on Ubuntu 24.04 — libfuse2t64 provides libfuse.so.2.
  • Three generated CMake files were committed in 8b4897b, pinning CMAKE_RELATIVE_PATH_TOP_SOURCE to a developer's absolute path. The aarch64 leg built and passed the full test suite, then failed at install trying to read /Users/… on a Linux runner. Untracked, and in-source CMake output is now gitignored.
  • The Linux job never got the Node/web-dmx step that Windows and macOS received in ce0fda1. The CMake install rule is guarded by EXISTS, so the AppImage would have silently shipped without the SPA.

Verification

Beyond a green run, the published artifacts were executed in containers:

Target Result
Debian 13 Trixie arm64 (Raspberry Pi OS proxy) runs, rc=0, correct version banner
Debian 12 Bookworm arm64 fails on GLIBC_2.38 / GLIBCXX_3.4.32
Ubuntu 24.04 arm64 runs, rc=0
x86_64 artifact (emulated) runs, rc=0

Bookworm rejects exactly the two symbol versions the new ABI gate measures, which is what the gate is for.

Three verification steps were added to the job:

  • Payload architecture — extracts the AppImage and checks the shipped ELF, not the runtime stub.
  • ABI floors (aarch64) — asserts glibc ≤ 2.41 and GLIBCXX ≤ 3.4.33, the floors Raspberry Pi OS Trixie provides. Both axes matter: the payload needs GLIBCXX_3.4.32 and libstdc++ is not bundled, so a single-axis check could stay green while Pi compatibility broke.
  • Standalone launch — clears eight Qt environment variables and uses the bundled minimal platform plugin, so the test cannot pass by silently linking against the runner's /opt/Qt. It also asserts the version string, not just the exit code.

Raspberry Pi

The aarch64 AppImage targets Raspberry Pi OS Trixie (64-bit). It does not run on Bookworm — that ships glibc 2.36 and GLIBCXX_3.4.30, below what an Ubuntu 24.04 build requires. CI enforces the Trixie floors on every build, so an AppImage that would fail this way is never published.

The AppImage bundles Qt but not the GL/audio/font stack, so it exits 127 on a bare system. docs/linux-appimage.md lists the nine required packages, the Bookworm incompatibility, and how to run without FUSE.

abossard and others added 5 commits August 13, 2026 10:27
build-linux has been disabled since cc03493 ("Temporarily disable Linux
and Windows builds to iterate on macOS signing"). Windows was restored in
018ca0c, Linux never was.

Rework it into an arch-parameterised matrix producing two AppImages:

- x86_64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm (both native, no
  emulation). The aarch64 build targets Raspberry Pi OS Trixie.
- ubuntu-22.04 is gone: it and ubuntu-22.04-arm start deprecating
  2026-09-17 and workflows on those labels are terminated with an error.
- libfuse2 does not exist on Ubuntu 24.04; libfuse2t64 provides
  libfuse.so.2.
- QTDIR is per-arch: aqt installs linux_gcc_arm64 into gcc_arm64, not
  gcc_64.
- AppRun/appimagetool URLs and the produced filename are arch-derived.
- Add the Node/web-dmx build step, which only Windows and macOS got in
  ce0fda1. The CMake install rule is guarded by EXISTS, so without it
  the Linux AppImage silently shipped without the SPA.
- release-mcp now needs build-linux, so it cannot publish before the
  Linux artifacts upload.

Narrow the matrix to build-v5-mcp, matching what Windows and macOS
already do. Drop continue-on-error so failures are actually visible.

Add three verification steps: AppImage payload architecture, web-dmx
presence, and a glibc ceiling check (<= 2.39) on the aarch64 leg.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9495b72-0428-415e-9a53-50e1ed80df8c
Two failures from the first Linux CI run on this branch.

x86_64 failed at configure with "Neither libftdi-0.X nor libftdi-1.X
found!". Root cause was earlier: cache-apt-pkgs-action hit a 404 for a
superseded libudev-dev .deb, installed zero packages, and still reported
the step as successful. Replace it with plain apt-get, which fails
loudly, and assert libftdi1/libusb-1.0 via pkg-config so a dependency
problem is diagnosed where it happens instead of 90s later. Also pass
--no-install-recommends; one of the two 404s was gdb's libc6-dbg
recommend, which the build does not need.

aarch64 got through configure, build and the test suite, then failed at
install:

  file INSTALL cannot find
  "/Users/abossard/Desktop/projects/qlcplus/resources/fixtures/ADB"

Three generated CMake files were committed by accident in 8b4897b.
resources/fixtures/CMakeFiles/CMakeDirectoryInformation.cmake pins
CMAKE_RELATIVE_PATH_TOP_SOURCE to that absolute path, and the
CREATE_LINK loop in resources/fixtures/CMakeLists.txt symlinks the
source CMakeFiles dir into a fresh build tree, so relative install()
paths resolved against another machine's checkout. Untrack all three and
ignore in-source CMake output so it cannot recur.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9495b72-0428-415e-9a53-50e1ed80df8c
Review found both new verification steps asserting less than their
output claimed.

The glibc gate printed "runs on Raspberry Pi OS Trixie" while checking
only GLIBC symbol versions. The payload also needs GLIBCXX_3.4.32 and
libstdc++ is not bundled, so a Qt/GCC bump could break Pi compatibility
with the gate still green. Check both ABI axes, gate against what the
target actually provides (Trixie/Debian 13: glibc 2.41, GCC 14 ->
GLIBCXX_3.4.33), and narrow the message to the symbol floors it really
verifies -- the ~29 unbundled host libraries are still unchecked.

Test Load AppImage ran with the build host's Qt on LD_LIBRARY_PATH,
QT_PLUGIN_PATH and QML2_IMPORT_PATH, and asked for --platform offscreen,
which is not shipped inside the AppImage. It could only ever have
resolved through /opt/Qt, so the step could not tell a self-contained
AppImage from one rescued by the host. Clear those variables and use the
bundled minimal plugin.

Verified locally before pushing: the rewritten gate passes on the real
artifact's values (GLIBC 2.38, GLIBCXX 3.4.32), passes at the exact
boundary, and fails on a GLIBCXX-only regression that the previous
version would have let through, on a too-new glibc, and on a broken
scan that finds no symbols at all.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9495b72-0428-415e-9a53-50e1ed80df8c
Follow-up to 6797f84. Four issues, one of them a regression I introduced.

The '|| true' I added to reach the zero-symbol diagnostic also converted
a PARTIAL scan failure from red to green: if objdump failed on one file
but succeeded on others, the step reported the maximum of whatever
happened to scan and passed. Scan once, up front, and fail the step if
find/xargs/objdump fails; keep '|| true' only on the grep, where it
means "matched nothing" and the explicit test -n reports it. Scanning
once also halves the work now that there are two axes.

Test Load AppImage asserted the exit code only, so the "print the
APPVERSION string" half of the criterion was never machine-checked. A
literal grep for APPVERSION would have failed: it is 5.3.0-GIT-abc1234
with dashes, while the banner prints spaces. Map dashes back before
comparing.

env -u listed five variables and missed QML_IMPORT_PATH, the Qt6
spelling of QML2_IMPORT_PATH. Not currently exported by
install-qt-action, but it would silently reintroduce host QML if that
changes. Also clear QT_QPA_PLATFORM and QTDIR, the latter set at job
level. The step now prints the child's view of all eight, so the log
carries positive evidence they were cleared rather than resting on
env -u semantics.

Verified locally: partial-scan failure, objdump broken everywhere, and a
missing AppDir are all red with a diagnostic; a healthy scan finding no
symbols is still red with its own message; real values (2.38 / 3.4.32)
and a GLIBCXX-only regression behave as before.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9495b72-0428-415e-9a53-50e1ed80df8c
Review ran the published artifacts in Debian containers and found three
things.

Qt's QML modules install relocatable .o files (~1 MB) into the AppImage.
They are dead weight, and objdump -T rejects them, so the ABI scan would
fail on them if their mode ever gained +x -- demonstrated by chmod u+x
on one, which turned the step red on an otherwise-healthy build. They
are excluded today only by their 0644 mode. Delete them instead.
Verified on the real artifact: 4540 -> 4537 files, nothing else removed,
and the app still starts on Debian 13 arm64 afterwards.

The APPVERSION assertion added in b8a206d degenerates to grep -qF ""
if APPVERSION ever parses as empty, which matches any output and makes
AC5 self-satisfying. Nothing upstream fails in that case because the
artifact filename stays internally consistent. Guard it.

The AppImage bundles Qt but not the GL/audio/font stack, so it exits 127
with libGLX.so.0 on a bare system -- CI never noticed because the runner
has those preinstalled. Document the nine packages, the Bookworm
incompatibility with its exact symbol errors, and the FUSE-less run
methods. Every claim in that file was executed in a container first,
including that AppRun must be used rather than the binary directly,
since chrpath sets a CWD-relative RPATH.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9495b72-0428-415e-9a53-50e1ed80df8c
@abossard
abossard merged commit 97d5ff5 into mcp-server Aug 16, 2026
8 of 10 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