ci: create iOS simulator when missing

main
Peter Steinberger 2025-12-14 04:09:32 +00:00
parent 97fe3972c8
commit 265a3dff27
1 changed files with 73 additions and 37 deletions

View File

@ -132,48 +132,84 @@ jobs:
cd apps/ios cd apps/ios
xcodegen generate xcodegen generate
- name: iOS tests - name: iOS tests
run: | run: |
set -euo pipefail set -euo pipefail
RESULT_BUNDLE_PATH="$RUNNER_TEMP/Clawdis-iOS.xcresult" RESULT_BUNDLE_PATH="$RUNNER_TEMP/Clawdis-iOS.xcresult"
DEST_ID="$( DEST_ID="$(
python3 - <<'PY' python3 - <<'PY'
import json import json
import re import subprocess
import subprocess import sys
import sys import uuid
def sh(args: list[str]) -> str:
return subprocess.check_output(args, text=True).strip()
data = json.loads( # Prefer an already-created iPhone simulator if it exists.
subprocess.check_output(["xcrun", "simctl", "list", "devices", "available", "-j"], text=True) devices = json.loads(sh(["xcrun", "simctl", "list", "devices", "-j"]))
) candidates: list[tuple[str, str]] = []
runtimes = [] for runtime, devs in (devices.get("devices") or {}).items():
for runtime in data.get("devices", {}).keys(): for dev in devs or []:
m = re.search(r"\\.iOS-(\\d+)-(\\d+)$", runtime) if not dev.get("isAvailable"):
if m: continue
runtimes.append((int(m.group(1)), int(m.group(2)), runtime)) name = str(dev.get("name") or "")
udid = str(dev.get("udid") or "")
if not udid or not name.startswith("iPhone"):
continue
candidates.append((name, udid))
runtimes.sort(reverse=True) candidates.sort(key=lambda it: (0 if "iPhone 16" in it[0] else 1, it[0]))
if candidates:
print(candidates[0][1])
sys.exit(0)
def pick_device(devices): # Otherwise, create one from the newest available iOS runtime.
iphones = [d for d in devices if d.get("isAvailable") and d.get("name", "").startswith("iPhone")] runtimes = json.loads(sh(["xcrun", "simctl", "list", "runtimes", "-j"])).get("runtimes") or []
if not iphones: ios = [rt for rt in runtimes if rt.get("platform") == "iOS" and rt.get("isAvailable")]
return None if not ios:
prefer = [d for d in iphones if "iPhone 16" in d.get("name", "")] print("No available iOS runtimes found.", file=sys.stderr)
return (prefer[0] if prefer else iphones[0]).get("udid") sys.exit(1)
for _, __, runtime in runtimes: def version_key(rt: dict) -> tuple[int, ...]:
udid = pick_device(data["devices"].get(runtime, [])) parts = []
if udid: for p in str(rt.get("version") or "0").split("."):
print(udid) try:
sys.exit(0) parts.append(int(p))
except ValueError:
parts.append(0)
return tuple(parts)
print("No available iPhone simulators found.", file=sys.stderr) ios.sort(key=version_key, reverse=True)
sys.exit(1) runtime = ios[0]
PY runtime_id = str(runtime.get("identifier") or "")
)" if not runtime_id:
echo "Using iOS Simulator id: $DEST_ID" print("Missing iOS runtime identifier.", file=sys.stderr)
xcodebuild test \ sys.exit(1)
-project apps/ios/Clawdis.xcodeproj \
supported = runtime.get("supportedDeviceTypes") or []
iphones = [dt for dt in supported if dt.get("productFamily") == "iPhone"]
if not iphones:
print("No iPhone device types for iOS runtime.", file=sys.stderr)
sys.exit(1)
iphones.sort(key=lambda dt: (0 if "iPhone 16" in str(dt.get("name") or "") else 1, str(dt.get("name") or "")))
device_type_id = str(iphones[0].get("identifier") or "")
if not device_type_id:
print("Missing iPhone device type identifier.", file=sys.stderr)
sys.exit(1)
sim_name = f"CI iPhone {uuid.uuid4().hex[:8]}"
udid = sh(["xcrun", "simctl", "create", sim_name, device_type_id, runtime_id])
if not udid:
print("Failed to create iPhone simulator.", file=sys.stderr)
sys.exit(1)
print(udid)
PY
)"
echo "Using iOS Simulator id: $DEST_ID"
xcodebuild test \
-project apps/ios/Clawdis.xcodeproj \
-scheme Clawdis \ -scheme Clawdis \
-destination "platform=iOS Simulator,id=$DEST_ID" \ -destination "platform=iOS Simulator,id=$DEST_ID" \
-resultBundlePath "$RESULT_BUNDLE_PATH" \ -resultBundlePath "$RESULT_BUNDLE_PATH" \