Device Discovery via devicectl#
Overview#
Tuist wraps Apple's devicectl CLI (available via xcrun devicectl) to discover and interact with physical Apple devices. The abstraction lives in TuistAutomation and is consumed by RunCommandService to support tuist run on real hardware.
Key files:
| File | Purpose |
|---|---|
DeviceController.swift | Protocol + concrete implementation wrapping devicectl |
PhysicalDevice.swift | Model struct representing a physical device |
DeviceControllerTests.swift | Unit tests with fixture JSON from a real devicectl response |
RunCommandService.swift | Primary caller β discovery, install, launch flow |
DeviceControlling Protocol#
The DeviceControlling protocol (annotated @Mockable for test injection) exposes three async operations:
findAvailableDevices() -> [PhysicalDevice]β enumerate all known physical devicesinstallApp(at:device:)β sideload a.appbundle onto a devicelaunchApp(bundleId:device:)β launch an installed app by bundle ID
Device Discovery β findAvailableDevices()#
findAvailableDevices() runs:
/usr/bin/xcrun devicectl list devices --json-output <tmp_file>.json
The JSON output is written to a temporary directory (via FileSystem.runInTemporaryDirectory) and then decoded into the private DeviceList struct. Any decode failure surfaces as DeviceControllerError.fetchingDevicesFailed .
JSON Decoding & Filtering#
The private DeviceList hierarchy mirrors the devicectl JSON schema:
connectionProperties: holdstransportType(.localNetwork|.wired) andtunnelState(.connecting|.connected|.disconnected|.unavailable)deviceProperties:name,osVersionNumberhardwareProperties:udid,platform(.iOS|.tvOS|.watchOS|.visionOS)
A device is silently dropped (compactMap) if any of udid, platform, or name is missing . This guards against partially-populated entries that devicectl may return (e.g., paired-but-never-seen devices).
Transport Type Mapping#
The devicectl transportType field is mapped to PhysicalDevice.TransportType :
devicectl value | PhysicalDevice.TransportType |
|---|---|
localNetwork | .wifi |
wired | .usb |
absent / nil | nil |
Note on
sameMachine: TheConnectionProperties.TransportTypeenum in the current code only modelslocalNetworkandwired. A device reporting any other raw value (including a hypotheticalsameMachinestring) will decodetransportTypeasnil, which is legal β the device is still included and simply has no transport type set.
Connection State Mapping#
tunnelState is mapped to PhysicalDevice.ConnectionState : only connected maps to .connected; all other states (connecting, disconnected, unavailable, or absent) map to .disconnected.
PhysicalDevice Model#
Defined in PhysicalDevice.swift, it is a Codable, Equatable, Identifiable struct:
id: Stringβ the device UDID (fromhardwareProperties.udid)name: Stringplatform: Platformβ XcodeGraphPlatformenumosVersion: String?transportType: TransportType?β.wifior.usbconnectionState: ConnectionStateβ.connectedor.disconnected
Installation & Launch#
Once devices are discovered, RunCommandService calls:
deviceController.installApp(at:device:)βxcrun devicectl device install app --device <udid> <path>deviceController.launchApp(bundleId:device:)βxcrun devicectl device process launch --device <udid> <bundleId>
If install stderr contains "ApplicationVerificationFailed", the error is caught and re-thrown as DeviceControllerError.applicationVerificationFailed with a user-friendly message directing the developer to register the device in their Apple Developer account .
Integration with RunCommandService#
During tuist run, RunCommandService calls deviceController.findAvailableDevices() (macOS only, guarded with #if os(macOS)) and appends results as DestinationDevice.physical entries alongside simulators and Android devices. If a --device flag is provided, the service matches by name; otherwise it prompts interactively or auto-selects the single ready device.
Error Cases#
| Error | Trigger | Message |
|---|---|---|
fetchingDevicesFailed | JSON decode fails | "Fetching the list of devices failed." |
applicationVerificationFailed | devicectl install exits with ApplicationVerificationFailed in stderr | "Make sure that your device is registered in your Apple Developer account." |
Both are .abort errors , causing the CLI to exit immediately.