mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
modernize iOS device lookup in driver (#10780)
This commit is contained in:
parent
b474557ef4
commit
3b6d84b083
|
@ -4,11 +4,9 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import '../android/android_device.dart' show AndroidDevice;
|
||||
import '../application_package.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
|
@ -16,7 +14,6 @@ import '../dart/package_map.dart';
|
|||
import '../dart/sdk.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils;
|
||||
import '../resident_runner.dart';
|
||||
import 'run.dart';
|
||||
|
||||
|
@ -198,56 +195,15 @@ Future<Device> findTargetDevice() async {
|
|||
return devices.first;
|
||||
}
|
||||
|
||||
|
||||
if (platform.isMacOS) {
|
||||
// On Mac we look for the iOS Simulator. If available, we use that. Then
|
||||
// we look for an Android device. If there's one, we use that. Otherwise,
|
||||
// we launch a new iOS Simulator.
|
||||
Device reusableDevice;
|
||||
for (Device device in devices) {
|
||||
if (await device.isLocalEmulator) {
|
||||
reusableDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (reusableDevice == null) {
|
||||
for (Device device in devices) {
|
||||
if (device is AndroidDevice) {
|
||||
reusableDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reusableDevice != null) {
|
||||
printStatus('Found connected ${await reusableDevice.isLocalEmulator ? "emulator" : "device"} "${reusableDevice.name}"; will reuse it.');
|
||||
return reusableDevice;
|
||||
}
|
||||
|
||||
// No running emulator found. Attempt to start one.
|
||||
printStatus('Starting iOS Simulator, because did not find existing connected devices.');
|
||||
final bool started = await SimControl.instance.boot();
|
||||
if (started) {
|
||||
return IOSSimulatorUtils.instance.getAttachedDevices().first;
|
||||
} else {
|
||||
printError('Failed to start iOS Simulator.');
|
||||
return null;
|
||||
}
|
||||
} else if (platform.isLinux || platform.isWindows) {
|
||||
// On Linux and Windows, for now, we just grab the first connected device we can find.
|
||||
if (devices.isEmpty) {
|
||||
printError('No devices found.');
|
||||
return null;
|
||||
} else if (devices.length > 1) {
|
||||
printStatus('Found multiple connected devices:');
|
||||
printStatus(devices.map((Device d) => ' - ${d.name}\n').join(''));
|
||||
}
|
||||
printStatus('Using device ${devices.first.name}.');
|
||||
return devices.first;
|
||||
} else {
|
||||
printError('The operating system on this computer is not supported.');
|
||||
if (devices.isEmpty) {
|
||||
printError('No devices found.');
|
||||
return null;
|
||||
} else if (devices.length > 1) {
|
||||
printStatus('Found multiple connected devices:');
|
||||
printStatus(devices.map((Device d) => ' - ${d.name}\n').join(''));
|
||||
}
|
||||
printStatus('Using device ${devices.first.name}.');
|
||||
return devices.first;
|
||||
}
|
||||
|
||||
/// Starts the application on the device given command configuration.
|
||||
|
|
|
@ -59,117 +59,6 @@ class SimControl {
|
|||
/// Returns [SimControl] active in the current app context (i.e. zone).
|
||||
static SimControl get instance => context[SimControl];
|
||||
|
||||
Future<bool> boot({ String deviceName }) async {
|
||||
if (_isAnyConnected())
|
||||
return true;
|
||||
|
||||
if (deviceName == null) {
|
||||
final SimDevice testDevice = _createTestDevice();
|
||||
if (testDevice == null) {
|
||||
return false;
|
||||
}
|
||||
deviceName = testDevice.name;
|
||||
}
|
||||
|
||||
// `xcrun instruments` requires a template (-t). @yjbanov has no idea what
|
||||
// "template" is but the built-in 'Blank' seems to work. -l causes xcrun to
|
||||
// quit after a time limit without killing the simulator. We quit after
|
||||
// 1 second.
|
||||
final List<String> args = <String>[_xcrunPath, 'instruments', '-w', deviceName, '-t', 'Blank', '-l', '1'];
|
||||
printTrace(args.join(' '));
|
||||
runDetached(args);
|
||||
printStatus('Waiting for iOS Simulator to boot...');
|
||||
|
||||
bool connected = false;
|
||||
int attempted = 0;
|
||||
while (!connected && attempted < 20) {
|
||||
connected = _isAnyConnected();
|
||||
if (!connected) {
|
||||
printStatus('Still waiting for iOS Simulator to boot...');
|
||||
await new Future<Null>.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
attempted++;
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
printStatus('Connected to iOS Simulator.');
|
||||
return true;
|
||||
} else {
|
||||
printStatus('Timed out waiting for iOS Simulator to boot.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SimDevice _createTestDevice() {
|
||||
final SimDeviceType deviceType = _findSuitableDeviceType();
|
||||
if (deviceType == null)
|
||||
return null;
|
||||
|
||||
final String runtime = _findSuitableRuntime();
|
||||
if (runtime == null)
|
||||
return null;
|
||||
|
||||
// Delete any old test devices
|
||||
getDevices()
|
||||
.where((SimDevice d) => d.name.endsWith(_kFlutterTestDeviceSuffix))
|
||||
.forEach(_deleteDevice);
|
||||
|
||||
// Create new device
|
||||
final String deviceName = '${deviceType.name} $_kFlutterTestDeviceSuffix';
|
||||
final List<String> args = <String>[_xcrunPath, 'simctl', 'create', deviceName, deviceType.identifier, runtime];
|
||||
printTrace(args.join(' '));
|
||||
runCheckedSync(args);
|
||||
|
||||
return getDevices().firstWhere((SimDevice d) => d.name == deviceName);
|
||||
}
|
||||
|
||||
SimDeviceType _findSuitableDeviceType() {
|
||||
final List<Map<String, dynamic>> allTypes = _list(SimControlListSection.devicetypes);
|
||||
final List<Map<String, dynamic>> usableTypes = allTypes
|
||||
.where((Map<String, dynamic> info) => info['name'].startsWith('iPhone'))
|
||||
.toList()
|
||||
..sort((Map<String, dynamic> r1, Map<String, dynamic> r2) => -compareIphoneVersions(r1['identifier'], r2['identifier']));
|
||||
|
||||
if (usableTypes.isEmpty) {
|
||||
printError(
|
||||
'No suitable device type found.\n'
|
||||
'You may launch an iOS Simulator manually and Flutter will attempt to use it.'
|
||||
);
|
||||
}
|
||||
|
||||
return new SimDeviceType(
|
||||
usableTypes.first['name'],
|
||||
usableTypes.first['identifier']
|
||||
);
|
||||
}
|
||||
|
||||
String _findSuitableRuntime() {
|
||||
final List<Map<String, dynamic>> allRuntimes = _list(SimControlListSection.runtimes);
|
||||
final List<Map<String, dynamic>> usableRuntimes = allRuntimes
|
||||
.where((Map<String, dynamic> info) => info['name'].startsWith('iOS'))
|
||||
.toList()
|
||||
..sort((Map<String, dynamic> r1, Map<String, dynamic> r2) => -compareIosVersions(r1['version'], r2['version']));
|
||||
|
||||
if (usableRuntimes.isEmpty) {
|
||||
printError(
|
||||
'No suitable iOS runtime found.\n'
|
||||
'You may launch an iOS Simulator manually and Flutter will attempt to use it.'
|
||||
);
|
||||
}
|
||||
|
||||
return usableRuntimes.first['identifier'];
|
||||
}
|
||||
|
||||
void _deleteDevice(SimDevice device) {
|
||||
try {
|
||||
final List<String> args = <String>[_xcrunPath, 'simctl', 'delete', device.name];
|
||||
printTrace(args.join(' '));
|
||||
runCheckedSync(args);
|
||||
} catch(e) {
|
||||
printError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `simctl list --json` and returns the JSON of the corresponding
|
||||
/// [section].
|
||||
///
|
||||
|
@ -226,8 +115,6 @@ class SimControl {
|
|||
return getDevices().where((SimDevice device) => device.isBooted).toList();
|
||||
}
|
||||
|
||||
bool _isAnyConnected() => getConnectedDevices().isNotEmpty;
|
||||
|
||||
Future<bool> isInstalled(String deviceId, String appId) {
|
||||
return exitsHappyAsync(<String>[
|
||||
_xcrunPath,
|
||||
|
|
|
@ -13,7 +13,6 @@ import 'package:flutter_tools/src/base/platform.dart';
|
|||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/drive.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/ios/simulators.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -238,52 +237,7 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('findTargetDevice on iOS', () {
|
||||
Platform macOsPlatform() => new FakePlatform(operatingSystem: 'macos');
|
||||
|
||||
testUsingContext('uses existing emulator', () async {
|
||||
withMockDevice();
|
||||
when(mockDevice.name).thenReturn('mock-simulator');
|
||||
when(mockDevice.isLocalEmulator).thenReturn(new Future<bool>.value(true));
|
||||
|
||||
final Device device = await findTargetDevice();
|
||||
expect(device.name, 'mock-simulator');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
Platform: macOsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('uses existing Android device if and there are no simulators', () async {
|
||||
mockDevice = new MockAndroidDevice();
|
||||
when(mockDevice.name).thenReturn('mock-android-device');
|
||||
when(mockDevice.isLocalEmulator).thenReturn(new Future<bool>.value(false));
|
||||
withMockDevice(mockDevice);
|
||||
|
||||
final Device device = await findTargetDevice();
|
||||
expect(device.name, 'mock-android-device');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
Platform: macOsPlatform,
|
||||
});
|
||||
|
||||
testUsingContext('launches emulator', () async {
|
||||
when(SimControl.instance.boot()).thenReturn(true);
|
||||
final Device emulator = new MockDevice();
|
||||
when(emulator.name).thenReturn('new-simulator');
|
||||
when(IOSSimulatorUtils.instance.getAttachedDevices())
|
||||
.thenReturn(<Device>[emulator]);
|
||||
|
||||
final Device device = await findTargetDevice();
|
||||
expect(device.name, 'new-simulator');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
Platform: macOsPlatform,
|
||||
});
|
||||
});
|
||||
|
||||
void findTargetDeviceOnOperatingSystem(String operatingSystem) {
|
||||
assert(operatingSystem == 'windows' || operatingSystem == 'linux');
|
||||
|
||||
Platform platform() => new FakePlatform(operatingSystem: operatingSystem);
|
||||
|
||||
testUsingContext('returns null if no devices found', () async {
|
||||
|
@ -313,6 +267,24 @@ void main() {
|
|||
group('findTargetDevice on Windows', () {
|
||||
findTargetDeviceOnOperatingSystem('windows');
|
||||
});
|
||||
|
||||
group('findTargetDevice on macOS', () {
|
||||
findTargetDeviceOnOperatingSystem('macos');
|
||||
|
||||
Platform macOsPlatform() => new FakePlatform(operatingSystem: 'macos');
|
||||
|
||||
testUsingContext('uses existing simulator', () async {
|
||||
withMockDevice();
|
||||
when(mockDevice.name).thenReturn('mock-simulator');
|
||||
when(mockDevice.isLocalEmulator).thenReturn(new Future<bool>.value(true));
|
||||
|
||||
final Device device = await findTargetDevice();
|
||||
expect(device.name, 'mock-simulator');
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fs,
|
||||
Platform: macOsPlatform,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue