Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,14 @@ jobs:
- name: Generate code
run: dart run build_runner build --delete-conflicting-outputs

# Flutter hands FLUTTER_XCODE_* to xcodebuild as build settings; plain
# CODE_SIGNING_* variables never reach it, and the Release target's
# automatic signing then wants a provisioning profile CI cannot have.
- name: Build Desktop App
env:
CODE_SIGNING_ALLOWED: "NO"
CODE_SIGNING_REQUIRED: "NO"
CODE_SIGN_IDENTITY: ""
FLUTTER_XCODE_CODE_SIGNING_ALLOWED: "NO"
FLUTTER_XCODE_CODE_SIGNING_REQUIRED: "NO"
FLUTTER_XCODE_CODE_SIGN_IDENTITY: ""
run: flutter build ${{ matrix.platform }} --release

# One step instead of four `if:`-guarded ones. Every matrix leg used to
Expand Down
240 changes: 240 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
name: Release

# Builds the desktop app for macOS, Windows and Linux and makes a release of
# them: a .dmg, the Inno Setup installer (windows/packaging/installer.iss) and
# a .tar.gz, with SHA256SUMS.txt and the files the app's updater reads
# (appcast-macos.xml, appcast-windows.xml and latest.json, written by
# tools/release_metadata.dart). The download page lists the newest release.
#
# git tag v0.2.0 && git push origin v0.2.0
#
# A tag makes a *draft* release: look it over, then publish it, which is when
# the updater and the download page see it. A pull request that changes this
# file, the installer or the metadata tool builds everything without
# releasing, which is how a change here is tested.
#
# Not signed yet: there is no Apple Developer ID or Windows certificate. Until
# there is, the macOS app is signed ad hoc, so macOS asks to confirm opening it
# (right-click, Open), and Windows shows a SmartScreen prompt for the installer.
on:
push:
tags: ["v*"]
pull_request:
paths:
- .github/workflows/release.yml
- windows/packaging/**
- tools/release_metadata.dart

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
version:
name: Version
runs-on: ubuntu-latest
outputs:
name: ${{ steps.v.outputs.name }}
number: ${{ steps.v.outputs.number }}
steps:
- uses: actions/checkout@v7
# A tag v1.2.3 is version 1.2.3 with build number 10203, which only ever
# grows from one release to the next: Sparkle compares build numbers. A
# pull request's trial build takes pubspec.yaml's version.
- id: v
run: |
if [ "$GITHUB_REF_TYPE" = tag ]; then
name="${GITHUB_REF_NAME#v}"
else
name="$(sed -n 's/^version: *\([0-9.]*\).*/\1/p' pubspec.yaml)"
fi
IFS=. read -r major minor patch <<< "$name"
echo "name=$name" >> "$GITHUB_OUTPUT"
echo "number=$((major * 10000 + minor * 100 + patch))" >> "$GITHUB_OUTPUT"

build:
name: Build ${{ matrix.target }}
needs: version
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
platform: macos
target: macOS
- os: windows-latest
platform: windows
target: Windows
- os: ubuntu-latest
platform: linux
target: Linux
env:
VERSION: ${{ needs.version.outputs.name }}
BUILD_NUMBER: ${{ needs.version.outputs.number }}
steps:
- uses: actions/checkout@v7

# The two format packages are private: submodules here, and git
# dependencies for pub. See ci.yml.
- name: Load the deploy keys for the private format repositories
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
with:
ssh-private-key: |
${{ secrets.PDL_DEPLOY_KEY }}
${{ secrets.CDL_DEPLOY_KEY }}

- name: Fetch submodules
run: git submodule update --init --recursive

- name: Install Linux build dependencies
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev libblkid-dev libasound2-dev

- name: Set up Flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
flutter-version-file: .fvmrc
channel: stable
cache: true

- run: flutter config --enable-${{ matrix.platform }}-desktop
- run: flutter pub get
- run: dart run build_runner build --delete-conflicting-outputs

