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 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dylan M. Taylor 2026-10-11 02:15:28 -04:00 committed by GitHub
parent 633f09c7cc
commit e8614afa3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 129 additions and 11 deletions

View File

@ -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

View File

@ -119,7 +119,7 @@ Release assets (keep these names exact; the launcher's own self-update and the R
|---|---|
| Windows | `artcraft-launcher-<ver>-windows-{x64,x86,arm64}.msi`, `…-windows-{x64,x86,arm64}-portable.zip` |
| macOS | `artcraft-launcher-<ver>-macos-universal.dmg` |
| Linux | `artcraft-launcher-<ver>-linux-{x86_64,aarch64}.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}` |
| Linux | `artcraft-launcher-<ver>-linux-{x86_64,aarch64}.{AppImage,AppImage.zsync,deb,rpm,tar.gz,flatpak}`, `artcraft-launcher-<ver>-linux-riscv64.{deb,rpm,tar.gz}` |
| FreeBSD | `artcraft-launcher-<ver>-freebsd-x86_64.tar.gz` |
| Web | `artcraft-launcher-web-<ver>.zip` |

View File

@ -145,6 +145,8 @@ Download the build for your computer from the [latest release](https://github.co
| `artcraft-launcher-<ver>-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-<ver>-linux-{x86_64,aarch64}.tar.gz` | Plain tree (`bin/`, `share/`) for everyone else and for scripts |
RISC-V (riscv64): `artcraft-launcher-<ver>-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)

View File

@ -212,7 +212,13 @@ fn visitor_platform() -> Option<Platform> {
} 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 })
}

View File

@ -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")
);
}
}

View File

@ -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}")))

View File

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Build and package ArtCraft Launcher for Linux (<arch> is x86_64 or aarch64):
# Build and package ArtCraft Launcher for Linux (<arch> is x86_64, aarch64 or riscv64):
#
# $DIST/artcraft-launcher-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
# $DIST/artcraft-launcher-<version>-linux-<arch>.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"