ADR 0034: Linux packaging (AppImage, Arch PKGBUILD, .deb)¶
Status¶
Accepted
Context¶
The project had no install() rules at all — nothing to build a
package from — and no packaging scripts. User asked to package the
first alpha release for distribution: an AppImage plus "several
packages."
Decision¶
One install() rule set, three consumers¶
gui/CMakeLists.txt gained standard freedesktop install() rules
(GNUInstallDirs): the ase_gui binary to bin/, a new
packaging/linux/ase.desktop to share/applications/, and a new
square, padded icon (packaging/linux/ase-icon-512.png — the existing
gui/resources/ase.png wordmark logo is 1536×1024, not square;
padded via ImageMagick onto a transparent canvas rather than cropped,
so the existing artwork isn't touched or distorted) to
share/icons/hicolor/512x512/apps/. Every packaging format below is
just cmake --install against this, wrapped differently — nothing
package-specific reimplements "where do the files go."
AppImage (packaging/appimage/build-appimage.sh)¶
Fetches linuxdeploy + linuxdeploy-plugin-qt (cached after the
first run), builds Release, stages an AppDir via the install rules
above, runs linuxdeploy to bundle Qt6 and every other shared library
dependency. Two real bugs hit and fixed during actual verification,
not just written and assumed to work:
linuxdeploy-plugin-qtautodetects aqmakeonPATHto identify which Qt install to bundle — on Arch (this dev machine has both Qt5 and Qt6 installed) it foundqmake-qt5first and failed with "Could not find Qt modules to deploy" because the detected major version didn't match whatase_guiactually links against. Fixed by explicitly settingQMAKEto the realqmake6binary before invoking the plugin (linuxdeploy-plugin-qtrespects that env var).- linuxdeploy's own bundled
strip(from its "continuous" build, older than this rolling-release distro's binutils) doesn't understand a newer ELF section type (.relr.dyn, RELR relative relocations) and aborted the whole run rather than skipping that one file. Fixed withNO_STRIP=1, linuxdeploy's own documented escape hatch — unstripped bundled libraries just make the AppImage a little larger, not a correctness issue.
Arch Linux (packaging/arch/PKGBUILD)¶
A standard makepkg recipe, fetching the source tarball from this
project's own GitHub release tag (v0.1.0-alpha) rather than building
the local checkout — this is the shape a real AUR submission needs.
pkgver can't contain a hyphen (pacman's own restriction), but the
git tag does (v0.1.0-alpha) — pkgver=0.1.0_alpha (underscore) is
the pacman-safe rendering, with a separate _tag/_srcdir pair
spelling out that GitHub's tag tarball extracts to
ase-0.1.0-alpha (hyphen, stripped of the leading v), which does
not equal $pkgname-$pkgver — assuming they matched (makepkg's
usual default) would have pointed build()/package() at a directory
that doesn't exist.
Documented, not solved, limitation: build() needs network access
(Lua and Tree-sitter are both fetched via CMake FetchContent at
configure time — see core/CMakeLists.txt,
modules/syntax/CMakeLists.txt), so this won't build inside a
network-isolated clean chroot yet. Vendoring those dependencies is out
of scope here.
Debian/Ubuntu (packaging/debian/build-deb.sh)¶
Deliberately skips debhelper/devscripts — stages an install root
with the same cmake --install, generates DEBIAN/control with
runtime deps resolved from the actual linked binary (ldd piped
through dpkg -S, not a hand-maintained guess), and calls
dpkg-deb --build directly. Real bug hit during verification, not
assumed away: this project is developed on Arch, whose dpkg has no
package database at all, so dpkg -S fails every lookup — and because
the script uses set -o pipefail, that failure aborted the whole
script before it ever reached the intended fallback
(DEPENDS="${DEPENDS:-libqt6widgets6}"). Fixed by isolating the
dpkg -S call in its own { ... || true; } group so a failed lookup
degrades to empty output (triggering the fallback) rather than killing
the pipeline.
Consequences¶
All three were actually built and run, not just scripted:
- AppImage: built (45MB), launched standalone from
/tmp(outside the dev tree, proving it's genuinely self-contained), opened a test file correctly. - Arch package:
makepkg -fsucceeded against a real download of thev0.1.0-alphaGitHub tag tarball (--printsrcinfovalidated too); see the note below about why a second verification pass was needed after this ADR's own changes landed. .deb: built,dpkg-deb --contentsconfirms the expected file layout,dpkg-deb -x'd and the extracted binary launched and rendered correctly.
One sequencing issue worth recording: the first makepkg attempt
against the already-pushed v0.1.0-alpha tag produced an empty
package (metadata files only, no binary) — that tag was cut before
this ADR's install() rules existed, so the fetched source simply had
nothing for cmake --install to install. v0.1.0-alpha was moved
(force-updated, both locally and on the remote) to include this
commit rather than cutting a new tag, since it had only just been
pushed moments earlier in the same release process with nothing else
depending on it yet — a normal new tag is the right call once a tag
has actually been consumed by anyone else. The PKGBUILD's
sha256sums reflects the tarball GitHub serves for the moved tag.
Not attempted: Flatpak/Snap (heavier tooling, sandboxing manifests —
not asked for); an .rpm (no rpmbuild on this machine, not asked
for); vendoring Lua/Tree-sitter so builds work fully offline.