diff --git a/.gitignore b/.gitignore index 6ae9f0a..b8dc41d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build/ +build-appimage/ .cache/ compile_commands.json *.user diff --git a/packaging/appimage/AppRun b/packaging/appimage/AppRun new file mode 100755 index 0000000..885b4cf --- /dev/null +++ b/packaging/appimage/AppRun @@ -0,0 +1,11 @@ +#!/bin/sh +# Start the real binary by its path inside the AppImage. A symlinked AppRun makes Qt take +# the AppImage root as the application dir, so it misses usr/bin/qt.conf and finds no +# platform plugins. +HERE="$(dirname "$(readlink -f "$0")")" +# The Plasma platform theme's own file dialog lists folders through KIO workers, which are +# not bundled (it would show every folder as empty). Use the host's dialog via the +# xdg-desktop-portal instead. +export PLASMA_INTEGRATION_USE_PORTAL=1 +export XDG_DATA_DIRS="$HERE/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" +exec "$HERE/usr/bin/qtpotato" "$@" diff --git a/packaging/appimage/build-appimage.sh b/packaging/appimage/build-appimage.sh new file mode 100755 index 0000000..8da1cec --- /dev/null +++ b/packaging/appimage/build-appimage.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# Build a QtPotato AppImage from the host's Qt/KF6/mpv libraries. +# +# The result only runs on distros whose glibc is at least as new as the build host's, +# so build on the oldest distro you want to support. +# +# Usage: packaging/appimage/build-appimage.sh [build-dir] +set -euo pipefail + +SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +BUILD_DIR="$(realpath -m "${1:-$SRC_DIR/build-appimage}")" +TOOLS_DIR="${TOOLS_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/qtpotato-appimage-tools}" +APPDIR="$BUILD_DIR/AppDir" +QT_PLUGINS="$(qmake6 -query QT_INSTALL_PLUGINS)" + +export APPIMAGE_EXTRACT_AND_RUN=1 # works without FUSE +# The strip bundled with linuxdeploy is too old for binaries from current distros +# (e.g. DT_RELR sections) and aborts the deployment. +export NO_STRIP=1 + +fetch_tool() { + local repo=$1 file=$2 + if [[ ! -x "$TOOLS_DIR/$file" ]]; then + curl -fL --create-dirs -o "$TOOLS_DIR/$file" \ + "https://github.com/linuxdeploy/$repo/releases/download/continuous/$file" + chmod +x "$TOOLS_DIR/$file" + fi +} +fetch_tool linuxdeploy linuxdeploy-x86_64.AppImage +fetch_tool linuxdeploy-plugin-qt linuxdeploy-plugin-qt-x86_64.AppImage +fetch_tool linuxdeploy-plugin-appimage linuxdeploy-plugin-appimage-x86_64.AppImage +export PATH="$TOOLS_DIR:$PATH" + +cmake -S "$SRC_DIR" -B "$BUILD_DIR" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_TESTING=OFF +cmake --build "$BUILD_DIR" +rm -rf "$APPDIR" +DESTDIR="$APPDIR" cmake --install "$BUILD_DIR" + +# Plugins that are loaded at runtime by name, so linuxdeploy-plugin-qt cannot find them: +# Kirigami's style plugins, the Plasma platform theme (colors, fonts, icon theme and the +# QtQuick style from the user's Plasma settings) and the Breeze/Union widget styles. +copy_plugins() { + local subdir=$1; shift + mkdir -p "$APPDIR/usr/plugins/$subdir" + for pattern in "$@"; do + for f in "$QT_PLUGINS/$subdir"/$pattern; do + if [[ -e "$f" ]]; then cp "$f" "$APPDIR/usr/plugins/$subdir/"; fi + done + done +} +copy_plugins kf6/kirigami/platform '*.so' +copy_plugins platformthemes KDEPlasmaPlatformTheme6.so +copy_plugins styles breeze6.so UnionWidgetsStyle.so +copy_plugins union '*.so' +copy_plugins kf6 FrameworkIntegrationPlugin.so +# EGL integration for Wayland; without it Qt Quick cannot create an OpenGL context. +copy_plugins wayland-graphics-integration-client '*.so' + +# QtQuick styles and their private modules are also picked at runtime; list them in a +# dummy QML file so qmlimportscanner deploys them. +EXTRA_QML="$BUILD_DIR/appimage-extra-qml" +mkdir -p "$EXTRA_QML" +cat > "$EXTRA_QML/Styles.qml" <<'EOF' +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Fusion +import QtQuick.Templates +import org.kde.kirigami +import org.kde.kirigami.platform +import org.kde.desktop +import org.kde.qqc2desktopstyle.private +import org.kde.breeze +import org.kde.union +Item {} +EOF + +deps_only=() +while IFS= read -r -d '' f; do + deps_only+=(--deploy-deps-only="$f") +done < <(find "$APPDIR/usr/plugins" -name '*.so' -print0) + +# linuxdeploy-plugin-qt deploys every image format plugin and fails on any with a missing +# dependency (e.g. kimg_jxr without libjpegxr). Hand it a filtered view of the plugin dir +# through a qmake wrapper. +FILTERED_PLUGINS="$BUILD_DIR/appimage-qt-plugins" +rm -rf "$FILTERED_PLUGINS" +mkdir -p "$FILTERED_PLUGINS/imageformats" +for d in "$QT_PLUGINS"/*; do + [[ "$(basename "$d")" == imageformats ]] || ln -s "$d" "$FILTERED_PLUGINS/" +done +for f in "$QT_PLUGINS"/imageformats/*.so; do + if ldd "$f" | grep -q 'not found'; then + echo "Skipping $(basename "$f"): missing dependencies" + else + ln -s "$f" "$FILTERED_PLUGINS/imageformats/" + fi +done +cat > "$BUILD_DIR/qmake-wrapper" </dev/null || echo dev)-x86_64.AppImage}" + +cd "$BUILD_DIR" +linuxdeploy-x86_64.AppImage \ + --appdir "$APPDIR" \ + --executable "$APPDIR/usr/bin/qtpotato" \ + --desktop-file "$APPDIR/usr/share/applications/dev.trivk.qtpotato.desktop" \ + --icon-file "$APPDIR/usr/share/icons/hicolor/scalable/apps/dev.trivk.qtpotato.svg" \ + --custom-apprun "$SRC_DIR/packaging/appimage/AppRun" \ + "${deps_only[@]}" \ + --plugin qt \ + --output appimage + +echo "AppImage: $LDAI_OUTPUT"