From 37bb5f1300e67fe590c44bb9ecda653b2967e347 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 16 Jun 2017 16:02:28 -0700 Subject: [PATCH] Use Xcode instruments to list devices (#10801) Eliminates the dependency on idevice_id from libimobiledevice. Instead, uses Xcode built-in functionality. --- .../flutter_tools/lib/src/ios/devices.dart | 34 +++++++++++----- packages/flutter_tools/lib/src/ios/mac.dart | 10 +---- .../flutter_tools/test/ios/devices_test.dart | 40 ++++++++++--------- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index bb3eca7261..748fb7144d 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -40,7 +40,7 @@ class IOSDevices extends PollingDeviceDiscovery { } class IOSDevice extends Device { - IOSDevice(String id, { this.name }) : super(id) { + IOSDevice(String id, { this.name, String sdkVersion }) : _sdkVersion = sdkVersion, super(id) { _installerPath = _checkForCommand('ideviceinstaller'); _iproxyPath = _checkForCommand('iproxy'); _pusherPath = _checkForCommand( @@ -55,6 +55,8 @@ class IOSDevice extends Device { String _iproxyPath; String _pusherPath; + final String _sdkVersion; + @override bool get supportsHotMode => true; @@ -71,14 +73,30 @@ class IOSDevice extends Device { @override bool get supportsStartPaused => false; + // Physical device line format to be matched: + // My iPhone (10.3.2) [75b90e947c5f429fa67f3e9169fda0d89f0492f1] + // + // Other formats in output (desktop, simulator) to be ignored: + // my-mac-pro [2C10513E-4dA5-405C-8EF5-C44353DB3ADD] + // iPhone 6s (9.3) [F6CEE7CF-81EB-4448-81B4-1755288C7C11] (Simulator) + static final RegExp _deviceRegex = new RegExp(r'^(.*) +\((.*)\) +\[(.*)\]$'); + static List getAttachedDevices() { - if (!iMobileDevice.isInstalled) + if (!xcode.isInstalled) return []; final List devices = []; - for (String id in iMobileDevice.getAttachedDeviceIDs()) { - final String name = iMobileDevice.getInfoForDevice(id, 'DeviceName'); - devices.add(new IOSDevice(id, name: name)); + final Iterable deviceLines = xcode.getAvailableDevices() + .split('\n') + .map((String line) => line.trim()); + for (String line in deviceLines) { + final Match match = _deviceRegex.firstMatch(line); + if (match != null) { + final String deviceName = match.group(1); + final String sdkVersion = match.group(2); + final String deviceID = match.group(3); + devices.add(new IOSDevice(deviceID, name: deviceName, sdkVersion: sdkVersion)); + } } return devices; } @@ -311,11 +329,7 @@ class IOSDevice extends Device { Future get targetPlatform async => TargetPlatform.ios; @override - Future get sdkNameAndVersion async => 'iOS $_sdkVersion ($_buildVersion)'; - - String get _sdkVersion => iMobileDevice.getInfoForDevice(id, 'ProductVersion'); - - String get _buildVersion => iMobileDevice.getInfoForDevice(id, 'BuildVersion'); + Future get sdkNameAndVersion async => 'iOS $_sdkVersion'; @override DeviceLogReader getLogReader({ApplicationPackage app}) { diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 7d1bfa6022..eabf98c4fe 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -70,14 +70,6 @@ class IMobileDevice { return await exitsHappyAsync(['idevicename']); } - List getAttachedDeviceIDs() { - return runSync(['idevice_id', '-l']) - .trim() - .split('\n') - .where((String line) => line.isNotEmpty) - .toList(); - } - /// Returns the value associated with the specified `ideviceinfo` key for a device. /// /// If either the specified key or device does not exist, returns the empty string. @@ -165,6 +157,8 @@ class Xcode { return _xcodeVersionCheckValid(_xcodeMajorVersion, _xcodeMinorVersion); } + + String getAvailableDevices() => runSync(['/usr/bin/instruments', '-s', 'devices']); } bool _xcodeVersionCheckValid(int major, int minor) { diff --git a/packages/flutter_tools/test/ios/devices_test.dart b/packages/flutter_tools/test/ios/devices_test.dart index 4e2726874b..8da07d3db7 100644 --- a/packages/flutter_tools/test/ios/devices_test.dart +++ b/packages/flutter_tools/test/ios/devices_test.dart @@ -14,7 +14,7 @@ import 'package:test/test.dart'; import '../src/context.dart'; class MockProcessManager extends Mock implements ProcessManager {} -class MockIMobileDevice extends Mock implements IMobileDevice {} +class MockXcode extends Mock implements Xcode {} class MockFile extends Mock implements File {} void main() { @@ -22,38 +22,40 @@ void main() { osx.operatingSystem = 'macos'; group('getAttachedDevices', () { - MockIMobileDevice mockIMobileDevice; + MockXcode mockXcode; setUp(() { - mockIMobileDevice = new MockIMobileDevice(); + mockXcode = new MockXcode(); }); - testUsingContext('return no devices if libimobiledevice is not installed', () async { - when(mockIMobileDevice.isInstalled).thenReturn(false); + testUsingContext('return no devices if Xcode is not installed', () async { + when(mockXcode.isInstalled).thenReturn(false); expect(IOSDevice.getAttachedDevices(), isEmpty); }, overrides: { - IMobileDevice: () => mockIMobileDevice, + Xcode: () => mockXcode, }); testUsingContext('returns no devices if none are attached', () async { - when(mockIMobileDevice.isInstalled).thenReturn(true); - when(mockIMobileDevice.getAttachedDeviceIDs()).thenReturn([]); + when(mockXcode.isInstalled).thenReturn(true); + when(mockXcode.getAvailableDevices()).thenReturn(''); final List devices = IOSDevice.getAttachedDevices(); expect(devices, isEmpty); }, overrides: { - IMobileDevice: () => mockIMobileDevice, + Xcode: () => mockXcode, }); testUsingContext('returns attached devices', () async { - when(mockIMobileDevice.isInstalled).thenReturn(true); - when(mockIMobileDevice.getAttachedDeviceIDs()).thenReturn([ - '98206e7a4afd4aedaff06e687594e089dede3c44', - 'f577a7903cc54959be2e34bc4f7f80b7009efcf4', - ]); - when(mockIMobileDevice.getInfoForDevice('98206e7a4afd4aedaff06e687594e089dede3c44', 'DeviceName')) - .thenReturn('La tele me regarde'); - when(mockIMobileDevice.getInfoForDevice('f577a7903cc54959be2e34bc4f7f80b7009efcf4', 'DeviceName')) - .thenReturn('Puits sans fond'); + when(mockXcode.isInstalled).thenReturn(true); + when(mockXcode.getAvailableDevices()).thenReturn(''' +Known Devices: +je-mappelle-horse [ED6552C4-B774-5A4E-8B5A-606710C87C77] +La tele me regarde (10.3.2) [98206e7a4afd4aedaff06e687594e089dede3c44] +Puits sans fond (10.3.2) [f577a7903cc54959be2e34bc4f7f80b7009efcf4] +iPhone 6 Plus (9.3) [FBA880E6-4020-49A5-8083-DCD50CA5FA09] (Simulator) +iPhone 6s (11.0) [E805F496-FC6A-4EA4-92FF-B7901FF4E7CC] (Simulator) +iPhone 7 (11.0) + Apple Watch Series 2 - 38mm (4.0) [60027FDD-4A7A-42BF-978F-C2209D27AD61] (Simulator) +iPhone SE (11.0) [667E8DCD-5DCD-4C80-93A9-60D1D995206F] (Simulator) +'''); final List devices = IOSDevice.getAttachedDevices(); expect(devices, hasLength(2)); expect(devices[0].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); @@ -61,7 +63,7 @@ void main() { expect(devices[1].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4'); expect(devices[1].name, 'Puits sans fond'); }, overrides: { - IMobileDevice: () => mockIMobileDevice, + Xcode: () => mockXcode, }); });