From 265a3dff27a3c14d11bf8efe6124bc0a89968bc9 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 14 Dec 2025 04:09:32 +0000 Subject: [PATCH] ci: create iOS simulator when missing --- .github/workflows/ci.yml | 110 ++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20c57855f..20acc6316 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,48 +132,84 @@ jobs: cd apps/ios xcodegen generate - - name: iOS tests - run: | - set -euo pipefail - RESULT_BUNDLE_PATH="$RUNNER_TEMP/Clawdis-iOS.xcresult" - DEST_ID="$( - python3 - <<'PY' - import json - import re - import subprocess - import sys + - name: iOS tests + run: | + set -euo pipefail + RESULT_BUNDLE_PATH="$RUNNER_TEMP/Clawdis-iOS.xcresult" + DEST_ID="$( + python3 - <<'PY' + import json + import subprocess + import sys + import uuid + + def sh(args: list[str]) -> str: + return subprocess.check_output(args, text=True).strip() - data = json.loads( - subprocess.check_output(["xcrun", "simctl", "list", "devices", "available", "-j"], text=True) - ) - runtimes = [] - for runtime in data.get("devices", {}).keys(): - m = re.search(r"\\.iOS-(\\d+)-(\\d+)$", runtime) - if m: - runtimes.append((int(m.group(1)), int(m.group(2)), runtime)) + # Prefer an already-created iPhone simulator if it exists. + devices = json.loads(sh(["xcrun", "simctl", "list", "devices", "-j"])) + candidates: list[tuple[str, str]] = [] + for runtime, devs in (devices.get("devices") or {}).items(): + for dev in devs or []: + if not dev.get("isAvailable"): + continue + 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): - iphones = [d for d in devices if d.get("isAvailable") and d.get("name", "").startswith("iPhone")] - if not iphones: - return None - prefer = [d for d in iphones if "iPhone 16" in d.get("name", "")] - return (prefer[0] if prefer else iphones[0]).get("udid") + # Otherwise, create one from the newest available iOS runtime. + runtimes = json.loads(sh(["xcrun", "simctl", "list", "runtimes", "-j"])).get("runtimes") or [] + ios = [rt for rt in runtimes if rt.get("platform") == "iOS" and rt.get("isAvailable")] + if not ios: + print("No available iOS runtimes found.", file=sys.stderr) + sys.exit(1) - for _, __, runtime in runtimes: - udid = pick_device(data["devices"].get(runtime, [])) - if udid: - print(udid) - sys.exit(0) + def version_key(rt: dict) -> tuple[int, ...]: + parts = [] + for p in str(rt.get("version") or "0").split("."): + try: + parts.append(int(p)) + except ValueError: + parts.append(0) + return tuple(parts) - print("No available iPhone simulators found.", file=sys.stderr) - sys.exit(1) - PY - )" - echo "Using iOS Simulator id: $DEST_ID" - xcodebuild test \ - -project apps/ios/Clawdis.xcodeproj \ + ios.sort(key=version_key, reverse=True) + runtime = ios[0] + runtime_id = str(runtime.get("identifier") or "") + if not runtime_id: + print("Missing iOS runtime identifier.", file=sys.stderr) + sys.exit(1) + + 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 \ -destination "platform=iOS Simulator,id=$DEST_ID" \ -resultBundlePath "$RESULT_BUNDLE_PATH" \