feat: discover and add connected iOS devices
Tests / Test apps.device-host-agent.tests.test_mcp_token.test_load_or_create_concurrent_calls_do_not_corrupt failed
Tests / Test apps.device-host-agent.tests.test_mcp_token.test_load_or_create_concurrent_calls_do_not_corrupt failed
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
class IOSDiscoveryError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def discover_connected_ios_devices(*, timeout_seconds: int = 10) -> list[dict[str, str]]:
|
||||
"""Return paired, currently connected physical iOS devices from CoreDevice."""
|
||||
with tempfile.TemporaryDirectory(prefix="ios-device-discovery-") as temp_dir:
|
||||
output_path = Path(temp_dir) / "devices.json"
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
[
|
||||
"xcrun",
|
||||
"devicectl",
|
||||
"list",
|
||||
"devices",
|
||||
"--json-output",
|
||||
str(output_path),
|
||||
"--timeout",
|
||||
str(timeout_seconds),
|
||||
"--quiet",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_seconds + 2,
|
||||
check=False,
|
||||
)
|
||||
except FileNotFoundError as exc:
|
||||
raise IOSDiscoveryError("xcrun is unavailable; install Xcode command line tools") from exc
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
raise IOSDiscoveryError("iOS device discovery timed out") from exc
|
||||
|
||||
if completed.returncode != 0:
|
||||
detail = completed.stderr.strip() or completed.stdout.strip()
|
||||
raise IOSDiscoveryError(detail or "devicectl failed to discover iOS devices")
|
||||
try:
|
||||
payload = json.loads(output_path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as exc:
|
||||
raise IOSDiscoveryError("devicectl returned invalid device data") from exc
|
||||
|
||||
raw_devices = payload.get("result", {}).get("devices", [])
|
||||
devices: list[dict[str, str]] = []
|
||||
for item in raw_devices if isinstance(raw_devices, list) else []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
hardware = item.get("hardwareProperties", {})
|
||||
properties = item.get("deviceProperties", {})
|
||||
connection = item.get("connectionProperties", {})
|
||||
if not all(isinstance(value, dict) for value in (hardware, properties, connection)):
|
||||
continue
|
||||
udid = hardware.get("udid")
|
||||
if (
|
||||
hardware.get("platform") != "iOS"
|
||||
or hardware.get("reality") != "physical"
|
||||
or connection.get("pairingState") != "paired"
|
||||
or connection.get("tunnelState") != "connected"
|
||||
or not isinstance(udid, str)
|
||||
or not udid
|
||||
):
|
||||
continue
|
||||
devices.append(
|
||||
{
|
||||
"udid": udid,
|
||||
"name": str(properties.get("name") or hardware.get("marketingName") or "iPhone"),
|
||||
"model": str(hardware.get("marketingName") or hardware.get("productType") or "iPhone"),
|
||||
"os_version": str(properties.get("osVersionNumber") or ""),
|
||||
"transport": str(connection.get("transportType") or "unknown"),
|
||||
}
|
||||
)
|
||||
return sorted(devices, key=lambda device: (device["name"], device["udid"]))
|
||||
|
||||
Reference in New Issue
Block a user