Skip to content

Commit

Permalink
Launch named iOS simulators (#72323)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmagman committed Dec 15, 2020
1 parent d2d0c73 commit 84a7a61
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class AndroidEmulator extends Emulator {
Category get category => Category.mobile;

@override
PlatformType get platformType => PlatformType.android;
String get platformDisplay => PlatformType.android.toString();

String _prop(String name) => _properties != null ? _properties[name] : null;

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/commands/daemon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ Map<String, dynamic> _emulatorToMap(Emulator emulator) {
'id': emulator.id,
'name': emulator.name,
'category': emulator.category?.toString(),
'platformType': emulator.platformType?.toString(),
'platformType': emulator.platformDisplay,
};
}

Expand Down
2 changes: 1 addition & 1 deletion packages/flutter_tools/lib/src/commands/emulators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class EmulatorsCommand extends FlutterCommand {
final String description = 'List, launch and create emulators.';

@override
final List<String> aliases = <String>['emulator'];
final List<String> aliases = <String>['emulator', 'simulators', 'simulator'];

@override
Future<FlutterCommandResult> runCommand() async {
Expand Down
4 changes: 0 additions & 4 deletions packages/flutter_tools/lib/src/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -614,10 +614,6 @@ abstract class Device {
/// Check if the device is supported by Flutter.
bool isSupported();

// String meant to be displayed to the user indicating if the device is
// supported by Flutter, and, if not, why.
String supportMessage() => isSupported() ? 'Supported' : 'Unsupported';

/// The device's platform.
Future<TargetPlatform> get targetPlatform;

Expand Down
4 changes: 2 additions & 2 deletions packages/flutter_tools/lib/src/emulator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ abstract class Emulator {
String get name;
String get manufacturer;
Category get category;
PlatformType get platformType;
String get platformDisplay;

@override
int get hashCode => id.hashCode;
Expand Down Expand Up @@ -283,7 +283,7 @@ abstract class Emulator {
emulator.id ?? '',
emulator.name ?? '',
emulator.manufacturer ?? '',
emulator.platformType?.toString() ?? '',
emulator.platformDisplay ?? '',
],
];

Expand Down
60 changes: 23 additions & 37 deletions packages/flutter_tools/lib/src/ios/ios_emulators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@ class IOSEmulators extends EmulatorDiscovery {
bool get canListAnything => globals.iosWorkflow.canListEmulators;

@override
Future<List<Emulator>> get emulators async => getEmulators();
Future<List<Emulator>> get emulators async {
final List<IOSSimulator> simulators = await globals.iosSimulatorUtils.getAvailableDevices();
return simulators.map<Emulator>((IOSSimulator device) {
return IOSEmulator(device);
}).toList();
}

@override
bool get canLaunchAnything => canListAnything;
}

class IOSEmulator extends Emulator {
const IOSEmulator(String id) : super(id, true);
IOSEmulator(IOSSimulator simulator)
: _simulator = simulator,
super(simulator.id, true);

final IOSSimulator _simulator;

@override
String get name => 'iOS Simulator';
String get name => _simulator.name;

@override
String get manufacturer => 'Apple';
Expand All @@ -35,43 +44,20 @@ class IOSEmulator extends Emulator {
Category get category => Category.mobile;

@override
PlatformType get platformType => PlatformType.ios;
String get platformDisplay =>
// com.apple.CoreSimulator.SimRuntime.iOS-10-3 => iOS-10-3
_simulator.simulatorCategory?.split('.')?.last ?? 'ios';

@override
Future<void> launch() async {
Future<bool> launchSimulator(List<String> additionalArgs) async {
final List<String> args = <String>[
'open',
...additionalArgs,
'-a',
globals.xcode.getSimulatorPath(),
];

final RunResult launchResult = await globals.processUtils.run(args);
if (launchResult.exitCode != 0) {
globals.printError('$launchResult');
return false;
}
return true;
}

// First run with `-n` to force a device to boot if there isn't already one
if (!await launchSimulator(<String>['-n'])) {
return;
final RunResult launchResult = await globals.processUtils.run(<String>[
'open',
'-a',
globals.xcode.getSimulatorPath(),
]);
if (launchResult.exitCode != 0) {
globals.printError('$launchResult');
}

// Run again to force it to Foreground (using -n doesn't force existing
// devices to the foreground)
await launchSimulator(<String>[]);
return _simulator.boot();
}
}

/// Return the list of iOS Simulators (there can only be zero or one).
List<IOSEmulator> getEmulators() {
final String simulatorPath = globals.xcode.getSimulatorPath();
if (simulatorPath == null) {
return <IOSEmulator>[];
}

return <IOSEmulator>[const IOSEmulator(iosSimulatorId)];
}
83 changes: 70 additions & 13 deletions packages/flutter_tools/lib/src/ios/simulators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class IOSSimulatorUtils {
return <IOSSimulator>[];
}

final List<SimDevice> connected = await _simControl.getConnectedDevices();
final List<SimDevice> connected = (await _simControl.getAvailableDevices())
.where((SimDevice device) => device.isBooted)
.toList();
return connected.map<IOSSimulator>((SimDevice device) {
return IOSSimulator(
device.udid,
Expand All @@ -77,6 +79,26 @@ class IOSSimulatorUtils {
);
}).toList();
}

Future<List<IOSSimulator>> getAvailableDevices() async {
if (!_xcode.isInstalledAndMeetsVersionCheck) {
return <IOSSimulator>[];
}

final List<SimDevice> available = await _simControl.getAvailableDevices();
return available
.map<IOSSimulator>((SimDevice device) {
return IOSSimulator(
device.udid,
name: device.name,
simControl: _simControl,
simulatorCategory: device.category,
xcode: _xcode,
);
})
.where((IOSSimulator simulator) => simulator.isSupported())
.toList();
}
}

/// A wrapper around the `simctl` command line tool.
Expand All @@ -89,6 +111,23 @@ class SimControl {
_xcode = xcode,
_processUtils = ProcessUtils(processManager: processManager, logger: logger);

/// Create a [SimControl] for testing.
///
/// Defaults to a buffer logger.
@visibleForTesting
factory SimControl.test({
@required ProcessManager processManager,
Logger logger,
Xcode xcode,
}) {
logger ??= BufferLogger.test();
return SimControl(
logger: logger,
xcode: xcode,
processManager: processManager,
);
}

final Logger _logger;
final ProcessUtils _processUtils;
final Xcode _xcode;
Expand Down Expand Up @@ -160,10 +199,10 @@ class SimControl {
return devices;
}

/// Returns all the connected simulator devices.
Future<List<SimDevice>> getConnectedDevices() async {
/// Returns all the available simulator devices.
Future<List<SimDevice>> getAvailableDevices() async {
final List<SimDevice> simDevices = await getDevices();
return simDevices.where((SimDevice device) => device.isBooted).toList();
return simDevices.where((SimDevice device) => device.isAvailable).toList();
}

Future<bool> isInstalled(String deviceId, String appId) {
Expand Down Expand Up @@ -234,6 +273,17 @@ class SimControl {
return result;
}

Future<RunResult> boot(String deviceId) {
return _processUtils.run(
<String>[
..._xcode.xcrunCommand(),
'simctl',
'boot',
deviceId,
],
);
}

Future<void> takeScreenshot(String deviceId, String outputPath) async {
try {
await _processUtils.run(
Expand Down Expand Up @@ -296,7 +346,11 @@ class SimDevice {
final Map<String, dynamic> data;

String get state => data['state']?.toString();
String get availability => data['availability']?.toString();

bool get isAvailable =>
data['isAvailable'] == true ||
data['availability']?.toString() == '(available)';

String get name => data['name']?.toString();
String get udid => data['udid']?.toString();

Expand Down Expand Up @@ -394,29 +448,32 @@ class IOSSimulator extends Device {
@override
bool isSupported() {
if (!globals.platform.isMacOS) {
_supportMessage = 'iOS devices require a Mac host machine.';
return false;
}

// Check if the device is part of a blocked category.
// We do not yet support WatchOS or tvOS devices.
final RegExp blocklist = RegExp(r'Apple (TV|Watch)', caseSensitive: false);
if (blocklist.hasMatch(name)) {
_supportMessage = 'Flutter does not support Apple TV or Apple Watch.';
return false;
}
return true;
}

String _supportMessage;
Future<bool> boot() async {
final RunResult result = await _simControl.boot(id);

@override
String supportMessage() {
if (isSupported()) {
return 'Supported';
if (result.exitCode == 0) {
return true;
}
// 149 exit code means the device is already booted. Ignore this error.
if (result.exitCode == 149) {
globals.printTrace('Simulator "$id" already booted.');
return true;
}

return _supportMessage ?? 'Unknown';
globals.logger.printError('$result');
return false;
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void main() {
expect(emulator.name, displayName);
expect(emulator.manufacturer, manufacturer);
expect(emulator.category, Category.mobile);
expect(emulator.platformType, PlatformType.android);
expect(emulator.platformDisplay, 'android');
});

testWithoutContext('prefers displayname for name', () {
Expand Down
Loading

0 comments on commit 84a7a61

Please sign in to comment.