Skip to content

Fix six Swift 6 strict-concurrency errors #39

Fix six Swift 6 strict-concurrency errors

Fix six Swift 6 strict-concurrency errors #39

name: Build & Release
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
# Cancel in-progress runs for the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
SCHEME: TopPresenter
PRODUCT_NAME: TopPresenter
# EXACT parity with the dev Mac: Xcode 26.3 (17C529) on the macos-26 arm64
# image. Older Xcode (16.x) silently IGNORES the project's Swift 6.x settings
# (SWIFT_DEFAULT_ACTOR_ISOLATION, approachable concurrency) — never build the
# release with a different major Xcode than the one used for development.
XCODE_VERSION: '26.3'
jobs:
build:
name: Build & Package
runs-on: macos-26
timeout-minutes: 40
outputs:
version: ${{ steps.package.outputs.VERSION }}
prerelease_tag: ${{ steps.package.outputs.PRERELEASE_TAG }}
prerelease_version: ${{ steps.package.outputs.PRERELEASE_VERSION }}
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Select Xcode
run: |
sudo xcode-select -s "/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer" || {
echo "::error::Xcode ${XCODE_VERSION} not on this image. Available:"
ls /Applications | grep -i '^Xcode' || true
exit 1
}
xcodebuild -version
- name: Resolve dependencies
run: xcodebuild -resolvePackageDependencies -scheme "$SCHEME" -project "$PRODUCT_NAME.xcodeproj"
- name: Build
run: |
# Monotonic CFBundleVersion so Sparkle can tell releases apart (the project's
# CURRENT_PROJECT_VERSION is a fixed 1). Ad-hoc code signing ("-") so the app's
# entitlements (sandbox + Sparkle XPC) apply with no Apple certs; the bundle is
# uniformly RE-signed below so every nested binary carries the same identity.
xcodebuild \
-scheme "$SCHEME" \
-project "$PRODUCT_NAME.xcodeproj" \
-configuration Release \
-derivedDataPath build \
-destination 'platform=macOS' \
CODE_SIGN_IDENTITY="-" \
CODE_SIGN_STYLE=Manual \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=YES \
DEVELOPMENT_TEAM="" \
CURRENT_PROJECT_VERSION=${{ github.run_number }} \
clean build
- name: Run unit tests (gate — failures fail the build)
run: |
xcodebuild \
-scheme "$SCHEME" \
-project "$PRODUCT_NAME.xcodeproj" \
-destination 'platform=macOS' \
-only-testing:TopPresenterTests \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
DEVELOPMENT_TEAM="" \
test
- name: Re-sign uniformly (ad-hoc, inside-out) & verify
run: |
# DYLD kills the app if the process and a mapped framework carry DIFFERENT
# Team IDs ("code signature not valid ... different Team IDs"). Guarantee
# uniformity: re-sign every nested Sparkle binary and then the app itself
# with the SAME ad-hoc identity, inside-out. No --options runtime here:
# hardened runtime + ad-hoc (team-less) signatures = library validation
# rejects the frameworks; plain ad-hoc launches everywhere.
APP="build/Build/Products/Release/$PRODUCT_NAME.app"
SPK="$APP/Contents/Frameworks/Sparkle.framework"
for target in \
"$SPK/Versions/B/XPCServices/Downloader.xpc" \
"$SPK/Versions/B/XPCServices/Installer.xpc" \
"$SPK/Versions/B/Autoupdate" \
"$SPK/Versions/B/Updater.app" \
"$SPK"; do
if [ -e "$target" ]; then
codesign --force --sign - --preserve-metadata=entitlements "$target"
fi
done
codesign --force --sign - --preserve-metadata=entitlements "$APP"
echo "── verify ──"
codesign --verify --deep --strict --verbose=2 "$APP"
echo "── identities (must ALL be adhoc, no TeamIdentifier) ──"
for bin in "$APP" "$SPK" "$SPK/Versions/B/Autoupdate" "$SPK/Versions/B/Updater.app"; do
codesign -dv "$bin" 2>&1 | grep -E 'Signature|TeamIdentifier' | sed "s|^|$(basename "$bin"): |"
done
- name: Package app
id: package
run: |
APP_PATH="build/Build/Products/Release/$PRODUCT_NAME.app"
if [ ! -d "$APP_PATH" ]; then
echo "❌ App not found at $APP_PATH"
find build -name "*.app" -type d
exit 1
fi
# Get version info
VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP_PATH/Contents/Info.plist" 2>/dev/null || echo "1.0")
BUILD=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$APP_PATH/Contents/Info.plist" 2>/dev/null || echo "1")
# Alpha counter is PER VERSION: bumping MARKETING_VERSION restarts at alpha.1
git fetch --tags --force --quiet
ALPHA_N=$(( $(git tag -l "v$VERSION-alpha.*" | wc -l | tr -d ' ') + 1 ))
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "BUILD=$BUILD" >> $GITHUB_ENV
echo "PRERELEASE_VERSION=$VERSION-alpha.${ALPHA_N}" >> $GITHUB_ENV
echo "PRERELEASE_TAG=v$VERSION-alpha.${ALPHA_N}" >> $GITHUB_ENV
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "PRERELEASE_VERSION=$VERSION-alpha.${ALPHA_N}" >> $GITHUB_OUTPUT
echo "PRERELEASE_TAG=v$VERSION-alpha.${ALPHA_N}" >> $GITHUB_OUTPUT
# Create ZIP
cd "build/Build/Products/Release"
ditto -c -k --keepParent "$PRODUCT_NAME.app" "$PRODUCT_NAME-$VERSION.zip"
mv "$PRODUCT_NAME-$VERSION.zip" "$GITHUB_WORKSPACE/"
# Create DMG
hdiutil create -volname "$PRODUCT_NAME" \
-srcfolder "$PRODUCT_NAME.app" \
-ov -format UDZO \
"$GITHUB_WORKSPACE/$PRODUCT_NAME-$VERSION.dmg"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: TopPresenter-${{ env.VERSION }}
path: |
TopPresenter-*.zip
TopPresenter-*.dmg
retention-days: 30
# Unique pre-release on every push to main
pre-release:
name: Pre-release (Alpha)
needs: build
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: macos-26
permissions:
contents: write
pages: write # deploy the appcast to Pages (no gh-pages branch)
id-token: write # required by actions/deploy-pages
environment:
name: github-pages
env:
# Empty until you add the repo secret — the appcast steps then no-op (CI stays green).
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
steps:
- name: Checkout (for appcast scripts)
uses: actions/checkout@v7
- name: Download artifact
uses: actions/download-artifact@v8
with:
pattern: TopPresenter-*
merge-multiple: true
- name: Get short SHA
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
- name: Create alpha pre-release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ needs.build.outputs.prerelease_tag }}
name: "TopPresenter ${{ needs.build.outputs.prerelease_version }}"
prerelease: true
generate_release_notes: true
body: |
### ⚡ Automatic Alpha Build
**Version:** `${{ needs.build.outputs.prerelease_version }}`
**Tag:** `${{ needs.build.outputs.prerelease_tag }}`
**Commit:** `${{ github.sha }}`
**Branch:** `main`
**Built:** ${{ github.event.head_commit.timestamp }}
> ⚠️ This is an unsigned build. To open on macOS:
> Right-click the app → Open → Open (bypass Gatekeeper).
> Or run: `xattr -cr TopPresenter.app`
---
**Commit message:** ${{ github.event.head_commit.message }}
files: |
TopPresenter-*.zip
TopPresenter-*.dmg
- name: Build signed appcast (beta channel)
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
env:
ZIP: TopPresenter-${{ needs.build.outputs.version }}.zip
DOWNLOAD_URL: https://github.com/${{ github.repository }}/releases/download/${{ needs.build.outputs.prerelease_tag }}/TopPresenter-${{ needs.build.outputs.version }}.zip
SHORT_VERSION: ${{ needs.build.outputs.prerelease_version }}
BUILD_VERSION: ${{ github.run_number }}
CHANNEL: beta
FEED_URL: https://robyrew.github.io/TopPresenter/appcast.xml
NOTES: ${{ github.event.head_commit.message }}
run: |
chmod +x scripts/publish_appcast.sh
scripts/publish_appcast.sh
- name: Setup Pages
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/configure-pages@v6
- name: Upload appcast artifact
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/upload-pages-artifact@v5
with:
path: site
- name: Deploy appcast to Pages
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/deploy-pages@v5
# Stable release on final version tags (v1.0.0, v1.1.0, etc.)
release:
name: Stable Release
needs: build
if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-')
runs-on: macos-26
permissions:
contents: write
pages: write # deploy the appcast to Pages (no gh-pages branch)
id-token: write # required by actions/deploy-pages
environment:
name: github-pages
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
steps:
- name: Checkout (for appcast scripts)
uses: actions/checkout@v7
- name: Download artifact
uses: actions/download-artifact@v8
with:
pattern: TopPresenter-*
merge-multiple: true
- name: Create stable release
uses: softprops/action-gh-release@v3
with:
name: "TopPresenter ${{ github.ref_name }}"
generate_release_notes: true
body: |
### 🎉 TopPresenter ${{ github.ref_name }}
> ⚠️ This is an unsigned build. To open on macOS:
> Right-click the app → Open → Open (bypass Gatekeeper).
> Or run: `xattr -cr TopPresenter.app`
files: |
TopPresenter-*.zip
TopPresenter-*.dmg
- name: Build signed appcast (stable channel)
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
env:
ZIP: TopPresenter-${{ needs.build.outputs.version }}.zip
DOWNLOAD_URL: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/TopPresenter-${{ needs.build.outputs.version }}.zip
SHORT_VERSION: ${{ needs.build.outputs.version }}
BUILD_VERSION: ${{ github.run_number }}
CHANNEL: ""
FEED_URL: https://robyrew.github.io/TopPresenter/appcast.xml
run: |
chmod +x scripts/publish_appcast.sh
scripts/publish_appcast.sh
- name: Setup Pages
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/configure-pages@v6
- name: Upload appcast artifact
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/upload-pages-artifact@v5
with:
path: site
- name: Deploy appcast to Pages
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}
uses: actions/deploy-pages@v5