Unverified Commit 2e18cd34 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Fix simctl process exceptions not being caught (#51916)

parent a42d38a2
...@@ -137,7 +137,10 @@ Future<T> runInContext<T>( ...@@ -137,7 +137,10 @@ Future<T> runInContext<T>(
platform: globals.platform, platform: globals.platform,
processManager: globals.processManager, processManager: globals.processManager,
), ),
IOSSimulatorUtils: () => IOSSimulatorUtils(), IOSSimulatorUtils: () => IOSSimulatorUtils(
simControl: globals.simControl,
xcode: globals.xcode,
),
IOSWorkflow: () => const IOSWorkflow(), IOSWorkflow: () => const IOSWorkflow(),
KernelCompilerFactory: () => const KernelCompilerFactory(), KernelCompilerFactory: () => const KernelCompilerFactory(),
Logger: () => globals.platform.isWindows Logger: () => globals.platform.isWindows
...@@ -174,7 +177,10 @@ Future<T> runInContext<T>( ...@@ -174,7 +177,10 @@ Future<T> runInContext<T>(
Pub: () => const Pub(), Pub: () => const Pub(),
ShutdownHooks: () => ShutdownHooks(logger: globals.logger), ShutdownHooks: () => ShutdownHooks(logger: globals.logger),
Signals: () => Signals(), Signals: () => Signals(),
SimControl: () => SimControl(), SimControl: () => SimControl(
logger: globals.logger,
processManager: globals.processManager,
),
Stdio: () => Stdio(), Stdio: () => Stdio(),
SystemClock: () => const SystemClock(), SystemClock: () => const SystemClock(),
TimeoutConfiguration: () => const TimeoutConfiguration(), TimeoutConfiguration: () => const TimeoutConfiguration(),
......
...@@ -71,7 +71,7 @@ class DeviceManager { ...@@ -71,7 +71,7 @@ class DeviceManager {
final List<DeviceDiscovery> _deviceDiscoverers = List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[ final List<DeviceDiscovery> _deviceDiscoverers = List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[
AndroidDevices(), AndroidDevices(),
IOSDevices(), IOSDevices(),
IOSSimulators(), IOSSimulators(iosSimulatorUtils: globals.iosSimulatorUtils),
FuchsiaDevices(), FuchsiaDevices(),
FlutterTesterDevices(), FlutterTesterDevices(),
MacOSDevices(), MacOSDevices(),
......
...@@ -24,6 +24,7 @@ import 'cache.dart'; ...@@ -24,6 +24,7 @@ import 'cache.dart';
import 'ios/ios_deploy.dart'; import 'ios/ios_deploy.dart';
import 'ios/mac.dart'; import 'ios/mac.dart';
import 'ios/plist_parser.dart'; import 'ios/plist_parser.dart';
import 'ios/simulators.dart';
import 'macos/xcode.dart'; import 'macos/xcode.dart';
import 'persistent_tool_state.dart'; import 'persistent_tool_state.dart';
import 'reporting/reporting.dart'; import 'reporting/reporting.dart';
...@@ -69,6 +70,8 @@ AndroidSdk get androidSdk => context.get<AndroidSdk>(); ...@@ -69,6 +70,8 @@ AndroidSdk get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>(); FlutterVersion get flutterVersion => context.get<FlutterVersion>();
IMobileDevice get iMobileDevice => context.get<IMobileDevice>(); IMobileDevice get iMobileDevice => context.get<IMobileDevice>();
IOSDeploy get iosDeploy => context.get<IOSDeploy>(); IOSDeploy get iosDeploy => context.get<IOSDeploy>();
IOSSimulatorUtils get iosSimulatorUtils => context.get<IOSSimulatorUtils>();
SimControl get simControl => context.get<SimControl>();
UserMessages get userMessages => context.get<UserMessages>(); UserMessages get userMessages => context.get<UserMessages>();
Xcode get xcode => context.get<Xcode>(); Xcode get xcode => context.get<Xcode>();
......
...@@ -6,12 +6,13 @@ import 'dart:async'; ...@@ -6,12 +6,13 @@ import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
...@@ -19,6 +20,7 @@ import '../bundle.dart'; ...@@ -19,6 +20,7 @@ import '../bundle.dart';
import '../convert.dart'; import '../convert.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../macos/xcode.dart';
import '../project.dart'; import '../project.dart';
import '../protocol_discovery.dart'; import '../protocol_discovery.dart';
import 'ios_workflow.dart'; import 'ios_workflow.dart';
...@@ -29,7 +31,12 @@ const String _xcrunPath = '/usr/bin/xcrun'; ...@@ -29,7 +31,12 @@ const String _xcrunPath = '/usr/bin/xcrun';
const String iosSimulatorId = 'apple_ios_simulator'; const String iosSimulatorId = 'apple_ios_simulator';
class IOSSimulators extends PollingDeviceDiscovery { class IOSSimulators extends PollingDeviceDiscovery {
IOSSimulators() : super('iOS simulators'); IOSSimulators({
@required IOSSimulatorUtils iosSimulatorUtils,
}) : _iosSimulatorUtils = iosSimulatorUtils,
super('iOS simulators');
final IOSSimulatorUtils _iosSimulatorUtils;
@override @override
bool get supportsPlatform => globals.platform.isMacOS; bool get supportsPlatform => globals.platform.isMacOS;
...@@ -38,29 +45,47 @@ class IOSSimulators extends PollingDeviceDiscovery { ...@@ -38,29 +45,47 @@ class IOSSimulators extends PollingDeviceDiscovery {
bool get canListAnything => iosWorkflow.canListDevices; bool get canListAnything => iosWorkflow.canListDevices;
@override @override
Future<List<Device>> pollingGetDevices() async => IOSSimulatorUtils.instance.getAttachedDevices(); Future<List<Device>> pollingGetDevices() async => _iosSimulatorUtils.getAttachedDevices();
} }
class IOSSimulatorUtils { class IOSSimulatorUtils {
/// Returns [IOSSimulatorUtils] active in the current app context (i.e. zone). IOSSimulatorUtils({
static IOSSimulatorUtils get instance => context.get<IOSSimulatorUtils>(); @required SimControl simControl,
@required Xcode xcode,
}) : _simControl = simControl,
_xcode = xcode;
final SimControl _simControl;
final Xcode _xcode;
Future<List<IOSSimulator>> getAttachedDevices() async { Future<List<IOSSimulator>> getAttachedDevices() async {
if (!globals.xcode.isInstalledAndMeetsVersionCheck) { if (!_xcode.isInstalledAndMeetsVersionCheck) {
return <IOSSimulator>[]; return <IOSSimulator>[];
} }
final List<SimDevice> connected = await SimControl.instance.getConnectedDevices(); final List<SimDevice> connected = await _simControl.getConnectedDevices();
return connected.map<IOSSimulator>((SimDevice device) { return connected.map<IOSSimulator>((SimDevice device) {
return IOSSimulator(device.udid, name: device.name, simulatorCategory: device.category); return IOSSimulator(
device.udid,
name: device.name,
simControl: _simControl,
simulatorCategory: device.category,
xcode: _xcode,
);
}).toList(); }).toList();
} }
} }
/// A wrapper around the `simctl` command line tool. /// A wrapper around the `simctl` command line tool.
class SimControl { class SimControl {
/// Returns [SimControl] active in the current app context (i.e. zone). SimControl({
static SimControl get instance => context.get<SimControl>(); @required Logger logger,
@required ProcessManager processManager,
}) : _logger = logger,
_processUtils = ProcessUtils(processManager: processManager, logger: logger);
final Logger _logger;
final ProcessUtils _processUtils;
/// Runs `simctl list --json` and returns the JSON of the corresponding /// Runs `simctl list --json` and returns the JSON of the corresponding
/// [section]. /// [section].
...@@ -83,10 +108,10 @@ class SimControl { ...@@ -83,10 +108,10 @@ class SimControl {
// "pairs": { ... }, // "pairs": { ... },
final List<String> command = <String>[_xcrunPath, 'simctl', 'list', '--json', section.name]; final List<String> command = <String>[_xcrunPath, 'simctl', 'list', '--json', section.name];
globals.printTrace(command.join(' ')); _logger.printTrace(command.join(' '));
final ProcessResult results = await globals.processManager.run(command); final RunResult results = await _processUtils.run(command);
if (results.exitCode != 0) { if (results.exitCode != 0) {
globals.printError('Error executing simctl: ${results.exitCode}\n${results.stderr}'); _logger.printError('Error executing simctl: ${results.exitCode}\n${results.stderr}');
return <String, Map<String, dynamic>>{}; return <String, Map<String, dynamic>>{};
} }
try { try {
...@@ -94,13 +119,13 @@ class SimControl { ...@@ -94,13 +119,13 @@ class SimControl {
if (decodeResult is Map<String, dynamic>) { if (decodeResult is Map<String, dynamic>) {
return decodeResult; return decodeResult;
} }
globals.printError('simctl returned unexpected JSON response: ${results.stdout}'); _logger.printError('simctl returned unexpected JSON response: ${results.stdout}');
return <String, dynamic>{}; return <String, dynamic>{};
} on FormatException { } on FormatException {
// We failed to parse the simctl output, or it returned junk. // We failed to parse the simctl output, or it returned junk.
// One known message is "Install Started" isn't valid JSON but is // One known message is "Install Started" isn't valid JSON but is
// returned sometimes. // returned sometimes.
globals.printError('simctl returned non-JSON response: ${results.stdout}'); _logger.printError('simctl returned non-JSON response: ${results.stdout}');
return <String, dynamic>{}; return <String, dynamic>{};
} }
} }
...@@ -130,7 +155,7 @@ class SimControl { ...@@ -130,7 +155,7 @@ class SimControl {
} }
Future<bool> isInstalled(String deviceId, String appId) { Future<bool> isInstalled(String deviceId, String appId) {
return processUtils.exitsHappy(<String>[ return _processUtils.exitsHappy(<String>[
_xcrunPath, _xcrunPath,
'simctl', 'simctl',
'get_app_container', 'get_app_container',
...@@ -139,23 +164,23 @@ class SimControl { ...@@ -139,23 +164,23 @@ class SimControl {
]); ]);
} }
Future<RunResult> install(String deviceId, String appPath) { Future<RunResult> install(String deviceId, String appPath) async {
Future<RunResult> result; RunResult result;
try { try {
result = processUtils.run( result = await _processUtils.run(
<String>[_xcrunPath, 'simctl', 'install', deviceId, appPath], <String>[_xcrunPath, 'simctl', 'install', deviceId, appPath],
throwOnError: true, throwOnError: true,
); );
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to install $appPath on $deviceId:\n$exception'); throwToolExit('Unable to install $appPath on $deviceId. This is sometimes caused by a malformed plist file:\n$exception');
} }
return result; return result;
} }
Future<RunResult> uninstall(String deviceId, String appId) { Future<RunResult> uninstall(String deviceId, String appId) async {
Future<RunResult> result; RunResult result;
try { try {
result = processUtils.run( result = await _processUtils.run(
<String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId], <String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId],
throwOnError: true, throwOnError: true,
); );
...@@ -165,10 +190,10 @@ class SimControl { ...@@ -165,10 +190,10 @@ class SimControl {
return result; return result;
} }
Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) { Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) async {
Future<RunResult> result; RunResult result;
try { try {
result = processUtils.run( result = await _processUtils.run(
<String>[ <String>[
_xcrunPath, _xcrunPath,
'simctl', 'simctl',
...@@ -187,12 +212,12 @@ class SimControl { ...@@ -187,12 +212,12 @@ class SimControl {
Future<void> takeScreenshot(String deviceId, String outputPath) async { Future<void> takeScreenshot(String deviceId, String outputPath) async {
try { try {
await processUtils.run( await _processUtils.run(
<String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath], <String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath],
throwOnError: true, throwOnError: true,
); );
} on ProcessException catch (exception) { } on ProcessException catch (exception) {
throwToolExit('Unable to take screenshot of $deviceId:\n$exception'); _logger.printError('Unable to take screenshot of $deviceId:\n$exception');
} }
} }
} }
...@@ -248,7 +273,15 @@ class SimDevice { ...@@ -248,7 +273,15 @@ class SimDevice {
} }
class IOSSimulator extends Device { class IOSSimulator extends Device {
IOSSimulator(String id, { this.name, this.simulatorCategory }) : super( IOSSimulator(
String id, {
this.name,
this.simulatorCategory,
@required SimControl simControl,
@required Xcode xcode,
}) : _simControl = simControl,
_xcode = xcode,
super(
id, id,
category: Category.mobile, category: Category.mobile,
platformType: PlatformType.ios, platformType: PlatformType.ios,
...@@ -260,6 +293,9 @@ class IOSSimulator extends Device { ...@@ -260,6 +293,9 @@ class IOSSimulator extends Device {
final String simulatorCategory; final String simulatorCategory;
final SimControl _simControl;
final Xcode _xcode;
@override @override
Future<bool> get isLocalEmulator async => true; Future<bool> get isLocalEmulator async => true;
...@@ -279,7 +315,7 @@ class IOSSimulator extends Device { ...@@ -279,7 +315,7 @@ class IOSSimulator extends Device {
@override @override
Future<bool> isAppInstalled(ApplicationPackage app) { Future<bool> isAppInstalled(ApplicationPackage app) {
return SimControl.instance.isInstalled(id, app.id); return _simControl.isInstalled(id, app.id);
} }
@override @override
...@@ -289,7 +325,7 @@ class IOSSimulator extends Device { ...@@ -289,7 +325,7 @@ class IOSSimulator extends Device {
Future<bool> installApp(covariant IOSApp app) async { Future<bool> installApp(covariant IOSApp app) async {
try { try {
final IOSApp iosApp = app; final IOSApp iosApp = app;
await SimControl.instance.install(id, iosApp.simulatorBundlePath); await _simControl.install(id, iosApp.simulatorBundlePath);
return true; return true;
} on Exception { } on Exception {
return false; return false;
...@@ -299,7 +335,7 @@ class IOSSimulator extends Device { ...@@ -299,7 +335,7 @@ class IOSSimulator extends Device {
@override @override
Future<bool> uninstallApp(ApplicationPackage app) async { Future<bool> uninstallApp(ApplicationPackage app) async {
try { try {
await SimControl.instance.uninstall(id, app.id); await _simControl.uninstall(id, app.id);
return true; return true;
} on Exception { } on Exception {
return false; return false;
...@@ -394,7 +430,7 @@ class IOSSimulator extends Device { ...@@ -394,7 +430,7 @@ class IOSSimulator extends Device {
final String plistPath = globals.fs.path.join(package.simulatorBundlePath, 'Info.plist'); final String plistPath = globals.fs.path.join(package.simulatorBundlePath, 'Info.plist');
final String bundleIdentifier = globals.plistParser.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey); final String bundleIdentifier = globals.plistParser.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
await SimControl.instance.launch(id, bundleIdentifier, args); await _simControl.launch(id, bundleIdentifier, args);
} on Exception catch (error) { } on Exception catch (error) {
globals.printError('$error'); globals.printError('$error');
return LaunchResult.failed(); return LaunchResult.failed();
...@@ -449,7 +485,7 @@ class IOSSimulator extends Device { ...@@ -449,7 +485,7 @@ class IOSSimulator extends Device {
} }
// Step 3: Install the updated bundle to the simulator. // Step 3: Install the updated bundle to the simulator.
await SimControl.instance.install(id, globals.fs.path.absolute(bundle.path)); await _simControl.install(id, globals.fs.path.absolute(bundle.path));
} }
@visibleForTesting @visibleForTesting
...@@ -527,7 +563,7 @@ class IOSSimulator extends Device { ...@@ -527,7 +563,7 @@ class IOSSimulator extends Device {
} }
bool get _xcodeVersionSupportsScreenshot { bool get _xcodeVersionSupportsScreenshot {
return globals.xcode.majorVersion > 8 || (globals.xcode.majorVersion == 8 && globals.xcode.minorVersion >= 2); return _xcode.majorVersion > 8 || (_xcode.majorVersion == 8 && _xcode.minorVersion >= 2);
} }
@override @override
...@@ -535,7 +571,7 @@ class IOSSimulator extends Device { ...@@ -535,7 +571,7 @@ class IOSSimulator extends Device {
@override @override
Future<void> takeScreenshot(File outputFile) { Future<void> takeScreenshot(File outputFile) {
return SimControl.instance.takeScreenshot(id, outputFile.path); return _simControl.takeScreenshot(id, outputFile.path);
} }
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment