Files
qbz/.github/workflows/release-linux.yml
T
vicrodh 395e537b59 fix(build): drop compile-time x86 target-features — runtime AES dispatch
Users on CPUs without AES-NI (Nehalem i7-860, pre-2010 Intel Macs) or
without SSSE3 (AMD Phenom II) crashed at launch with SIGILL. The
global `-C target-feature=+aes,+ssse3` was kept after the #353/#343
x86-64-v3 incident on the belief that the aes crate's cpufeatures
runtime check would protect CPUs without AES-NI — but a compile-time
target feature makes cpufeatures compile its check to a constant, so
the AES-NI intrinsics ran unconditionally (credentials AES-GCM decrypt
at startup), and +ssse3 let LLVM autovectorize anywhere, breaking
Phenom II. Measured on the qbz-cmaf hot path (Ctr128BE<Aes128>
apply_keystream, 256 MB, release): 2145 MB/s compiled-in vs 2162 MB/s
runtime-dispatched, identical output — the flags bought nothing on
capable CPUs. Removed from .cargo/config.toml, all dev/build scripts
and the release/build workflows; old CPUs now get the software AES
fallback instead of a crash.

Fixes #549

cc
2026-07-05 14:17:46 -06:00

290 lines
12 KiB
YAML

# Linux x86_64 release — Slint binary (crates/ workspace).
#
# Rewritten 2026-07-02 for the Tauri -> Slint migration (v2.0). Pattern:
# build once, package many. The build job produces the binary with the
# proven public-runner recipe from build-slint.yml (16 GB RAM + 18 GB extra
# swap covers the ~30 GB qbz_ui rustc peak, MIN memory tier, mold, pinned
# nightly); the package job turns it into deb/rpm (nfpm), AppImage, and the
# AUR/Gentoo/APT tarball, then creates the GitHub Release.
#
# CONTRACT (do not break): the release must keep publishing
# qbz_${VERSION}_amd64.tar.gz (dir qbz_${VERSION}_amd64/{qbz, qbz.desktop, icons/hicolor/...})
# QBZ_${VERSION}_amd64.deb / QBZ-${VERSION}-1.x86_64.rpm / QBZ_${VERSION}_amd64.AppImage
# — release-aur.yml, release-gentoo.yml, qbz-apt and the website consume
# these names.
#
# Triggers: version tags (cut from main — enforced by the ancestry guard)
# + manual dispatch (guard skipped, for testing on any branch).
name: release-linux
on:
workflow_dispatch:
inputs:
tag:
description: "Optional release tag to build (e.g. v2.0.0). Empty = build HEAD with the Cargo.toml version, no Release publish."
required: false
type: string
push:
tags:
- "v*"
concurrency:
group: release-linux-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 300
outputs:
version: ${{ steps.release.outputs.version }}
prerelease: ${{ steps.release.outputs.prerelease }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve release version
id: release
run: |
VERSION=""
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
VERSION="${GITHUB_REF#refs/tags/v}"
elif [[ -n "${{ inputs.tag }}" ]]; then
VERSION="${{ inputs.tag }}"
VERSION="${VERSION#v}"
else
# Manual dispatch without a tag: test build at the Cargo.toml version.
VERSION="$(grep -m1 '^version = ' crates/Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')"
fi
PRERELEASE=false
if [[ "$VERSION" =~ -(rc|beta|alpha|pre) ]]; then
PRERELEASE=true
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "prerelease=$PRERELEASE" >> "$GITHUB_OUTPUT"
echo "Building version $VERSION (prerelease=$PRERELEASE)"
# Release tags must be cut from main. A tag on any other commit fires
# this workflow too (tags are not branch-scoped in Actions), so verify
# ancestry instead of trusting convention. Manual dispatch skips this
# (that is the testing path).
- name: Verify tag is on main
if: startsWith(github.ref, 'refs/tags/')
run: |
git fetch origin main
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
echo "::error::Tag ${GITHUB_REF#refs/tags/} points to a commit that is NOT on main. Releases are cut from main only."
exit 1
fi
# Fail fast if the version bump was forgotten: the binary embeds the
# workspace version (About dialog, diagnostics), so a mismatched tag
# would ship a binary that reports the wrong version.
- name: Assert Cargo.toml version matches tag
if: startsWith(github.ref, 'refs/tags/')
env:
VERSION: ${{ steps.release.outputs.version }}
run: |
CARGO_VERSION="$(grep -m1 '^version = ' crates/Cargo.toml | sed 's/version = \"\(.*\)\"/\1/')"
if [[ "$CARGO_VERSION" != "$VERSION" ]]; then
echo "::error::crates/Cargo.toml workspace version ($CARGO_VERSION) != tag version ($VERSION). Bump it before tagging (see RELEASE_CHECKLIST)."
exit 1
fi
# The qbz_ui rustc needs ~30 GB (RSS+swap). Public runner: 16 GB RAM
# + ~4 GB stock swap; free disk by dropping preinstalled toolchains,
# then add 18 GB swap -> ~38 GB total. Same recipe as build-slint.yml.
- name: Free disk space and add swap
run: |
sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \
/opt/hostedtoolcache /usr/local/share/boost /usr/share/swift \
/usr/local/lib/node_modules || true
sudo docker system prune -af || true
if sudo fallocate -l 18G /swap_extra; then
sudo chmod 600 /swap_extra
sudo mkswap /swap_extra
sudo swapon /swap_extra
else
echo "::warning::could not allocate extra swap; build may OOM"
fi
free -h
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
mold nasm \
libasound2-dev libjack-jackd2-dev \
libfontconfig1-dev libfreetype-dev \
libxkbcommon-dev libwayland-dev libxcb1-dev \
libgl1-mesa-dev libegl1-mesa-dev \
libdbus-1-dev libssl-dev \
libharfbuzz-dev libjbig2dec0-dev libopenjp2-7-dev \
libjpeg-dev zlib1g-dev libgumbo-dev
# Pinned nightly: matches build-slint.yml (same measured memory
# behavior, -Z threads support). Bump both files together.
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-23
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
shared-key: slint-linux
cache-on-failure: true
# Releases only READ the cache; build-slint.yml owns writing it.
save-if: false
- name: Build (release, MIN memory tier)
env:
CARGO_INCREMENTAL: "0"
CARGO_BUILD_JOBS: "1"
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "256"
CARGO_PROFILE_RELEASE_OPT_LEVEL: "2"
# Keep symbols in this build: the unstripped binary is archived for
# crash symbolication, then stripped for every shipped artifact.
CARGO_PROFILE_RELEASE_STRIP: "none"
RUSTFLAGS: "-C link-arg=-fuse-ld=mold -Z threads=1"
run: |
cargo build --release --manifest-path crates/Cargo.toml -p qbz
cp crates/target/release/qbz qbz-unstripped
strip -o qbz crates/target/release/qbz
ls -la qbz qbz-unstripped
- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: qbz-linux-x86_64
path: qbz
if-no-files-found: error
# Symbolication artifact: panic backtraces in shipped binaries are
# address-only (profile strips symbols); this copy resolves them.
- name: Upload unstripped binary (symbolication)
uses: actions/upload-artifact@v4
with:
name: qbz-linux-x86_64-unstripped
path: qbz-unstripped
retention-days: 90
package:
needs: build
runs-on: ubuntu-24.04
permissions:
contents: write
env:
VERSION: ${{ needs.build.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download binary
uses: actions/download-artifact@v4
with:
name: qbz-linux-x86_64
- name: Install nfpm
run: |
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | \
sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt-get update
sudo apt-get install -y nfpm
nfpm --version
- name: Build deb + rpm (nfpm)
env:
QBZ_VERSION: ${{ env.VERSION }}
QBZ_ARCH: amd64
QBZ_BINARY: ./qbz
run: |
chmod +x qbz
mkdir -p dist
nfpm package -f packaging/nfpm/nfpm.yaml -p deb \
-t "dist/QBZ_${VERSION}_amd64.deb"
nfpm package -f packaging/nfpm/nfpm.yaml -p rpm \
-t "dist/QBZ-${VERSION}-1.x86_64.rpm"
- name: Build AppImage
run: |
# Minimal AppDir: the measured runtime deps (alsa/fontconfig/
# freetype/png/bz2/expat/zlib) are base-system libraries on every
# target distro, so nothing is bundled beyond the binary.
APPDIR="QBZ.AppDir"
mkdir -p "$APPDIR/usr/bin"
cp qbz "$APPDIR/usr/bin/qbz"
cp packaging/linux/qbz.desktop "$APPDIR/qbz.desktop"
cp packaging/icons/256x256.png "$APPDIR/qbz.png"
cp packaging/icons/256x256.png "$APPDIR/.DirIcon"
ln -s usr/bin/qbz "$APPDIR/AppRun"
curl -fsSL -o appimagetool \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
ARCH=x86_64 ./appimagetool --appimage-extract-and-run "$APPDIR" \
"dist/QBZ_${VERSION}_amd64.AppImage"
# Tauri-updater bridge: v1.2.x AppImage installs poll latest.json and
# verify artifacts with the Tauri minisign pubkey baked into the old
# binaries. Signing the Slint AppImage with the SAME private key lets
# them auto-update straight into v2 (the updater just swaps the
# AppImage file). release-updater-manifest.yml collects the .sig into
# latest.json. The Slint app itself has no auto-updater — this exists
# only to migrate old installs.
- name: Sign AppImage (Tauri-updater bridge)
if: startsWith(github.ref, 'refs/tags/')
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
npx --yes @tauri-apps/cli@^2 signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"dist/QBZ_${VERSION}_amd64.AppImage"
ls -la dist
- name: Build AUR/Gentoo tarball
run: |
# CONTRACT: release-aur.yml, the Gentoo binary ebuild, qbz-apt and
# the website hard-code this name and internal layout.
TARBALL_DIR="qbz_${VERSION}_amd64"
mkdir -p "tarball-staging/${TARBALL_DIR}/icons/hicolor"/{32x32,48x48,64x64,128x128,256x256}/apps
cp qbz "tarball-staging/${TARBALL_DIR}/qbz"
chmod +x "tarball-staging/${TARBALL_DIR}/qbz"
cp packaging/icons/32x32.png "tarball-staging/${TARBALL_DIR}/icons/hicolor/32x32/apps/qbz.png"
cp packaging/icons/48x48.png "tarball-staging/${TARBALL_DIR}/icons/hicolor/48x48/apps/qbz.png"
cp packaging/icons/64x64.png "tarball-staging/${TARBALL_DIR}/icons/hicolor/64x64/apps/qbz.png"
cp packaging/icons/128x128.png "tarball-staging/${TARBALL_DIR}/icons/hicolor/128x128/apps/qbz.png"
cp packaging/icons/256x256.png "tarball-staging/${TARBALL_DIR}/icons/hicolor/256x256/apps/qbz.png"
cp packaging/linux/qbz.desktop "tarball-staging/${TARBALL_DIR}/qbz.desktop"
tar -czvf "dist/qbz_${VERSION}_amd64.tar.gz" -C tarball-staging "${TARBALL_DIR}"
ls -la dist
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: qbz-linux-packages
path: dist/*
- name: Publish GitHub Release Assets
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: dist/*
prerelease: ${{ needs.build.outputs.prerelease }}
make_latest: ${{ needs.build.outputs.prerelease == 'true' && 'false' || 'true' }}
- name: Trigger APT repo update
if: startsWith(github.ref, 'refs/tags/')
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.APT_REPO_TOKEN }}
repository: vicrodh/qbz-apt
event-type: new-release
client-payload: '{"tag": "${{ github.ref_name }}"}'