open-standards/.github/workflows/wai-quantum-desktop-release.yml
Transaction Science ec45aaaca9 toolchain: Rust 1.98.0 -> 1.99.1 (change-controlled pin)
Approved by the owner on 2026-10-22. Procedure: change `channel`, run
`rustup show`, then run the pipeline.

What changed
- rust-toolchain.toml and eoc/eoc-rs/rust-toolchain.toml: channel "1.98.0"
  becomes "1.99.1". These are the only two pins in the tree, and the root one
  governs every other cargo root.
- 64 workflows under .github/workflows (the forge's CI): every `Install Rust
  1.98.0`, `rustup toolchain install / default / component add / target add
  --toolchain 1.98.0` and `--default-toolchain 1.98.0` now names 1.99.1
  (409 lines). Two of those lines are in build steps rather than install
  steps (wai-conformance.yml's two `wasm32 (unix)` steps each
  `rustup target add --toolchain`), and they move with the rest: a target
  added to a toolchain that is not installed is an error.
- Left as they are, because they record what was measured on 1.98.0:
  standards-build.yml's "Verified on a native arm64 Linux container (rustc
  1.98.0)", wai-conformance.yml's note on Rust 1.98's LLD 22, and the
  "rustc 1.98.0 (example)" strings in receipts, vectors and docs. The
  one-codegen-unit workaround that note explains also stays. 1.99.1 ships
  LLD 23.1.1, which the note says links those inputs, but removing the
  workaround should wait for a run at the CI path on x86_64.

Why
- 1.98.0 has a P-critical miscompile, rust-lang/rust#161441 (I-miscompile,
  regression-from-stable-to-stable). rustc emits a vacant (null) method slot
  in a trait-object vtable, so a safe dynamic call jumps to address zero
  (undefined behaviour). RELEASES.md records the fix in 1.98.1 ("rustc: fix
  miscompilation in generating vtables").
- 1.99.1 is the current stable (channel manifest dated 2026-10-15; rustc
  58cf8f9c3 2026-10-13). It also fixes 1.99.0's own regressions: wcslen on
  UEFI and embedded targets, x86_64 FFI `bool` returns, and the rust-lld rpath
  on macOS.

Evidence
All of it ran on the build host ([email protected], aarch64-apple-darwin)
through ~/kani-logs/remote/rb. Nothing was built on the dev Mac.

- `rustup show`: the active toolchain is 1.99.1-aarch64-apple-darwin,
  "overridden by .../rust-toolchain.toml" (and by eoc/eoc-rs's own file
  there). rustc 1.99.1 (58cf8f9c3 2026-10-13), LLVM 23.1.1, clippy 0.1.99,
  rustfmt 1.10.0. The baseline is rustc 1.98.0 (88d9e12ae 2026-08-18).
- Scope: every cargo root the pin governs, which is 46 of the 49 tracked
  lockfiles. The other three: two are vendored crates that resolve inside
  their parents, and wai/wai-rs/fuzz builds on its own pinned nightly.
- Trees. The final tree is main at 96117c570 plus these two commits. Main
  moved four times while this ran, and each root's evidence comes from the
  newest tree it was run on:
  - 13 roots on the final tree: every root main changed after 1a465f23d,
    and every root that consumes such a crate by path.
  - 12 roots on 1a465f23d plus these two commits.
  - 7 roots on b16816671 plus these two commits.
  - 14 roots on aa61ab3b9 plus the pin change.
  For each root in the last three groups, checked with git diff and the
  lockfiles' path packages: none of its files, and no crate it reaches by
  path, differs on the final tree.
- Each root got:
  (a) `cargo fmt --all -- --check` on the pin and on 1.98.0. The output is
      byte-identical in all 46. CI gates no root on rustfmt, and none is
      rustfmt-clean on main.
  (b) `cargo clippy --locked --keep-going --workspace --all-targets` on both
      toolchains, with the diagnostics compared line by line. After the
      previous commit, no root has a diagnostic that only 1.99.1 reports.
      openpay's one differing line is the same lint at the same site,
      reworded.
  (c) `cargo test --workspace --locked`.
  (d) the root's other CI steps that can run on this host: feature-gated
      tests, conformance suites, release-profile tests, the aac page-set
      checks, and every clippy -D warnings gate. The gates are
      standards-build's macOS meter and omni lints (jouleclaw-energy x2,
      jouleclaw-omni metal, jouleclaw-loader-gguf, jcp, proof, eoc, sandbox),
      wai-pack, wai-quantum-cli (default, no-default and metal),
      wai-quantum-service, wai-quantum-qpu and wai-quantum-desktop.
- Totals: `cargo test --workspace` ran in 43 roots: 12,532 passed, 1 failed,
  101 ignored. The other 3 roots are wasm32-only, and their
  `cargo build --release --target wasm32-unknown-unknown` passed. Every
  other CI step passed (62 of 62).
- On the final tree: scripts/check-lockfiles.sh (cargo 1.99.1) found all 47
  lockfiles current, and scripts/check-advisories.py passes.
- tokio is 1.54.1 in every lockfile cargo reads. That is patched for both
  RUSTSEC-2026-0386 (>= 1.53.3) and RUSTSEC-2026-0384 (>= 1.54.1). The 1.53.1
  in vendor/wtransport/Cargo.lock is the upstream file kept with the vendored
  source; cargo resolves wtransport inside its parents, and both gates skip
  that file.
- What is not green, each one reproduced on 1.98.0:
  - wai-ops-gpu: under the debug profile, `corpus` trips the debug_assert at
    exec.rs:187 on both toolchains. CI runs it in release only, and that
    passes (6/6).
  - wai-ops: clippy::derived_hash_with_manual_eq (deny-by-default) on both
    toolchains. No CI job runs clippy there.
  - wai-vector/wasm: `--all-targets` cannot build the lib-test target (its
    own `panic_impl`) on either toolchain. `clippy --lib` for wasm32 reports
    0 diagnostics on both.
  - eoc-cascade's hit_calibration_process_energy test reads real energy
    counters, and it fails some of the time on the loaded shared host. In
    interleaved runs each toolchain passed 18 of 20. The full eoc-rs suite
    passed on the final tree (741 passed).
- Environment, not code:
  - wai-quantum-desktop embeds the Studio's build, so wai/quantum-studio was
    built first (`npm ci`, `node build.mjs`), as CI does.
- Not covered here: the x86_64 and arm64 Linux and Windows legs; the GPU,
  NPU, FPGA, emulator and hardware-meter legs; wasm32 tests under Node,
  wasmtime and wasip2; and the fuzz job.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-03 17:42:20 -05: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.99.1 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.99.1
rustup toolchain install 1.99.1 --profile minimal
rustup default 1.99.1
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