From b8e96584f96ce0f83d058f686867a28e24ebf0a4 Mon Sep 17 00:00:00 2001 From: Bare7a Date: Thu, 5 Nov 2026 15:19:22 +0200 Subject: [PATCH] feat(BAR-35): Adds a Linux ARM64 build --- .github/workflows/release.yml | 53 +++++++++++++++++++++------ README.md | 4 +- RELEASING.md | 16 ++++---- crates/barsql-app/src/update/tests.rs | 34 +++++++++++++---- docs/index.html | 1 + docs/script.js | 20 +++++++++- docs/styles.css | 2 + scripts/package-linux.sh | 14 ++++--- 8 files changed, 108 insertions(+), 36 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9237eb..c63335a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,23 +77,39 @@ jobs: strategy: matrix: include: - # Built on 22.04 so the binary also runs on distros with an older glibc. - - os: ubuntu-22.04 + # Built in an Ubuntu 22.04 container so the binaries also run on distros with an older glibc. + - os: ubuntu-24.04 target: linux + container: ubuntu:22.04 + artifact: dist-linux + - os: ubuntu-24.04-arm + target: linux + container: ubuntu:22.04 + artifact: dist-linux-arm64 - os: windows-latest target: windows + artifact: dist-windows - os: macos-latest target: macos + artifact: dist-macos runs-on: ${{ matrix.os }} + # Windows and macOS have none, so they build on the runner itself. + container: ${{ matrix.container }} steps: - uses: actions/checkout@v7 - - name: Install GPUI build dependencies + # The bare image has no compilers, pkg-config, certificates, Rust or the file command appimagetool runs. + - name: Install the build tools, GPUI's libraries and rustup if: matrix.target == 'linux' + env: + DEBIAN_FRONTEND: noninteractive run: | - sudo apt-get update - sudo apt-get install -y clang libfontconfig-dev libfreetype-dev libwayland-dev \ - libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev libvulkan-dev libfuse2 + set -euo pipefail + apt-get update + apt-get install -y build-essential clang pkg-config curl ca-certificates file libfontconfig-dev libfreetype-dev \ + libwayland-dev libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev libvulkan-dev + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain none + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - name: Install the pinned toolchain run: rustup show active-toolchain || rustup toolchain install @@ -108,13 +124,17 @@ jobs: key: release save-if: ${{ github.ref == 'refs/heads/master' }} - - name: Install cargo-deb, cargo-generate-rpm and linuxdeploy + # package-linux.sh makes the deb and the rpm on x86-64 only. + - name: Install cargo-deb and cargo-generate-rpm + if: matrix.target == 'linux' && runner.arch == 'X64' + run: cargo install --locked cargo-deb@3.8.0 cargo-generate-rpm@0.21.1 + + - name: Install linuxdeploy if: matrix.target == 'linux' run: | set -euo pipefail - cargo install --locked cargo-deb@3.8.0 cargo-generate-rpm@0.21.1 curl -fsSL -o "$RUNNER_TEMP/linuxdeploy" \ - https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" chmod +x "$RUNNER_TEMP/linuxdeploy" echo "LINUXDEPLOY=$RUNNER_TEMP/linuxdeploy" >> "$GITHUB_ENV" @@ -186,13 +206,22 @@ jobs: APPIMAGE_EXTRACT_AND_RUN: 1 run: scripts/package-${{ matrix.target }}.sh - - name: Show the libraries the Linux binary links + # A newer container image would raise the glibc it needs and drop the distros still on 2.35, like Ubuntu 22.04. + - name: Check the libraries the Linux binary needs if: matrix.target == 'linux' - run: readelf -d target/package/linux/BarSQL | grep NEEDED + run: | + set -euo pipefail + readelf -d target/package/linux/BarSQL | grep NEEDED + glibc="$(objdump -T target/package/linux/BarSQL | grep -o 'GLIBC_[0-9.]*' | sort -uV | tail -n1)" + echo "Needs $glibc" + if [ "$(printf '%s\n' "$glibc" GLIBC_2.35 | sort -V | tail -n1)" != GLIBC_2.35 ]; then + echo "::error::The binary needs $glibc, newer than Ubuntu 22.04's GLIBC_2.35." + exit 1 + fi - uses: actions/upload-artifact@v7 with: - name: dist-${{ matrix.target }} + name: ${{ matrix.artifact }} path: target/package/dist/* if-no-files-found: error retention-days: 7 diff --git a/README.md b/README.md index 7bc2d2d..9641c42 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ Grab the latest build for **Windows**, **macOS**, or **Linux** from the [**Relea ### Recommended assets: - **Windows**: `BarSQL-windows-amd64.zip` (portable) **or** `BarSQL-amd64-installer.exe` (installer) -- **Linux**: `BarSQL-linux-amd64.tar.gz` (portable) +- **Linux**: `BarSQL-linux-amd64.tar.gz` (x86-64) **or** `BarSQL-linux-arm64.tar.gz` (ARM64), both portable - **macOS**: `BarSQL-darwin-arm64.zip` (Apple Silicon) **or** `BarSQL-darwin-amd64.zip` (Intel) ### Additional options: - **Windows**: `BarSQL.exe` (standalone) -- **Linux**: `BarSQL-x86_64.AppImage`, `BarSQL.deb`, `BarSQL.rpm`, `BarSQL.pkg.tar.zst`, `BarSQL` (standalone) +- **Linux**: `BarSQL-x86_64.AppImage` and `BarSQL-aarch64.AppImage` (ARM64), plus `BarSQL.deb`, `BarSQL.rpm`, `BarSQL.pkg.tar.zst` and `BarSQL` (standalone) for x86-64 - **macOS**: `BarSQL-macos-universal.dmg` No account required - just download and run. diff --git a/RELEASING.md b/RELEASING.md index bff4fbf..661c73c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,7 +2,7 @@ 1. The workspace starts at 1.0.0. For later releases, run `cargo xtask bump-version --patch` (or `--minor`, `--major`, or an explicit version such as `1.1.0`), then commit and push. `cargo xtask version` prints the current version. 2. Tag and push (`git tag v1.0.0 && git push origin v1.0.0`), or start the workflow by hand on any branch (Actions → Release BarSQL → Run workflow) with the version, and it tags the commit it runs on. -3. `.github/workflows/release.yml` checks that the tag matches `Cargo.toml` and isn't already on another commit, then builds on Ubuntu 22.04, Windows and macOS while the full test suite runs on all three. Once both pass, it publishes every asset plus `SHA256SUMS`, each with a build provenance attestation that `gh attestation verify --repo Bare7a/BarSQL` checks. +3. `.github/workflows/release.yml` checks that the tag matches `Cargo.toml` and isn't already on another commit, then builds for Linux (x86-64 and ARM64, in Ubuntu 22.04 containers), Windows and macOS while the full test suite runs on all three. Once both pass, it publishes every asset plus `SHA256SUMS`, each with a build provenance attestation that `gh attestation verify --repo Bare7a/BarSQL` checks. Run the workflow by hand with the version left empty to build the packages without publishing them; they stay on the run's page for 7 days. @@ -10,18 +10,18 @@ Run the workflow by hand with the version left empty to build the packages witho ## Packages -| Script | Portable updater asset (one top-level entry) | Other downloads | -| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------- | -| `scripts/package-macos.sh` | `BarSQL-darwin-arm64.zip`, `BarSQL-darwin-amd64.zip` (a `BarSQL.app` per architecture) | `BarSQL-macos-universal.dmg` | -| `scripts/package-windows.sh` | `BarSQL-windows-amd64.zip` (`BarSQL.exe`) | `BarSQL.exe`, `BarSQL-amd64-installer.exe` | -| `scripts/package-linux.sh` | `BarSQL-linux-amd64.tar.gz` (`BarSQL`) | `BarSQL`, `BarSQL.deb`, `BarSQL.rpm`, `BarSQL-x86_64.AppImage` | -| `packaging/linux/PKGBUILD` (release workflow, in an Arch Linux container) | | `BarSQL.pkg.tar.zst` | +| Script | Portable updater asset (one top-level entry) | Other downloads | +| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `scripts/package-macos.sh` | `BarSQL-darwin-arm64.zip`, `BarSQL-darwin-amd64.zip` (a `BarSQL.app` per architecture) | `BarSQL-macos-universal.dmg` | +| `scripts/package-windows.sh` | `BarSQL-windows-amd64.zip` (`BarSQL.exe`) | `BarSQL.exe`, `BarSQL-amd64-installer.exe` | +| `scripts/package-linux.sh` | `BarSQL-linux-amd64.tar.gz`, `BarSQL-linux-arm64.tar.gz` (`BarSQL`) | `BarSQL-x86_64.AppImage`, `BarSQL-aarch64.AppImage`, and on x86-64 `BarSQL`, `BarSQL.deb`, `BarSQL.rpm` | +| `packaging/linux/PKGBUILD` (release workflow, in an Arch Linux container) | | `BarSQL.pkg.tar.zst` | - Only the portable updater assets name both a platform and an architecture, so portable copies never pick another download. The AppImage and the packages update from their own asset, and the installer and the dmg from the `.zip`. - Release builds turn on the `production` feature, which enables the update check at launch. The release profile uses thin LTO and strips symbols. - **macOS:** an `.app` for macOS 12 and later, signed with the Developer ID and notarized through the [signing secrets](#macos-signing-and-notarization), which a release requires. A fork or a build-only run without them signs it ad-hoc. Each zip holds its own architecture alone, half the size of a universal app, since the updater downloads the zip for the Mac's own chip, even from an Intel build running under Rosetta. The dmg holds a `lipo` universal app and an `Applications` link, so a copy installed from it keeps only its own architecture after its first update. `BARSQL_NATIVE_ONLY=1` builds this Mac's architecture alone. - **Windows:** `crates/barsql/build.rs` embeds the icon and the version block. Neither the `.exe` nor the installer is code-signed, so SmartScreen asks once before the first launch. `packaging/windows/installer.nsi` installs the app with the `SQLite Database` file class for `.db`, `.sqlite`, `.sqlite3`, `.s3db` and `.sl3`. -- **Linux:** [`cargo-deb`](https://crates.io/crates/cargo-deb) and [`cargo-generate-rpm`](https://crates.io/crates/cargo-generate-rpm) build the deb and rpm from `crates/barsql/Cargo.toml`, and `makepkg` builds the Arch package from `packaging/linux/PKGBUILD`. Each installs `/usr/bin/BarSQL` and a desktop entry that opens SQLite files; the deb and rpm work out their library dependencies from the binary. The window's app id is `BarSQL`, so Wayland docks match it to `BarSQL.desktop`. +- **Linux:** [`cargo-deb`](https://crates.io/crates/cargo-deb) and [`cargo-generate-rpm`](https://crates.io/crates/cargo-generate-rpm) build the deb and rpm from `crates/barsql/Cargo.toml`, and `makepkg` builds the Arch package from `packaging/linux/PKGBUILD`. Each installs `/usr/bin/BarSQL` and a desktop entry that opens SQLite files; the deb and rpm work out their library dependencies from the binary. The window's app id is `BarSQL`, so Wayland docks match it to `BarSQL.desktop`. Both builds run in an `ubuntu:22.04` container on GitHub's `ubuntu-24.04` and `ubuntu-24.04-arm` runners, so the binaries need glibc 2.35 at most, which the build checks, and run on Ubuntu 22.04, Debian 12, Fedora 36, RHEL 10 and later. The ARM64 build makes only the tarball and the AppImage: every released version picks a `.deb`, `.rpm` or `.pkg.tar.zst` by its extension alone, so an ARM package would reach x86-64 copies too. - **Compression:** the dmg is LZMA (`ULMO`, which macOS 10.15 and later open), the installer uses a 64 MB LZMA dictionary, the rpm an xz payload and the Arch package `zstd --ultra -20`. The deb keeps cargo-deb's xz and the AppImage appimagetool's zstd. The updater assets stay deflate `.zip` and gzip `.tar.gz`, the only formats released versions unpack. - The icon's source is `packaging/icon.svg`. The macOS `.icns` sets it on Apple's icon grid (an 824px body on a 1024px canvas, with a shadow). Windows and Linux show the stack without its tile, since their taskbars and docks are mostly dark and the dark tile would disappear there, leaving the stack looking small. The title bar shows the stack too, in a dark and a light version (`crates/barsql-ui/images/barsql-mark-*.svg`). `cargo xtask icons` regenerates the set from it: the Linux PNGs, `icon.ico`, `icons.icns` and `docs/icon.png`, each size rendered from the vector, and the SVG copies for the app (the marks and its icon) and `docs/`. diff --git a/crates/barsql-app/src/update/tests.rs b/crates/barsql-app/src/update/tests.rs index fa1a8f3..a21026e 100644 --- a/crates/barsql-app/src/update/tests.rs +++ b/crates/barsql-app/src/update/tests.rs @@ -58,13 +58,15 @@ fn serve(mut stream: TcpStream, routes: &Routes) { } // Keep in sync with the assets release.yml publishes. -const RELEASE_ASSETS: [&str; 13] = [ +const RELEASE_ASSETS: [&str; 15] = [ "SHA256SUMS", "BarSQL", + "BarSQL-aarch64.AppImage", "BarSQL-amd64-installer.exe", "BarSQL-darwin-amd64.zip", "BarSQL-darwin-arm64.zip", "BarSQL-linux-amd64.tar.gz", + "BarSQL-linux-arm64.tar.gz", "BarSQL-macos-universal.dmg", "BarSQL-windows-amd64.zip", "BarSQL-x86_64.AppImage", @@ -122,7 +124,8 @@ fn each_platform_gets_its_updater_archive() { assert_eq!(picked("darwin", "amd64", &RELEASE_ASSETS).as_deref(), Some("BarSQL-darwin-amd64.zip")); assert_eq!(picked("linux", "amd64", &RELEASE_ASSETS).as_deref(), Some("BarSQL-linux-amd64.tar.gz")); assert_eq!(picked("windows", "amd64", &RELEASE_ASSETS).as_deref(), Some("BarSQL-windows-amd64.zip")); - assert_eq!(picked("linux", "arm64", &RELEASE_ASSETS), None); + assert_eq!(picked("linux", "arm64", &RELEASE_ASSETS).as_deref(), Some("BarSQL-linux-arm64.tar.gz")); + assert_eq!(picked("windows", "arm64", &RELEASE_ASSETS), None); assert_eq!(picked("windows", "386", &RELEASE_ASSETS), None); } @@ -130,12 +133,29 @@ fn each_platform_gets_its_updater_archive() { fn appimages_and_packages_get_their_own_asset() { let pick = |kind, arch| pick_asset(kind, "linux", arch, &assets(&RELEASE_ASSETS)).map(|ix| RELEASE_ASSETS[ix]); assert_eq!(pick(Kind::AppImage, "amd64"), Some("BarSQL-x86_64.AppImage")); - assert_eq!(pick(Kind::AppImage, "arm64"), None); + assert_eq!(pick(Kind::AppImage, "arm64"), Some("BarSQL-aarch64.AppImage")); assert_eq!(pick(Kind::Deb, "amd64"), Some("BarSQL.deb")); assert_eq!(pick(Kind::Rpm, "amd64"), Some("BarSQL.rpm")); assert_eq!(pick(Kind::Pacman, "amd64"), Some("BarSQL.pkg.tar.zst")); } +// pick_asset takes the first match and GitHub promises no order, so no copy may match two assets. Every release +// so far picks the same way, so this covers the copies already out there too. +#[test] +fn every_copy_matches_one_asset_at_most() { + for kind in [Kind::Archive, Kind::AppImage, Kind::Deb, Kind::Rpm, Kind::Pacman] { + for platform in ["darwin", "windows", "linux"] { + for arch in ["amd64", "arm64"] { + let matches: Vec<_> = RELEASE_ASSETS + .into_iter() + .filter(|name| pick_asset(kind, platform, arch, &assets(&[*name])).is_some()) + .collect(); + assert!(matches.len() <= 1, "{kind:?} on {platform}/{arch} matches {matches:?}"); + } + } + } +} + #[test] fn installs_are_told_apart() { let none = |_: &Path| None; @@ -227,14 +247,14 @@ fn a_newer_release_comes_with_its_asset_and_digest() { assert_eq!(check(&base, "v1.6.0", Kind::Archive, "darwin", "arm64"), Ok(None), "up to date"); assert_eq!( - check(&base, "1.5.3", Kind::Archive, "linux", "arm64"), - Err("github: release v1.6.0 has no asset for linux/arm64".into()) + check(&base, "1.5.3", Kind::Archive, "linux", "riscv64"), + Err("github: release v1.6.0 has no asset for linux/riscv64".into()) ); let image = check(&base, "1.5.3", Kind::AppImage, "linux", "amd64").unwrap().unwrap(); assert_eq!((image.asset.name.as_str(), image.digest), ("BarSQL-x86_64.AppImage", Some(vec![0xf0, 0x0d]))); assert_eq!( - check(&base, "1.5.3", Kind::AppImage, "linux", "arm64"), - Err("github: release v1.6.0 has no AppImage for linux/arm64".into()) + check(&base, "1.5.3", Kind::AppImage, "linux", "riscv64"), + Err("github: release v1.6.0 has no AppImage for linux/riscv64".into()) ); } diff --git a/docs/index.html b/docs/index.html index 198646e..6f71af5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -635,6 +635,7 @@