- name: Build
shell: bash
# Flutter hands FLUTTER_XCODE_* to xcodebuild as build settings; plain
# CODE_SIGNING_* variables never reach it, and the Release target's
# automatic signing then wants a provisioning profile CI cannot have.
env:
FLUTTER_XCODE_CODE_SIGNING_ALLOWED: "NO"
FLUTTER_XCODE_CODE_SIGNING_REQUIRED: "NO"
FLUTTER_XCODE_CODE_SIGN_IDENTITY: ""
run: flutter build ${{ matrix.platform }} --release --build-name "$VERSION" --build-number "$BUILD_NUMBER"

# Apple silicon will not run an unsigned app, so until there is a
# Developer ID it is signed ad hoc, with the release entitlements less
# keychain-access-groups: that one needs a provisioning profile, and an
# app claiming it without one is killed at launch. (It serves the pro
# edition's sign-in, which this build does not include.) Then it is
# started, to prove macOS lets it run.
- name: Sign ad hoc, and launch (macOS)
if: matrix.platform == 'macos'
run: |
app=build/macos/Build/Products/Release/pinbench.app
cp macos/Runner/Release.entitlements "$RUNNER_TEMP/adhoc.entitlements"
plutil -remove keychain-access-groups "$RUNNER_TEMP/adhoc.entitlements"
codesign --force --deep --sign - --entitlements "$RUNNER_TEMP/adhoc.entitlements" "$app"
codesign --verify --deep --strict --verbose=2 "$app"
"$app/Contents/MacOS/pinbench" & pid=$!
sleep 15
kill -0 "$pid" || { echo "::error::the app exited within 15 s of launch"; exit 1; }
kill "$pid"

- name: Package (macOS disk image)
if: matrix.platform == 'macos'
run: |
mkdir -p dist
hdiutil create -volname PinBench -srcfolder build/macos/Build/Products/Release/pinbench.app \
-ov -format UDZO "dist/pinbench-$VERSION-macos.dmg"

- name: Package (Windows installer)
if: matrix.platform == 'windows'
shell: pwsh
run: |
& "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" `
"/DAppVersion=$env:VERSION" `
"/DSourceDir=$env:GITHUB_WORKSPACE\build\windows\x64\runner\Release" `
"/DOutputDir=$env:GITHUB_WORKSPACE\dist" `
windows\packaging\installer.iss
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Package (Linux tarball)
if: matrix.platform == 'linux'
run: |
mkdir -p dist
tar -czf "dist/pinbench-$VERSION-linux.tar.gz" -C build/linux/x64/release/bundle .

