From e8614afa3f9c189b51a7e5954890147dfdae4878 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Sun, 11 Oct 2026 02:15:28 -0400 Subject: [PATCH] Add a Linux riscv64 release build (deb, rpm, tar.gz) (#102) * Add Linux riscv64 release build and switch runners to Ubuntu 26.04 - release.yml: new linux-riscv64 job cross-compiled on ubuntu-26.04 against ports.ubuntu.com riscv64 libs, tar.gz only, artcraft-launcher --version smoke-tested under QEMU. Updated flatpak runner matrix to ubuntu-26.04. Draft-release job now requires linux-riscv64 and checks the tarball. - packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support. - platform.rs: add Arch::Riscv64 variant, map linux-riscv64 token, and prioritize tarball format for riscv64 on Linux. - web.rs: detect riscv64 architecture in visitor user agent. - README and AGENTS.md: document the riscv64 tarball. * linux: build deb and rpm packages for riscv64 * ci: configure actionlint to recognize ubuntu-26.04 runner labels * Keep riscv64 and Flatpak release jobs on Ubuntu 24.04 24.04 is what PhotoCraft's linux-riscv64 job uses, so the riscv64 binaries need glibc 2.39 (as the README says) rather than 26.04's newer glibc, and the release pipeline keeps runner images it has already proven on. Drop the actionlint label config that 26.04 needed, revert the default_kinds and download_kinds matches to match on the OS alone, simplify the riscv UA check, and rustfmt. Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Brandon Thomas Co-authored-by: Claude Opus 5.5 --- .github/workflows/release.yml | 75 ++++++++++++++++++++++++++- AGENTS.md | 2 +- README.md | 2 + apps/artcraft-launcher-web/src/web.rs | 8 ++- crates/core/src/platform.rs | 24 +++++++++ crates/native/src/backend.rs | 3 +- packaging/linux/package.sh | 26 +++++++--- 7 files changed, 129 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 885039f..087bb04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -335,9 +335,75 @@ jobs: path: dist/release/* if-no-files-found: error + linux-riscv64: + name: Linux riscv64 + needs: version + # Cross-compiled on x86_64 (no hosted RISC-V runners) against the runner's own Ubuntu release + # from ports.ubuntu.com, so the binaries need that release's glibc (24.04: 2.39). 22.04's + # riscv64 ports lag its amd64 updates, which breaks the multiarch install. deb, rpm, and + # tar.gz; artcraft-launcher --version is smoke-tested under QEMU. Follows PhotoCraft's job. + runs-on: ubuntu-24.04 + # No `environment: release`: this job reads no secrets, so a branch dry run can run it. + env: + ARTCRAFT_LAUNCHER_VERSION: ${{ needs.version.outputs.version }} + ARTCRAFT_LAUNCHER_BUILD_DATE: ${{ needs.version.outputs.date }} + NFPM_VERSION: 2.47.0 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: riscv64gc-unknown-linux-gnu + - uses: Swatinem/rust-cache@v2 + with: + key: release-linux-riscv64 + - name: Apply version override + if: inputs.version != '' + run: cargo xtask version set "$ARTCRAFT_LAUNCHER_VERSION" + - name: Install cross-compilation dependencies + run: | + . /etc/os-release + sudo dpkg --add-architecture riscv64 + # Pin the existing (archive.ubuntu.com) sources to amd64; riscv64 comes from ports. + if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then + sudo sed -i '/^Types: deb/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources + fi + if [ -f /etc/apt/sources.list ]; then + sudo sed -i 's/^deb /deb [arch=amd64] /g' /etc/apt/sources.list + sudo sed -i 's/^deb-src /deb-src [arch=amd64] /g' /etc/apt/sources.list + fi + for suite in "$VERSION_CODENAME" "$VERSION_CODENAME-updates" "$VERSION_CODENAME-security"; do + echo "deb [arch=riscv64] http://ports.ubuntu.com/ubuntu-ports $suite main universe restricted multiverse" + done | sudo tee /etc/apt/sources.list.d/riscv64.list + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu libc6-dev-riscv64-cross libc6-dev:riscv64 qemu-user \ + libxkbcommon-dev:riscv64 libwayland-dev:riscv64 libx11-dev:riscv64 libxrandr-dev:riscv64 \ + libxi-dev:riscv64 libgl1-mesa-dev:riscv64 \ + desktop-file-utils appstream + curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \ + "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_amd64.deb" + sudo dpkg -i "$RUNNER_TEMP/nfpm.deb" + - name: Build and package (deb, rpm, tar.gz) + env: + CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc + PKG_CONFIG_SYSROOT_DIR: / + PKG_CONFIG_PATH: /usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig + PKG_CONFIG_ALLOW_CROSS: "1" + CROSS_ARCH: riscv64 + CROSS_TARGET: riscv64gc-unknown-linux-gnu + CROSS_COMPILE: riscv64-linux-gnu- + EMULATOR: qemu-riscv64 + QEMU_LD_PREFIX: /usr/riscv64-linux-gnu + run: packaging/linux/package.sh --formats "deb rpm tar" + - uses: actions/upload-artifact@v4 + with: + name: release-linux-riscv64 + path: dist/release/* + if-no-files-found: error + release: name: Draft GitHub Release - needs: [version, macos, windows, linux, flatpak, freebsd, web] + needs: [version, macos, windows, linux, linux-riscv64, flatpak, freebsd, web] runs-on: ubuntu-latest environment: release permissions: @@ -364,8 +430,13 @@ jobs: for a in x64 x86 arm64; do expected+=("artcraft-launcher-$VERSION-windows-$a.msi" "artcraft-launcher-$VERSION-windows-$a-portable.zip") done + for a in x86_64 aarch64 riscv64; do + for ext in deb rpm tar.gz; do + expected+=("artcraft-launcher-$VERSION-linux-$a.$ext") + done + done for a in x86_64 aarch64; do - for ext in AppImage AppImage.zsync deb rpm tar.gz flatpak; do + for ext in AppImage AppImage.zsync flatpak; do expected+=("artcraft-launcher-$VERSION-linux-$a.$ext") done done diff --git a/AGENTS.md b/AGENTS.md index a75e2a8..ce0097b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -119,7 +119,7 @@ Release assets (keep these names exact; the launcher's own self-update and the R |---|---| | Windows | `artcraft-launcher--windows-{x64,x86,arm64}.msi`, `…-windows-{x64,x86,arm64}-portable.zip` | | macOS | `artcraft-launcher--macos-universal.dmg` | -| Linux | `artcraft-launcher--linux-{x86_64,aarch64}.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}` | +| Linux | `artcraft-launcher--linux-{x86_64,aarch64}.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}`, `artcraft-launcher--linux-riscv64.{deb,rpm,tar.gz}` | | FreeBSD | `artcraft-launcher--freebsd-x86_64.tar.gz` | | Web | `artcraft-launcher-web-.zip` | diff --git a/README.md b/README.md index 20b1bf4..8a83c71 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,8 @@ Download the build for your computer from the [latest release](https://github.co | `artcraft-launcher--linux-{x86_64,aarch64}.flatpak` | Flatpak bundle: `flatpak install --user artcraft-launcher-…flatpak`. Inside the sandbox the launcher can download apps but has limited power to install them on the host; prefer the AppImage, .deb or .rpm | | `artcraft-launcher--linux-{x86_64,aarch64}.tar.gz` | Plain tree (`bin/`, `share/`) for everyone else and for scripts | +RISC-V (riscv64): `artcraft-launcher--linux-riscv64.{deb,rpm,tar.gz}`, built against Ubuntu 24.04 (needs glibc 2.39+). + The tray icon uses the StatusNotifierItem standard: KDE, Xfce, Cinnamon and most other desktops show it; on GNOME install the AppIndicator extension. **FreeBSD** (14, x86_64) diff --git a/apps/artcraft-launcher-web/src/web.rs b/apps/artcraft-launcher-web/src/web.rs index cd26acd..d4f19c0 100644 --- a/apps/artcraft-launcher-web/src/web.rs +++ b/apps/artcraft-launcher-web/src/web.rs @@ -212,7 +212,13 @@ fn visitor_platform() -> Option { } else { return None; }; - let arch = if ua.contains("aarch64") || ua.contains("arm64") || ua.contains("armv8") { Arch::Arm64 } else { Arch::X64 }; + let arch = if ua.contains("aarch64") || ua.contains("arm64") || ua.contains("armv8") { + Arch::Arm64 + } else if ua.contains("riscv") { + Arch::Riscv64 + } else { + Arch::X64 + }; Some(Platform { os, arch }) } diff --git a/crates/core/src/platform.rs b/crates/core/src/platform.rs index 8201eec..c8b2fac 100644 --- a/crates/core/src/platform.rs +++ b/crates/core/src/platform.rs @@ -34,6 +34,7 @@ pub enum Arch { X64, X86, Arm64, + Riscv64, } impl Arch { @@ -42,6 +43,7 @@ impl Arch { Self::X64 => "x64", Self::X86 => "x86", Self::Arm64 => "ARM64", + Self::Riscv64 => "RISC-V", } } } @@ -118,6 +120,8 @@ impl Platform { Arch::X86 } else if cfg!(target_arch = "aarch64") { Arch::Arm64 + } else if cfg!(target_arch = "riscv64") { + Arch::Riscv64 } else { return None; }; @@ -138,9 +142,11 @@ impl Platform { (Os::Windows, Arch::X64) => &["windows-x64", "windows-x86"], (Os::Windows, Arch::X86) => &["windows-x86"], (Os::Windows, Arch::Arm64) => &["windows-arm64", "windows-x64", "windows-x86"], + (Os::Windows, _) => &[], (Os::MacOs, _) => &["macos-universal", "macos-aarch64", "macos-arm64", "macos-x86_64"], (Os::Linux, Arch::X64) => &["linux-x86_64"], (Os::Linux, Arch::Arm64) => &["linux-aarch64"], + (Os::Linux, Arch::Riscv64) => &["linux-riscv64"], (Os::Linux, Arch::X86) => &[], (Os::FreeBsd, Arch::X64) => &["freebsd-x86_64"], (Os::FreeBsd, _) => &[], @@ -234,6 +240,7 @@ mod tests { "photocraft-0.5.0-linux-aarch64.AppImage", "photocraft-0.5.0-linux-aarch64.AppImage.zsync", "photocraft-0.5.0-linux-aarch64.deb", + "photocraft-0.5.0-linux-riscv64.tar.gz", "photocraft-0.5.0-linux-x86_64.AppImage", "photocraft-0.5.0-linux-x86_64.tar.gz", "photocraft-0.5.0-macos-universal.dmg", @@ -268,6 +275,7 @@ mod tests { assert_eq!(pick(Os::Windows, Arch::Arm64, &[PackageKind::Msi]).as_deref(), Some("photocraft-0.5.0-windows-x64.msi")); assert_eq!(pick(Os::Linux, Arch::Arm64, &[PackageKind::AppImage]).as_deref(), Some("photocraft-0.5.0-linux-aarch64.AppImage")); assert_eq!(pick(Os::Linux, Arch::Arm64, &[PackageKind::TarGz]), None); + assert_eq!(pick(Os::Linux, Arch::Riscv64, &[PackageKind::TarGz]).as_deref(), Some("photocraft-0.5.0-linux-riscv64.tar.gz")); assert_eq!(pick(Os::FreeBsd, Arch::X64, &[PackageKind::TarGz]).as_deref(), Some("photocraft-0.5.0-freebsd-x86_64.tar.gz")); // Preference order across kinds. assert_eq!( @@ -275,5 +283,21 @@ mod tests { Some("photocraft-0.5.0-windows-x64-portable.zip") ); assert_eq!(pick(Os::Windows, Arch::X86, &[PackageKind::PortableZip, PackageKind::Msi]).as_deref(), Some("photocraft-0.5.0-windows-x86.msi")); + + let p_riscv = Platform { os: Os::Linux, arch: Arch::Riscv64 }; + assert_eq!( + pick_asset(&r, &["photocraft"], p_riscv, p_riscv.default_kinds()).map(|(a, _)| a.name.clone()).as_deref(), + Some("photocraft-0.5.0-linux-riscv64.tar.gz") + ); + assert_eq!( + pick_asset(&r, &["photocraft"], p_riscv, p_riscv.download_kinds()).map(|(a, _)| a.name.clone()).as_deref(), + Some("photocraft-0.5.0-linux-riscv64.tar.gz") + ); + let r_riscv_deb = + release(&["photocraft-0.5.0-linux-riscv64.tar.gz", "photocraft-0.5.0-linux-riscv64.deb", "photocraft-0.5.0-linux-riscv64.rpm"]); + assert_eq!( + pick_asset(&r_riscv_deb, &["photocraft"], p_riscv, p_riscv.download_kinds()).map(|(a, _)| a.name.clone()).as_deref(), + Some("photocraft-0.5.0-linux-riscv64.deb") + ); } } diff --git a/crates/native/src/backend.rs b/crates/native/src/backend.rs index 04150d7..56526e3 100644 --- a/crates/native/src/backend.rs +++ b/crates/native/src/backend.rs @@ -1080,7 +1080,8 @@ mod tests { /// installs is there. fn release(version: &str) -> Release { let suffixes = ["-portable.zip", ".tar.gz", ".msi", ".dmg", ".AppImage", ".deb", ".rpm", ".flatpak"]; - let tokens = ["windows-x64", "windows-x86", "windows-arm64", "macos-universal", "linux-x86_64", "linux-aarch64", "freebsd-x86_64"]; + let tokens = + ["windows-x64", "windows-x86", "windows-arm64", "macos-universal", "linux-x86_64", "linux-aarch64", "linux-riscv64", "freebsd-x86_64"]; let assets = tokens .iter() .flat_map(|t| suffixes.iter().map(move |s| format!("{APP}-{version}-{t}{s}"))) diff --git a/packaging/linux/package.sh b/packaging/linux/package.sh index fbbf45b..a99cf6a 100755 --- a/packaging/linux/package.sh +++ b/packaging/linux/package.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Build and package ArtCraft Launcher for Linux ( is x86_64 or aarch64): +# Build and package ArtCraft Launcher for Linux ( is x86_64, aarch64 or riscv64): # # $DIST/artcraft-launcher--linux-.AppImage any distro with glibc >= the build host's # $DIST/artcraft-launcher--linux-.AppImage.zsync delta updates (needs zsyncmake) @@ -13,6 +13,10 @@ # (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04, # glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli, # zsyncmake (the zsync package) for the AppImage's .zsync. +# +# Cross builds (CI: riscv64, deb/rpm/tar.gz): set CROSS_ARCH (riscv64), CROSS_TARGET (the Rust +# triple), CROSS_COMPILE (binutils prefix, for strip) and optionally EMULATOR (e.g. qemu-riscv64) +# to smoke-test the CLI. set -euo pipefail # shellcheck source=../env.sh . "$(dirname "${BASH_SOURCE[0]}")/../env.sh" @@ -26,13 +30,14 @@ while [ $# -gt 0 ]; do case "$1" in --skip-build) SKIP_BUILD=1; shift ;; --formats) FORMATS="$2"; shift 2 ;; - -h | --help) sed -n '2,15p' "$0"; exit 0 ;; + -h | --help) sed -n '2,19p' "$0"; exit 0 ;; *) echo "unknown argument: $1" >&2; exit 2 ;; esac done -ARCH="$(uname -m)" +ARCH="${CROSS_ARCH:-$(uname -m)}" case "$ARCH" in + riscv64) DEB_ARCH=riscv64 ;; x86_64) DEB_ARCH=amd64 ;; aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;; *) echo "unsupported architecture $ARCH" >&2; exit 2 ;; @@ -43,16 +48,20 @@ BASENAME="$PKG-$VERSION-linux-$ARCH" echo "==> ArtCraft Launcher $VERSION for Linux $ARCH ($FORMATS)" if [ "$SKIP_BUILD" = 0 ]; then - (cd "$ROOT" && cargo build --release --locked -p "$PKG") + if [ -n "${CROSS_TARGET:-}" ]; then + (cd "$ROOT" && cargo build --release --locked -p "$PKG" --target "$CROSS_TARGET") + else + (cd "$ROOT" && cargo build --release --locked -p "$PKG") + fi fi -BIN="$CARGO_TARGET_DIR/release" +BIN="$CARGO_TARGET_DIR/${CROSS_TARGET:+$CROSS_TARGET/}release" WORK="$CARGO_TARGET_DIR/linux-package" STAGE="$WORK/root" rm -rf "$WORK" # ---- stage an FHS tree (shared by every format) ------------------------------------------------- install -Dm755 "$BIN/$PKG" "$STAGE/usr/bin/$PKG" -strip "$STAGE/usr/bin/$PKG" 2>/dev/null || true +"${CROSS_COMPILE:-}strip" "$STAGE/usr/bin/$PKG" 2>/dev/null || true install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop" mkdir -p "$STAGE/usr/share/metainfo" sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$ARTCRAFT_LAUNCHER_BUILD_DATE/g" \ @@ -139,5 +148,10 @@ if has appimage; then fi fi +if [ -z "${CROSS_TARGET:-}" ]; then + "$STAGE/usr/bin/$PKG" --version +elif [ -n "${EMULATOR:-}" ]; then + "$EMULATOR" "$STAGE/usr/bin/$PKG" --version +fi echo "==> done" ls -lh "$DIST"