136 lines
6.1 KiB
YAML
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
|