Skip to content

ADR 0045: v0.2.0-alpha, and packaging finally built on pinned old bases

Status

Accepted

Context

A real batch of user-facing work has landed since v0.1.0-alpha: the mouse hit-test drift fix, the italic glyph clipping fix, the diagnostic underline redesign, the empty-buffer welcome overlay, faster global easing, and full floating-panel isolation with a themed quit-confirmation dialog. That's new functionality, not just fixes — worth a real version, not a patch.

Separately, docs/feedback/0001 recorded two real, still-open packaging bugs from the first external review: the .deb requires Qt 6.11 (Debian stable ships 6.10.2) and the AppImage requires glibc 2.43 — both because they were built natively on this rolling-release Arch machine, baking in whatever happens to be newest here instead of a real, portable minimum. That was deliberately deferred to "a future batch of fixes" rather than fixed in isolation. This is that batch.

Decision

Version: 0.2.0

CMakeLists.txt's project(VERSION 0.2.0) — every consumer (ASE_VERSION_STRING, the packaging scripts' version detection, the About panel) reads from this one place already, so nothing else needed a matching edit. Still -alpha (appended at the GUI compile-definition level, per ADR 0033) — nowhere near feature-complete for v1 yet.

.deb built inside debian:bookworm (current stable), not natively

packaging/debian/build-deb.sh was already written to derive its dependency floor from whatever machine actually runs it (ldd + dpkg -S against the just-built binary) — the bug was never the script, it was running it on the wrong machine. Run via docker run -v <repo>:/src -w /src debian:bookworm ... build-deb.sh instead, so the recorded Depends: line reflects Debian stable's own Qt6 packages (6.4.2 in bookworm), achievable on the distributions this format actually targets.

Building inside the container surfaced a second real, previously- invisible bug in the script itself: ldd reports library paths through /lib, which on a usrmerge system (Debian/Ubuntu today) is a symlink to /usr/lib — but dpkg -S does a literal path match against its own database, which records the canonical /usr/lib path. Every Qt6 library silently failed that lookup and got dropped from Depends entirely; the .deb "built successfully" with no Qt6 dependency at all. Fixed with readlink -f before the dpkg -S call. Would never have been caught building natively on Arch, which doesn't have dpkg at all — the script's own || true fallback silently swallowed the lookup failure there instead of surfacing it.

AppImage built inside ubuntu:22.04, not natively

Same root cause as the .deb, same fix shape. Ubuntu 22.04 is the standard, widely-used choice for Qt6 AppImages specifically: old enough to keep the glibc floor low (2.35, vastly older than this dev machine's), and new enough to have Qt6 available as a native apt package (qt6-base-dev, 6.2.4 here) — unlike 20.04, which predates Debian/Ubuntu packaging Qt6 at all.

A real environment gap, not a script bug: --no-install-recommends (used for a lean container image) drops OpenGL headers, which some of Qt6's own CMake package-config files transitively find_dependency() on — without them, find_package(Qt6 COMPONENTS Widgets) reported not found even with Qt6 itself fully installed, tripping the very FATAL_ERROR this project added in the previous release for exactly this "Qt6 missing" case (working as designed — it was right to fail loudly here). Fixed by adding libgl-dev to the container's package list; nothing in the project's own CMake needed to change.

Arch PKGBUILD unaffected

Not rebuilt in a container — a PKGBUILD is a recipe makepkg runs on the user's own system, so it never bakes in a build-host version floor the way a distributed binary (.deb, AppImage) does. Only its pkgver/_tag/sha256sums needed updating for the new tag.

Consequences

Closes docs/feedback/0001 items 1 and 2 for real:

  • .deb: installed cleanly via apt-get install ./ase_*.deb inside a fresh debian:bookworm container (a separate one from the build, so nothing lingered from having Qt6 already unpacked) and launched successfully — Depends: now correctly includes libqt6core6/libqt6gui6/libqt6widgets6/libqt6dbus6 alongside a full, realistic transitive list (fonts, X11, ICU, ...).
  • AppImage: objdump -T on the bundled binary shows a GLIBC_2.34 ceiling, down from 2.43 — confirmed by actually running the built .AppImage on this dev machine's real X11 display, not just inspecting it.

One diagnostic detour worth recording: smoke-testing the AppImage headlessly inside a bare ubuntu:22.04 container (no desktop packages at all beyond what the Dockerfile-equivalent install line above added) surfaced missing libX11/libxcb/libfontconfig/ libfreetype/libharfbuzz/GL-stack libraries. That's not a bundling bug — linuxdeploy deliberately excludes exactly this class of library (X11/GL are tied to the host's display/driver stack; fontconfig/ freetype are tied to the host's actual font configuration, and bundling a mismatched copy causes worse problems than relying on the host's own). A bare server-style container simply isn't a realistic stand-in for "a Linux desktop," which always has these already — confirmed by installing a minimal, realistic desktop library set and then, more conclusively, by running the actual AppImage for real.

Containerized builds are slower and need Docker, but the pinned base is the correct fix, matching real AppImage/Debian-packaging convention rather than a workaround.

Addendum: the containers caught exactly what they were for

Cutting v0.3.0-alpha, the .deb build failed outright:

error: 'QVector<unsigned char>' has no member named 'assign'

QList::assign() arrived in Qt 6.6. debian:bookworm ships Qt 6.4, ubuntu:22.04 ships Qt 6.2. The call went in with ADR 0053's render hot-path fix and had been on main through several rounds of work, because every build since was native on this rolling-release machine — Qt 6.10, where it compiles fine. Neither package had been buildable for that entire stretch, and nothing said so.

That is precisely the failure this ADR introduced the pinned bases to catch, and it only surfaced because a release was cut. Two changes so the next one surfaces sooner and louder:

  • fill() instead of assign() — same operation, available since Qt 5.
  • find_package(Qt6 6.2 ...), so a too-old Qt fails at configure time with a version number instead of at 84% of a compile with a missing-member error. The floor is stated, not implied by whatever happens to compile.

The remaining gap, stated plainly: nothing runs a pinned-base build except a human cutting a release. A CI job doing one build against the floor on every push is the real fix; until then this will be found the same way again.