blob: d9f8fe3b242ffe832018b2123e32685c50c1a001 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Devon Carew67046f92016-02-20 22:00:11 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
Devon Carew7ae584b2016-06-03 12:54:27 -07006import 'dart:math' as math;
Devon Carew67046f92016-02-20 22:00:11 -08007
Jonah Williams79323f72019-10-30 19:40:19 -07008import 'package:meta/meta.dart';
9
Devon Carew67046f92016-02-20 22:00:11 -080010import '../application_package.dart';
Dan Rubele20ee042016-10-13 10:44:20 -040011import '../base/common.dart';
Yegor Jbanov677e63b2016-02-25 15:58:09 -080012import '../base/context.dart';
Todd Volkert8bb27032017-01-06 16:51:44 -080013import '../base/file_system.dart';
Todd Volkert016b5ab2017-01-09 08:37:00 -080014import '../base/io.dart';
Devon Carew67046f92016-02-20 22:00:11 -080015import '../base/process.dart';
Devon Carew9d9836f2018-07-09 12:22:46 -070016import '../base/utils.dart';
Jason Simmonsa590ee22016-05-12 12:22:15 -070017import '../build_info.dart';
Emmanuel Garciad9c19622019-06-11 14:46:00 -070018import '../bundle.dart';
Jonah Williams91fd89e2019-01-25 16:16:26 -080019import '../convert.dart';
Devon Carew67046f92016-02-20 22:00:11 -080020import '../device.dart';
Jonah Williamsee7a37f2020-01-06 11:04:20 -080021import '../globals.dart' as globals;
Jonah Williams6b191842019-04-25 12:25:12 -070022import '../project.dart';
Devon Carew40c0d6e2016-05-12 18:15:23 -070023import '../protocol_discovery.dart';
Chris Bracken615410d2017-06-15 16:11:08 -070024import 'ios_workflow.dart';
Devon Carew67046f92016-02-20 22:00:11 -080025import 'mac.dart';
Todd Volkertc22ce952019-08-16 17:10:07 -070026import 'plist_parser.dart';
Devon Carew67046f92016-02-20 22:00:11 -080027
28const String _xcrunPath = '/usr/bin/xcrun';
Danny Tuppenyf3be1d92019-06-26 16:39:23 +010029const String iosSimulatorId = 'apple_ios_simulator';
Devon Carew67046f92016-02-20 22:00:11 -080030
Devon Carew67046f92016-02-20 22:00:11 -080031class IOSSimulators extends PollingDeviceDiscovery {
Todd Volkert6a4b08b2017-05-03 16:12:08 -070032 IOSSimulators() : super('iOS simulators');
Devon Carew67046f92016-02-20 22:00:11 -080033
Hixie797e27e2016-03-14 13:31:43 -070034 @override
Jonah Williamsee7a37f2020-01-06 11:04:20 -080035 bool get supportsPlatform => globals.platform.isMacOS;
Hixie797e27e2016-03-14 13:31:43 -070036
37 @override
Chris Bracken615410d2017-06-15 16:11:08 -070038 bool get canListAnything => iosWorkflow.canListDevices;
Todd Volkert6a4b08b2017-05-03 16:12:08 -070039
40 @override
Chris Bracken1d9f0092017-06-19 13:14:57 -070041 Future<List<Device>> pollingGetDevices() async => IOSSimulatorUtils.instance.getAttachedDevices();
Yegor Jbanov677e63b2016-02-25 15:58:09 -080042}
43
44class IOSSimulatorUtils {
45 /// Returns [IOSSimulatorUtils] active in the current app context (i.e. zone).
Jonah Williams0acd3e62019-04-25 15:51:08 -070046 static IOSSimulatorUtils get instance => context.get<IOSSimulatorUtils>();
Yegor Jbanov677e63b2016-02-25 15:58:09 -080047
Jonah Williams21858252019-08-10 00:57:23 -070048 Future<List<IOSSimulator>> getAttachedDevices() async {
Jenn Magder91f79022020-01-25 11:18:02 -080049 if (!globals.xcode.isInstalledAndMeetsVersionCheck) {
Yegor Jbanov677e63b2016-02-25 15:58:09 -080050 return <IOSSimulator>[];
Zachary Andersone2340c62019-09-13 14:51:35 -070051 }
Yegor Jbanov677e63b2016-02-25 15:58:09 -080052
Jonah Williams21858252019-08-10 00:57:23 -070053 final List<SimDevice> connected = await SimControl.instance.getConnectedDevices();
54 return connected.map<IOSSimulator>((SimDevice device) {
Jonah Williams5c524982019-06-18 15:23:14 -070055 return IOSSimulator(device.udid, name: device.name, simulatorCategory: device.category);
Yegor Jbanov677e63b2016-02-25 15:58:09 -080056 }).toList();
57 }
Devon Carew67046f92016-02-20 22:00:11 -080058}
59
60/// A wrapper around the `simctl` command line tool.
61class SimControl {
Yegor Jbanov677e63b2016-02-25 15:58:09 -080062 /// Returns [SimControl] active in the current app context (i.e. zone).
Jonah Williams0acd3e62019-04-25 15:51:08 -070063 static SimControl get instance => context.get<SimControl>();
Yegor Jbanov677e63b2016-02-25 15:58:09 -080064
Yegor Jbanov93834662016-03-10 11:04:02 -080065 /// Runs `simctl list --json` and returns the JSON of the corresponding
66 /// [section].
Jonah Williams21858252019-08-10 00:57:23 -070067 Future<Map<String, dynamic>> _list(SimControlListSection section) async {
Yegor Jbanov93834662016-03-10 11:04:02 -080068 // Sample output from `simctl list --json`:
69 //
Devon Carew67046f92016-02-20 22:00:11 -080070 // {
Yegor Jbanov93834662016-03-10 11:04:02 -080071 // "devicetypes": { ... },
72 // "runtimes": { ... },
Devon Carew67046f92016-02-20 22:00:11 -080073 // "devices" : {
74 // "com.apple.CoreSimulator.SimRuntime.iOS-8-2" : [
75 // {
76 // "state" : "Shutdown",
77 // "availability" : " (unavailable, runtime profile not found)",
78 // "name" : "iPhone 4s",
79 // "udid" : "1913014C-6DCB-485D-AC6B-7CD76D322F5B"
80 // },
81 // ...
Yegor Jbanov93834662016-03-10 11:04:02 -080082 // },
83 // "pairs": { ... },
Devon Carew67046f92016-02-20 22:00:11 -080084
Chris Bracken7a093162017-03-03 17:50:46 -080085 final List<String> command = <String>[_xcrunPath, 'simctl', 'list', '--json', section.name];
Jonah Williamsee7a37f2020-01-06 11:04:20 -080086 globals.printTrace(command.join(' '));
87 final ProcessResult results = await globals.processManager.run(command);
Devon Carew67046f92016-02-20 22:00:11 -080088 if (results.exitCode != 0) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -080089 globals.printError('Error executing simctl: ${results.exitCode}\n${results.stderr}');
Yegor Jbanov93834662016-03-10 11:04:02 -080090 return <String, Map<String, dynamic>>{};
Devon Carew67046f92016-02-20 22:00:11 -080091 }
Jonah Williams21858252019-08-10 00:57:23 -070092 try {
Jonah Williamsca5411e2019-08-12 15:21:28 -070093 final Object decodeResult = json.decode(results.stdout?.toString())[section.name];
94 if (decodeResult is Map<String, dynamic>) {
95 return decodeResult;
96 }
Jonah Williamsee7a37f2020-01-06 11:04:20 -080097 globals.printError('simctl returned unexpected JSON response: ${results.stdout}');
Jonah Williamsca5411e2019-08-12 15:21:28 -070098 return <String, dynamic>{};
Jonah Williams21858252019-08-10 00:57:23 -070099 } on FormatException {
100 // We failed to parse the simctl output, or it returned junk.
101 // One known message is "Install Started" isn't valid JSON but is
102 // returned sometimes.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800103 globals.printError('simctl returned non-JSON response: ${results.stdout}');
Jonah Williams21858252019-08-10 00:57:23 -0700104 return <String, dynamic>{};
105 }
Yegor Jbanov93834662016-03-10 11:04:02 -0800106 }
107
108 /// Returns a list of all available devices, both potential and connected.
Jonah Williams21858252019-08-10 00:57:23 -0700109 Future<List<SimDevice>> getDevices() async {
Chris Bracken7a093162017-03-03 17:50:46 -0800110 final List<SimDevice> devices = <SimDevice>[];
Devon Carew67046f92016-02-20 22:00:11 -0800111
Jonah Williams21858252019-08-10 00:57:23 -0700112 final Map<String, dynamic> devicesSection = await _list(SimControlListSection.devices);
Devon Carew67046f92016-02-20 22:00:11 -0800113
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100114 for (final String deviceCategory in devicesSection.keys) {
Jonah Williamsca5411e2019-08-12 15:21:28 -0700115 final Object devicesData = devicesSection[deviceCategory];
116 if (devicesData != null && devicesData is List<dynamic>) {
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100117 for (final Map<String, dynamic> data in devicesData.map<Map<String, dynamic>>(castStringKeyedMap)) {
Jonah Williamsca5411e2019-08-12 15:21:28 -0700118 devices.add(SimDevice(deviceCategory, data));
119 }
Devon Carew67046f92016-02-20 22:00:11 -0800120 }
121 }
122
123 return devices;
124 }
125
126 /// Returns all the connected simulator devices.
Jonah Williams21858252019-08-10 00:57:23 -0700127 Future<List<SimDevice>> getConnectedDevices() async {
128 final List<SimDevice> simDevices = await getDevices();
129 return simDevices.where((SimDevice device) => device.isBooted).toList();
Devon Carew67046f92016-02-20 22:00:11 -0800130 }
131
xster432ffde2017-06-15 18:25:09 -0700132 Future<bool> isInstalled(String deviceId, String appId) {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700133 return processUtils.exitsHappy(<String>[
Todd Volkert51d4ab32016-05-27 11:05:10 -0700134 _xcrunPath,
135 'simctl',
136 'get_app_container',
xster432ffde2017-06-15 18:25:09 -0700137 deviceId,
Todd Volkert51d4ab32016-05-27 11:05:10 -0700138 appId,
139 ]);
140 }
141
Devon Carew9d9836f2018-07-09 12:22:46 -0700142 Future<RunResult> install(String deviceId, String appPath) {
Greg Spencerc81f4c72018-10-08 19:11:31 -0700143 Future<RunResult> result;
144 try {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700145 result = processUtils.run(
146 <String>[_xcrunPath, 'simctl', 'install', deviceId, appPath],
147 throwOnError: true,
148 );
Greg Spencerc81f4c72018-10-08 19:11:31 -0700149 } on ProcessException catch (exception) {
150 throwToolExit('Unable to install $appPath on $deviceId:\n$exception');
151 }
152 return result;
Devon Carew67046f92016-02-20 22:00:11 -0800153 }
154
Devon Carew9d9836f2018-07-09 12:22:46 -0700155 Future<RunResult> uninstall(String deviceId, String appId) {
Greg Spencerc81f4c72018-10-08 19:11:31 -0700156 Future<RunResult> result;
157 try {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700158 result = processUtils.run(
159 <String>[_xcrunPath, 'simctl', 'uninstall', deviceId, appId],
160 throwOnError: true,
161 );
Greg Spencerc81f4c72018-10-08 19:11:31 -0700162 } on ProcessException catch (exception) {
163 throwToolExit('Unable to uninstall $appId from $deviceId:\n$exception');
164 }
165 return result;
Todd Volkert51d4ab32016-05-27 11:05:10 -0700166 }
167
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100168 Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String> launchArgs ]) {
Greg Spencerc81f4c72018-10-08 19:11:31 -0700169 Future<RunResult> result;
170 try {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700171 result = processUtils.run(
172 <String>[
173 _xcrunPath,
174 'simctl',
175 'launch',
176 deviceId,
177 appIdentifier,
178 ...?launchArgs,
179 ],
180 throwOnError: true,
181 );
Greg Spencerc81f4c72018-10-08 19:11:31 -0700182 } on ProcessException catch (exception) {
183 throwToolExit('Unable to launch $appIdentifier on $deviceId:\n$exception');
184 }
185 return result;
Devon Carew67046f92016-02-20 22:00:11 -0800186 }
xster3985ddb2017-02-16 12:50:27 -0800187
Devon Carew9d9836f2018-07-09 12:22:46 -0700188 Future<void> takeScreenshot(String deviceId, String outputPath) async {
Greg Spencerc81f4c72018-10-08 19:11:31 -0700189 try {
Zachary Anderson73c10e82019-09-11 18:20:42 -0700190 await processUtils.run(
191 <String>[_xcrunPath, 'simctl', 'io', deviceId, 'screenshot', outputPath],
192 throwOnError: true,
193 );
Greg Spencerc81f4c72018-10-08 19:11:31 -0700194 } on ProcessException catch (exception) {
195 throwToolExit('Unable to take screenshot of $deviceId:\n$exception');
196 }
xster3985ddb2017-02-16 12:50:27 -0800197 }
Devon Carew67046f92016-02-20 22:00:11 -0800198}
199
Yegor Jbanov93834662016-03-10 11:04:02 -0800200/// Enumerates all data sections of `xcrun simctl list --json` command.
201class SimControlListSection {
Ian Hicksond745e202016-03-12 00:32:34 -0800202 const SimControlListSection._(this.name);
Yegor Jbanov93834662016-03-10 11:04:02 -0800203
204 final String name;
Ian Hicksond745e202016-03-12 00:32:34 -0800205
Alexandre Ardhuineda03e22018-08-02 12:02:32 +0200206 static const SimControlListSection devices = SimControlListSection._('devices');
207 static const SimControlListSection devicetypes = SimControlListSection._('devicetypes');
208 static const SimControlListSection runtimes = SimControlListSection._('runtimes');
209 static const SimControlListSection pairs = SimControlListSection._('pairs');
Yegor Jbanov93834662016-03-10 11:04:02 -0800210}
211
Yegor Jbanov225686b2016-03-14 22:31:32 -0700212/// A simulated device type.
213///
214/// Simulated device types can be listed using the command
215/// `xcrun simctl list devicetypes`.
216class SimDeviceType {
217 SimDeviceType(this.name, this.identifier);
218
219 /// The name of the device type.
220 ///
221 /// Examples:
222 ///
223 /// "iPhone 6s"
224 /// "iPhone 6 Plus"
225 final String name;
226
227 /// The identifier of the device type.
228 ///
229 /// Examples:
230 ///
231 /// "com.apple.CoreSimulator.SimDeviceType.iPhone-6s"
232 /// "com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus"
233 final String identifier;
234}
235
Devon Carew67046f92016-02-20 22:00:11 -0800236class SimDevice {
237 SimDevice(this.category, this.data);
238
239 final String category;
Mark Fielbigaf5d4c62018-06-30 17:17:35 -0700240 final Map<String, dynamic> data;
Devon Carew67046f92016-02-20 22:00:11 -0800241
Jonah Williamsca5411e2019-08-12 15:21:28 -0700242 String get state => data['state']?.toString();
243 String get availability => data['availability']?.toString();
244 String get name => data['name']?.toString();
245 String get udid => data['udid']?.toString();
Devon Carew67046f92016-02-20 22:00:11 -0800246
247 bool get isBooted => state == 'Booted';
248}
249
250class IOSSimulator extends Device {
Jonah Williams5c524982019-06-18 15:23:14 -0700251 IOSSimulator(String id, { this.name, this.simulatorCategory }) : super(
252 id,
253 category: Category.mobile,
254 platformType: PlatformType.ios,
255 ephemeral: true,
256 );
Devon Carew67046f92016-02-20 22:00:11 -0800257
Hixie797e27e2016-03-14 13:31:43 -0700258 @override
Devon Carew67046f92016-02-20 22:00:11 -0800259 final String name;
260
Jonah Williams5c524982019-06-18 15:23:14 -0700261 final String simulatorCategory;
Dan Rubel0b49d812016-10-27 09:07:21 +0100262
Hixie797e27e2016-03-14 13:31:43 -0700263 @override
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700264 Future<bool> get isLocalEmulator async => true;
Yegor Jbanov677e63b2016-02-25 15:58:09 -0800265
John McCutchan0de69162016-07-20 12:59:30 -0700266 @override
Danny Tuppenyf3be1d92019-06-26 16:39:23 +0100267 Future<String> get emulatorId async => iosSimulatorId;
268
269 @override
Jonah Williamsc11633e2018-11-09 10:33:22 -0800270 bool get supportsHotReload => true;
271
272 @override
273 bool get supportsHotRestart => true;
John McCutchan0de69162016-07-20 12:59:30 -0700274
Todd Volkert9cb914d2016-11-16 17:19:00 -0800275 Map<ApplicationPackage, _IOSSimulatorLogReader> _logReaders;
John McCutchan5e140b72016-03-10 15:48:37 -0800276 _IOSSimulatorDevicePortForwarder _portForwarder;
John McCutchan8803cec2016-03-07 13:52:27 -0800277
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800278 String get xcrunPath => globals.fs.path.join('/usr', 'bin', 'xcrun');
Devon Carew67046f92016-02-20 22:00:11 -0800279
Devon Carew67046f92016-02-20 22:00:11 -0800280 @override
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700281 Future<bool> isAppInstalled(ApplicationPackage app) {
xster432ffde2017-06-15 18:25:09 -0700282 return SimControl.instance.isInstalled(id, app.id);
Todd Volkert51d4ab32016-05-27 11:05:10 -0700283 }
284
285 @override
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700286 Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
Jakob Andersena745fd52017-02-22 14:35:49 +0100287
288 @override
Jonah Williamsca5411e2019-08-12 15:21:28 -0700289 Future<bool> installApp(covariant IOSApp app) async {
Devon Carew67046f92016-02-20 22:00:11 -0800290 try {
Chris Bracken7a093162017-03-03 17:50:46 -0800291 final IOSApp iosApp = app;
xstere28765a2017-09-20 17:24:43 -0700292 await SimControl.instance.install(id, iosApp.simulatorBundlePath);
Devon Carew67046f92016-02-20 22:00:11 -0800293 return true;
Zachary Anderson483f4992020-02-26 23:42:19 -0800294 } catch (e) {
Devon Carew67046f92016-02-20 22:00:11 -0800295 return false;
296 }
297 }
298
Devon Carew67046f92016-02-20 22:00:11 -0800299 @override
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700300 Future<bool> uninstallApp(ApplicationPackage app) async {
Todd Volkert51d4ab32016-05-27 11:05:10 -0700301 try {
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700302 await SimControl.instance.uninstall(id, app.id);
Todd Volkert51d4ab32016-05-27 11:05:10 -0700303 return true;
Zachary Anderson483f4992020-02-26 23:42:19 -0800304 } catch (e) {
Todd Volkert51d4ab32016-05-27 11:05:10 -0700305 return false;
306 }
307 }
308
309 @override
Devon Carew67046f92016-02-20 22:00:11 -0800310 bool isSupported() {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800311 if (!globals.platform.isMacOS) {
Chris Brackencce70d72017-02-03 12:54:07 -0800312 _supportMessage = 'iOS devices require a Mac host machine.';
Devon Carew67046f92016-02-20 22:00:11 -0800313 return false;
314 }
315
Chris Brackena7516782018-05-09 13:34:39 -0700316 // Check if the device is part of a blacklisted category.
317 // We do not yet support WatchOS or tvOS devices.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200318 final RegExp blacklist = RegExp(r'Apple (TV|Watch)', caseSensitive: false);
Devon Carew67046f92016-02-20 22:00:11 -0800319 if (blacklist.hasMatch(name)) {
Chris Brackena7516782018-05-09 13:34:39 -0700320 _supportMessage = 'Flutter does not support Apple TV or Apple Watch.';
Devon Carew67046f92016-02-20 22:00:11 -0800321 return false;
322 }
Chris Brackenfb72f212017-02-03 12:25:05 -0800323 return true;
Devon Carew67046f92016-02-20 22:00:11 -0800324 }
325
326 String _supportMessage;
327
328 @override
329 String supportMessage() {
Zachary Andersone2340c62019-09-13 14:51:35 -0700330 if (isSupported()) {
Chris Bracken104fcf82017-02-03 11:50:49 -0800331 return 'Supported';
Zachary Andersone2340c62019-09-13 14:51:35 -0700332 }
Devon Carew67046f92016-02-20 22:00:11 -0800333
Alexandre Ardhuin4fa32df2019-05-16 22:25:51 +0200334 return _supportMessage ?? 'Unknown';
Devon Carew67046f92016-02-20 22:00:11 -0800335 }
336
337 @override
Devon Carewb0dca792016-04-27 14:43:42 -0700338 Future<LaunchResult> startApp(
Jonah Williamsca5411e2019-08-12 15:21:28 -0700339 covariant IOSApp package, {
Devon Carew67046f92016-02-20 22:00:11 -0800340 String mainPath,
341 String route,
Devon Carewb0dca792016-04-27 14:43:42 -0700342 DebuggingOptions debuggingOptions,
John McCutchanca8070f2016-09-28 08:46:16 -0700343 Map<String, dynamic> platformArgs,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200344 bool prebuiltApplication = false,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200345 bool ipv6 = false,
Devon Carew67046f92016-02-20 22:00:11 -0800346 }) async {
Jonah Williamsca5411e2019-08-12 15:21:28 -0700347 if (!prebuiltApplication && package is BuildableIOSApp) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800348 globals.printTrace('Building ${package.name} for $id.');
Devon Carew67046f92016-02-20 22:00:11 -0800349
Dan Rubele384c0d2016-11-13 22:09:03 -0500350 try {
Jonah Williams6f71ce22019-08-27 14:20:51 -0700351 await _setupUpdatedApplicationBundle(package, debuggingOptions.buildInfo, mainPath);
Todd Volkert75364042016-12-06 10:09:00 -0800352 } on ToolExit catch (e) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800353 globals.printError(e.message);
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200354 return LaunchResult.failed();
Dan Rubele384c0d2016-11-13 22:09:03 -0500355 }
John McCutchanb8403c72016-11-23 07:33:33 -0800356 } else {
Zachary Andersone2340c62019-09-13 14:51:35 -0700357 if (!await installApp(package)) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200358 return LaunchResult.failed();
Zachary Andersone2340c62019-09-13 14:51:35 -0700359 }
Todd Volkert904d5242016-10-13 16:17:50 -0700360 }
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800361
362 // Prepare launch arguments.
Alexandre Ardhuindf4bf452019-09-17 16:23:44 +0200363 final List<String> args = <String>[
364 '--enable-dart-profiling',
365 if (debuggingOptions.debuggingEnabled) ...<String>[
366 if (debuggingOptions.buildInfo.isDebug) ...<String>[
sjindel-google95b34d72019-01-30 16:33:55 +0100367 '--enable-checked-mode',
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100368 '--verify-entry-points',
Alexandre Ardhuindf4bf452019-09-17 16:23:44 +0200369 ],
370 if (debuggingOptions.startPaused) '--start-paused',
371 if (debuggingOptions.disableServiceAuthCodes) '--disable-service-auth-codes',
372 if (debuggingOptions.skiaDeterministicRendering) '--skia-deterministic-rendering',
373 if (debuggingOptions.useTestFonts) '--use-test-fonts',
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800374 '--observatory-port=${debuggingOptions.hostVmServicePort ?? 0}',
Alexandre Ardhuin89427d62019-09-24 08:06:09 +0200375 ],
Alexandre Ardhuindf4bf452019-09-17 16:23:44 +0200376 ];
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800377
Dan Rubelb265a8f2016-12-06 12:41:55 -0800378 ProtocolDiscovery observatoryDiscovery;
Zachary Andersone2340c62019-09-13 14:51:35 -0700379 if (debuggingOptions.debuggingEnabled) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200380 observatoryDiscovery = ProtocolDiscovery.observatory(
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800381 getLogReader(app: package),
382 ipv6: ipv6,
383 hostPort: debuggingOptions.hostVmServicePort,
384 devicePort: debuggingOptions.deviceVmServicePort,
385 );
Zachary Andersone2340c62019-09-13 14:51:35 -0700386 }
Dan Rubelb265a8f2016-12-06 12:41:55 -0800387
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800388 // Launch the updated application in the simulator.
389 try {
Victor Maraccini74c62372019-05-01 14:21:43 -0300390 // Use the built application's Info.plist to get the bundle identifier,
391 // which should always yield the correct value and does not require
392 // parsing the xcodeproj or configuration files.
393 // See https://github.com/flutter/flutter/issues/31037 for more information.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800394 final String plistPath = globals.fs.path.join(package.simulatorBundlePath, 'Info.plist');
Jonah Williams80619f12020-02-26 18:31:42 -0800395 final String bundleIdentifier = globals.plistParser.getValueFromFile(plistPath, PlistParser.kCFBundleIdentifierKey);
Victor Maraccini74c62372019-05-01 14:21:43 -0300396
397 await SimControl.instance.launch(id, bundleIdentifier, args);
Zachary Anderson483f4992020-02-26 23:42:19 -0800398 } catch (error) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800399 globals.printError('$error');
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200400 return LaunchResult.failed();
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800401 }
402
Devon Carewb0dca792016-04-27 14:43:42 -0700403 if (!debuggingOptions.debuggingEnabled) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200404 return LaunchResult.succeeded();
Dan Rubel93e662a2016-12-06 09:19:12 -0800405 }
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800406
Dan Rubel93e662a2016-12-06 09:19:12 -0800407 // Wait for the service protocol port here. This will complete once the
408 // device has printed "Observatory is listening on..."
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800409 globals.printTrace('Waiting for observatory port to be available...');
Dan Rubel93e662a2016-12-06 09:19:12 -0800410
Dan Rubel93e662a2016-12-06 09:19:12 -0800411 try {
Todd Volkert10decc72017-05-16 08:25:51 -0700412 final Uri deviceUri = await observatoryDiscovery.uri;
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200413 return LaunchResult.succeeded(observatoryUri: deviceUri);
Zachary Anderson483f4992020-02-26 23:42:19 -0800414 } catch (error) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800415 globals.printError('Error waiting for a debug connection: $error');
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200416 return LaunchResult.failed();
Dan Rubel93e662a2016-12-06 09:19:12 -0800417 } finally {
xstere28765a2017-09-20 17:24:43 -0700418 await observatoryDiscovery.cancel();
Devon Carew2fc8e9a2016-03-25 11:49:07 -0700419 }
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800420 }
421
Jonah Williams6f71ce22019-08-27 14:20:51 -0700422 Future<void> _setupUpdatedApplicationBundle(covariant BuildableIOSApp app, BuildInfo buildInfo, String mainPath) async {
Jonah Williams79323f72019-10-30 19:40:19 -0700423 await sideloadUpdatedAssetsForInstalledApplicationBundle(buildInfo, mainPath);
Yegor Jbanov58b2c632016-03-03 15:58:34 -0800424
Devon Carew67046f92016-02-20 22:00:11 -0800425 // Step 1: Build the Xcode project.
Chinmay Garde66fee3a2016-05-23 12:58:42 -0700426 // The build mode for the simulator is always debug.
Alexander Aprelev99176572018-01-11 13:24:51 -0800427
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200428 final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor,
Jacob Richman62febaa2018-05-30 14:53:35 -0700429 trackWidgetCreation: buildInfo.trackWidgetCreation,
Alexander Aprelev99176572018-01-11 13:24:51 -0800430 extraFrontEndOptions: buildInfo.extraFrontEndOptions,
Dan Field4b8efad2020-02-04 20:34:24 -0800431 extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
432 treeShakeIcons: buildInfo.treeShakeIcons);
Alexander Aprelev99176572018-01-11 13:24:51 -0800433
Simon Lightfootbe6501a2018-05-21 11:54:38 +0100434 final XcodeBuildResult buildResult = await buildXcodeProject(
435 app: app,
436 buildInfo: debugBuildInfo,
437 targetOverride: mainPath,
438 buildForDevice: false,
Simon Lightfootbe6501a2018-05-21 11:54:38 +0100439 );
Zachary Andersone2340c62019-09-13 14:51:35 -0700440 if (!buildResult.success) {
Dan Rubele384c0d2016-11-13 22:09:03 -0500441 throwToolExit('Could not build the application for the simulator.');
Zachary Andersone2340c62019-09-13 14:51:35 -0700442 }
Devon Carew67046f92016-02-20 22:00:11 -0800443
444 // Step 2: Assert that the Xcode project was successfully built.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800445 final Directory bundle = globals.fs.directory(app.simulatorBundlePath);
Chris Bracken7a093162017-03-03 17:50:46 -0800446 final bool bundleExists = bundle.existsSync();
Zachary Andersone2340c62019-09-13 14:51:35 -0700447 if (!bundleExists) {
Dan Rubele384c0d2016-11-13 22:09:03 -0500448 throwToolExit('Could not find the built application bundle at ${bundle.path}.');
Zachary Andersone2340c62019-09-13 14:51:35 -0700449 }
Devon Carew67046f92016-02-20 22:00:11 -0800450
451 // Step 3: Install the updated bundle to the simulator.
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800452 await SimControl.instance.install(id, globals.fs.path.absolute(bundle.path));
Devon Carew67046f92016-02-20 22:00:11 -0800453 }
454
Jonah Williams79323f72019-10-30 19:40:19 -0700455 @visibleForTesting
456 Future<void> sideloadUpdatedAssetsForInstalledApplicationBundle(BuildInfo buildInfo, String mainPath) {
Chris Brackened0b8be2018-09-04 10:05:26 -0700457 // Run compiler to produce kernel file for the application.
Emmanuel Garciad9c19622019-06-11 14:46:00 -0700458 return BundleBuilder().build(
Jonah Williams79323f72019-10-30 19:40:19 -0700459 platform: TargetPlatform.ios,
Simon Lightfootbe6501a2018-05-21 11:54:38 +0100460 mainPath: mainPath,
Chris Brackened0b8be2018-09-04 10:05:26 -0700461 precompiledSnapshot: false,
Ryan Macnakb7773da2019-10-14 15:26:14 -0700462 buildMode: buildInfo.mode,
Jacob Richman2c05eca2018-02-12 10:44:31 -0800463 trackWidgetCreation: buildInfo.trackWidgetCreation,
Dan Field4b8efad2020-02-04 20:34:24 -0800464 treeShakeIcons: false,
Jacob Richman2c05eca2018-02-12 10:44:31 -0800465 );
466 }
Chinmay Garde9f4fdcb2016-03-02 11:40:10 -0800467
Devon Carew67046f92016-02-20 22:00:11 -0800468 @override
469 Future<bool> stopApp(ApplicationPackage app) async {
470 // Currently we don't have a way to stop an app running on iOS.
471 return false;
472 }
473
Devon Carew67046f92016-02-20 22:00:11 -0800474 String get logFilePath {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800475 return globals.platform.environment.containsKey('IOS_SIMULATOR_LOG_FILE_PATH')
Zachary Anderson390ed1c2020-02-03 14:33:03 -0800476 ? globals.platform.environment['IOS_SIMULATOR_LOG_FILE_PATH'].replaceAll('%{id}', id)
477 : globals.fs.path.join(
478 globals.fsUtils.homeDirPath,
479 'Library',
480 'Logs',
481 'CoreSimulator',
482 id,
483 'system.log',
484 );
Devon Carew67046f92016-02-20 22:00:11 -0800485 }
486
487 @override
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700488 Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios;
Devon Carew67046f92016-02-20 22:00:11 -0800489
Hixie797e27e2016-03-14 13:31:43 -0700490 @override
Jonah Williams5c524982019-06-18 15:23:14 -0700491 Future<String> get sdkNameAndVersion async => simulatorCategory;
Dan Rubel0b49d812016-10-27 09:07:21 +0100492
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200493 final RegExp _iosSdkRegExp = RegExp(r'iOS( |-)(\d+)');
Todd Volkert137f0752017-11-29 08:22:05 -0800494
495 Future<int> get sdkMajorVersion async {
496 final Match sdkMatch = _iosSdkRegExp.firstMatch(await sdkNameAndVersion);
Jonah Williamsca5411e2019-08-12 15:21:28 -0700497 return int.parse(sdkMatch?.group(2) ?? '11');
Todd Volkert137f0752017-11-29 08:22:05 -0800498 }
499
Dan Rubel0b49d812016-10-27 09:07:21 +0100500 @override
Jonah Williamsca5411e2019-08-12 15:21:28 -0700501 DeviceLogReader getLogReader({ covariant IOSApp app }) {
Todd Volkertce594122017-09-28 13:11:16 -0700502 assert(app is IOSApp);
Todd Volkert9cb914d2016-11-16 17:19:00 -0800503 _logReaders ??= <ApplicationPackage, _IOSSimulatorLogReader>{};
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200504 return _logReaders.putIfAbsent(app, () => _IOSSimulatorLogReader(this, app));
John McCutchan8803cec2016-03-07 13:52:27 -0800505 }
Devon Carew67046f92016-02-20 22:00:11 -0800506
Hixie797e27e2016-03-14 13:31:43 -0700507 @override
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200508 DevicePortForwarder get portForwarder => _portForwarder ??= _IOSSimulatorDevicePortForwarder(this);
John McCutchan5e140b72016-03-10 15:48:37 -0800509
Hixie797e27e2016-03-14 13:31:43 -0700510 @override
Devon Carew67046f92016-02-20 22:00:11 -0800511 void clearLogs() {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800512 final File logFile = globals.fs.file(logFilePath);
Devon Carew67046f92016-02-20 22:00:11 -0800513 if (logFile.existsSync()) {
Leaf Petersen32f94442018-07-20 15:07:24 -0700514 final RandomAccessFile randomFile = logFile.openSync(mode: FileMode.write);
Devon Carew67046f92016-02-20 22:00:11 -0800515 randomFile.truncateSync(0);
516 randomFile.closeSync();
517 }
518 }
519
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200520 Future<void> ensureLogsExists() async {
Todd Volkert137f0752017-11-29 08:22:05 -0800521 if (await sdkMajorVersion < 11) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800522 final File logFile = globals.fs.file(logFilePath);
Zachary Andersone2340c62019-09-13 14:51:35 -0700523 if (!logFile.existsSync()) {
Todd Volkert137f0752017-11-29 08:22:05 -0800524 logFile.writeAsBytesSync(<int>[]);
Zachary Andersone2340c62019-09-13 14:51:35 -0700525 }
Todd Volkert137f0752017-11-29 08:22:05 -0800526 }
Devon Carew67046f92016-02-20 22:00:11 -0800527 }
Devon Carew15b9e1d2016-03-25 16:04:22 -0700528
xster3985ddb2017-02-16 12:50:27 -0800529 bool get _xcodeVersionSupportsScreenshot {
Jenn Magder91f79022020-01-25 11:18:02 -0800530 return globals.xcode.majorVersion > 8 || (globals.xcode.majorVersion == 8 && globals.xcode.minorVersion >= 2);
xster3985ddb2017-02-16 12:50:27 -0800531 }
Devon Carew15b9e1d2016-03-25 16:04:22 -0700532
533 @override
xster3985ddb2017-02-16 12:50:27 -0800534 bool get supportsScreenshot => _xcodeVersionSupportsScreenshot;
Devon Carew15b9e1d2016-03-25 16:04:22 -0700535
xster3985ddb2017-02-16 12:50:27 -0800536 @override
Keerti Parthasarathyaecb7d92018-06-12 09:30:10 -0700537 Future<void> takeScreenshot(File outputFile) {
xster432ffde2017-06-15 18:25:09 -0700538 return SimControl.instance.takeScreenshot(id, outputFile.path);
Devon Carew15b9e1d2016-03-25 16:04:22 -0700539 }
Jonah Williams6b191842019-04-25 12:25:12 -0700540
541 @override
542 bool isSupportedForProject(FlutterProject flutterProject) {
543 return flutterProject.ios.existsSync();
544 }
Zachary Anderson99684ce2019-12-05 08:48:00 -0800545
546 @override
547 Future<void> dispose() async {
548 _logReaders?.forEach(
549 (ApplicationPackage application, _IOSSimulatorLogReader logReader) {
550 logReader.dispose();
551 },
552 );
553 await _portForwarder?.dispose();
554 }
Devon Carew67046f92016-02-20 22:00:11 -0800555}
556
Chris Brackendd7e3132017-09-14 12:28:21 -0700557/// Launches the device log reader process on the host.
558Future<Process> launchDeviceLogTool(IOSSimulator device) async {
Chris Brackendd7e3132017-09-14 12:28:21 -0700559 // Versions of iOS prior to iOS 11 log to the simulator syslog file.
Zachary Anderson73c10e82019-09-11 18:20:42 -0700560 if (await device.sdkMajorVersion < 11) {
561 return processUtils.start(<String>['tail', '-n', '0', '-F', device.logFilePath]);
562 }
Chris Brackendd7e3132017-09-14 12:28:21 -0700563
564 // For iOS 11 and above, use /usr/bin/log to tail process logs.
565 // Run in interactive mode (via script), otherwise /usr/bin/log buffers in 4k chunks. (radar: 34420207)
Zachary Anderson73c10e82019-09-11 18:20:42 -0700566 return processUtils.start(<String>[
Chris Brackendd7e3132017-09-14 12:28:21 -0700567 'script', '/dev/null', '/usr/bin/log', 'stream', '--style', 'syslog', '--predicate', 'processImagePath CONTAINS "${device.id}"',
568 ]);
569}
570
571Future<Process> launchSystemLogTool(IOSSimulator device) async {
Chris Brackendd7e3132017-09-14 12:28:21 -0700572 // Versions of iOS prior to 11 tail the simulator syslog file.
Zachary Anderson73c10e82019-09-11 18:20:42 -0700573 if (await device.sdkMajorVersion < 11) {
574 return processUtils.start(<String>['tail', '-n', '0', '-F', '/private/var/log/system.log']);
575 }
Chris Brackendd7e3132017-09-14 12:28:21 -0700576
577 // For iOS 11 and later, all relevant detail is in the device log.
578 return null;
579}
580
Devon Carew67046f92016-02-20 22:00:11 -0800581class _IOSSimulatorLogReader extends DeviceLogReader {
Todd Volkertce594122017-09-28 13:11:16 -0700582 _IOSSimulatorLogReader(this.device, IOSApp app) {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200583 _linesController = StreamController<String>.broadcast(
Alexandre Ardhuin2166ea52017-03-15 23:09:58 +0100584 onListen: _start,
Alexandre Ardhuin387f8852019-03-01 08:17:55 +0100585 onCancel: _stop,
Devon Carewb0dca792016-04-27 14:43:42 -0700586 );
Todd Volkert9cb914d2016-11-16 17:19:00 -0800587 _appName = app == null ? null : app.name.replaceAll('.app', '');
Devon Carewb0dca792016-04-27 14:43:42 -0700588 }
Devon Carew67046f92016-02-20 22:00:11 -0800589
590 final IOSSimulator device;
591
Alexandre Ardhuin2ea1d812018-10-04 07:28:07 +0200592 String _appName;
593
Devon Carewb0dca792016-04-27 14:43:42 -0700594 StreamController<String> _linesController;
Devon Carew67046f92016-02-20 22:00:11 -0800595
Devon Carewb0dca792016-04-27 14:43:42 -0700596 // We log from two files: the device and the system log.
Todd Volkert016b5ab2017-01-09 08:37:00 -0800597 Process _deviceProcess;
598 Process _systemProcess;
John McCutchan8803cec2016-03-07 13:52:27 -0800599
Hixie797e27e2016-03-14 13:31:43 -0700600 @override
Devon Carewb0dca792016-04-27 14:43:42 -0700601 Stream<String> get logLines => _linesController.stream;
John McCutchan8803cec2016-03-07 13:52:27 -0800602
Hixie797e27e2016-03-14 13:31:43 -0700603 @override
Devon Carew67046f92016-02-20 22:00:11 -0800604 String get name => device.name;
605
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200606 Future<void> _start() async {
John McCutchan8803cec2016-03-07 13:52:27 -0800607 // Device log.
Todd Volkert137f0752017-11-29 08:22:05 -0800608 await device.ensureLogsExists();
Chris Brackendd7e3132017-09-14 12:28:21 -0700609 _deviceProcess = await launchDeviceLogTool(device);
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200610 _deviceProcess.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_onDeviceLine);
611 _deviceProcess.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_onDeviceLine);
Devon Carew67046f92016-02-20 22:00:11 -0800612
613 // Track system.log crashes.
614 // ReportCrash[37965]: Saved crash report for FlutterRunner[37941]...
Chris Brackendd7e3132017-09-14 12:28:21 -0700615 _systemProcess = await launchSystemLogTool(device);
616 if (_systemProcess != null) {
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200617 _systemProcess.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_onSystemLine);
618 _systemProcess.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_onSystemLine);
Chris Brackendd7e3132017-09-14 12:28:21 -0700619 }
Devon Carew67046f92016-02-20 22:00:11 -0800620
xstere28765a2017-09-20 17:24:43 -0700621 // We don't want to wait for the process or its callback. Best effort
622 // cleanup in the callback.
xster36c5e322019-02-05 15:00:51 -0800623 unawaited(_deviceProcess.exitCode.whenComplete(() {
Zachary Andersone2340c62019-09-13 14:51:35 -0700624 if (_linesController.hasListener) {
Devon Carewb0dca792016-04-27 14:43:42 -0700625 _linesController.close();
Zachary Andersone2340c62019-09-13 14:51:35 -0700626 }
xster36c5e322019-02-05 15:00:51 -0800627 }));
John McCutchan8803cec2016-03-07 13:52:27 -0800628 }
629
630 // Match the log prefix (in order to shorten it):
Jenn Magder85e9be32019-12-20 17:45:16 -0800631 // * Xcode 8: Sep 13 15:28:51 cbracken-macpro localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/
632 // * Xcode 9: 2017-09-13 15:26:57.228948-0700 localhost Runner[37195]: (Flutter) Observatory listening on http://127.0.0.1:57701/
633 static final RegExp _mapRegex = RegExp(r'\S+ +\S+ +\S+ +(\S+ +)?(\S+)\[\d+\]\)?: (\(.*?\))? *(.*)$');
John McCutchan8803cec2016-03-07 13:52:27 -0800634
635 // Jan 31 19:23:28 --- last message repeated 1 time ---
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200636 static final RegExp _lastMessageSingleRegex = RegExp(r'\S+ +\S+ +\S+ --- last message repeated 1 time ---$');
637 static final RegExp _lastMessageMultipleRegex = RegExp(r'\S+ +\S+ +\S+ --- last message repeated (\d+) times ---$');
John McCutchan8803cec2016-03-07 13:52:27 -0800638
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200639 static final RegExp _flutterRunnerRegex = RegExp(r' FlutterRunner\[\d+\] ');
John McCutchan8803cec2016-03-07 13:52:27 -0800640
641 String _filterDeviceLine(String string) {
Chris Bracken7a093162017-03-03 17:50:46 -0800642 final Match match = _mapRegex.matchAsPrefix(string);
John McCutchan8803cec2016-03-07 13:52:27 -0800643 if (match != null) {
Jenn Magder85e9be32019-12-20 17:45:16 -0800644 final String category = match.group(2);
645 final String tag = match.group(3);
646 final String content = match.group(4);
Chris Brackendd7e3132017-09-14 12:28:21 -0700647
648 // Filter out non-Flutter originated noise from the engine.
Zachary Andersone2340c62019-09-13 14:51:35 -0700649 if (_appName != null && category != _appName) {
Chris Brackendd7e3132017-09-14 12:28:21 -0700650 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700651 }
Chris Brackendd7e3132017-09-14 12:28:21 -0700652
Zachary Andersone2340c62019-09-13 14:51:35 -0700653 if (tag != null && tag != '(Flutter)') {
Chris Brackendd7e3132017-09-14 12:28:21 -0700654 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700655 }
Todd Volkertec7c49e2016-11-22 10:47:24 -0800656
657 // Filter out some messages that clearly aren't related to Flutter.
Zachary Andersone2340c62019-09-13 14:51:35 -0700658 if (string.contains(': could not find icon for representation -> com.apple.')) {
John McCutchan8803cec2016-03-07 13:52:27 -0800659 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700660 }
John McCutchan8803cec2016-03-07 13:52:27 -0800661
Devon Carewfa043212017-02-22 20:55:08 -0800662 // assertion failed: 15G1212 13E230: libxpc.dylib + 57882 [66C28065-C9DB-3C8E-926F-5A40210A6D1B]: 0x7d
Zachary Andersone2340c62019-09-13 14:51:35 -0700663 if (content.startsWith('assertion failed: ') && content.contains(' libxpc.dylib ')) {
Devon Carewfa043212017-02-22 20:55:08 -0800664 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700665 }
Devon Carewfa043212017-02-22 20:55:08 -0800666
Zachary Andersone2340c62019-09-13 14:51:35 -0700667 if (_appName == null) {
Todd Volkertec7c49e2016-11-22 10:47:24 -0800668 return '$category: $content';
Zachary Andersone2340c62019-09-13 14:51:35 -0700669 } else if (category == _appName) {
John McCutchan8803cec2016-03-07 13:52:27 -0800670 return content;
Zachary Andersone2340c62019-09-13 14:51:35 -0700671 }
Todd Volkertec7c49e2016-11-22 10:47:24 -0800672
Devon Carew7ae584b2016-06-03 12:54:27 -0700673 return null;
Todd Volkertec7c49e2016-11-22 10:47:24 -0800674 }
675
Zachary Andersone2340c62019-09-13 14:51:35 -0700676 if (string.startsWith('Filtering the log data using ')) {
Chris Brackendd7e3132017-09-14 12:28:21 -0700677 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700678 }
Chris Brackendd7e3132017-09-14 12:28:21 -0700679
Zachary Andersone2340c62019-09-13 14:51:35 -0700680 if (string.startsWith('Timestamp (process)[PID]')) {
Chris Brackendd7e3132017-09-14 12:28:21 -0700681 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700682 }
Chris Brackendd7e3132017-09-14 12:28:21 -0700683
Zachary Andersone2340c62019-09-13 14:51:35 -0700684 if (_lastMessageSingleRegex.matchAsPrefix(string) != null) {
Todd Volkertec7c49e2016-11-22 10:47:24 -0800685 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700686 }
Todd Volkertec7c49e2016-11-22 10:47:24 -0800687
Zachary Andersone2340c62019-09-13 14:51:35 -0700688 if (RegExp(r'assertion failed: .* libxpc.dylib .* 0x7d$').matchAsPrefix(string) != null) {
Todd Volkertec7c49e2016-11-22 10:47:24 -0800689 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700690 }
Todd Volkertec7c49e2016-11-22 10:47:24 -0800691
John McCutchan8803cec2016-03-07 13:52:27 -0800692 return string;
693 }
694
Devon Carew7ae584b2016-06-03 12:54:27 -0700695 String _lastLine;
696
John McCutchan8803cec2016-03-07 13:52:27 -0800697 void _onDeviceLine(String line) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800698 globals.printTrace('[DEVICE LOG] $line');
Chris Bracken7a093162017-03-03 17:50:46 -0800699 final Match multi = _lastMessageMultipleRegex.matchAsPrefix(line);
Devon Carew7ae584b2016-06-03 12:54:27 -0700700
701 if (multi != null) {
702 if (_lastLine != null) {
703 int repeat = int.parse(multi.group(1));
704 repeat = math.max(0, math.min(100, repeat));
Zachary Andersone2340c62019-09-13 14:51:35 -0700705 for (int i = 1; i < repeat; i++) {
Devon Carew7ae584b2016-06-03 12:54:27 -0700706 _linesController.add(_lastLine);
Zachary Andersone2340c62019-09-13 14:51:35 -0700707 }
Devon Carew7ae584b2016-06-03 12:54:27 -0700708 }
709 } else {
710 _lastLine = _filterDeviceLine(line);
Zachary Andersone2340c62019-09-13 14:51:35 -0700711 if (_lastLine != null) {
Devon Carew7ae584b2016-06-03 12:54:27 -0700712 _linesController.add(_lastLine);
Zachary Andersone2340c62019-09-13 14:51:35 -0700713 }
Devon Carew7ae584b2016-06-03 12:54:27 -0700714 }
John McCutchan8803cec2016-03-07 13:52:27 -0800715 }
716
717 String _filterSystemLog(String string) {
Chris Bracken7a093162017-03-03 17:50:46 -0800718 final Match match = _mapRegex.matchAsPrefix(string);
John McCutchan8803cec2016-03-07 13:52:27 -0800719 return match == null ? string : '${match.group(1)}: ${match.group(2)}';
720 }
721
722 void _onSystemLine(String line) {
Jonah Williamsee7a37f2020-01-06 11:04:20 -0800723 globals.printTrace('[SYS LOG] $line');
Zachary Andersone2340c62019-09-13 14:51:35 -0700724 if (!_flutterRunnerRegex.hasMatch(line)) {
John McCutchan8803cec2016-03-07 13:52:27 -0800725 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700726 }
John McCutchan8803cec2016-03-07 13:52:27 -0800727
Chris Bracken7a093162017-03-03 17:50:46 -0800728 final String filteredLine = _filterSystemLog(line);
Zachary Andersone2340c62019-09-13 14:51:35 -0700729 if (filteredLine == null) {
John McCutchan8803cec2016-03-07 13:52:27 -0800730 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700731 }
John McCutchan8803cec2016-03-07 13:52:27 -0800732
Devon Carewb0dca792016-04-27 14:43:42 -0700733 _linesController.add(filteredLine);
Devon Carew67046f92016-02-20 22:00:11 -0800734 }
735
Devon Carewb0dca792016-04-27 14:43:42 -0700736 void _stop() {
737 _deviceProcess?.kill();
738 _systemProcess?.kill();
Devon Carew67046f92016-02-20 22:00:11 -0800739 }
Zachary Anderson99684ce2019-12-05 08:48:00 -0800740
741 @override
742 void dispose() {
743 _stop();
744 }
Devon Carew67046f92016-02-20 22:00:11 -0800745}
Yegor Jbanov93834662016-03-10 11:04:02 -0800746
747int compareIosVersions(String v1, String v2) {
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200748 final List<int> v1Fragments = v1.split('.').map<int>(int.parse).toList();
749 final List<int> v2Fragments = v2.split('.').map<int>(int.parse).toList();
Yegor Jbanov93834662016-03-10 11:04:02 -0800750
751 int i = 0;
Ian Hicksonefb45ea2017-09-27 16:13:48 -0700752 while (i < v1Fragments.length && i < v2Fragments.length) {
Chris Bracken7a093162017-03-03 17:50:46 -0800753 final int v1Fragment = v1Fragments[i];
754 final int v2Fragment = v2Fragments[i];
Zachary Andersone2340c62019-09-13 14:51:35 -0700755 if (v1Fragment != v2Fragment) {
Yegor Jbanov93834662016-03-10 11:04:02 -0800756 return v1Fragment.compareTo(v2Fragment);
Zachary Andersone2340c62019-09-13 14:51:35 -0700757 }
Ian Hicksonefb45ea2017-09-27 16:13:48 -0700758 i += 1;
Yegor Jbanov93834662016-03-10 11:04:02 -0800759 }
760 return v1Fragments.length.compareTo(v2Fragments.length);
761}
762
763/// Matches on device type given an identifier.
764///
765/// Example device type identifiers:
766/// ✓ com.apple.CoreSimulator.SimDeviceType.iPhone-5
767/// ✓ com.apple.CoreSimulator.SimDeviceType.iPhone-6
768/// ✓ com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus
769/// ✗ com.apple.CoreSimulator.SimDeviceType.iPad-2
770/// ✗ com.apple.CoreSimulator.SimDeviceType.Apple-Watch-38mm
771final RegExp _iosDeviceTypePattern =
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200772 RegExp(r'com.apple.CoreSimulator.SimDeviceType.iPhone-(\d+)(.*)');
Yegor Jbanov93834662016-03-10 11:04:02 -0800773
774int compareIphoneVersions(String id1, String id2) {
Chris Bracken7a093162017-03-03 17:50:46 -0800775 final Match m1 = _iosDeviceTypePattern.firstMatch(id1);
776 final Match m2 = _iosDeviceTypePattern.firstMatch(id2);
Yegor Jbanov93834662016-03-10 11:04:02 -0800777
Chris Bracken7a093162017-03-03 17:50:46 -0800778 final int v1 = int.parse(m1[1]);
779 final int v2 = int.parse(m2[1]);
Yegor Jbanov93834662016-03-10 11:04:02 -0800780
Zachary Andersone2340c62019-09-13 14:51:35 -0700781 if (v1 != v2) {
Yegor Jbanov93834662016-03-10 11:04:02 -0800782 return v1.compareTo(v2);
Zachary Andersone2340c62019-09-13 14:51:35 -0700783 }
Yegor Jbanov93834662016-03-10 11:04:02 -0800784
785 // Sorted in the least preferred first order.
Alexandre Ardhuineda03e22018-08-02 12:02:32 +0200786 const List<String> qualifiers = <String>['-Plus', '', 's-Plus', 's'];
Yegor Jbanov93834662016-03-10 11:04:02 -0800787
Chris Bracken7a093162017-03-03 17:50:46 -0800788 final int q1 = qualifiers.indexOf(m1[2]);
789 final int q2 = qualifiers.indexOf(m2[2]);
Yegor Jbanov93834662016-03-10 11:04:02 -0800790 return q1.compareTo(q2);
791}
John McCutchan5e140b72016-03-10 15:48:37 -0800792
793class _IOSSimulatorDevicePortForwarder extends DevicePortForwarder {
794 _IOSSimulatorDevicePortForwarder(this.device);
795
796 final IOSSimulator device;
797
798 final List<ForwardedPort> _ports = <ForwardedPort>[];
799
Hixie797e27e2016-03-14 13:31:43 -0700800 @override
Zachary Anderson99684ce2019-12-05 08:48:00 -0800801 List<ForwardedPort> get forwardedPorts => _ports;
John McCutchan5e140b72016-03-10 15:48:37 -0800802
Hixie797e27e2016-03-14 13:31:43 -0700803 @override
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +0100804 Future<int> forward(int devicePort, { int hostPort }) async {
Ian Hickson35ad2a72018-06-27 16:44:28 -0700805 if (hostPort == null || hostPort == 0) {
John McCutchan5e140b72016-03-10 15:48:37 -0800806 hostPort = devicePort;
807 }
808 assert(devicePort == hostPort);
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200809 _ports.add(ForwardedPort(devicePort, hostPort));
John McCutchan5e140b72016-03-10 15:48:37 -0800810 return hostPort;
811 }
812
Hixie797e27e2016-03-14 13:31:43 -0700813 @override
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200814 Future<void> unforward(ForwardedPort forwardedPort) async {
John McCutchan5e140b72016-03-10 15:48:37 -0800815 _ports.remove(forwardedPort);
816 }
Zachary Anderson99684ce2019-12-05 08:48:00 -0800817
818 @override
819 Future<void> dispose() async {
Christopher Fujino0c2f51f2020-01-23 18:23:03 -0800820 final List<ForwardedPort> portsCopy = List<ForwardedPort>.from(_ports);
821 for (final ForwardedPort port in portsCopy) {
Zachary Anderson99684ce2019-12-05 08:48:00 -0800822 await unforward(port);
823 }
824 }
John McCutchan5e140b72016-03-10 15:48:37 -0800825}