- uses: actions/upload-artifact@v7
with:
name: pinbench-${{ matrix.platform }}
path: dist/*
if-no-files-found: error
retention-days: 14

release:
name: ${{ github.event_name == 'pull_request' && 'Assemble (trial, not released)' || 'Draft the release' }}
needs: [version, build]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
env:
VERSION: ${{ needs.version.outputs.name }}
BUILD_NUMBER: ${{ needs.version.outputs.number }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v7

# tools/release_metadata.dart needs the app's dependencies (crypto),
# which resolve through the private format packages.
- name: Load the deploy keys for the private format repositories
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
with:
ssh-private-key: |
${{ secrets.PDL_DEPLOY_KEY }}
${{ secrets.CDL_DEPLOY_KEY }}
- run: git submodule update --init --recursive
- uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
with:
flutter-version-file: .fvmrc
channel: stable
cache: true
- run: flutter pub get

- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true

- name: Write the updater's files and the checksums
run: |
tag="v$VERSION"
base="https://github.com/$GITHUB_REPOSITORY/releases/download/$tag"
dart run tools/release_metadata.dart \
--version "$VERSION" \
--build-number "$BUILD_NUMBER" \
--base-url "$base" \
--notes-url "https://github.com/$GITHUB_REPOSITORY/releases/tag/$tag" \
--published-at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--out dist \
--asset "macos=dist/pinbench-$VERSION-macos.dmg" \
--asset "windows=dist/pinbench-$VERSION-windows-setup.exe" \
--asset "linux=dist/pinbench-$VERSION-linux.tar.gz"
(cd dist && sha256sum pinbench-* > SHA256SUMS.txt)
ls -la dist
cat dist/SHA256SUMS.txt

- name: Keep the trial's files
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v7
with:
name: release-trial
path: dist/*
retention-days: 7

- name: Draft the release
if: github.event_name == 'push'
run: |
gh release create "$GITHUB_REF_NAME" dist/* \
--draft \
--title "PinBench $VERSION" \
--generate-notes \
--notes "Desktop builds for macOS (Apple silicon and Intel), Windows and Linux. Not yet code-signed: on macOS, right-click the app and choose Open the first time; on Windows, the installer shows a SmartScreen prompt."
41 changes: 37 additions & 4 deletions .github/workflows/template-firmware.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ on:
- assets/templates/**
- tools/check_template_firmware.dart
- .github/workflows/template-firmware.yml
# Weekly too, for the service-toolchain job: the service can change
# without anything here changing.
schedule:
- cron: "41 6 * * 1"
workflow_dispatch:

concurrency:
Expand All @@ -25,19 +29,48 @@ concurrency:
permissions:
contents: read

# The compile service's toolchain (PinBench/compile-service tools/setup-vm.sh),
# pinned: a build only reproduces with the same versions. When the service
# moves to newer ones, bump these to match and rebuild the templates with
# --write in the same pull request.
# The compile service's toolchain, pinned (its source of truth is
# PinBench/compile-service deploy/toolchain.env): a build only reproduces with
# the same versions. The service-toolchain job checks these against what the
# live service reports. When the service moves to newer ones, bump these to
# match and rebuild the templates with --write in the same pull request.
env:
ARDUINO_CLI_VERSION: 1.5.1
ARDUINO_CORES: arduino:avr@1.8.8 rp2040:rp2040@6.2.0
ARDUINO_LIBRARIES: Adafruit SSD1306@2.5.17|Adafruit GFX Library@1.12.6|Adafruit BusIO@1.17.4|IRremote@4.7.1
RP2040_INDEX: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

jobs:
# The live service's /health lists the versions it builds with; if they are
# not these, the bundled firmware no longer matches what an edited sketch
# compiles to, and this pinned set is out of date.
service-toolchain:
name: Pinned toolchain matches the compile service
runs-on: ubuntu-latest
timeout-minutes: 5
env:
COMPILE_SERVICE: ${{ vars.COMPILE_API_URL || 'https://138-3-254-103.sslip.io' }}
steps:
- name: Compare with /health
run: |
health=$(curl -fsS --max-time 30 "$COMPILE_SERVICE/health")
problems=0
check() { # what, pinned, live
if [ "$2" = "$3" ]; then echo "ok $1 $2"; else echo "DRIFT $1: pinned $2, the service has $3"; problems=1; fi
}
check arduino-cli "$ARDUINO_CLI_VERSION" "$(jq -r '.arduinoCliVersion' <<<"$health")"
for core in $ARDUINO_CORES; do
check "${core%@*}" "${core#*@}" "$(jq -r --arg k "${core%@*}" '.toolchain.cores[$k] // "nothing"' <<<"$health")"
done
IFS='|' read -ra libraries <<< "$ARDUINO_LIBRARIES"
for lib in "${libraries[@]}"; do
check "${lib%@*}" "${lib#*@}" "$(jq -r --arg k "${lib%@*}" '.toolchain.libraries[$k] // "nothing"' <<<"$health")"
done
exit "$problems"

check:
name: Templates match their firmware
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand Down
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,18 @@ For anything security-related, **do not open a public issue** — see
## Code of conduct

By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).

## Releasing

Push a version tag; `.github/workflows/release.yml` does the rest:

```bash
git tag v0.2.0 && git push origin v0.2.0
```

It builds macOS, Windows and Linux, packages a `.dmg`, the Inno Setup
installer and a `.tar.gz`, writes the updater's appcasts and `latest.json`
(`tools/release_metadata.dart`) and `SHA256SUMS.txt`, and makes a **draft**
release. Publishing the draft is what the updater and the download page see.
A tag `vX.Y.Z` gets build number `X*10000 + Y*100 + Z`, so tags must only go
up. The builds are not code-signed yet.
Loading