diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6995359..f3fcbdb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -230,7 +230,8 @@ jobs: gui-${{ matrix.target }}-cargo- # Tauri v2 build dependencies (webkit2gtk + appindicator; patchelf/file for - # the AppImage). Only Linux needs them; macOS/Windows use system webviews. + # the AppImage, squashfs-tools to unpack it again below). Only Linux needs + # them; macOS/Windows use system webviews. - name: Install Linux bundle dependencies if: runner.os == 'Linux' timeout-minutes: 6 @@ -248,7 +249,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ libwebkit2gtk-4.1-dev libgtk-3-dev librsvg2-dev \ - libayatana-appindicator3-dev libssl-dev patchelf file + libayatana-appindicator3-dev libssl-dev patchelf file squashfs-tools - name: Install the Tauri CLI run: npm install -g @tauri-apps/cli@^2 @@ -310,6 +311,28 @@ jobs: done ls -lh "${{ matrix.asset }}".* + # linuxdeploy bundles the build host's display-stack libraries into the + # AppImage and AppRun puts them ahead of the system's, so a current Mesa fails + # to create an EGL display and the app never opens (tauri-apps/tauri#15976). + # Rebuilding the bundle without them needs appimagetool, which upstream ships + # only as a release asset — pinned and checksummed, since it rewrites the file + # this job publishes. + - name: Install appimagetool + if: contains(matrix.bundles, 'appimage') + run: | + curl -fsSL -o appimagetool \ + https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage + echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 appimagetool" | + sha256sum -c - + chmod +x appimagetool + sudo mv appimagetool /usr/local/bin/ + + - name: Unbundle the display stack from the AppImage + if: contains(matrix.bundles, 'appimage') + env: + APPIMAGE_EXTRACT_AND_RUN: 1 # the runners have no FUSE + run: scripts/appimage-unbundle-display-stack.sh "${{ matrix.asset }}.AppImage" + - name: Collect bundles (Windows) if: runner.os == 'Windows' shell: pwsh diff --git a/CHANGELOG.md b/CHANGELOG.md index 80778b2..9c2c4b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ Versions follow [Semantic Versioning](https://semver.org/). --- +## Unreleased + +### Bug Fixes +- **The Linux AppImage opens on current graphics drivers.** On distributions with a recent Mesa — Arch and CachyOS, Bazzite, Nobara — it aborted at launch with `Could not create default EGL display: EGL_BAD_PARAMETER` and never showed a window, and the software-rendering restart added in 1.1.2 ran into the same abort: the failure happens while the graphics driver is being loaded, before WebKit picks a renderer, so no WebKit setting can reach it. The cause was the bundle itself. It carried the build machine's own Wayland and X client libraries and put them ahead of yours on the library path, so your Mesa was loaded against a libwayland older than the one it is built against and a symbol it needs was missing. Those ten libraries are no longer packed into the AppImage; your system's copies are used, as they already were for libX11. The `.deb`, `.rpm`, macOS and Windows builds were never affected. + +--- + ## 1.1.2 — 2026-08-22 ### Features diff --git a/scripts/appimage-unbundle-display-stack.sh b/scripts/appimage-unbundle-display-stack.sh new file mode 100755 index 0000000..927c12b --- /dev/null +++ b/scripts/appimage-unbundle-display-stack.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Rebuilds an AppImage without the display-stack libraries linuxdeploy bundled into +# it. AppRun.wrapped puts $APPDIR/usr/lib ahead of the system directories on +# LD_LIBRARY_PATH, so the 22.04 build host's copies shadow the user's for everything +# loaded into the process — the host's own Mesa included. Mesa 25+ against a 1.20 +# libwayland fails to create an EGL display, which aborts the web process before +# WebKit ever picks a renderer, so no WebKit environment variable can heal it and the +# window simply never opens (tauri-apps/tauri#15976). +# +# This makes the host's copies load-bearing: the bundled GTK names libwayland-client, +# libwayland-cursor and libwayland-egl in DT_NEEDED even under the GDK_BACKEND=x11 the +# AppRun hook forces, so a host with no Wayland stack at all now fails to load. That is +# the same bargain libX11 is already on — it was never bundled either — and every host +# new enough for the glibc the bundle requires (2.35) carries all ten, at versions at +# or above the ones dropped here. +set -eo pipefail + +appimage=${1:?usage: $0 } + +# Sonames, matched with a trailing wildcard for the version suffix. Never a pattern +# loose enough to catch a GTK module beside them (usr/lib/im-wayland.so). +libs=( + libwayland-client.so libwayland-cursor.so libwayland-egl.so libwayland-server.so + libxkbcommon.so + libxcb-randr.so libxcb-render.so libxcb-shm.so + libXau.so libXdmcp.so +) + +# A type 2 AppImage is its runtime ELF with the squashfs appended, so the payload +# starts where the ELF ends. Computed rather than asked for (`--appimage-offset`): +# the step must not depend on executing the bundle it is repacking. +elf_end() { + local shoff shentsize shnum + shoff=$(od -An -tu8 -j40 -N8 "$1" | tr -d ' ') + shentsize=$(od -An -tu2 -j58 -N2 "$1" | tr -d ' ') + shnum=$(od -An -tu2 -j60 -N2 "$1" | tr -d ' ') + echo $((shoff + shentsize * shnum)) +} + +payload_at() { + [ "$(dd if="$1" bs=1 skip="$2" count=4 2>/dev/null)" = hsqs ] +} + +# What ships must carry none of them, whether or not this run removed any: a bundler +# that stopped shipping them is fine, a match that stopped matching is the original +# bug coming back with the step still reporting success. +assert_unbundled() { + local listing lib + listing=$(unsquashfs -quiet -no-progress -offset "$2" -ls "$1") + # Fail closed: a listing that came back empty would clear every name below. + grep -q '/AppRun$' <<<"$listing" || + { echo "$1: could not list the payload" >&2; exit 1; } + for lib in "${libs[@]}"; do + if grep -qF "/$lib" <<<"$listing"; then + echo "$1: still carries $lib" >&2 + exit 1 + fi + done +} + +offset=$(elf_end "$appimage") +payload_at "$appimage" "$offset" || + { echo "$appimage: no squashfs at $offset — not a type 2 AppImage" >&2; exit 1; } + +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +# Keep the runtime the bundler shipped, and pack for it: another runtime would change +# the FUSE version users need, and a compressor it was not built with leaves an +# AppImage that mounts nowhere. +head -c "$offset" "$appimage" >"$work/runtime" +comp=$(unsquashfs -quiet -no-progress -offset "$offset" -s "$appimage" | + awk '$1 == "Compression" { print $2 }') +[ -n "$comp" ] || { echo "$appimage: could not read the payload's compressor" >&2; exit 1; } + +# umask: unsquashfs masks the modes it restores unless it runs as root, and a build +# agent's default 022 would quietly relax every mode the bundle recorded. The work +# directory is already private, so nothing is exposed by dropping it here. +(umask 000 && unsquashfs -quiet -no-progress -dest "$work/AppDir" -offset "$offset" "$appimage") + +removed=0 +for lib in "${libs[@]}"; do + while IFS= read -r -d '' path; do + rm -f "$path" + echo " dropped ${path#"$work/AppDir/"}" + removed=$((removed + 1)) + done < <(find "$work/AppDir" -name "$lib*" -print0) +done + +# The search covers the whole AppDir, so nothing found means the bundler stopped +# shipping them — leave the artifact the release built exactly as it is. +if [ "$removed" -eq 0 ]; then + assert_unbundled "$appimage" "$offset" + echo "$appimage: no bundled display-stack libraries — left untouched" + exit 0 +fi + +appimagetool --runtime-file "$work/runtime" --comp "$comp" --no-appstream \ + "$work/AppDir" "$work/repacked" +mv "$work/repacked" "$appimage" +chmod +x "$appimage" + +offset=$(elf_end "$appimage") +payload_at "$appimage" "$offset" || + { echo "$appimage: repacked payload is not a squashfs" >&2; exit 1; } +assert_unbundled "$appimage" "$offset" + +echo "$appimage: dropped $removed bundled display-stack libraries"