Files
qbz/.github/workflows/release-macos.yml
T

216 lines
8.4 KiB
YAML

# macOS release — Slint binary (crates/ workspace).
#
# Rewritten 2026-07-02 for the Tauri -> Slint migration (v2.0). Builds the
# qbz binary per target, assembles a minimal QBZ.app bundle (Info.plist +
# icns + binary), ad-hoc signs it (same trust level as the unsigned Tauri
# builds), and packs a dmg with hdiutil. No Tauri updater: the .app.tar.gz
# + .sig updater assets are gone, dmg is the distribution.
#
# MEMORY NOTE: hosted macOS runners have ~7 GB RAM; the qbz_ui rustc peaks
# ~30 GB on Linux. macOS swaps dynamically to SSD so the build may survive
# (slowly) — if it proves unreliable, point runs-on at a self-hosted runner
# on the M2 Mini instead. continue-on-error keeps a mac failure from
# blocking the Linux release.
name: release-macos
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-macos-${{ github.ref }}
cancel-in-progress: false
jobs:
build-macos:
strategy:
matrix:
include:
- target: aarch64-apple-darwin
artifact: qbz-macos-aarch64
arch: aarch64
- target: x86_64-apple-darwin
artifact: qbz-macos-x86_64
# Historical dmg naming: intel assets ship as "x64".
arch: x64
runs-on: macos-latest
timeout-minutes: 360
continue-on-error: true
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v7
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
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"
- 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/} is not on main. Releases are cut from main only."
exit 1
fi
- 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)."
exit 1
fi
# Same pinned nightly as the Linux builders (-Z threads support,
# consistent measured behavior). Bump all release workflows together.
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-06-23
targets: ${{ matrix.target }}
- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: crates -> target
shared-key: slint-macos-${{ matrix.target }}
cache-on-failure: true
save-if: true
# MIN memory tier — the 7 GB hosted runner relies on macOS dynamic
# swap for the qbz_ui rustc peak. Binary is stripped by the workspace
# profile (strip = "symbols"), which rustc applies correctly to Mach-O.
- 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"
RUSTFLAGS: "-Z threads=1"
run: |
cargo build --release --manifest-path crates/Cargo.toml \
-p qbz --target ${{ matrix.target }}
- name: Assemble QBZ.app
env:
VERSION: ${{ steps.release.outputs.version }}
run: |
set -euo pipefail
APP="dist/QBZ.app"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
cp "crates/target/${{ matrix.target }}/release/qbz" "$APP/Contents/MacOS/qbz"
chmod +x "$APP/Contents/MacOS/qbz"
cp packaging/icons/icon.icns "$APP/Contents/Resources/icon.icns"
cat > "$APP/Contents/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleIdentifier</key><string>com.blitzfc.qbz</string>
<key>CFBundleName</key><string>QBZ</string>
<key>CFBundleDisplayName</key><string>QBZ</string>
<key>CFBundleExecutable</key><string>qbz</string>
<key>CFBundleIconFile</key><string>icon</string>
<key>CFBundleShortVersionString</key><string>${VERSION}</string>
<key>CFBundleVersion</key><string>${VERSION}</string>
<key>LSMinimumSystemVersion</key><string>11.0</string>
<key>NSHighResolutionCapable</key><true/>
<key>LSApplicationCategoryType</key><string>public.app-category.music</string>
<key>NSSupportsAutomaticGraphicsSwitching</key><true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key><string>com.blitzfc.qbz</string>
<key>CFBundleURLSchemes</key>
<array><string>qbz</string><string>qobuzapp</string></array>
</dict>
</array>
</dict>
</plist>
PLIST
# Ad-hoc signature: required for arm64 Macs to launch at all;
# matches the trust level of the previous unsigned Tauri builds.
codesign --force --deep -s - "$APP"
codesign --verify --verbose "$APP"
- name: Build dmg
env:
VERSION: ${{ steps.release.outputs.version }}
ARCH: ${{ matrix.arch }}
run: |
hdiutil create -volname "QBZ" -srcfolder dist/QBZ.app -ov \
-format UDZO "dist/QBZ_${VERSION}_${ARCH}.dmg"
ls -la dist
# Tauri-updater bridge: v1.2.x installs poll latest.json and verify
# with the Tauri minisign pubkey baked into the old binaries. The
# updater expects QBZ_${ARCH}.app.tar.gz — keep producing it, signed
# with the SAME key, so old installs update straight into the Slint
# .app. release-updater-manifest.yml collects the .sig files.
- name: Package .app.tar.gz + sign (Tauri-updater bridge)
if: startsWith(github.ref, 'refs/tags/')
env:
ARCH: ${{ matrix.arch }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
tar -czf "dist/QBZ_${ARCH}.app.tar.gz" -C dist QBZ.app
npx --yes @tauri-apps/cli@^2 signer sign \
--private-key "$TAURI_SIGNING_PRIVATE_KEY" \
--password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
"dist/QBZ_${ARCH}.app.tar.gz"
ls -la dist
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}
path: |
dist/*.dmg
dist/*.app.tar.gz
dist/*.app.tar.gz.sig
# Attaches to the Release created by release-linux.yml; same
# prerelease logic (the old workflow hardcoded prerelease: true).
- name: Publish GitHub Release Assets
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
with:
files: |
dist/*.dmg
dist/*.app.tar.gz
dist/*.app.tar.gz.sig
prerelease: ${{ steps.release.outputs.prerelease }}
make_latest: ${{ steps.release.outputs.prerelease == 'true' && 'false' || 'true' }}