open-standards/.github/workflows/wai-quantum-desktop-release.yml
Transaction Science 6073a83fdc
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 9s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 11s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 17s
WAI operation catalogue / does this change touch it (pull_request) Successful in 1m16s
WAI conformance / does this change touch it (pull_request) Successful in 51s
wai-quantum on every processor / emulated · aarch64-unknown-linux-gnu (pull_request) Successful in 2m13s
WAI energy receipts / does this change touch it (pull_request) Successful in 11s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 10s
wai-quantum contract / does this change touch it (pull_request) Successful in 12s
wai-quantum command line / does this change touch it (pull_request) Successful in 13s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 32s
wai-quantum dataflow member / does this change touch it (pull_request) Successful in 6s
wai-quantum CUDA member / does this change touch it (pull_request) Successful in 8s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m46s
wai-quantum docs, course and locales / does this change touch it (pull_request) Successful in 8s
wai-quantum signal processor / does this change touch it (pull_request) Successful in 8s
wai-quantum editors / does this change touch it (pull_request) Successful in 6s
wai-quantum resource estimation / does this change touch it (pull_request) Successful in 7s
wai-quantum FPGA member / does this change touch it (pull_request) Successful in 7s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 2m31s
wai-quantum HIP member / does this change touch it (pull_request) Successful in 7s
wai-quantum on every processor / emulated · loongarch64-unknown-linux-gnu (pull_request) Successful in 3m1s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 8s
wai-quantum OpenQASM reader / does this change touch it (pull_request) Successful in 9s
wai-quantum CUDA member / member · CUDA (jetson-hub) (pull_request) Successful in 31s
wai-quantum Metal member / does this change touch it (pull_request) Successful in 10s
wai-quantum npu-cores member / does this change touch it (pull_request) Successful in 10s
wai-quantum neural-processor member / does this change touch it (pull_request) Successful in 9s
wai-quantum OpenCL member / does this change touch it (pull_request) Successful in 8s
wai-quantum for Python / does this change touch it (pull_request) Successful in 8s
wai-quantum error correction and mitigation / does this change touch it (pull_request) Successful in 7s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m24s
wai-quantum-qpu / does this change touch it (pull_request) Successful in 7s
wai-quantum matrix-extension member / does this change touch it (pull_request) Successful in 8s
wai-quantum service / does this change touch it (pull_request) Successful in 9s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 9s
wai-quantum tensor-engine member / does this change touch it (pull_request) Successful in 8s
wai-quantum portable tensor program / does this change touch it (pull_request) Successful in 10s
wai-quantum CPU vector units / does this change touch it (pull_request) Successful in 9s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (jetson-hub) (pull_request) Successful in 32s
wai-quantum verdicts and repairs / does this change touch it (pull_request) Successful in 10s
wai-quantum views in agent hosts / does this change touch it (pull_request) Successful in 12s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 17s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 18s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 17s
wai-quantum on every processor / emulated · armv7-unknown-linux-gnueabihf (pull_request) Successful in 4m33s
WAI Studio / does this change touch it (pull_request) Successful in 28s
wai-web wasm32 / does this change touch it (pull_request) Successful in 27s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m59s
wai-quantum service / service energy · linux packages (pull_request) Successful in 1m22s
wai-quantum HIP member / member · HIP (framework-128gb) (pull_request) Successful in 31s
wai-quantum portable tensor program / tensor program · GPUs (jetson-hub) (pull_request) Successful in 23s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 1m8s
wai-quantum command line / command line · macos (pull_request) Successful in 2m39s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m13s
wai-quantum on every processor / emulated · powerpc64le-unknown-linux-gnu (pull_request) Successful in 6m26s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 6s
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Successful in 45s
wai-quantum editors / editors · macos (pull_request) Successful in 59s
wai-quantum npu-cores member / member · npu-cores, neural processor (framework-128gb silicon) (pull_request) Successful in 1m36s
wai-quantum OpenCL member / member · OpenCL GPU (framework-128gb) (pull_request) Successful in 25s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m23s
wai-quantum portable tensor program / tensor program · integrated GPU (framework-128gb) (pull_request) Successful in 12s
wai-quantum on every processor / emulated · s390x-unknown-linux-gnu (pull_request) Successful in 6m5s
wai-quantum CPU vector units / member · vector units, avx2 and avx512 (framework-128gb silicon) (pull_request) Successful in 23s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 2m30s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 7m22s
wai-quantum on every processor / emulated · i686-unknown-linux-gnu (pull_request) Successful in 5m30s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 2m11s
wai-quantum Metal member / member · Metal (macos) (pull_request) Successful in 57s
wai-quantum on every processor / emulated · powerpc64-unknown-linux-gnu (pull_request) Successful in 10m31s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 37s
wai-quantum-qpu / credentials in the keychain (macos) (pull_request) Successful in 27s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m49s
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Successful in 15s
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Successful in 4m31s
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m24s
wai-quantum for Python / python · macos (pull_request) Successful in 2m7s
wai-quantum service / service · macos (pull_request) Successful in 1m17s
wai-quantum on every processor / emulated · riscv64gc-unknown-linux-gnu (pull_request) Successful in 12m42s
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Successful in 23s
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Successful in 20s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 35s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 10m23s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m17s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m40s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 10m4s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m22s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 5m29s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 2m44s
wai-quantum command line / command line · linux (pull_request) Successful in 3m15s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 4m36s
wai-quantum on every processor / emulated · sparc64-unknown-linux-gnu (pull_request) Successful in 14m48s
wai-quantum contract / the policy is linked, and the deprecations page reads (pull_request) Successful in 8s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 5m29s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Successful in 10m43s
wai-quantum contract / API compatibility · linux (pull_request) Successful in 5m31s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 9m7s
wai-quantum contract / schemas and the committed tool lists · linux (pull_request) Successful in 5m12s
wai-quantum CUDA member / member · CUDA absent (linux) (pull_request) Successful in 40s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m56s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:401e) (pull_request) Successful in 2m21s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (pull_request) Successful in 20s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:b140) (pull_request) Successful in 6m37s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m0s
wai-quantum docs, course and locales / the course and the command line's languages · linux (pull_request) Successful in 1m55s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 11m38s
wai-quantum resource estimation / estimation and calibration · linux, release (pull_request) Successful in 1m53s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 11m55s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 13m15s
wai-quantum FPGA member / member · FPGA simulation absent (linux) (pull_request) Successful in 22s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
wai-quantum editors / editors · linux (pull_request) Successful in 2m47s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 33s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 24m19s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 23m6s
wai-quantum OpenQASM reader / reader and corpus · linux (pull_request) Successful in 1m58s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m46s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 16m56s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 43s
wai-quantum Metal member / member · Metal absent (linux) (pull_request) Successful in 27s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 2m51s
wai-quantum neural-processor member / member · neural processor absent (linux) (pull_request) Successful in 22s
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Successful in 28s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Successful in 40s
wai-quantum matrix-extension member / member · matrix extension, emulated (aarch64 Linux) (pull_request) Successful in 22s
wai-quantum for Python / python · linux (pull_request) Successful in 2m48s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (pull_request) Successful in 2m43s
wai-quantum service / service · linux (pull_request) Successful in 1m51s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 4m48s
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 25s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 25m52s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 7m8s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 25s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 19s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Successful in 1m4s
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Successful in 32s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m9s
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Successful in 44s
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Successful in 14s
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 10s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 5m26s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 21s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 21s
wai-quantum signal processor / processor and member · signal processor, emulated (pull_request) Successful in 15m43s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m28s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (pull_request) Successful in 1m55s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 7m53s
wai-quantum views in agent hosts / views · the page and its engine (pull_request) Successful in 2m36s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 8m1s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m8s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m33s
wai-quantum views in agent hosts / views · a scripted host (pull_request) Successful in 3m40s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 21m55s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 4m9s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m54s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m53s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 6m59s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 10m43s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h1m21s
wai-quantum-desktop: the update manifest served by the website, not a standing pre-release
The app asked latest.json on a pre-release, wai-quantum-desktop-latest, which
each signed release replaced. That release would have sat in the repository's
release list beside the versioned ones. Now:

- the app asks https://wai.transaction.science/quantum-studio-desktop/latest.json;
- each signed release attaches its own latest.json and nothing else;
- the website takes it from that release when it is next deployed, with
  scripts/sync-desktop-update.sh <version> in its repository. That script
  refuses a manifest for another version, an update not on that release, a
  signature not made for that version, or a version older than the one the
  site serves.

manifests.py loses its pointer file and the check for a newer manifest, which
moves to the website's script. publish.sh loses RELEASE_PRERELEASE and
RELEASE_BODY, which only that release used.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 10:49:03 -04:00

417 lines
22 KiB
YAML

name: wai-quantum desktop release
# On a tag wai-quantum-desktop-v<version> (the version wai/wai-quantum-desktop/Cargo.toml
# and its tauri.conf.json state), builds Quantum Studio for the desktop on each runner and
# attaches its installers to the forge release of that tag, each with its SHA-256:
# macOS aarch64-apple-darwin and x86_64-apple-darwin: a disk image (.dmg), with the
# GPU state vector;
# Linux x86_64- and aarch64-unknown-linux-gnu: an AppImage, a Debian package and an
# RPM package;
# Windows x86_64-pc-windows-msvc: an installer (-setup.exe) and a portable archive
# (.zip) for package managers.
# Each build runs what it made before it is attached: the app's `check` (a Bell pair run in
# its worker, sealed and verified) from the bundle where the runner can run it, and its
# `update-status` (the app, the AppImage and the Debian package's app know their installer;
# the portable archive's copy knows none, so it never updates itself), the macOS app's code
# signature, the disk image's checksums, and the Debian package unpacked.
#
# Signing. Each kind is skipped, and the log says so, when its secrets are absent:
# - Updates: with the secret WAI_QUANTUM_DESKTOP_UPDATER_KEY (and, if the key has one,
# WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD), every installer an update installs is
# signed (the macOS app as a .app.tar.gz, the AppImage, the packages, the Windows
# installer), and the built app's `verify-update` checks each signature with the public
# key in tauri.conf.json, for this version, before anything is attached. Without the
# secret the release has installers and no update: copies already installed are not
# offered it, and update themselves no further until a release is signed.
# - macOS: with APPLE_CERTIFICATE (a Developer ID Application certificate, .p12 in
# base64), APPLE_CERTIFICATE_PASSWORD and APPLE_SIGNING_IDENTITY, the app is signed with
# that identity; with APPLE_API_ISSUER, APPLE_API_KEY and APPLE_API_KEY_P8 (or APPLE_ID,
# APPLE_PASSWORD and APPLE_TEAM_ID) it is also notarised and stapled. Without them the
# app is signed ad hoc and not notarised, and the cask says how to open it.
#
# A last job writes the Homebrew cask and the Scoop manifest from the assets just
# attached, and, when the builds were signed for updates, the update manifest
# (latest.json), and attaches them to this version's release. The app asks the website
# for the update manifest (plugins.updater.endpoints), and the website takes it from
# this release when it is next deployed. Nothing is pushed to the tap or the bucket:
# their repositories take the cask and the manifest by hand.
#
# Uploads go to the forge directly over the private network (FORGE_API), never through
# the public tunnel. A runner that is down leaves its platform's assets missing: the
# release lists what each build attached, and the manifests leave that platform out.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
tags: ['wai-quantum-desktop-v*']
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
FORGE_API: http://100.64.224.0:3400/api/v1
FORGE_REPO: Transaction-Science/open-standards
PUBLIC: https://git.transaction.science/Transaction-Science/open-standards
# The command that bundles, pinned (cargo install --locked).
TAURI_CLI: 2.12.3
jobs:
version:
name: the tag is the app's version
runs-on: linux
outputs:
version: ${{ steps.v.outputs.version }}
steps:
- uses: actions/checkout@v5
- id: v
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
v="${tag#wai-quantum-desktop-v}"
crate="$(sed -n 's/^version = "\(.*\)"$/\1/p' wai/wai-quantum-desktop/Cargo.toml | head -1)"
conf="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' wai/wai-quantum-desktop/tauri.conf.json)"
[ "$v" = "$crate" ] || { echo "::error::the tag says $v, wai-quantum-desktop/Cargo.toml says $crate"; exit 1; }
[ "$v" = "$conf" ] || { echo "::error::the tag says $v, wai-quantum-desktop/tauri.conf.json says $conf"; exit 1; }
echo "version=$v" >> "$GITHUB_OUTPUT"
macos:
name: macOS · ${{ matrix.triple }}
needs: version
runs-on: macos
timeout-minutes: 120
strategy:
fail-fast: false
max-parallel: 1
matrix:
triple: [aarch64-apple-darwin, x86_64-apple-darwin]
env:
SECRET_UPDATER_KEY: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY }}
SECRET_UPDATER_KEY_PASSWORD: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD }}
SECRET_APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
SECRET_APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
SECRET_APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
SECRET_APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
SECRET_APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
SECRET_APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
SECRET_APPLE_ID: ${{ secrets.APPLE_ID }}
SECRET_APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
SECRET_APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
- uses: actions/checkout@v5
# launchd's PATH omits cargo and node on the macOS host runner.
- name: Put cargo and node on PATH
shell: bash
run: |
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
echo "/opt/homebrew/bin" >> "$GITHUB_PATH"
# The app embeds the Studio's build, so it is built first.
- name: Build the Studio
shell: bash
working-directory: wai/quantum-studio
run: |
set -euo pipefail
npm ci --no-audit --no-fund
node build.mjs
# In a folder of its own, by version: the host's own cargo bin may hold another
# version for other work, which this leaves alone.
- name: The bundler, and the target
shell: bash
run: |
set -euo pipefail
rustup target add "${{ matrix.triple }}"
root="$HOME/.cache/wai-ci/tauri-cli-$TAURI_CLI"
[ -x "$root/bin/cargo-tauri" ] || nice -n 10 cargo install --locked tauri-cli --version "$TAURI_CLI" --root "$root" --target-dir "$RUNNER_TEMP/tauri-cli-build"
rm -rf "$RUNNER_TEMP/tauri-cli-build"
echo "$root/bin" >> "$GITHUB_PATH"
"$root/bin/cargo-tauri" tauri --version
- name: Build, sign where the secrets allow, and check
shell: bash
working-directory: wai/wai-quantum-desktop
env:
CI: 'true'
run: |
set -euo pipefail
t="${{ matrix.triple }}"; v="${{ needs.version.outputs.version }}"
cli="$(cargo tauri --version)"; [[ "$cli" == *" $TAURI_CLI" ]] || { echo "::error::cargo tauri is $cli, not $TAURI_CLI"; exit 1; }
source release/signing.sh
if [ -n "${SECRET_APPLE_SIGNING_IDENTITY:-}" ]; then
export APPLE_SIGNING_IDENTITY="$SECRET_APPLE_SIGNING_IDENTITY"
if [ -n "${SECRET_APPLE_CERTIFICATE:-}" ]; then
export APPLE_CERTIFICATE="$SECRET_APPLE_CERTIFICATE" APPLE_CERTIFICATE_PASSWORD="${SECRET_APPLE_CERTIFICATE_PASSWORD:-}"
fi
echo "macOS signing: with the identity APPLE_SIGNING_IDENTITY names"
if [ -n "${SECRET_APPLE_API_KEY_P8:-}" ]; then
(umask 077; printf '%s\n' "$SECRET_APPLE_API_KEY_P8" > "$RUNNER_TEMP/notary-key.p8")
export APPLE_API_ISSUER="$SECRET_APPLE_API_ISSUER" APPLE_API_KEY="$SECRET_APPLE_API_KEY" APPLE_API_KEY_PATH="$RUNNER_TEMP/notary-key.p8"
echo "notarisation: with the API key"
elif [ -n "${SECRET_APPLE_ID:-}" ]; then
export APPLE_ID="$SECRET_APPLE_ID" APPLE_PASSWORD="$SECRET_APPLE_PASSWORD" APPLE_TEAM_ID="$SECRET_APPLE_TEAM_ID"
echo "notarisation: with the Apple ID"
else
echo "::warning::notarisation skipped: neither APPLE_API_KEY_P8 nor APPLE_ID is set; the app is signed and not notarised"
fi
else
echo "macOS signing skipped: the secret APPLE_SIGNING_IDENTITY is not set; the app is signed ad hoc and not notarised"
fi
nice -n 10 cargo tauri build --ci --target "$t" --bundles app,dmg --features metal --config "$UPDATER_CONFIG" -- --locked
rm -f "$RUNNER_TEMP/notary-key.p8"
b="target/$t/release/bundle"
app="$b/macos/Quantum Studio.app"
exe="$app/Contents/MacOS/$(plutil -extract CFBundleExecutable raw -o - "$app/Contents/Info.plist")"
codesign --verify --deep --strict --verbose=2 "$app"
for d in "$b"/dmg/*.dmg; do hdiutil verify "$d"; done
if [ "$t" = "$(rustc -vV | sed -n 's/^host: //p')" ]; then
"$exe" check
# The app knows it was installed as an app (so, with a key, it updates itself).
s="$("$exe" update-status)"; echo "update-status: $s"
[[ "$s" == *'"installed_by":"app"'* ]] || { echo "::error::the bundled app does not know it is the installed app"; exit 1; }
if [ "$UPDATES_SIGNED" = 1 ]; then
for f in "$b"/macos/*.app.tar.gz; do "$exe" verify-update "$f" "$f.sig" "$v"; done
fi
else
echo "note: the $t app is not run here (another architecture)"
fi
bash release/collect.sh "$b" "$v" "$t" "$RUNNER_TEMP/dist"
- name: Attach to the release
shell: bash
working-directory: wai/wai-quantum-desktop
env:
FORGE_TOKEN: ${{ github.token }}
run: bash release/publish.sh "$GITHUB_REF_NAME" "Quantum Studio ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist/*
linux:
name: Linux · ${{ matrix.triple }}
needs: version
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
include:
- runner: linux
triple: x86_64-unknown-linux-gnu
- runner: linux-arm64
triple: aarch64-unknown-linux-gnu
env:
SECRET_UPDATER_KEY: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY }}
SECRET_UPDATER_KEY_PASSWORD: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0 and the web view's libraries
shell: bash
run: |
export PATH="$HOME/.cargo/bin:$PATH"
command -v rustup >/dev/null 2>&1 || \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
rustup toolchain install 1.98.0 --profile minimal
rustup default 1.98.0
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq libwebkit2gtk-4.1-dev libxdo-dev libayatana-appindicator3-dev librsvg2-dev file xdg-utils
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Build the Studio
shell: bash
working-directory: wai/quantum-studio
run: |
set -euo pipefail
npm ci --no-audit --no-fund
node build.mjs
- name: The bundler
shell: bash
run: |
set -euo pipefail
cargo install --locked tauri-cli --version "$TAURI_CLI"
cargo tauri --version
- name: Build, sign for updates if the key is set, and check
shell: bash
working-directory: wai/wai-quantum-desktop
run: |
set -euo pipefail
t="${{ matrix.triple }}"; v="${{ needs.version.outputs.version }}"
cli="$(cargo tauri --version)"; [[ "$cli" == *" $TAURI_CLI" ]] || { echo "::error::cargo tauri is $cli, not $TAURI_CLI"; exit 1; }
source release/signing.sh
cargo tauri build --ci --target "$t" --bundles appimage,deb,rpm --config "$UPDATER_CONFIG" -- --locked
b="target/$t/release/bundle"
"target/$t/release/wai-quantum-desktop" check
# The AppImage runs (unpacked first: a container has no FUSE), and knows it is one.
for a in "$b"/appimage/*.AppImage; do
APPIMAGE_EXTRACT_AND_RUN=1 "$a" check
s="$(APPIMAGE_EXTRACT_AND_RUN=1 "$a" update-status)"; echo "update-status: $s"
[[ "$s" == *'"installed_by":"appimage"'* ]] || { echo "::error::the AppImage does not know it is one"; exit 1; }
done
# The Debian package holds the app, which runs and knows the package installed it.
for d in "$b"/deb/*.deb; do
dpkg-deb --info "$d" | grep -E '^ (Package|Version|Depends):'
rm -rf "$RUNNER_TEMP/deb" && dpkg-deb -x "$d" "$RUNNER_TEMP/deb"
"$RUNNER_TEMP/deb/usr/bin/wai-quantum-desktop" check
s="$("$RUNNER_TEMP/deb/usr/bin/wai-quantum-desktop" update-status)"; echo "update-status: $s"
[[ "$s" == *'"installed_by":"deb"'* ]] || { echo "::error::the Debian package's app does not know the package installed it"; exit 1; }
done
if [ "$UPDATES_SIGNED" = 1 ]; then
for f in "$b"/appimage/*.AppImage "$b"/deb/*.deb "$b"/rpm/*.rpm; do
"target/$t/release/wai-quantum-desktop" verify-update "$f" "$f.sig" "$v"
done
fi
bash release/collect.sh "$b" "$v" "$t" "$RUNNER_TEMP/dist"
- name: Attach to the release
shell: bash
working-directory: wai/wai-quantum-desktop
env:
FORGE_TOKEN: ${{ github.token }}
run: bash release/publish.sh "$GITHUB_REF_NAME" "Quantum Studio ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist/*
windows:
name: Windows · x86_64-pc-windows-msvc
needs: version
runs-on: windows
timeout-minutes: 120
env:
SECRET_UPDATER_KEY: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY }}
SECRET_UPDATER_KEY_PASSWORD: ${{ secrets.WAI_QUANTUM_DESKTOP_UPDATER_KEY_PASSWORD }}
steps:
- uses: actions/checkout@v5
- name: Build the Studio
shell: pwsh
working-directory: wai/quantum-studio
run: |
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
node --version
npm ci --no-audit --no-fund
if ($LASTEXITCODE -ne 0) { throw "npm ci failed" }
node build.mjs
if ($LASTEXITCODE -ne 0) { throw "the Studio's build failed" }
- name: Build, sign for updates if the key is set, check and package
shell: pwsh
working-directory: wai/wai-quantum-desktop
run: |
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
$t = 'x86_64-pc-windows-msvc'
$v = "${{ needs.version.outputs.version }}"
$name = "wai-quantum-desktop-$v-$t"
$dist = Join-Path $env:RUNNER_TEMP 'dist'
$stage = Join-Path $env:RUNNER_TEMP $name
Remove-Item $stage, $dist -Recurse -Force -ErrorAction Ignore
New-Item -ItemType Directory -Force -Path $stage, $dist | Out-Null
# A run of the built app, waited for (it is a windowed program), its output kept.
function Run-App($exe, [string[]] $appArgs) {
$out = Join-Path $env:RUNNER_TEMP 'app.out'; $err = Join-Path $env:RUNNER_TEMP 'app.err'
$quoted = ($appArgs | ForEach-Object { '"' + $_ + '"' }) -join ' '
$p = Start-Process -FilePath $exe -ArgumentList $quoted -Wait -PassThru -NoNewWindow -RedirectStandardOutput $out -RedirectStandardError $err
$o = (Get-Content $out -Raw) + ''
Write-Host $o (Get-Content $err -Raw)
if ($p.ExitCode -ne 0) { throw "$exe $($appArgs -join ' ') exited $($p.ExitCode)" }
return $o
}
# The portable copy first, as cargo builds it: the installer's copy is marked as
# installed by it (and so updates itself), the archive's is not (a package manager
# updates it).
cargo build --locked --release --target $t
if ($LASTEXITCODE -ne 0) { throw "the build failed" }
$exe = "target\$t\release\wai-quantum-desktop.exe"
Copy-Item $exe, README.md, ..\LICENSE $stage
Run-App (Join-Path $stage 'wai-quantum-desktop.exe') @('check') | Out-Null
# The portable copy knows no installer made it, so it never updates itself.
$s = Run-App (Join-Path $stage 'wai-quantum-desktop.exe') @('update-status')
if ($s -notmatch '"installed_by":null' -or $s -notmatch '"updates_itself":false') { throw "the portable copy would update itself: $s" }
$updaterConfig = '{}'
if ($env:SECRET_UPDATER_KEY) {
$pub = (Get-Content tauri.conf.json -Raw | ConvertFrom-Json).plugins.updater.pubkey
if (-not $pub) { throw "the update key is set, but tauri.conf.json has no plugins.updater.pubkey: put the public key there first" }
$env:TAURI_SIGNING_PRIVATE_KEY = $env:SECRET_UPDATER_KEY
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "$env:SECRET_UPDATER_KEY_PASSWORD"
$updaterConfig = '{"bundle":{"createUpdaterArtifacts":true}}'
Write-Output "update signing: on, with the key the secret WAI_QUANTUM_DESKTOP_UPDATER_KEY holds"
} else {
Write-Output "update signing skipped: the secret WAI_QUANTUM_DESKTOP_UPDATER_KEY is not set; this release has installers and no update, and installed copies are not offered it"
}
# The bundler in a folder of its own, by version, ahead of any other on PATH.
$root = Join-Path $env:USERPROFILE ".cache\wai-ci\tauri-cli-$env:TAURI_CLI"
if (-not (Test-Path (Join-Path $root 'bin\cargo-tauri.exe'))) {
cargo install --locked tauri-cli --version $env:TAURI_CLI --root $root --target-dir (Join-Path $env:RUNNER_TEMP 'tauri-cli-build')
if ($LASTEXITCODE -ne 0) { throw "the bundler did not install" }
Remove-Item (Join-Path $env:RUNNER_TEMP 'tauri-cli-build') -Recurse -Force -ErrorAction Ignore
}
$env:PATH = (Join-Path $root 'bin') + [IO.Path]::PathSeparator + $env:PATH
$cli = (cargo tauri --version) -join ''
if ($cli -notmatch " $([regex]::Escape($env:TAURI_CLI))$") { throw "cargo tauri is $cli, not $env:TAURI_CLI" }
# '--' quoted, so PowerShell hands it to cargo as it is.
cargo tauri build --ci --target $t --bundles nsis --config $updaterConfig '--' --locked
if ($LASTEXITCODE -ne 0) { throw "the bundle failed" }
$setup = @(Get-ChildItem "target\$t\release\bundle\nsis\*-setup.exe")
if ($setup.Count -ne 1) { throw "the bundle holds $($setup.Count) installers, not one" }
$installer = Join-Path $dist "$name-setup.exe"
Copy-Item $setup[0].FullName $installer
if ($updaterConfig -ne '{}') {
Copy-Item "$($setup[0].FullName).sig" "$installer.sig"
Run-App (Join-Path $stage 'wai-quantum-desktop.exe') @('verify-update', $installer, "$installer.sig", $v) | Out-Null
}
Compress-Archive -Path $stage -DestinationPath (Join-Path $dist "$name.zip") -Force
foreach ($f in "$name-setup.exe", "$name.zip") {
$h = (Get-FileHash (Join-Path $dist $f) -Algorithm SHA256).Hash.ToLower()
Set-Content -NoNewline -Path (Join-Path $dist "$f.sha256") -Value "$h $f`n"
Write-Output "packaged $f ($h)"
}
- name: Attach to the release
shell: pwsh
env:
FORGE_TOKEN: ${{ github.token }}
run: |
$ErrorActionPreference = 'Stop'
$H = @{ Authorization = "token $env:FORGE_TOKEN" }
$base = "$env:FORGE_API/repos/$env:FORGE_REPO/releases"
$tag = $env:GITHUB_REF_NAME
$rel = $null
for ($i = 0; $i -lt 6 -and -not $rel; $i++) {
try { $rel = Invoke-RestMethod -Headers $H -Uri "$base/tags/$tag" } catch {
try { Invoke-RestMethod -Headers $H -Method Post -Uri $base -ContentType 'application/json' -Body (@{ tag_name = $tag; name = "Quantum Studio ${{ needs.version.outputs.version }}" } | ConvertTo-Json) | Out-Null } catch {}
Start-Sleep -Seconds 3
}
}
if (-not $rel) { throw "no release for $tag" }
foreach ($f in Get-ChildItem (Join-Path $env:RUNNER_TEMP 'dist')) {
foreach ($a in (Invoke-RestMethod -Headers $H -Uri "$base/$($rel.id)/assets" | Where-Object { $_.name -eq $f.Name })) {
Invoke-RestMethod -Headers $H -Method Delete -Uri "$base/$($rel.id)/assets/$($a.id)" | Out-Null
}
curl.exe -fsS -H "Authorization: token $env:FORGE_TOKEN" -X POST "$base/$($rel.id)/assets?name=$($f.Name)" -F "attachment=@$($f.FullName);type=application/octet-stream" -o NUL -w "uploaded $($f.Name): http %{http_code}`n"
if ($LASTEXITCODE -ne 0) { throw "the upload of $($f.Name) failed" }
}
# The package managers' entries and the update manifest, for the assets just attached:
# a Homebrew cask (macOS), a Scoop manifest (Windows) and latest.json. A platform whose
# build did not attach its assets is left out, and the log says so.
manifests:
name: Homebrew cask, Scoop manifest and update manifest
needs: [version, macos, linux, windows]
if: always() && needs.version.result == 'success'
runs-on: linux
steps:
- uses: actions/checkout@v5
- name: Write and attach them
shell: bash
working-directory: wai/wai-quantum-desktop
env:
FORGE_TOKEN: ${{ github.token }}
MACOS_NOTARISED: ${{ secrets.APPLE_SIGNING_IDENTITY != '' && (secrets.APPLE_API_KEY_P8 != '' || secrets.APPLE_ID != '') }}
run: |
set -euo pipefail
v="${{ needs.version.outputs.version }}"
out="$RUNNER_TEMP/manifests"; mkdir -p "$out"
python3 release/manifests.py "$v" "$GITHUB_REF_NAME" "$FORGE_API" "$FORGE_REPO" "$PUBLIC" "$out"
files=()
for f in wai-quantum-desktop.rb wai-quantum-desktop.json latest.json; do
if [ -f "$out/$f" ]; then files+=("$out/$f"); fi
done
bash release/publish.sh "$GITHUB_REF_NAME" "Quantum Studio $v" "${files[@]}"
if [ -f "$out/latest.json" ]; then
echo "latest.json is on this release; installed copies find $v once the website serves it (scripts/sync-desktop-update.sh $v in its repository)"
fi