From 3c5aaed0872f99e22629f89b889c477cc8278a31 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 24 Oct 2026 12:09:17 -0400 Subject: [PATCH] fix(appimage): bundle libcom_err runtime --- .github/workflows/build.yml | 12 +++++- contrib/AppImage/check-libcom-err.sh | 9 +++++ .../tests/test_appimage_dependency_bundle.py | 40 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100755 contrib/AppImage/check-libcom-err.sh create mode 100644 contrib/release/tests/test_appimage_dependency_bundle.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10404ac6..4e76180b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: qt6-wayland-dev qt6-declarative-dev libzip-dev libsodium-dev libgcrypt20-dev libx11-xcb-dev qml6-module-qtquick qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtqml-models protobuf-compiler libprotobuf-dev libhidapi-dev libzxing-dev libusb-dev - libusb-1.0-0-dev patchelf + libusb-1.0-0-dev libcom-err2 patchelf - name: configure git run: git config --global --add safe.directory '*' - uses: actions/checkout@v4 @@ -77,10 +77,20 @@ jobs: export EXTRA_QT_PLUGINS="svg;multimedia;tls" export QML_SOURCES_PATHS="$PWD/src/assets/qml" export PATH="$PWD:$PATH" + # linuxdeploy's AppImage excludelist assumes libcom_err.so.2 is present on the host. + # Minimal Debian and openSUSE systems may omit it, but it is a transitive runtime dependency. + LIBCOM_ERR="$(ldconfig -p | awk '$1 == "libcom_err.so.2" { print $NF; exit }')" + if [[ -z "$LIBCOM_ERR" || ! -f "$LIBCOM_ERR" ]]; then + echo "ERROR: libcom_err.so.2 is unavailable in the build image; cannot make a portable AppImage." >&2 + exit 1 + fi # Populate AppDir (libs + Qt plugins + QML modules) — no --output yet, so we can patch QML first. ./linuxdeploy --appdir AppDir --plugin qt \ + --library "$LIBCOM_ERR" \ -d AppDir/usr/share/applications/wowlet.desktop \ -i AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png + # Guard this packaging regression: the AppImage must carry the excluded system library. + bash contrib/AppImage/check-libcom-err.sh AppDir # --------------------------------------------------------------------------------------- # Make the QML bundle COMPLETE and deterministic (no whack-a-mole). # diff --git a/contrib/AppImage/check-libcom-err.sh b/contrib/AppImage/check-libcom-err.sh new file mode 100755 index 00000000..b054f58e --- /dev/null +++ b/contrib/AppImage/check-libcom-err.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +appdir="${1:?usage: check-libcom-err.sh APPDIR}" +library="$(find "$appdir/usr/lib" -name libcom_err.so.2 -print -quit 2>/dev/null || true)" +if [[ -z "$library" || ! -e "$library" ]]; then + echo "ERROR: AppDir is missing the bundled libcom_err.so.2 runtime library." >&2 + exit 1 +fi diff --git a/contrib/release/tests/test_appimage_dependency_bundle.py b/contrib/release/tests/test_appimage_dependency_bundle.py new file mode 100644 index 00000000..4b4f7a6f --- /dev/null +++ b/contrib/release/tests/test_appimage_dependency_bundle.py @@ -0,0 +1,40 @@ +from pathlib import Path +import subprocess +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[3] +CHECKER = ROOT / "contrib/AppImage/check-libcom-err.sh" + + +class AppImageDependencyBundleTest(unittest.TestCase): + def test_accepts_appdir_with_libcom_err(self): + with tempfile.TemporaryDirectory() as temporary: + appdir = Path(temporary) / "AppDir" + library = appdir / "usr/lib/libcom_err.so.2" + library.parent.mkdir(parents=True) + library.write_bytes(b"fixture") + + result = subprocess.run(["bash", str(CHECKER), str(appdir)], check=False) + + self.assertEqual(result.returncode, 0) + + def test_refuses_appdir_without_libcom_err(self): + with tempfile.TemporaryDirectory() as temporary: + appdir = Path(temporary) / "AppDir" + (appdir / "usr/lib").mkdir(parents=True) + + result = subprocess.run( + ["bash", str(CHECKER), str(appdir)], + check=False, + capture_output=True, + text=True, + ) + + self.assertNotEqual(result.returncode, 0) + self.assertTrue(result.stderr.strip()) + + +if __name__ == "__main__": + unittest.main()