From 7ed93bcbc22698369a7c9522afee84223aa78495 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 19:04:07 +0000 Subject: [PATCH] fix(desktop): package AppImage with appimagetool, not linuxdeploy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linuxdeploy auto-walks every binary in the staged AppDir with ldd to bundle their shared-library deps. That fights jpackage's self-contained output on two axes and aborts the createReleaseAppImage task before any AppImage is produced: - The bundled JRE puts libjvm.so at usr/lib/runtime/lib/server/, while its sibling libs (libmanagement.so, libawt_xawt.so, libfontmanager.so and others in usr/lib/runtime/lib/) have RPATH=$ORIGIN. ldd cannot resolve libjvm.so from those libs without help, so linuxdeploy errors: ERROR: Could not find dependency: libjvm.so ERROR: Failed to deploy dependencies for existing files - The bundled libvlc plugins under usr/lib/app/resources/vlc/ are UPX-compressed by the vlc-setup plugin. patchelf cannot read their section headers, so linuxdeploy aborts: ERROR: Call to patchelf failed: patchelf: no section headers. The input file is probably a statically linked, self-decompressing binary - Even after working around libjvm.so via LD_LIBRARY_PATH, several VLC libs have RUNPATH pointing at the wrong directory, so ldd fails on libva.so.2 / libvlccore.so.9 next. This is why v1.09.2 shipped .deb + .rpm from the linux leg but no .AppImage and no linux .tar.gz from the linux-portable leg — the gradle step failed on linuxdeploy, so the subsequent "Build portable archives" step never ran. Swap to appimagetool, which only embeds the AppDir as-is into a SquashFS-backed, runtime-prepended AppImage and never touches the contents. jpackage already bundles a complete, self-contained app — we don't need linuxdeploy's dep-discovery. AppRun continues to handle LD_LIBRARY_PATH at launch. Verified locally on ubuntu-24.04: a fresh :desktopApp:createReleaseAppImage now produces a valid 254 MB Amethyst-1.09.2-x86_64.AppImage in ~30 s on top of an up-to-date createReleaseDistributable. Side changes: - Workflow installs desktop-file-utils (appimagetool 1.9.0 calls desktop-file-validate on the .desktop entry). - BUILDING.md updated with the new local-dev fetch command. - .gitignore replaces the linuxdeploy paths with appimagetool's. --- .github/workflows/create-release.yml | 24 +++++++++++------- .gitignore | 3 +-- BUILDING.md | 14 +++++++---- desktopApp/build.gradle.kts | 37 +++++++++++++++++----------- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 843aba8f3..70e4d3ca6 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -21,9 +21,13 @@ permissions: env: # Asset naming contract: amethyst-desktop---. # Single source of truth in scripts/asset-name.sh. - # linuxdeploy pinned release — bump via Dependabot, verify SHA256 via env var below. - LINUXDEPLOY_URL: https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20240109-1/linuxdeploy-x86_64.AppImage - LINUXDEPLOY_SHA256: c86d6540f1df31061f02f539a2d3445f8d7f85cc3994eee1e74cd1ac97b76df0 + # appimagetool pinned release — bump via Dependabot, verify SHA256 via env var below. + # We used to use linuxdeploy here, but it auto-walks the AppDir with ldd to + # bundle deps — that fights jpackage's self-contained JRE (libjvm.so RPATH + # mismatch) and the UPX-compressed VLC plugins. appimagetool only embeds the + # AppDir as-is, which is what we actually want. + APPIMAGETOOL_URL: https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage + APPIMAGETOOL_SHA256: 46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1 jobs: # --------------------------------------------------------------------------- @@ -84,17 +88,19 @@ jobs: if: matrix.family == 'linux' run: sudo apt-get update && sudo apt-get install -y rpm fakeroot - - name: Fetch linuxdeploy (linux-portable only, SHA-verified) + - name: Fetch appimagetool (linux-portable only, SHA-verified) if: matrix.family == 'linux-portable' run: | set -euo pipefail - curl -fsSL --retry 3 "$LINUXDEPLOY_URL" -o desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage - actual=$(sha256sum desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage | awk '{print $1}') - if [[ "$actual" != "$LINUXDEPLOY_SHA256" ]]; then - echo "::error::linuxdeploy SHA256 mismatch. Expected $LINUXDEPLOY_SHA256, got $actual" + # appimagetool 1.9.0 validates the .desktop file via desktop-file-validate. + sudo apt-get update && sudo apt-get install -y desktop-file-utils + curl -fsSL --retry 3 "$APPIMAGETOOL_URL" -o desktopApp/packaging/appimage/appimagetool-x86_64.AppImage + actual=$(sha256sum desktopApp/packaging/appimage/appimagetool-x86_64.AppImage | awk '{print $1}') + if [[ "$actual" != "$APPIMAGETOOL_SHA256" ]]; then + echo "::error::appimagetool SHA256 mismatch. Expected $APPIMAGETOOL_SHA256, got $actual" exit 1 fi - chmod +x desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage + chmod +x desktopApp/packaging/appimage/appimagetool-x86_64.AppImage - name: Build desktop artifacts uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 diff --git a/.gitignore b/.gitignore index 63cf8a9ee..459dc6eff 100644 --- a/.gitignore +++ b/.gitignore @@ -167,8 +167,7 @@ desktopApp/src/jvmMain/appResources/macos/ desktopApp/src/jvmMain/appResources/windows/ # CI-fetched AppImage tooling (downloaded by create-release workflow; not committed) -desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage -desktopApp/packaging/appimage/linuxdeploy-extracted/ +desktopApp/packaging/appimage/appimagetool-x86_64.AppImage desktopApp/packaging/appimage/squashfs-root/ # Git worktrees diff --git a/BUILDING.md b/BUILDING.md index 4261fb0e7..05c238e42 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -27,7 +27,8 @@ Platform-specific: - **macOS**: Xcode Command Line Tools (`xcode-select --install`) - **Windows**: WiX Toolset 3.x on PATH (for MSI). `winget install WiXToolset.WiXToolset` -- **Linux (all)**: nothing extra for `.deb`; `rpm` + `fakeroot` for `.rpm`; `linuxdeploy` for AppImage +- **Linux (all)**: nothing extra for `.deb`; `rpm` + `fakeroot` for `.rpm`; + `appimagetool` + `desktop-file-utils` for AppImage Install Linux RPM tooling: @@ -39,12 +40,15 @@ sudo apt-get install -y rpm fakeroot sudo dnf install -y rpm-build ``` -Install linuxdeploy locally (CI fetches its own — SHA-verified): +Install appimagetool locally (CI fetches its own — SHA-verified): ```bash -curl -fsSL -o desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage \ - https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20240109-1/linuxdeploy-x86_64.AppImage -chmod +x desktopApp/packaging/appimage/linuxdeploy-x86_64.AppImage +# Debian/Ubuntu — appimagetool calls desktop-file-validate on the .desktop entry +sudo apt-get install -y desktop-file-utils + +curl -fsSL -o desktopApp/packaging/appimage/appimagetool-x86_64.AppImage \ + https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage +chmod +x desktopApp/packaging/appimage/appimagetool-x86_64.AppImage ``` --- diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 36fa717a0..0ec8f6836 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -186,27 +186,39 @@ tasks.withType().configureEach { // --- AppImage packaging (Linux) --- // // Compose Multiplatform's TargetFormat.AppImage is known-broken in 1.10.x (CMP-7101). -// Instead: wrap `createReleaseDistributable` output with `linuxdeploy` (which -// auto-bundles libraries, handles rpath, and calls appimagetool internally). +// Instead: wrap `createReleaseDistributable` output with `appimagetool`, which +// just packages an AppDir as-is. We deliberately avoid `linuxdeploy` here — +// linuxdeploy auto-walks every binary in the AppDir with ldd to bundle deps, +// but jpackage already ships a self-contained tree we don't want it touching: +// - The bundled JRE puts libjvm.so under usr/lib/runtime/lib/server/ while +// sibling libs (libmanagement.so, libawt_xawt.so, libfontmanager.so) have +// RPATH=$ORIGIN, so ldd cannot resolve libjvm.so without help. +// - The bundled VLC plugins are UPX-compressed; linuxdeploy aborts on those +// with "patchelf: no section headers" because they look like static ELFs. +// - Several VLC libs have RUNPATH that does not point at sibling libs in +// the same directory, so ldd errors with "Could not find dependency". +// appimagetool sidesteps all of this — it only embeds the AppDir into a +// SquashFS, runtime-prepended, signed AppImage. AppRun handles LD_LIBRARY_PATH +// at launch. // // Build inputs live in desktopApp/packaging/appimage/: // - AppRun shell launcher (sets LD_LIBRARY_PATH including bundled VLC) // - amethyst.desktop XDG desktop entry // - amethyst.png 512x512 icon // -// linuxdeploy binary is fetched by CI (SHA-verified) into -// desktopApp/packaging/appimage/ as linuxdeploy-x86_64.AppImage. +// appimagetool binary is fetched by CI (SHA-verified) into +// desktopApp/packaging/appimage/ as appimagetool-x86_64.AppImage. // BUILDING.md documents local-dev fetch. val createReleaseAppImage by tasks.registering(Exec::class) { group = "compose desktop" - description = "Bundle createReleaseDistributable output into a Linux AppImage via linuxdeploy." + description = "Package createReleaseDistributable output into a Linux AppImage via appimagetool." dependsOn("createReleaseDistributable") val distDir = layout.buildDirectory.dir("compose/binaries/main-release/app/Amethyst") val appDir = layout.buildDirectory.dir("appimage/Amethyst.AppDir") val outFile = layout.buildDirectory.file("appimage/Amethyst-$appVersion-x86_64.AppImage") val toolRoot = layout.projectDirectory.dir("packaging/appimage") - val linuxdeployTool = toolRoot.file("linuxdeploy-x86_64.AppImage") + val appimagetool = toolRoot.file("appimagetool-x86_64.AppImage") inputs.dir(distDir) inputs.dir(toolRoot) @@ -231,19 +243,16 @@ val createReleaseAppImage by tasks.registering(Exec::class) { if (dirIcon.exists()) dirIcon.delete() Files.createSymbolicLink(dirIcon.toPath(), File("amethyst.png").toPath()) - if (!linuxdeployTool.asFile.canExecute()) { - linuxdeployTool.asFile.setExecutable(true) + if (!appimagetool.asFile.canExecute()) { + appimagetool.asFile.setExecutable(true) } } commandLine( - linuxdeployTool.asFile.absolutePath, - "--appdir", appDir.get().asFile.absolutePath, - "--output", "appimage", - "--desktop-file", "${appDir.get().asFile}/amethyst.desktop", - "--icon-file", "${appDir.get().asFile}/amethyst.png", + appimagetool.asFile.absolutePath, + appDir.get().asFile.absolutePath, + outFile.get().asFile.absolutePath, ) - environment("OUTPUT", outFile.get().asFile.absolutePath) environment("ARCH", "x86_64") // Bypass FUSE requirement on CI runners (ubuntu-latest lacks libfuse.so.2). // AppImage standard env var: extracts + runs without mounting.