Skip to content

Release v0.32.0

Release v0.32.0 #90

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
BIN_NAME: bowecho
jobs:
create-release:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Create release
env:
GH_TOKEN: ${{ github.token }}
run: |
notes_file="docs/releases/${GITHUB_REF_NAME}.md"
if gh release view "${GITHUB_REF_NAME}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
echo "Release ${GITHUB_REF_NAME} already exists."
if [ -f "$notes_file" ]; then
gh release edit "${GITHUB_REF_NAME}" \
--repo "${GITHUB_REPOSITORY}" \
--notes-file "$notes_file"
fi
else
release_args=(
"${GITHUB_REF_NAME}"
--repo "${GITHUB_REPOSITORY}"
--title "BowEcho ${GITHUB_REF_NAME}"
--draft
)
if [ -f "$notes_file" ]; then
release_args+=(--notes-file "$notes_file")
else
release_args+=(--generate-notes)
fi
gh release create "${release_args[@]}"
fi
build:
needs: create-release
if: ${{ always() && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
strategy:
fail-fast: false
matrix:
include:
# update_asset bakes the exact release-asset name into the binary
# (BOWECHO_UPDATE_ASSET) so the in-app updater can only download
# the variant it is already running. Windows and macOS matrix
# builds receive it; Linux and local builds without it keep the
# browser-based update flow.
- os: windows-latest
artifact_name: bowecho-windows-x64
package: windows
update_asset: bowecho-windows-x64.exe
# AVX2 variant for ~2015+ CPUs: benched -21% on the dealiased
# velocity raster and -30% on reflectivity vs baseline x86-64
# (KTLX 2013-05-20 Moore volume, pixel-identical output).
- os: windows-latest
artifact_name: bowecho-windows-x64-v3
package: windows
rustflags: "-C target-cpu=x86-64-v3"
update_asset: bowecho-windows-x64-v3.exe
- os: windows-11-arm
artifact_name: bowecho-windows-arm64
package: windows
update_asset: bowecho-windows-arm64.exe
- os: ubuntu-24.04
artifact_name: bowecho-linux-x64
package: linux
- os: ubuntu-24.04-arm
artifact_name: bowecho-linux-arm64
package: linux
- os: macos-15-intel
artifact_name: bowecho-macos-intel
package: macos
update_asset: bowecho-macos-intel.zip
- os: macos-15
artifact_name: bowecho-macos-apple-silicon
package: macos
update_asset: bowecho-macos-apple-silicon.zip
runs-on: ${{ matrix.os }}
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install Linux desktop build dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
pkg-config \
libgtk-3-dev \
libgl1-mesa-dev \
libx11-dev \
libxi-dev \
libxkbcommon-dev
- name: Use stable Rust
run: |
rustup toolchain install stable --profile minimal
rustup default stable
# Fat LTO links the whole app in one LLVM pass; the hosted Windows
# runner's default commit limit is what OOM'd the v0.27.1 build and
# forced the thin-LTO downgrade. A larger pagefile lets the linker
# spill instead of dying, so shipped binaries keep full optimization.
- name: Expand Windows pagefile for fat-LTO link
if: runner.os == 'Windows'
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 16GB
maximum-size: 32GB
disk-root: "C:"
- name: Build release binary
env:
RUSTFLAGS: ${{ matrix.rustflags }}
# In-app updater self-identification (empty for Linux matrix
# entries, which the code treats as "cannot self-update").
BOWECHO_UPDATE_ASSET: ${{ matrix.update_asset }}
run: cargo build --release -p app_ui --bin bowecho
- name: Detect Windows signing secrets
id: windows_signing
if: runner.os == 'Windows'
shell: bash
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TS_ENDPOINT: ${{ secrets.AZURE_TS_ENDPOINT }}
AZURE_TS_ACCOUNT: ${{ secrets.AZURE_TS_ACCOUNT }}
AZURE_TS_PROFILE: ${{ secrets.AZURE_TS_PROFILE }}
run: |
set -euo pipefail
if [[ -n "$AZURE_TENANT_ID" \
&& -n "$AZURE_CLIENT_ID" \
&& -n "$AZURE_CLIENT_SECRET" \
&& -n "$AZURE_TS_ENDPOINT" \
&& -n "$AZURE_TS_ACCOUNT" \
&& -n "$AZURE_TS_PROFILE" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
fi
# Signs bowecho.exe via Azure Trusted Signing when the secrets exist
# (docs/SIGNING.md) — silently skipped until then, so adding the
# secrets is the ONLY step needed to start shipping signed builds.
- name: Sign Windows binary (Azure Trusted Signing)
if: runner.os == 'Windows' && steps.windows_signing.outputs.enabled == 'true'
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
uses: azure/trusted-signing-action@v0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
endpoint: ${{ secrets.AZURE_TS_ENDPOINT }}
trusted-signing-account-name: ${{ secrets.AZURE_TS_ACCOUNT }}
certificate-profile-name: ${{ secrets.AZURE_TS_PROFILE }}
files-folder: target/release
files-folder-filter: exe
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
# Never cache the signing tooling: GitHub's 10 GB cache limit evicts
# the small SignCli entry while its siblings survive, and the action
# then skips the install and fails with "Invoke-TrustedSigning is
# not recognized" (hit v0.30.4 and v0.30.5; upstream issue). A fresh
# install costs ~1 minute per release build and cannot be poisoned.
cache-dependencies: false
- name: Package Windows binary
if: runner.os == 'Windows'
shell: pwsh
run: |
New-Item -ItemType Directory -Force dist | Out-Null
Copy-Item target\release\bowecho.exe "dist\${{ matrix.artifact_name }}.exe"
if ("${{ matrix.artifact_name }}" -eq "bowecho-windows-x64") {
New-Item -ItemType Directory -Force package | Out-Null
Copy-Item target\release\bowecho.exe package\bowecho.exe
Copy-Item README.md package\README.md
Copy-Item LICENSE-MIT package\LICENSE-MIT
Copy-Item LICENSE-APACHE package\LICENSE-APACHE
Compress-Archive -Path package\* -DestinationPath "dist\${{ matrix.artifact_name }}.zip" -Force
}
- name: Package macOS app
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
mkdir -p dist
app="BowEcho.app"
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources"
cp "target/release/${BIN_NAME}" "$app/Contents/MacOS/${BIN_NAME}"
cp README.md "$app/Contents/Resources/README.md"
cp LICENSE-MIT "$app/Contents/Resources/LICENSE-MIT"
cp LICENSE-APACHE "$app/Contents/Resources/LICENSE-APACHE"
chmod +x "$app/Contents/MacOS/${BIN_NAME}"
if [[ "${GITHUB_REF_NAME:-}" =~ ^v[0-9] ]]; then
version="${GITHUB_REF_NAME#v}"
else
version="$(awk -F '"' '/^version =/ { print $2; exit }' Cargo.toml)"
fi
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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>BowEcho</string>
<key>CFBundleExecutable</key>
<string>bowecho</string>
<key>CFBundleIdentifier</key>
<string>research.fahrenheit.bowecho</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>BowEcho</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${version}</string>
<key>CFBundleVersion</key>
<string>${version}</string>
<key>LSMinimumSystemVersion</key>
<string>12.0</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
PLIST
- name: Detect macOS signing secrets
id: macos_signing
if: runner.os == 'macOS'
shell: bash
env:
MACOS_CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
ASC_API_KEY_BASE64: ${{ secrets.ASC_API_KEY_BASE64 }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
run: |
set -euo pipefail
if [[ -n "$MACOS_CERTIFICATE_BASE64" \
&& -n "$MACOS_CERTIFICATE_PWD" \
&& -n "$ASC_API_KEY_BASE64" \
&& -n "$ASC_KEY_ID" \
&& -n "$ASC_ISSUER_ID" ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
if [[ "${GITHUB_REF:-}" == refs/tags/* ]]; then
echo "::error::Tagged macOS releases require every Developer ID and notarization secret (see docs/SIGNING.md)."
exit 1
fi
fi
# Signs + notarizes BowEcho.app with the repo's Developer ID
# certificate and App Store Connect API key (docs/SIGNING.md). Tagged
# release jobs cannot reach this point without every required secret.
- name: Sign and notarize macOS app
if: runner.os == 'macOS' && steps.macos_signing.outputs.enabled == 'true'
shell: bash
env:
MACOS_CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE_BASE64 }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
ASC_API_KEY_BASE64: ${{ secrets.ASC_API_KEY_BASE64 }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
run: |
set -euo pipefail
app="BowEcho.app"
keychain="$RUNNER_TEMP/signing.keychain-db"
keychain_password="$(openssl rand -base64 24)"
security create-keychain -p "$keychain_password" "$keychain"
security set-keychain-settings -lut 1800 "$keychain"
security unlock-keychain -p "$keychain_password" "$keychain"
echo "$MACOS_CERTIFICATE_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$keychain" \
-P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: \
-k "$keychain_password" "$keychain" > /dev/null
security list-keychains -d user -s "$keychain" login.keychain-db
identity="$(security find-identity -v -p codesigning "$keychain" \
| awk -F '"' '/Developer ID Application/ {print $2; exit}')"
if [ -z "$identity" ]; then
echo "No Developer ID Application identity found in certificate" >&2
exit 1
fi
echo "Signing with: $identity"
codesign --force --deep --options runtime --timestamp \
--sign "$identity" "$app"
codesign --verify --deep --strict "$app"
echo "$ASC_API_KEY_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8"
ditto -c -k --sequesterRsrc --keepParent "$app" "$RUNNER_TEMP/notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \
--key "$RUNNER_TEMP/AuthKey.p8" --key-id "$ASC_KEY_ID" \
--issuer "$ASC_ISSUER_ID" --wait
xcrun stapler staple "$app"
security delete-keychain "$keychain"
- name: Validate signed macOS app
if: runner.os == 'macOS' && steps.macos_signing.outputs.enabled == 'true'
shell: bash
run: |
set -euo pipefail
app="BowEcho.app"
codesign --verify --deep --strict --verbose=2 "$app"
xcrun stapler validate "$app"
spctl --assess --type execute --verbose=4 "$app"
- name: Zip macOS app
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
ditto -c -k --sequesterRsrc --keepParent "BowEcho.app" "dist/${{ matrix.artifact_name }}.zip"
- name: Package Linux binary
if: runner.os == 'Linux'
shell: bash
run: |
set -euo pipefail
mkdir -p "dist"
cp "target/release/${BIN_NAME}" "dist/${{ matrix.artifact_name }}"
chmod +x "dist/${{ matrix.artifact_name }}"
- name: Upload workflow artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: dist/*
if-no-files-found: error
- name: Generate SHA-256 checksums
shell: bash
# Published beside every asset so users can verify a download is the
# CI-built binary (antivirus ML heuristics flag unsigned Rust exes;
# a hash check against the release page settles provenance).
run: |
set -euo pipefail
cd dist
for f in *; do
sha256sum "$f" > "$f.sha256"
done
- name: Upload release asset
if: startsWith(github.ref, 'refs/tags/')
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "${GITHUB_REF_NAME}" dist/* --clobber --repo "${GITHUB_REPOSITORY}"
publish-release:
needs: build
if: startsWith(github.ref, 'refs/tags/') && needs.build.result == 'success'
runs-on: ubuntu-24.04
steps:
- name: Publish release after all assets upload
env:
GH_TOKEN: ${{ github.token }}
run: gh release edit "${GITHUB_REF_NAME}" --draft=false --repo "${GITHUB_REPOSITORY}"