From a110ce0a30e797145fb100de2ff81c630ff4e708 Mon Sep 17 00:00:00 2001 From: mstrofnone Date: Tue, 4 Aug 2026 10:51:16 +1000 Subject: [PATCH] ci: publish linux-arm64 desktop, amy, and geode release assets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Amethyst v1.13.1 (and every prior release) shipped only linux-x64 desktop binaries — .deb, .rpm, .AppImage, .flatpak, .tar.gz. Same for the amy CLI and geode relay. Users on aarch64 hardware (Pinebook, Ampere Altra, Raspberry Pi 4/5, AWS Graviton, arm64 servers, arm64 Chromebooks running crostini, etc.) can't install any of them. This teaches the release matrix about arm64: - Add `ubuntu-24.04-arm` legs to build-desktop, build-cli, and build-geode. This is a standard, free public-repo GitHub-hosted runner (4 CPU / 16 GB / 14 GB SSD / arm64) since early 2025. No cross-compilation: jpackage / jlink / Compose Multiplatform 1.11 all produce host-native artifacts. - Fetch the matching `appimagetool-.AppImage` from the same 1.9.0 release with an arch-specific SHA256 pin. `APPIMAGETOOL_URL` becomes `APPIMAGETOOL_VERSION` + per-arch SHA256 env vars. - Parametrize the portable tarball/zip filename by `matrix.arch` (`amethyst-desktop--linux-arm64.tar.gz` is now produced). - Parametrize the Flatpak bundle filename and rewrite the manifest's `GST_PLUGIN_SYSTEM_PATH` from `x86_64-linux-gnu` to `aarch64-linux-gnu` on the arm64 leg. The Flathub-submission manifest (`desktopApp/packaging/flatpak/flathub/`) still gates on `only-arches: x86_64` — flipping that to include aarch64 is a follow-up once a Flathub aarch64 build has been validated end-to-end. - Make the `createReleaseAppImage` gradle task pick its host arch from `System.getProperty("os.arch")` (amd64/x86_64 → `x86_64`, aarch64/ arm64 → `aarch64`). Same task, same command, drives both legs. - Fix `desktopApp/packaging/appimage/AppRun` to compute the multiarch library path from `uname -m` at launch time instead of hard-coding `x86_64-linux-gnu`. One script works in both AppImages on the target machine. - Extend the desktop smoke test to run the release .deb build + launch probe on `ubuntu-24.04-arm` too, so arch-specific ProGuard/jlink breakage (missing native lib, arch-specific reflection root) is caught at PR time. - Update BUILDING.md and scripts/asset-name.sh docs with the new arm64 asset names. Follow-up assets published for the next tag push (v1.13.2+): - amethyst-desktop--linux-arm64.{deb,rpm,AppImage,flatpak,tar.gz} - amy--linux-arm64.{deb,rpm,tar.gz} - geode--linux-arm64.{deb,rpm,tar.gz} Verification (local, before submitting): - `python3 -c 'import yaml; yaml.safe_load(open(".github/workflows/create-release.yml"))'` — parses clean - `bash -n scripts/asset-name.sh desktopApp/packaging/appimage/AppRun` — parses clean - `actionlint` — reports only pre-existing shellcheck style hints; no new errors - Confirmed `linuxdeploy-aarch64.AppImage` and `appimagetool-aarch64.AppImage` exist under the same pinned release tags used for x86_64; SHA256 recorded from a fresh download. Not addressed (out of scope for this PR): - Homebrew / winget bump workflows (`bump-homebrew*.yml`, `bump-winget.yml`) — those consume the assets by name; the new arm64 filenames don't change any x86_64 name they already reference. - Android arm64 continues to ship as before (already had it). --- .github/workflows/create-release.yml | 68 +++++++++++++++++------- .github/workflows/smoke-test-desktop.yml | 13 ++++- BUILDING.md | 13 +++-- desktopApp/build.gradle.kts | 20 +++++-- desktopApp/packaging/appimage/AppRun | 5 +- desktopApp/packaging/flatpak/README.md | 8 ++- scripts/asset-name.sh | 11 ++++ 7 files changed, 106 insertions(+), 32 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index ce5d002955..f87c0556f4 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -26,8 +26,12 @@ env: # bundle deps — that fights jpackage's self-contained JRE (libjvm.so has # $ORIGIN RPATH so ldd can't resolve it standalone). 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 + # + # Both arch binaries come from the same appimagetool release so their SHA256 + # values move in lockstep on version bumps. + APPIMAGETOOL_VERSION: '1.9.0' + APPIMAGETOOL_SHA256_X86_64: 46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1 + APPIMAGETOOL_SHA256_AARCH64: 04f45ea45b5aa07bb2b071aed9dbf7a5185d3953b11b47358c1311f11ea94a96 jobs: # --------------------------------------------------------------------------- @@ -38,11 +42,17 @@ jobs: strategy: fail-fast: false matrix: + # Linux legs run on x64 and arm64 GitHub-hosted runners (the + # ubuntu-24.04-arm label is a standard free public-repo runner as of + # early 2025). jpackage / jlink / Compose Multiplatform 1.11 all + # produce host-native artifacts — no cross-compilation needed. include: - - { os: macos-14, arch: arm64, family: macos, tasks: "packageReleaseDmg" } - - { os: windows-latest, arch: x64, family: windows, tasks: "packageReleaseMsi createReleaseDistributable" } - - { os: ubuntu-latest, arch: x64, family: linux, tasks: "packageReleaseDeb packageReleaseRpm" } - - { os: ubuntu-latest, arch: x64, family: linux-portable, tasks: "createReleaseAppImage createReleaseDistributable" } + - { os: macos-14, arch: arm64, family: macos, tasks: "packageReleaseDmg" } + - { os: windows-latest, arch: x64, family: windows, tasks: "packageReleaseMsi createReleaseDistributable" } + - { os: ubuntu-latest, arch: x64, family: linux, tasks: "packageReleaseDeb packageReleaseRpm" } + - { os: ubuntu-24.04-arm, arch: arm64, family: linux, tasks: "packageReleaseDeb packageReleaseRpm" } + - { os: ubuntu-latest, arch: x64, family: linux-portable, tasks: "createReleaseAppImage createReleaseDistributable" } + - { os: ubuntu-24.04-arm, arch: arm64, family: linux-portable, tasks: "createReleaseAppImage createReleaseDistributable" } runs-on: ${{ matrix.os }} timeout-minutes: 60 # linux-portable leg also downloads the freedesktop runtime + builds the Flatpak bundle defaults: @@ -93,13 +103,21 @@ jobs: set -euo pipefail # 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" + # Map runner arch → upstream AppImage suffix (x86_64 / aarch64). + case "${{ matrix.arch }}" in + x64) TOOL_ARCH=x86_64 ; EXPECTED_SHA="$APPIMAGETOOL_SHA256_X86_64" ;; + arm64) TOOL_ARCH=aarch64; EXPECTED_SHA="$APPIMAGETOOL_SHA256_AARCH64" ;; + *) echo "::error::unsupported arch for AppImage: ${{ matrix.arch }}"; exit 1 ;; + esac + URL="https://github.com/AppImage/appimagetool/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-${TOOL_ARCH}.AppImage" + DEST="desktopApp/packaging/appimage/appimagetool-${TOOL_ARCH}.AppImage" + curl -fsSL --retry 3 "$URL" -o "$DEST" + actual=$(sha256sum "$DEST" | awk '{print $1}') + if [[ "$actual" != "$EXPECTED_SHA" ]]; then + echo "::error::appimagetool SHA256 mismatch for $TOOL_ARCH. Expected $EXPECTED_SHA, got $actual" exit 1 fi - chmod +x desktopApp/packaging/appimage/appimagetool-x86_64.AppImage + chmod +x "$DEST" # Flatpak tooling + the freedesktop runtime/sdk the manifest pins # (runtime-version is greped from the manifest so this never drifts). @@ -208,12 +226,13 @@ jobs: run: | set -euo pipefail VER="${{ steps.ver.outputs.version }}" + ARCH="${{ matrix.arch }}" APP="desktopApp/build/compose/binaries/main-release/app" mkdir -p desktopApp/build/portable if [[ "${{ matrix.family }}" == "windows" ]]; then - ( cd "$APP" && 7z a -tzip "../../../../portable/amethyst-desktop-${VER}-windows-x64.zip" Amethyst/ ) + ( cd "$APP" && 7z a -tzip "../../../../portable/amethyst-desktop-${VER}-windows-${ARCH}.zip" Amethyst/ ) else - ( cd "$APP" && tar czf "../../../../portable/amethyst-desktop-${VER}-linux-x64.tar.gz" Amethyst/ ) + ( cd "$APP" && tar czf "../../../../portable/amethyst-desktop-${VER}-linux-${ARCH}.tar.gz" Amethyst/ ) fi # Flatpak bundle: wraps the same createReleaseDistributable tree the @@ -230,6 +249,17 @@ jobs: PKG="desktopApp/packaging/flatpak" APP_ID="com.vitorpamplona.amethyst.Desktop" OUT="desktopApp/build/flatpak" + # AppImage-style arch names for the bundle filename. + case "${{ matrix.arch }}" in + x64) BUNDLE_ARCH=x86_64 ; GST_TRIPLET=x86_64-linux-gnu ;; + arm64) BUNDLE_ARCH=aarch64 ; GST_TRIPLET=aarch64-linux-gnu ;; + *) echo "::error::unsupported arch for Flatpak: ${{ matrix.arch }}"; exit 1 ;; + esac + # Rewrite the arch-specific GStreamer plugin path in the manifest + # (checked-in default is x86_64-linux-gnu). Idempotent — the sed only + # matches the original triplet. + sed -i "s|/usr/lib/x86_64-linux-gnu/gstreamer-1.0|/usr/lib/${GST_TRIPLET}/gstreamer-1.0|g" \ + "${PKG}/${APP_ID}.yml" # Inject the AppStream entry for this build (the checked-in # metainfo deliberately carries none — CI is the source of truth). sed -i "s||\n |" \ @@ -241,7 +271,7 @@ jobs: "${OUT}/build-dir" \ "${PKG}/${APP_ID}.yml" flatpak build-bundle "${OUT}/repo" \ - "${OUT}/Amethyst-${VER}-x86_64.flatpak" \ + "${OUT}/Amethyst-${VER}-${BUNDLE_ARCH}.flatpak" \ "$APP_ID" \ --runtime-repo=https://dl.flathub.org/repo/flathub.flatpakrepo ls -la "$OUT" @@ -325,8 +355,9 @@ jobs: fail-fast: false matrix: include: - - { os: macos-14, arch: arm64, family: macos, tasks: "amyImage" } - - { os: ubuntu-latest, arch: x64, family: linux, tasks: "amyImage jpackageDeb jpackageRpm" } + - { os: macos-14, arch: arm64, family: macos, tasks: "amyImage" } + - { os: ubuntu-latest, arch: x64, family: linux, tasks: "amyImage jpackageDeb jpackageRpm" } + - { os: ubuntu-24.04-arm, arch: arm64, family: linux, tasks: "amyImage jpackageDeb jpackageRpm" } runs-on: ${{ matrix.os }} timeout-minutes: 45 # macOS leg also codesigns + notarizes the jlink image defaults: @@ -574,8 +605,9 @@ jobs: fail-fast: false matrix: include: - - { os: macos-14, arch: arm64, family: macos, tasks: "geodeImage" } - - { os: ubuntu-latest, arch: x64, family: linux, tasks: "geodeImage jpackageDeb jpackageRpm" } + - { os: macos-14, arch: arm64, family: macos, tasks: "geodeImage" } + - { os: ubuntu-latest, arch: x64, family: linux, tasks: "geodeImage jpackageDeb jpackageRpm" } + - { os: ubuntu-24.04-arm, arch: arm64, family: linux, tasks: "geodeImage jpackageDeb jpackageRpm" } runs-on: ${{ matrix.os }} timeout-minutes: 45 # macOS leg also codesigns + notarizes the jlink image defaults: diff --git a/.github/workflows/smoke-test-desktop.yml b/.github/workflows/smoke-test-desktop.yml index 3dafc575dd..6d8159ab0e 100644 --- a/.github/workflows/smoke-test-desktop.yml +++ b/.github/workflows/smoke-test-desktop.yml @@ -49,9 +49,17 @@ jobs: # package, installs it, and verifies the process stays alive for 10s. # Catches ProGuard stripping (JNI, reflection), missing jlink modules # (java.management, java.prefs), and native lib bundling issues. + # + # Runs on both x64 and arm64 hosted runners so release-time arm64 breakage + # (e.g. ProGuard rules missing an arch-specific reflection root) is caught + # at PR time instead of on the tag build. # ------------------------------------------------------------------------- release-deb-launch: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.os }} timeout-minutes: 45 steps: - name: Checkout code @@ -139,5 +147,6 @@ jobs: if: always() uses: actions/upload-artifact@v7 with: - name: Release DEB (smoke-tested) + # Artifact names must be unique across a run — disambiguate per arch. + name: Release DEB (smoke-tested, ${{ matrix.os }}) path: desktopApp/build/compose/binaries/main-release/deb/*.deb diff --git a/BUILDING.md b/BUILDING.md index 5a04173603..b931926c00 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -57,9 +57,12 @@ Install appimagetool locally (CI fetches its own — SHA-verified): # 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 +# createReleaseAppImage picks appimagetool-.AppImage matching the JVM's +# os.arch — fetch the one for your host (x86_64 on Intel/AMD, aarch64 on ARM). +ARCH="$(uname -m)" +curl -fsSL -o "desktopApp/packaging/appimage/appimagetool-${ARCH}.AppImage" \ + "https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-${ARCH}.AppImage" +chmod +x "desktopApp/packaging/appimage/appimagetool-${ARCH}.AppImage" ``` --- @@ -110,8 +113,8 @@ are **not** required to build Amethyst from the committed sources. | Windows MSI | `./gradlew :desktopApp:packageReleaseMsi` | `desktopApp/build/compose/binaries/main-release/msi/Amethyst-*.msi` | | Linux `.deb` | `./gradlew :desktopApp:packageReleaseDeb` | `desktopApp/build/compose/binaries/main-release/deb/amethyst_*.deb` | | Linux `.rpm` | `./gradlew :desktopApp:packageReleaseRpm` | `desktopApp/build/compose/binaries/main-release/rpm/amethyst-*.rpm` | -| Linux AppImage | `./gradlew :desktopApp:createReleaseAppImage` | `desktopApp/build/appimage/Amethyst-*-x86_64.AppImage` | -| Linux Flatpak | `flatpak-builder` over `createReleaseDistributable` output — see [`desktopApp/packaging/flatpak/README.md`](desktopApp/packaging/flatpak/README.md) | `desktopApp/build/flatpak/Amethyst-*-x86_64.flatpak` (CI) | +| Linux AppImage | `./gradlew :desktopApp:createReleaseAppImage` | `desktopApp/build/appimage/Amethyst-*-.AppImage` (x86_64 or aarch64, from host) | +| Linux Flatpak | `flatpak-builder` over `createReleaseDistributable` output — see [`desktopApp/packaging/flatpak/README.md`](desktopApp/packaging/flatpak/README.md) | `desktopApp/build/flatpak/Amethyst-*-.flatpak` (CI; x86_64 or aarch64) | | Windows `.zip` portable | See below (inline `7z`) | — | | Linux `.tar.gz` portable | See below (inline `tar`) | — | diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 10ca2b3d48..fa41a3e276 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -246,18 +246,30 @@ compose.desktop { // - amethyst.png 512x512 icon // // appimagetool binary is fetched by CI (SHA-verified) into -// desktopApp/packaging/appimage/ as appimagetool-x86_64.AppImage. +// desktopApp/packaging/appimage/ as appimagetool-.AppImage. +// The arch is selected at task-execution time from the host JVM's os.arch, so +// the same task builds the correct AppImage on both x86_64 and aarch64 hosts. // BUILDING.md documents local-dev fetch. val createReleaseAppImage by tasks.registering(Exec::class) { group = "compose desktop" description = "Package createReleaseDistributable output into a Linux AppImage via appimagetool." dependsOn("createReleaseDistributable") + // AppImage's ARCH env accepts the Linux kernel arch names: x86_64 / aarch64 + // / armhf / i686. jpackage produces host-native binaries, so mirror the + // host JVM arch. Do not read the property inside doFirst — it needs to be + // resolved at configuration time so outputs.file() below is stable. + val hostArch = when (val a = System.getProperty("os.arch").lowercase()) { + "amd64", "x86_64" -> "x86_64" + "aarch64", "arm64" -> "aarch64" + else -> a + } + 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 outFile = layout.buildDirectory.file("appimage/Amethyst-$appVersion-$hostArch.AppImage") val toolRoot = layout.projectDirectory.dir("packaging/appimage") - val appimagetool = toolRoot.file("appimagetool-x86_64.AppImage") + val appimagetool = toolRoot.file("appimagetool-$hostArch.AppImage") inputs.dir(distDir) inputs.dir(toolRoot) @@ -292,7 +304,7 @@ val createReleaseAppImage by tasks.registering(Exec::class) { appDir.get().asFile.absolutePath, outFile.get().asFile.absolutePath, ) - environment("ARCH", "x86_64") + environment("ARCH", hostArch) // Bypass FUSE requirement on CI runners (ubuntu-latest lacks libfuse.so.2). // AppImage standard env var: extracts + runs without mounting. environment("APPIMAGE_EXTRACT_AND_RUN", "1") diff --git a/desktopApp/packaging/appimage/AppRun b/desktopApp/packaging/appimage/AppRun index c42b59c001..7e208a8d54 100755 --- a/desktopApp/packaging/appimage/AppRun +++ b/desktopApp/packaging/appimage/AppRun @@ -7,7 +7,10 @@ # (Equivalent packages on Fedora/Arch.) set -eu HERE="$(dirname "$(readlink -f "${0}")")" -export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/x86_64-linux-gnu:${LD_LIBRARY_PATH:-}" +# Multiarch lib path is set by the host, not baked at build time — same +# AppRun works in both x86_64 and aarch64 AppImages. +GNU_TRIPLET="$(uname -m)-linux-gnu" +export LD_LIBRARY_PATH="${HERE}/usr/lib:${HERE}/usr/lib/${GNU_TRIPLET}:${LD_LIBRARY_PATH:-}" export PATH="${HERE}/usr/bin:${PATH}" export APPDIR="${HERE}" exec "${HERE}/usr/bin/Amethyst" "$@" diff --git a/desktopApp/packaging/flatpak/README.md b/desktopApp/packaging/flatpak/README.md index 552fba7e42..c6c1f7cd84 100644 --- a/desktopApp/packaging/flatpak/README.md +++ b/desktopApp/packaging/flatpak/README.md @@ -28,8 +28,12 @@ used two ways: manifest (archive source pinned to the release tarball URL + sha256, with `x-checker-data` so Flathub's update bot bumps it), its own metainfo (carries the permanent `` history Flathub requires), desktop - entry, icon, and `flathub.json` (`only-arches: x86_64` — we publish no - aarch64 tarball, and jpackage can't cross-compile one) + entry, icon, and `flathub.json` — currently gated to `only-arches: + x86_64` so the Flathub build machinery never tries the aarch64 tarball + before we've validated it end-to-end on Flathub's aarch64 builders. GitHub + releases already ship aarch64 flatpak bundles (built from the same source + tree on `ubuntu-24.04-arm`); flipping `only-arches` to include `aarch64` + is the follow-up once we've smoke-tested a Flathub aarch64 build. ## Local build diff --git a/scripts/asset-name.sh b/scripts/asset-name.sh index d419b10c0d..ee7b2bda98 100755 --- a/scripts/asset-name.sh +++ b/scripts/asset-name.sh @@ -29,15 +29,26 @@ # amethyst-desktop-1.08.0-linux-x64.AppImage # amethyst-desktop-1.08.0-linux-x64.flatpak # amethyst-desktop-1.08.0-linux-x64.tar.gz +# amethyst-desktop-1.08.0-linux-arm64.deb +# amethyst-desktop-1.08.0-linux-arm64.rpm +# amethyst-desktop-1.08.0-linux-arm64.AppImage +# amethyst-desktop-1.08.0-linux-arm64.flatpak +# amethyst-desktop-1.08.0-linux-arm64.tar.gz # amy-1.08.0-macos-arm64.tar.gz # amy-1.08.0-macos-x64.tar.gz # amy-1.08.0-linux-x64.tar.gz # amy-1.08.0-linux-x64.deb # amy-1.08.0-linux-x64.rpm +# amy-1.08.0-linux-arm64.tar.gz +# amy-1.08.0-linux-arm64.deb +# amy-1.08.0-linux-arm64.rpm # geode-1.08.0-macos-arm64.tar.gz # geode-1.08.0-linux-x64.tar.gz # geode-1.08.0-linux-x64.deb # geode-1.08.0-linux-x64.rpm +# geode-1.08.0-linux-arm64.tar.gz +# geode-1.08.0-linux-arm64.deb +# geode-1.08.0-linux-arm64.rpm # # Two assets break the family/arch shape on purpose: the no-JRE jar bundles for # Homebrew-core are pure JVM bytecode (no bundled runtime), so a single