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"