Linux

A portable x86-64 build, plus packages for Debian, Ubuntu, Fedora, RHEL and Arch.

Download .tar.gz +

For ARM64: .tar.gz or AppImage

Other formats
diff --git a/docs/script.js b/docs/script.js index 7a3cc57..59ab546 100644 --- a/docs/script.js +++ b/docs/script.js @@ -37,9 +37,18 @@ function detectOS() { return null; } +// Chromium's user agent says x86_64 on every Linux, so its client hints come first. +async function onArm() { + try { + const hints = await navigator.userAgentData?.getHighEntropyValues(["architecture"]); + if (hints) return hints.architecture === "arm"; + } catch {} + return /aarch64|arm64/i.test(navigator.userAgent); +} + // Point the hero button at the visitor's own build, highlight its card and pick the modifier key. // Phones and tablets keep the generic button, which leads to every download, and pages without downloads skip this. -function pointDownloadAtVisitor() { +async function pointDownloadAtVisitor() { const os = detectOS(); const hero = document.getElementById("hero-download"); const card = os && document.getElementById(`dl-${os}`); @@ -47,12 +56,13 @@ function pointDownloadAtVisitor() { const names = { windows: "Windows", macos: "macOS", linux: "Linux" }; const main = card.querySelector(".dl-main"); + const meta = document.getElementById("hero-download-meta"); const icon = hero.querySelector("svg"); hero.href = main.href; icon.setAttribute("class", "logo"); icon.querySelector("use").setAttribute("href", `logos.svg#l-${os === "macos" ? "apple" : os}`); document.getElementById("hero-download-label").textContent = `Download for ${names[os]}`; - document.getElementById("hero-download-meta").textContent = main.dataset.meta; + meta.textContent = main.dataset.meta; card.classList.add("dl-detected"); if (os === "macos") { @@ -61,6 +71,12 @@ function pointDownloadAtVisitor() { key.title = "Command"; } } + + const arm = card.querySelector(".dl-arm"); + if (arm && (await onArm())) { + hero.href = arm.href; + meta.textContent = arm.dataset.meta; + } } // Lightbox with every screenshot on the page, full size and in order. Arrow keys, swipes and the buttons move between them. diff --git a/docs/styles.css b/docs/styles.css index 095f5c9..82c71b8 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -644,6 +644,8 @@ kbd { .dl-desc { margin: 12px 0 20px; color: var(--muted); font-size: 0.92rem; } .dl-main { width: 100%; } +.dl-alt { margin: 10px 0 0; color: var(--faint); font-size: 0.82rem; text-align: center; } +.dl-alt a { font-family: var(--font-mono); } /* Every card gives its other formats two rows at the bottom, so the cards line up */ .dl-more { display: grid; justify-items: start; gap: 8px; margin-top: auto; padding-top: 20px; } diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index 52549e8..5b49644 100755 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Builds the Linux release files in target/package/dist. Needs cargo-deb and cargo-generate-rpm, and LINUXDEPLOY -# for the AppImage. release.yml builds the Arch package separately. +# Builds the Linux release files for this machine's architecture in target/package/dist. Needs cargo-deb and +# cargo-generate-rpm on x86-64, and LINUXDEPLOY for the AppImage. release.yml builds the Arch package separately. # Only the tarball has "linux" in its name, so portable copies never update from the other files. set -euo pipefail @@ -22,11 +22,15 @@ mkdir -p "$out" "$dist" install -m 0755 "$root_dir/target/release/BarSQL" "$out/BarSQL" rm -f "$dist/BarSQL-linux-$arch.tar.gz" "$dist/BarSQL" "$dist"/BarSQL.{deb,rpm} "$dist/BarSQL-$appimage_arch.AppImage" tar -C "$out" -czf "$dist/BarSQL-linux-$arch.tar.gz" BarSQL -cp "$out/BarSQL" "$dist/BarSQL" cd "$root_dir" -cargo deb -p barsql --no-build --no-strip --output "$dist/BarSQL.deb" -cargo generate-rpm -p crates/barsql --payload-compress xz --output "$dist/BarSQL.rpm" +# These names leave out the architecture, and the updater picks a .deb or .rpm by its extension alone, so only +# x86-64 makes them. +if [ "$arch" = amd64 ]; then + cp "$out/BarSQL" "$dist/BarSQL" + cargo deb -p barsql --no-build --no-strip --output "$dist/BarSQL.deb" + cargo generate-rpm -p crates/barsql --payload-compress xz --output "$dist/BarSQL.rpm" +fi if [ -n "${LINUXDEPLOY:-}" ]; then appdir="$out/BarSQL.AppDir"