Files
ios/.github/workflows/ios.yml
T

136 lines
6.1 KiB
YAML

name: iOS build and tests
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ios-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
simulator:
runs-on: xcode-27
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Inspect toolchain and project
run: |
xcodebuild -version
xcodebuild -version | head -n 1 | grep -E "^Xcode 27([.]|$)"
swift --version
python3 tools/validate_project.py
xcodebuild -list -project MusicBridge.xcodeproj
- name: Choose available iPhone simulator
run: |
xcrun simctl list devices available --json > /tmp/musicbridge-simulators.json
python3 tools/select_simulator.py /tmp/musicbridge-simulators.json "$(xcrun --sdk iphonesimulator --show-sdk-version)" >> "$GITHUB_ENV"
- name: Build and run XCTest
shell: bash
run: |
set -o pipefail
mkdir -p build
python3 -m venv build/test-tools
build/test-tools/bin/pip install cryptography==46.0.3
build/test-tools/bin/python -u tools/tls_fixture.py > build/tls-fixture.log 2>&1 &
TLS_PID=$!
trap 'kill "$TLS_PID" 2>/dev/null || true' EXIT
for attempt in {1..30}; do
if grep -q 'TLS fixtures ready' build/tls-fixture.log; then break; fi
if ! kill -0 "$TLS_PID" 2>/dev/null; then cat build/tls-fixture.log; exit 1; fi
sleep 1
done
grep -q 'TLS fixtures ready' build/tls-fixture.log || { cat build/tls-fixture.log; exit 1; }
xcodebuild test \
-project MusicBridge.xcodeproj \
-scheme MusicBridge \
-configuration Debug \
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
-destination-timeout 120 \
-derivedDataPath DerivedData \
-resultBundlePath build/MusicBridge.xcresult \
CODE_SIGNING_ALLOWED=NO \
| tee build/xcodebuild.log
- name: Save diagnostics
if: always()
uses: actions/upload-artifact@v4
with:
name: ios-test-results
path: |
build/xcodebuild.log
build/tls-fixture.log
build/MusicBridge.xcresult
if-no-files-found: warn
retention-days: 7
- name: Archive simulator application
run: ditto -c -k --sequesterRsrc --keepParent DerivedData/Build/Products/Debug-iphonesimulator/MusicBridge.app build/MusicBridge-simulator.zip
- name: Capture pairing screen
run: |
xcrun simctl bootstatus "$SIMULATOR_ID" -b
xcrun simctl install "$SIMULATOR_ID" DerivedData/Build/Products/Debug-iphonesimulator/MusicBridge.app
xcrun simctl launch "$SIMULATOR_ID" ru.yukinoki.musicbridge
sleep 3
xcrun simctl io "$SIMULATOR_ID" screenshot build/pairing-screen.png
xcrun simctl terminate "$SIMULATOR_ID" ru.yukinoki.musicbridge
xcrun simctl launch "$SIMULATOR_ID" ru.yukinoki.musicbridge --preview-player
sleep 2
xcrun simctl io "$SIMULATOR_ID" screenshot build/player-light-screen.png
xcrun simctl ui "$SIMULATOR_ID" appearance dark
sleep 1
xcrun simctl io "$SIMULATOR_ID" screenshot build/player-dark-screen.png
xcrun simctl terminate "$SIMULATOR_ID" ru.yukinoki.musicbridge
xcrun simctl launch "$SIMULATOR_ID" ru.yukinoki.musicbridge --preview-player --preview-large-text
sleep 2
xcrun simctl io "$SIMULATOR_ID" screenshot build/player-large-text-screen.png
xcrun simctl terminate "$SIMULATOR_ID" ru.yukinoki.musicbridge
xcrun simctl launch "$SIMULATOR_ID" ru.yukinoki.musicbridge --preview-player --preview-live-card
sleep 2
xcrun simctl io "$SIMULATOR_ID" screenshot build/live-card-dark-screen.png
xcrun simctl ui "$SIMULATOR_ID" appearance light
sleep 1
xcrun simctl io "$SIMULATOR_ID" screenshot build/live-card-light-screen.png
RUNTIME=$(python3 -c 'import json,os; d=json.load(open("/tmp/musicbridge-simulators.json")); print(next(k for k,v in d["devices"].items() if any(x["udid"]==os.environ["SIMULATOR_ID"] for x in v)))')
COMPACT_ID=$(python3 -c 'import json,os; d=json.load(open("/tmp/musicbridge-simulators.json")); v=next(v for v in d["devices"].values() if any(x["udid"]==os.environ["SIMULATOR_ID"] for x in v)); candidates=[x for x in v if x.get("isAvailable") and x.get("name", "").startswith("iPhone") and x["udid"]!=os.environ["SIMULATOR_ID"]]; print(min(candidates,key=lambda x:("Max" in x["name"] or "Plus" in x["name"], "Pro" in x["name"], x["name"]))["udid"])')
xcrun simctl bootstatus "$COMPACT_ID" -b
xcrun simctl install "$COMPACT_ID" DerivedData/Build/Products/Debug-iphonesimulator/MusicBridge.app
xcrun simctl launch "$COMPACT_ID" ru.yukinoki.musicbridge --preview-player
sleep 3
xcrun simctl io "$COMPACT_ID" screenshot build/player-compact-screen.png
- name: Save pairing screen
uses: actions/upload-artifact@v4
with:
name: ios-ui-preview
path: build/*-screen.png
retention-days: 7
- name: Save simulator application
uses: actions/upload-artifact@v4
with:
name: MusicBridge-simulator
path: build/MusicBridge-simulator.zip
retention-days: 7
- name: Build iPhone IPA for local signing
shell: bash
run: bash tools/package_ipa.sh
- name: Save iPhone IPA
uses: actions/upload-artifact@v4
with:
name: MusicBridge-iPhone
path: build/MusicBridge-iPhone.ipa
if-no-files-found: error
retention-days: 7
- name: Save device build log
if: always()
uses: actions/upload-artifact@v4
with:
name: ios-device-build-log
path: build/device-build.log
if-no-files-found: ignore
retention-days: 7