blob: 53c8861f5a8c36ae36d9b6bab0b70c5054ff20a0 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Fischer81746e92015-09-11 12:20:20 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Ian Fischer8cac55a2015-09-25 16:29:47 -07005import 'dart:async';
Devon Carew4daee0c2016-03-14 09:41:00 -07006import 'dart:math' as math;
Ian Fischerb72d67a2015-09-15 16:17:47 -07007
Jonah Williams5c524982019-06-18 15:23:14 -07008import 'package:meta/meta.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -07009import 'package:process/process.dart';
Jonah Williams9202e542020-04-20 15:15:54 -070010import 'package:vm_service/vm_service.dart' as vm_service;
Jonah Williams5c524982019-06-18 15:23:14 -070011
Jonah Williams46f0a5f2020-03-17 17:58:39 -070012import 'android/android_device_discovery.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070013import 'android/android_sdk.dart';
Jonah Williams46f0a5f2020-03-17 17:58:39 -070014import 'android/android_workflow.dart';
Ian Fischer0cc758d2015-09-18 17:11:30 -070015import 'application_package.dart';
Zachary Anderson61236c82019-05-06 09:26:58 -070016import 'artifacts.dart';
includecmath6d46ff72020-09-03 08:30:04 +080017import 'base/common.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070018import 'base/config.dart';
John McCutchan0b737ac2016-11-29 07:54:20 -080019import 'base/context.dart';
Ben Konyi3a5a3ea2020-07-29 10:05:40 -070020import 'base/dds.dart';
Todd Volkert8bb27032017-01-06 16:51:44 -080021import 'base/file_system.dart';
Christopher Fujinoed482c32019-10-09 16:30:27 -070022import 'base/io.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070023import 'base/logger.dart';
Jonah Williamsddb81772020-10-02 21:52:53 -070024import 'base/os.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070025import 'base/platform.dart';
Jonah Williams30c0fc12020-10-08 12:28:58 -070026import 'base/terminal.dart';
27import 'base/user_messages.dart' hide userMessages;
Devon Carew067715e2016-05-10 13:47:00 -070028import 'base/utils.dart';
Jason Simmonsa590ee22016-05-12 12:22:15 -070029import 'build_info.dart';
Jonah Williamsddab09f2020-10-08 13:40:19 -070030import 'devfs.dart';
Jonah Williams42ae15f2020-02-28 10:27:27 -080031import 'features.dart';
Jonah Williamsb7c9c962018-10-22 16:48:17 -070032import 'fuchsia/fuchsia_device.dart';
Jonah Williams0ecc7a42020-05-05 13:40:38 -070033import 'fuchsia/fuchsia_sdk.dart';
34import 'fuchsia/fuchsia_workflow.dart';
Jonah Williams30c0fc12020-10-08 12:28:58 -070035import 'globals.dart' as globals show logger;
Devon Carew67046f92016-02-20 22:00:11 -080036import 'ios/devices.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070037import 'ios/ios_workflow.dart';
Devon Carew67046f92016-02-20 22:00:11 -080038import 'ios/simulators.dart';
Jonah Williams3a694a62019-01-15 07:45:04 -080039import 'linux/linux_device.dart';
40import 'macos/macos_device.dart';
Jonah Williams9f040862020-07-22 18:23:20 -070041import 'macos/macos_workflow.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070042import 'macos/xcode.dart';
Jonah Williams6b191842019-04-25 12:25:12 -070043import 'project.dart';
Konstantin Scheglov38970f52018-04-10 09:37:16 -070044import 'tester/flutter_tester.dart';
Jonah Williams1c1ef132020-07-10 11:52:09 -070045import 'version.dart';
Jonah Williams6f5f0372019-02-26 16:58:24 -080046import 'web/web_device.dart';
Jonah Williams3a694a62019-01-15 07:45:04 -080047import 'windows/windows_device.dart';
Jonah Williamsddb81772020-10-02 21:52:53 -070048import 'windows/windows_workflow.dart';
Ian Fischerb72d67a2015-09-15 16:17:47 -070049
Jonah Williams0acd3e62019-04-25 15:51:08 -070050DeviceManager get deviceManager => context.get<DeviceManager>();
John McCutchan0b737ac2016-11-29 07:54:20 -080051
Jonah Williams5c524982019-06-18 15:23:14 -070052/// A description of the kind of workflow the device supports.
53class Category {
54 const Category._(this.value);
55
56 static const Category web = Category._('web');
57 static const Category desktop = Category._('desktop');
58 static const Category mobile = Category._('mobile');
59
60 final String value;
61
62 @override
63 String toString() => value;
64}
65
66/// The platform sub-folder that a device type supports.
67class PlatformType {
68 const PlatformType._(this.value);
69
70 static const PlatformType web = PlatformType._('web');
71 static const PlatformType android = PlatformType._('android');
72 static const PlatformType ios = PlatformType._('ios');
73 static const PlatformType linux = PlatformType._('linux');
74 static const PlatformType macos = PlatformType._('macos');
75 static const PlatformType windows = PlatformType._('windows');
76 static const PlatformType fuchsia = PlatformType._('fuchsia');
77
78 final String value;
79
80 @override
81 String toString() => value;
82}
83
Jonah Williams08576cb2020-10-12 09:31:02 -070084/// A discovery mechanism for flutter-supported development devices.
Jonah Williams741608a2020-07-08 18:07:27 -070085abstract class DeviceManager {
Jonah Williams30c0fc12020-10-08 12:28:58 -070086 DeviceManager({
87 @required Logger logger,
88 @required Terminal terminal,
89 @required UserMessages userMessages,
90 }) : _logger = logger,
91 _terminal = terminal,
92 _userMessages = userMessages;
93
94 final Logger _logger;
95 final Terminal _terminal;
96 final UserMessages _userMessages;
Jonah Williams8e2ea262019-01-31 18:42:30 -080097
Devon Carew2dbceaf2016-02-06 19:19:50 -080098 /// Constructing DeviceManagers is cheap; they only do expensive work if some
Adam Barthb1b62712016-05-24 17:53:04 -070099 /// of their methods are called.
Jonah Williams741608a2020-07-08 18:07:27 -0700100 List<DeviceDiscovery> get deviceDiscoverers;
Jonah Williams6f5f0372019-02-26 16:58:24 -0800101
Zachary Anderson0770c3c2017-04-26 21:49:38 -0700102 String _specifiedDeviceId;
Devon Carew2dbceaf2016-02-06 19:19:50 -0800103
Zachary Anderson0770c3c2017-04-26 21:49:38 -0700104 /// A user-specified device ID.
105 String get specifiedDeviceId {
Zachary Andersone2340c62019-09-13 14:51:35 -0700106 if (_specifiedDeviceId == null || _specifiedDeviceId == 'all') {
Zachary Anderson0770c3c2017-04-26 21:49:38 -0700107 return null;
Zachary Andersone2340c62019-09-13 14:51:35 -0700108 }
Zachary Anderson0770c3c2017-04-26 21:49:38 -0700109 return _specifiedDeviceId;
110 }
111
112 set specifiedDeviceId(String id) {
113 _specifiedDeviceId = id;
114 }
115
116 /// True when the user has specified a single specific device.
Devon Carew2dbceaf2016-02-06 19:19:50 -0800117 bool get hasSpecifiedDeviceId => specifiedDeviceId != null;
Devon Carew7ac4e622016-01-27 14:03:41 -0800118
Zachary Anderson0770c3c2017-04-26 21:49:38 -0700119 /// True when the user has specified all devices by setting
120 /// specifiedDeviceId = 'all'.
121 bool get hasSpecifiedAllDevices => _specifiedDeviceId == 'all';
122
Jenn Magder85b54d42020-02-19 17:40:24 -0800123 Future<List<Device>> getDevicesById(String deviceId) async {
Jenn Magderf9499f42020-07-24 16:15:44 -0700124 final String lowerDeviceId = deviceId.toLowerCase();
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700125 bool exactlyMatchesDeviceId(Device device) =>
Jenn Magderf9499f42020-07-24 16:15:44 -0700126 device.id.toLowerCase() == lowerDeviceId ||
127 device.name.toLowerCase() == lowerDeviceId;
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700128 bool startsWithDeviceId(Device device) =>
Jenn Magderf9499f42020-07-24 16:15:44 -0700129 device.id.toLowerCase().startsWith(lowerDeviceId) ||
130 device.name.toLowerCase().startsWith(lowerDeviceId);
Devon Carew5ad6a572016-03-11 08:49:55 -0800131
Jenn Magderf9499f42020-07-24 16:15:44 -0700132 // Some discoverers have hard-coded device IDs and return quickly, and others
133 // shell out to other processes and can take longer.
134 // Process discoverers as they can return results, so if an exact match is
135 // found quickly, we don't wait for all the discoverers to complete.
136 final List<Device> prefixMatches = <Device>[];
137 final Completer<Device> exactMatchCompleter = Completer<Device>();
138 final List<Future<List<Device>>> futureDevices = <Future<List<Device>>>[
139 for (final DeviceDiscovery discoverer in _platformDiscoverers)
140 discoverer
141 .devices
142 .then((List<Device> devices) {
143 for (final Device device in devices) {
144 if (exactlyMatchesDeviceId(device)) {
145 exactMatchCompleter.complete(device);
146 return null;
147 }
148 if (startsWithDeviceId(device)) {
149 prefixMatches.add(device);
150 }
151 }
152 return null;
153 }, onError: (dynamic error, StackTrace stackTrace) {
154 // Return matches from other discoverers even if one fails.
Jonah Williams30c0fc12020-10-08 12:28:58 -0700155 _logger.printTrace('Ignored error discovering $deviceId: $error');
Jenn Magderf9499f42020-07-24 16:15:44 -0700156 })
157 ];
158
159 // Wait for an exact match, or for all discoverers to return results.
160 await Future.any<dynamic>(<Future<dynamic>>[
161 exactMatchCompleter.future,
162 Future.wait<List<Device>>(futureDevices),
163 ]);
164
165 if (exactMatchCompleter.isCompleted) {
166 return <Device>[await exactMatchCompleter.future];
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700167 }
Jenn Magderf9499f42020-07-24 16:15:44 -0700168 return prefixMatches;
Devon Carew78e05882016-02-01 10:36:09 -0800169 }
170
Jenn Magder2f216ce2020-03-16 14:15:00 -0700171 /// Returns the list of connected devices, filtered by any user-specified device id.
Jenn Magder85b54d42020-02-19 17:40:24 -0800172 Future<List<Device>> getDevices() {
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700173 return hasSpecifiedDeviceId
174 ? getDevicesById(specifiedDeviceId)
175 : getAllConnectedDevices();
Devon Carew2dbceaf2016-02-06 19:19:50 -0800176 }
177
Chris Bracken1d9f0092017-06-19 13:14:57 -0700178 Iterable<DeviceDiscovery> get _platformDiscoverers {
Jonah Williams8e2ea262019-01-31 18:42:30 -0800179 return deviceDiscoverers.where((DeviceDiscovery discoverer) => discoverer.supportsPlatform);
Chris Bracken1d9f0092017-06-19 13:14:57 -0700180 }
181
Jenn Magder2f216ce2020-03-16 14:15:00 -0700182 /// Returns the list of all connected devices.
Jenn Magder85b54d42020-02-19 17:40:24 -0800183 Future<List<Device>> getAllConnectedDevices() async {
184 final List<List<Device>> devices = await Future.wait<List<Device>>(<Future<List<Device>>>[
185 for (final DeviceDiscovery discoverer in _platformDiscoverers)
186 discoverer.devices,
187 ]);
188
189 return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
Devon Carew7ac4e622016-01-27 14:03:41 -0800190 }
Devon Carew0350c9e2017-12-07 09:32:23 -0800191
Jenn Magder2f216ce2020-03-16 14:15:00 -0700192 /// Returns the list of all connected devices. Discards existing cache of devices.
193 Future<List<Device>> refreshAllConnectedDevices({ Duration timeout }) async {
194 final List<List<Device>> devices = await Future.wait<List<Device>>(<Future<List<Device>>>[
195 for (final DeviceDiscovery discoverer in _platformDiscoverers)
196 discoverer.discoverDevices(timeout: timeout),
197 ]);
198
199 return devices.expand<Device>((List<Device> deviceList) => deviceList).toList();
200 }
201
Devon Carew0350c9e2017-12-07 09:32:23 -0800202 /// Whether we're capable of listing any devices given the current environment configuration.
203 bool get canListAnything {
204 return _platformDiscoverers.any((DeviceDiscovery discoverer) => discoverer.canListAnything);
205 }
206
207 /// Get diagnostics about issues with any connected devices.
208 Future<List<String>> getDeviceDiagnostics() async {
Alexandre Ardhuin758009b2019-07-02 21:11:56 +0200209 return <String>[
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100210 for (final DeviceDiscovery discoverer in _platformDiscoverers)
Alexandre Ardhuin758009b2019-07-02 21:11:56 +0200211 ...await discoverer.getDiagnostics(),
212 ];
Devon Carew0350c9e2017-12-07 09:32:23 -0800213 }
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700214
215 /// Find and return a list of devices based on the current project and environment.
216 ///
Greg Spencera60bf8e2019-11-22 08:43:55 -0800217 /// Returns a list of devices specified by the user.
Jonah Williams1e26c412019-07-08 15:58:38 -0700218 ///
219 /// * If the user specified '-d all', then return all connected devices which
220 /// support the current project, except for fuchsia and web.
221 ///
222 /// * If the user specified a device id, then do nothing as the list is already
223 /// filtered by [getDevices].
224 ///
225 /// * If the user did not specify a device id and there is more than one
226 /// device connected, then filter out unsupported devices and prioritize
227 /// ephemeral devices.
Jonah Williams1bea5122020-10-02 21:12:50 -0700228 ///
229 /// * If [flutterProject] is null, then assume the project supports all
230 /// device types.
Jenn Magder61751832020-09-02 15:38:52 -0700231 Future<List<Device>> findTargetDevices(FlutterProject flutterProject, { Duration timeout }) async {
232 if (timeout != null) {
233 // Reset the cache with the specified timeout.
234 await refreshAllConnectedDevices(timeout: timeout);
235 }
236
Jenn Magder85b54d42020-02-19 17:40:24 -0800237 List<Device> devices = await getDevices();
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700238
Jonah Williams1e26c412019-07-08 15:58:38 -0700239 // Always remove web and fuchsia devices from `--all`. This setting
240 // currently requires devices to share a frontend_server and resident
Jonah Williams08576cb2020-10-12 09:31:02 -0700241 // runner instance. Both web and fuchsia require differently configured
Jonah Williams1e26c412019-07-08 15:58:38 -0700242 // compilers, and web requires an entirely different resident runner.
243 if (hasSpecifiedAllDevices) {
244 devices = <Device>[
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100245 for (final Device device in devices)
Zachary Anderson0dfabb22019-10-28 09:38:08 -0700246 if (await device.targetPlatform != TargetPlatform.fuchsia_arm64 &&
247 await device.targetPlatform != TargetPlatform.fuchsia_x64 &&
Jonah Williams1e26c412019-07-08 15:58:38 -0700248 await device.targetPlatform != TargetPlatform.web_javascript)
Alexandre Ardhuinf0553ba2019-09-30 18:48:23 +0200249 device,
Jonah Williams1e26c412019-07-08 15:58:38 -0700250 ];
251 }
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700252
Jonah Williams1e26c412019-07-08 15:58:38 -0700253 // If there is no specified device, the remove all devices which are not
254 // supported by the current application. For example, if there was no
255 // 'android' folder then don't attempt to launch with an Android device.
256 if (devices.length > 1 && !hasSpecifiedDeviceId) {
257 devices = <Device>[
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100258 for (final Device device in devices)
Jonah Williamsb257c332019-07-15 16:10:39 -0700259 if (isDeviceSupportedForProject(device, flutterProject))
Alexandre Ardhuinf0553ba2019-09-30 18:48:23 +0200260 device,
Jonah Williams1e26c412019-07-08 15:58:38 -0700261 ];
Jonah Williamsb2a4ebe2019-08-29 21:39:38 -0700262 } else if (devices.length == 1 &&
263 !hasSpecifiedDeviceId &&
264 !isDeviceSupportedForProject(devices.single, flutterProject)) {
265 // If there is only a single device but it is not supported, then return
266 // early.
267 return <Device>[];
Jonah Williams1e26c412019-07-08 15:58:38 -0700268 }
269
270 // If there are still multiple devices and the user did not specify to run
271 // all, then attempt to prioritize ephemeral devices. For example, if the
Natan Portilhof567a0c2020-07-08 13:41:02 -0300272 // user only typed 'flutter run' and both an Android device and desktop
Jonah Williams08576cb2020-10-12 09:31:02 -0700273 // device are available, choose the Android device.
Jonah Williams1e26c412019-07-08 15:58:38 -0700274 if (devices.length > 1 && !hasSpecifiedAllDevices) {
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700275 // Note: ephemeral is nullable for device types where this is not well
276 // defined.
277 if (devices.any((Device device) => device.ephemeral == true)) {
Natan Portilhof567a0c2020-07-08 13:41:02 -0300278 // if there is only one ephemeral device, get it
279 final List<Device> ephemeralDevices = devices
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700280 .where((Device device) => device.ephemeral == true)
281 .toList();
Natan Portilhof567a0c2020-07-08 13:41:02 -0300282
283 if (ephemeralDevices.length == 1){
284 devices = ephemeralDevices;
285 }
286 }
287 // If it was not able to prioritize a device. For example, if the user
288 // has two active Android devices running, then we request the user to
289 // choose one. If the user has two nonEphemeral devices running, we also
290 // request input to choose one.
Jonah Williams30c0fc12020-10-08 12:28:58 -0700291 if (devices.length > 1 && _terminal.stdinHasTerminal) {
292 _logger.printStatus(_userMessages.flutterMultipleDevicesFound);
293 await Device.printDevices(devices, _logger);
Natan Portilhof567a0c2020-07-08 13:41:02 -0300294 final Device chosenDevice = await _chooseOneOfAvailableDevices(devices);
Jonah Williams30c0fc12020-10-08 12:28:58 -0700295 specifiedDeviceId = chosenDevice.id;
Natan Portilhof567a0c2020-07-08 13:41:02 -0300296 devices = <Device>[chosenDevice];
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700297 }
298 }
299 return devices;
300 }
301
Natan Portilhof567a0c2020-07-08 13:41:02 -0300302 Future<Device> _chooseOneOfAvailableDevices(List<Device> devices) async {
303 _displayDeviceOptions(devices);
304 final String userInput = await _readUserInput(devices.length);
includecmath6d46ff72020-09-03 08:30:04 +0800305 if (userInput.toLowerCase() == 'q') {
306 throwToolExit('');
307 }
J-P Nurmi612ef202020-12-21 18:29:05 +0100308 return devices[int.parse(userInput) - 1];
Natan Portilhof567a0c2020-07-08 13:41:02 -0300309 }
310
311 void _displayDeviceOptions(List<Device> devices) {
J-P Nurmi612ef202020-12-21 18:29:05 +0100312 int count = 1;
Natan Portilhof567a0c2020-07-08 13:41:02 -0300313 for (final Device device in devices) {
Jonah Williams30c0fc12020-10-08 12:28:58 -0700314 _logger.printStatus(_userMessages.flutterChooseDevice(count, device.name, device.id));
Natan Portilhof567a0c2020-07-08 13:41:02 -0300315 count++;
316 }
317 }
318
319 Future<String> _readUserInput(int deviceCount) async {
Jonah Williams30c0fc12020-10-08 12:28:58 -0700320 _terminal.usesTerminalUi = true;
321 final String result = await _terminal.promptForCharInput(
J-P Nurmi612ef202020-12-21 18:29:05 +0100322 <String>[ for (int i = 0; i < deviceCount; i++) '${i + 1}', 'q', 'Q'],
Jonah Williams30c0fc12020-10-08 12:28:58 -0700323 displayAcceptedCharacters: false,
324 logger: _logger,
325 prompt: _userMessages.flutterChooseOne,
326 );
Natan Portilhof567a0c2020-07-08 13:41:02 -0300327 return result;
328 }
329
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700330 /// Returns whether the device is supported for the project.
Jonah Williamsb257c332019-07-15 16:10:39 -0700331 ///
Jonah Williams1bea5122020-10-02 21:12:50 -0700332 /// This exists to allow the check to be overridden for google3 clients. If
333 /// [flutterProject] is null then return true.
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700334 bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) {
Jonah Williams1bea5122020-10-02 21:12:50 -0700335 if (flutterProject == null) {
336 return true;
337 }
Lau Ching Junc8cefce2019-06-26 10:09:14 -0700338 return device.isSupportedForProject(flutterProject);
339 }
Devon Carew7ac4e622016-01-27 14:03:41 -0800340}
341
Jonah Williams741608a2020-07-08 18:07:27 -0700342class FlutterDeviceManager extends DeviceManager {
Jonah Williams1c1ef132020-07-10 11:52:09 -0700343 FlutterDeviceManager({
344 @required Logger logger,
345 @required Platform platform,
346 @required ProcessManager processManager,
347 @required FileSystem fileSystem,
348 @required AndroidSdk androidSdk,
349 @required FeatureFlags featureFlags,
350 @required IOSSimulatorUtils iosSimulatorUtils,
351 @required XCDevice xcDevice,
352 @required AndroidWorkflow androidWorkflow,
353 @required IOSWorkflow iosWorkflow,
354 @required FuchsiaWorkflow fuchsiaWorkflow,
355 @required FlutterVersion flutterVersion,
356 @required Config config,
357 @required Artifacts artifacts,
Jonah Williams9f040862020-07-22 18:23:20 -0700358 @required MacOSWorkflow macOSWorkflow,
Jonah Williams6180a4c2020-10-05 15:47:57 -0700359 @required UserMessages userMessages,
Jonah Williamsddb81772020-10-02 21:52:53 -0700360 @required OperatingSystemUtils operatingSystemUtils,
361 @required WindowsWorkflow windowsWorkflow,
Jonah Williams30c0fc12020-10-08 12:28:58 -0700362 @required Terminal terminal,
Jonah Williams1c1ef132020-07-10 11:52:09 -0700363 }) : deviceDiscoverers = <DeviceDiscovery>[
Jonah Williams741608a2020-07-08 18:07:27 -0700364 AndroidDevices(
Jonah Williams1c1ef132020-07-10 11:52:09 -0700365 logger: logger,
366 androidSdk: androidSdk,
Jonah Williams741608a2020-07-08 18:07:27 -0700367 androidWorkflow: androidWorkflow,
Jonah Williams1c1ef132020-07-10 11:52:09 -0700368 processManager: processManager,
Jonah Williams6180a4c2020-10-05 15:47:57 -0700369 fileSystem: fileSystem,
370 platform: platform,
371 userMessages: userMessages,
Jonah Williams741608a2020-07-08 18:07:27 -0700372 ),
373 IOSDevices(
Jonah Williams1c1ef132020-07-10 11:52:09 -0700374 platform: platform,
375 xcdevice: xcDevice,
376 iosWorkflow: iosWorkflow,
377 logger: logger,
Jonah Williams741608a2020-07-08 18:07:27 -0700378 ),
Jonah Williams1c1ef132020-07-10 11:52:09 -0700379 IOSSimulators(
380 iosSimulatorUtils: iosSimulatorUtils,
381 ),
Jonah Williams741608a2020-07-08 18:07:27 -0700382 FuchsiaDevices(
383 fuchsiaSdk: fuchsiaSdk,
Jonah Williams1c1ef132020-07-10 11:52:09 -0700384 logger: logger,
Jonah Williams741608a2020-07-08 18:07:27 -0700385 fuchsiaWorkflow: fuchsiaWorkflow,
Jonah Williams1c1ef132020-07-10 11:52:09 -0700386 platform: platform,
Jonah Williams741608a2020-07-08 18:07:27 -0700387 ),
Jonah Williams1c1ef132020-07-10 11:52:09 -0700388 FlutterTesterDevices(
389 fileSystem: fileSystem,
390 flutterVersion: flutterVersion,
391 processManager: processManager,
392 config: config,
393 logger: logger,
394 artifacts: artifacts,
395 ),
Jonah Williams9f040862020-07-22 18:23:20 -0700396 MacOSDevices(
397 processManager: processManager,
398 macOSWorkflow: macOSWorkflow,
399 logger: logger,
400 platform: platform,
Jonah Williams943b41b2020-09-22 16:57:04 -0700401 fileSystem: fileSystem,
Jonah Williamsddb81772020-10-02 21:52:53 -0700402 operatingSystemUtils: operatingSystemUtils,
Jonah Williams9f040862020-07-22 18:23:20 -0700403 ),
Jonah Williams741608a2020-07-08 18:07:27 -0700404 LinuxDevices(
Jonah Williams1c1ef132020-07-10 11:52:09 -0700405 platform: platform,
Jonah Williams741608a2020-07-08 18:07:27 -0700406 featureFlags: featureFlags,
Jonah Williams9f040862020-07-22 18:23:20 -0700407 processManager: processManager,
408 logger: logger,
Jonah Williams943b41b2020-09-22 16:57:04 -0700409 fileSystem: fileSystem,
Jonah Williamsddb81772020-10-02 21:52:53 -0700410 operatingSystemUtils: operatingSystemUtils,
Jonah Williams741608a2020-07-08 18:07:27 -0700411 ),
Jonah Williamsddb81772020-10-02 21:52:53 -0700412 WindowsDevices(
413 processManager: processManager,
414 operatingSystemUtils: operatingSystemUtils,
415 logger: logger,
416 fileSystem: fileSystem,
417 windowsWorkflow: windowsWorkflow,
418 ),
Jonah Williams741608a2020-07-08 18:07:27 -0700419 WebDevices(
420 featureFlags: featureFlags,
Jonah Williams1c1ef132020-07-10 11:52:09 -0700421 fileSystem: fileSystem,
422 platform: platform,
423 processManager: processManager,
424 logger: logger,
Jonah Williams741608a2020-07-08 18:07:27 -0700425 ),
Jonah Williams30c0fc12020-10-08 12:28:58 -0700426 ], super(
427 logger: logger,
428 terminal: terminal,
429 userMessages: userMessages,
430 );
Jonah Williams1c1ef132020-07-10 11:52:09 -0700431
432 @override
433 final List<DeviceDiscovery> deviceDiscoverers;
Jonah Williams741608a2020-07-08 18:07:27 -0700434}
435
Devon Carew7ac4e622016-01-27 14:03:41 -0800436/// An abstract class to discover and enumerate a specific type of devices.
437abstract class DeviceDiscovery {
438 bool get supportsPlatform;
Todd Volkert6a4b08b2017-05-03 16:12:08 -0700439
440 /// Whether this device discovery is capable of listing any devices given the
441 /// current environment configuration.
442 bool get canListAnything;
443
Jenn Magder2f216ce2020-03-16 14:15:00 -0700444 /// Return all connected devices, cached on subsequent calls.
Chris Bracken1d9f0092017-06-19 13:14:57 -0700445 Future<List<Device>> get devices;
Devon Carew0350c9e2017-12-07 09:32:23 -0800446
Jenn Magder2f216ce2020-03-16 14:15:00 -0700447 /// Return all connected devices. Discards existing cache of devices.
448 Future<List<Device>> discoverDevices({ Duration timeout });
449
Devon Carew0350c9e2017-12-07 09:32:23 -0800450 /// Gets a list of diagnostic messages pertaining to issues with any connected
451 /// devices (will be an empty list if there are no issues).
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200452 Future<List<String>> getDiagnostics() => Future<List<String>>.value(<String>[]);
Devon Carew7ac4e622016-01-27 14:03:41 -0800453}
454
Devon Carew67046f92016-02-20 22:00:11 -0800455/// A [DeviceDiscovery] implementation that uses polling to discover device adds
456/// and removals.
457abstract class PollingDeviceDiscovery extends DeviceDiscovery {
458 PollingDeviceDiscovery(this.name);
459
Alexandre Ardhuineda03e22018-08-02 12:02:32 +0200460 static const Duration _pollingInterval = Duration(seconds: 4);
461 static const Duration _pollingTimeout = Duration(seconds: 30);
Devon Carew67046f92016-02-20 22:00:11 -0800462
463 final String name;
Jenn Magderf4d26a32020-06-01 12:33:01 -0700464
465 @protected
466 @visibleForTesting
467 ItemListNotifier<Device> deviceNotifier;
468
Jonah Williams19e551c2019-10-28 19:35:43 -0700469 Timer _timer;
Devon Carew67046f92016-02-20 22:00:11 -0800470
Jenn Magder2f216ce2020-03-16 14:15:00 -0700471 Future<List<Device>> pollingGetDevices({ Duration timeout });
Devon Carew67046f92016-02-20 22:00:11 -0800472
Jonah Williams1bea5122020-10-02 21:12:50 -0700473 void startPolling() {
Jonah Williams19e551c2019-10-28 19:35:43 -0700474 if (_timer == null) {
Jenn Magderf4d26a32020-06-01 12:33:01 -0700475 deviceNotifier ??= ItemListNotifier<Device>();
Jenn Magderbd430082020-05-14 11:00:51 -0700476 // Make initial population the default, fast polling timeout.
477 _timer = _initTimer(null);
Devon Carew67046f92016-02-20 22:00:11 -0800478 }
479 }
480
Jenn Magderbd430082020-05-14 11:00:51 -0700481 Timer _initTimer(Duration pollingTimeout) {
Jonah Williams19e551c2019-10-28 19:35:43 -0700482 return Timer(_pollingInterval, () async {
483 try {
Jenn Magderbd430082020-05-14 11:00:51 -0700484 final List<Device> devices = await pollingGetDevices(timeout: pollingTimeout);
Jenn Magderf4d26a32020-06-01 12:33:01 -0700485 deviceNotifier.updateWithNewList(devices);
Jonah Williams19e551c2019-10-28 19:35:43 -0700486 } on TimeoutException {
Jonah Williams30c0fc12020-10-08 12:28:58 -0700487 // Do nothing on a timeout.
Jonah Williams19e551c2019-10-28 19:35:43 -0700488 }
Jenn Magderbd430082020-05-14 11:00:51 -0700489 // Subsequent timeouts after initial population should wait longer.
490 _timer = _initTimer(_pollingTimeout);
Jonah Williams19e551c2019-10-28 19:35:43 -0700491 });
492 }
493
Jonah Williams1bea5122020-10-02 21:12:50 -0700494 void stopPolling() {
Jonah Williams19e551c2019-10-28 19:35:43 -0700495 _timer?.cancel();
496 _timer = null;
Devon Carew67046f92016-02-20 22:00:11 -0800497 }
498
Hixie797e27e2016-03-14 13:31:43 -0700499 @override
Jonah Williams1bea5122020-10-02 21:12:50 -0700500 Future<List<Device>> get devices {
Jenn Magder2f216ce2020-03-16 14:15:00 -0700501 return _populateDevices();
502 }
503
504 @override
Jonah Williams1bea5122020-10-02 21:12:50 -0700505 Future<List<Device>> discoverDevices({ Duration timeout }) {
Jenn Magderf4d26a32020-06-01 12:33:01 -0700506 deviceNotifier = null;
Jenn Magder2f216ce2020-03-16 14:15:00 -0700507 return _populateDevices(timeout: timeout);
508 }
509
510 Future<List<Device>> _populateDevices({ Duration timeout }) async {
Jenn Magderf4d26a32020-06-01 12:33:01 -0700511 deviceNotifier ??= ItemListNotifier<Device>.from(await pollingGetDevices(timeout: timeout));
512 return deviceNotifier.items;
Devon Carew67046f92016-02-20 22:00:11 -0800513 }
514
515 Stream<Device> get onAdded {
Jenn Magderf4d26a32020-06-01 12:33:01 -0700516 deviceNotifier ??= ItemListNotifier<Device>();
517 return deviceNotifier.onAdded;
Devon Carew67046f92016-02-20 22:00:11 -0800518 }
519
520 Stream<Device> get onRemoved {
Jenn Magderf4d26a32020-06-01 12:33:01 -0700521 deviceNotifier ??= ItemListNotifier<Device>();
522 return deviceNotifier.onRemoved;
Devon Carew67046f92016-02-20 22:00:11 -0800523 }
524
Jonah Williams1bea5122020-10-02 21:12:50 -0700525 void dispose() => stopPolling();
Devon Carew67046f92016-02-20 22:00:11 -0800526
Hixie797e27e2016-03-14 13:31:43 -0700527 @override
Devon Carew67046f92016-02-20 22:00:11 -0800528 String toString() => '$name device discovery';
529}
530
Jonah Williams4f88ed12020-06-08 11:20:15 -0700531/// A device is a physical hardware that can run a flutter application.
532///
533/// This may correspond to a connected iOS or Android device, or represent
534/// the host operating system in the case of Flutter Desktop.
Adam Barthbdd20662015-10-11 19:42:50 -0700535abstract class Device {
Jonah Williams4f88ed12020-06-08 11:20:15 -0700536 Device(this.id, {
537 @required this.category,
538 @required this.platformType,
539 @required this.ephemeral,
540 });
Devon Carew9e6d45c2016-02-05 20:44:32 -0800541
Ian Fischer81746e92015-09-11 12:20:20 -0700542 final String id;
Ian Fischer81746e92015-09-11 12:20:20 -0700543
Jonah Williams5c524982019-06-18 15:23:14 -0700544 /// The [Category] for this device type.
545 final Category category;
546
547 /// The [PlatformType] for this device.
548 final PlatformType platformType;
549
550 /// Whether this is an ephemeral device.
551 final bool ephemeral;
552
Devon Carew8bb8e1d2016-01-19 15:14:05 -0800553 String get name;
554
Devon Carew4ed79452016-02-11 09:38:19 -0800555 bool get supportsStartPaused => true;
556
Yegor Jbanov677e63b2016-02-25 15:58:09 -0800557 /// Whether it is an emulated device running on localhost.
Jonah Williams4f88ed12020-06-08 11:20:15 -0700558 ///
559 /// This may return `true` for certain physical Android devices, and is
560 /// generally only a best effort guess.
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700561 Future<bool> get isLocalEmulator;
Yegor Jbanov677e63b2016-02-25 15:58:09 -0800562
Danny Tuppenyf3be1d92019-06-26 16:39:23 +0100563 /// The unique identifier for the emulator that corresponds to this device, or
564 /// null if it is not an emulator.
565 ///
566 /// The ID returned matches that in the output of `flutter emulators`. Fetching
567 /// this name may require connecting to the device and if an error occurs null
568 /// will be returned.
569 Future<String> get emulatorId;
570
Jonah Williams4f88ed12020-06-08 11:20:15 -0700571 /// Whether this device can run the provided [buildMode].
572 ///
573 /// For example, some emulator architectures cannot run profile or
574 /// release builds.
575 FutureOr<bool> supportsRuntimeMode(BuildMode buildMode) => true;
576
Jonah Williamsafabdfe2018-03-08 10:41:29 -0800577 /// Whether the device is a simulator on a platform which supports hardware rendering.
Jonah Williams4f88ed12020-06-08 11:20:15 -0700578 // This is soft-deprecated since the logic is not correct expect for iOS simulators.
Jonah Williamsafabdfe2018-03-08 10:41:29 -0800579 Future<bool> get supportsHardwareRendering async {
Jonah Williams4f88ed12020-06-08 11:20:15 -0700580 return true;
Jonah Williamsafabdfe2018-03-08 10:41:29 -0800581 }
582
Jonah Williams6b191842019-04-25 12:25:12 -0700583 /// Whether the device is supported for the current project directory.
584 bool isSupportedForProject(FlutterProject flutterProject);
585
Jenn Magder88631332020-06-08 11:28:02 -0700586 /// Check if a version of the given app is already installed.
587 ///
588 /// Specify [userIdentifier] to check if installed for a particular user (Android only).
589 Future<bool> isAppInstalled(
590 covariant ApplicationPackage app, {
591 String userIdentifier,
592 });
Todd Volkert51d4ab32016-05-27 11:05:10 -0700593
Jakob Andersena745fd52017-02-22 14:35:49 +0100594 /// Check if the latest build of the [app] is already installed.
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100595 Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app);
Jakob Andersena745fd52017-02-22 14:35:49 +0100596
Jenn Magder88631332020-06-08 11:28:02 -0700597 /// Install an app package on the current device.
598 ///
599 /// Specify [userIdentifier] to install for a particular user (Android only).
600 Future<bool> installApp(
601 covariant ApplicationPackage app, {
602 String userIdentifier,
603 });
Ian Fischer81746e92015-09-11 12:20:20 -0700604
Jenn Magder88631332020-06-08 11:28:02 -0700605 /// Uninstall an app package from the current device.
606 ///
607 /// Specify [userIdentifier] to uninstall for a particular user,
608 /// defaults to all users (Android only).
609 Future<bool> uninstallApp(
610 covariant ApplicationPackage app, {
611 String userIdentifier,
612 });
Todd Volkert51d4ab32016-05-27 11:05:10 -0700613
Nolan Scobie43c1b342020-08-06 19:18:52 -0400614 /// Check if the device is supported by Flutter.
Chinmay Garded7979952016-02-19 14:16:02 -0800615 bool isSupported();
616
Jenn Magdercdacae82020-12-16 11:52:59 -0800617 // String meant to be displayed to the user indicating if the device is
618 // supported by Flutter, and, if not, why.
619 String supportMessage() => isSupported() ? 'Supported' : 'Unsupported';
620
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700621 /// The device's platform.
622 Future<TargetPlatform> get targetPlatform;
Adam Barthbdd20662015-10-11 19:42:50 -0700623
Jenn Magderde5bf092020-10-30 13:48:04 -0700624 /// Platform name for display only.
625 Future<String> get targetPlatformDisplayName async =>
626 getNameForTargetPlatform(await targetPlatform);
627
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700628 Future<String> get sdkNameAndVersion;
Dan Rubel0b49d812016-10-27 09:07:21 +0100629
Jonah Williamsddab09f2020-10-08 13:40:19 -0700630 /// Create a platform-specific [DevFSWriter] for the given [app], or
631 /// null if the device does not support them.
632 ///
Jonah Williams08576cb2020-10-12 09:31:02 -0700633 /// For example, the desktop device classes can use a writer which
Jonah Williamsddab09f2020-10-08 13:40:19 -0700634 /// copies the files across the local file system.
635 DevFSWriter createDevFSWriter(
636 covariant ApplicationPackage app,
637 String userIdentifier,
638 ) {
639 return null;
640 }
641
Todd Volkert9cb914d2016-11-16 17:19:00 -0800642 /// Get a log reader for this device.
xster13767462020-03-25 16:45:49 -0700643 ///
644 /// If `app` is specified, this will return a log reader specific to that
Todd Volkert9cb914d2016-11-16 17:19:00 -0800645 /// application. Otherwise, a global log reader will be returned.
xster13767462020-03-25 16:45:49 -0700646 ///
647 /// If `includePastLogs` is true and the device type supports it, the log
648 /// reader will also include log messages from before the invocation time.
649 /// Defaults to false.
650 FutureOr<DeviceLogReader> getLogReader({
651 covariant ApplicationPackage app,
652 bool includePastLogs = false,
653 });
John McCutchan8803cec2016-03-07 13:52:27 -0800654
John McCutchan5e140b72016-03-10 15:48:37 -0800655 /// Get the port forwarder for this device.
656 DevicePortForwarder get portForwarder;
657
Ben Konyi3a5a3ea2020-07-29 10:05:40 -0700658 /// Get the DDS instance for this device.
659 DartDevelopmentService get dds => _dds ??= DartDevelopmentService(
660 logger: globals.logger,
661 );
662 DartDevelopmentService _dds;
663
John McCutchan8803cec2016-03-07 13:52:27 -0800664 /// Clear the device's logs.
665 void clearLogs();
Adam Barthbdd20662015-10-11 19:42:50 -0700666
Zachary Anderson61236c82019-05-06 09:26:58 -0700667 /// Optional device-specific artifact overrides.
668 OverrideArtifacts get artifactOverrides => null;
669
Devon Carew5bce2fb2016-01-21 11:56:14 -0800670 /// Start an app package on the current device.
671 ///
672 /// [platformArgs] allows callers to pass platform-specific arguments to the
Chinmay Garde66fee3a2016-05-23 12:58:42 -0700673 /// start call. The build mode is not used by all platforms.
Devon Carewb0dca792016-04-27 14:43:42 -0700674 Future<LaunchResult> startApp(
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100675 covariant ApplicationPackage package, {
Devon Carew5bce2fb2016-01-21 11:56:14 -0800676 String mainPath,
677 String route,
Devon Carewb0dca792016-04-27 14:43:42 -0700678 DebuggingOptions debuggingOptions,
John McCutchanca8070f2016-09-28 08:46:16 -0700679 Map<String, dynamic> platformArgs,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200680 bool prebuiltApplication = false,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200681 bool ipv6 = false,
Jenn Magder88631332020-06-08 11:28:02 -0700682 String userIdentifier,
Devon Carew5bce2fb2016-01-21 11:56:14 -0800683 });
Ian Fischer844678d2015-10-07 15:13:24 -0700684
Jonah Williamsc11633e2018-11-09 10:33:22 -0800685 /// Whether this device implements support for hot reload.
686 bool get supportsHotReload => true;
687
688 /// Whether this device implements support for hot restart.
689 bool get supportsHotRestart => true;
Devon Carewec751772016-05-26 15:26:14 -0700690
Jonah Williams891036c2019-01-07 21:58:15 -0800691 /// Whether flutter applications running on this device can be terminated
Jonah Williams08576cb2020-10-12 09:31:02 -0700692 /// from the VM Service.
Zachary Anderson55557252019-06-06 11:16:19 -0700693 bool get supportsFlutterExit => true;
Jonah Williams891036c2019-01-07 21:58:15 -0800694
695 /// Whether the device supports taking screenshots of a running flutter
696 /// application.
697 bool get supportsScreenshot => false;
698
Jonah Williamsbda9d902019-12-10 10:26:14 -0800699 /// Whether the device supports the '--fast-start' development mode.
700 bool get supportsFastStart => false;
701
Devon Carew5bce2fb2016-01-21 11:56:14 -0800702 /// Stop an app package on the current device.
Jenn Magder88631332020-06-08 11:28:02 -0700703 ///
704 /// Specify [userIdentifier] to stop app installed to a profile (Android only).
705 Future<bool> stopApp(
706 covariant ApplicationPackage app, {
707 String userIdentifier,
708 });
Devon Carewe36b07f2015-11-25 12:47:25 -0800709
Jonah Williams831163f2019-11-26 14:49:56 -0800710 /// Query the current application memory usage..
711 ///
712 /// If the device does not support this callback, an empty map
713 /// is returned.
714 Future<MemoryInfo> queryMemoryInfo() {
715 return Future<MemoryInfo>.value(const MemoryInfo.empty());
716 }
717
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +0200718 Future<void> takeScreenshot(File outputFile) => Future<void>.error('unimplemented');
Devon Carew15b9e1d2016-03-25 16:04:22 -0700719
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200720 @nonVirtual
Hixie797e27e2016-03-14 13:31:43 -0700721 @override
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200722 // ignore: avoid_equals_and_hash_code_on_mutable_classes
Devon Carew67046f92016-02-20 22:00:11 -0800723 int get hashCode => id.hashCode;
724
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200725 @nonVirtual
Hixie797e27e2016-03-14 13:31:43 -0700726 @override
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200727 // ignore: avoid_equals_and_hash_code_on_mutable_classes
Alexandre Ardhuin82262d82020-01-09 17:23:02 +0100728 bool operator ==(Object other) {
Zachary Andersone2340c62019-09-13 14:51:35 -0700729 if (identical(this, other)) {
Devon Carew67046f92016-02-20 22:00:11 -0800730 return true;
Zachary Andersone2340c62019-09-13 14:51:35 -0700731 }
Alexandre Ardhuin82262d82020-01-09 17:23:02 +0100732 return other is Device
733 && other.id == id;
Devon Carew67046f92016-02-20 22:00:11 -0800734 }
735
Hixie797e27e2016-03-14 13:31:43 -0700736 @override
Devon Carew5ad6a572016-03-11 08:49:55 -0800737 String toString() => name;
Devon Carew4daee0c2016-03-14 09:41:00 -0700738
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700739 static Stream<String> descriptions(List<Device> devices) async* {
Zachary Andersone2340c62019-09-13 14:51:35 -0700740 if (devices.isEmpty) {
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700741 return;
Zachary Andersone2340c62019-09-13 14:51:35 -0700742 }
Devon Carew4daee0c2016-03-14 09:41:00 -0700743
Dan Rubel0b49d812016-10-27 09:07:21 +0100744 // Extract device information
Chris Bracken7a093162017-03-03 17:50:46 -0800745 final List<List<String>> table = <List<String>>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100746 for (final Device device in devices) {
Devon Carew4daee0c2016-03-14 09:41:00 -0700747 String supportIndicator = device.isSupported() ? '' : ' (unsupported)';
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700748 final TargetPlatform targetPlatform = await device.targetPlatform;
749 if (await device.isLocalEmulator) {
750 final String type = targetPlatform == TargetPlatform.ios ? 'simulator' : 'emulator';
Devon Carewb00e6cd2016-10-04 20:29:10 -0700751 supportIndicator += ' ($type)';
752 }
Dan Rubel0b49d812016-10-27 09:07:21 +0100753 table.add(<String>[
James D. Lincf8fbc32020-06-30 11:28:02 -0700754 '${device.name} (${device.category})',
Dan Rubel0b49d812016-10-27 09:07:21 +0100755 device.id,
Jenn Magderde5bf092020-10-30 13:48:04 -0700756 await device.targetPlatformDisplayName,
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700757 '${await device.sdkNameAndVersion}$supportIndicator',
Dan Rubel0b49d812016-10-27 09:07:21 +0100758 ]);
759 }
760
761 // Calculate column widths
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200762 final List<int> indices = List<int>.generate(table[0].length - 1, (int i) => i);
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200763 List<int> widths = indices.map<int>((int i) => 0).toList();
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100764 for (final List<String> row in table) {
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200765 widths = indices.map<int>((int i) => math.max(widths[i], row[i].length)).toList();
Dan Rubel0b49d812016-10-27 09:07:21 +0100766 }
767
768 // Join columns into lines of text
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100769 for (final List<String> row in table) {
Alexandre Ardhuinf62afdc2018-10-01 21:29:08 +0200770 yield indices.map<String>((int i) => row[i].padRight(widths[i])).join(' • ') + ' • ${row.last}';
Todd Volkert60c5ffc2017-04-25 17:23:00 -0700771 }
Dan Rubel023b7de2016-09-01 17:55:16 -0400772 }
773
Jonah Williams30c0fc12020-10-08 12:28:58 -0700774 static Future<void> printDevices(List<Device> devices, Logger logger) async {
775 await descriptions(devices).forEach(logger.printStatus);
Devon Carew4daee0c2016-03-14 09:41:00 -0700776 }
Christopher Fujinoed482c32019-10-09 16:30:27 -0700777
JustWe6c8d7b02020-06-04 07:14:38 +0800778 static List<String> devicesPlatformTypes(List<Device> devices) {
779 return devices
780 .map(
781 (Device d) => d.platformType.toString(),
782 ).toSet().toList()..sort();
783 }
784
Kirill Pertsevd6806392020-04-09 09:14:16 -0700785 /// Convert the Device object to a JSON representation suitable for serialization.
786 Future<Map<String, Object>> toJson() async {
787 final bool isLocalEmu = await isLocalEmulator;
788 return <String, Object>{
789 'name': name,
790 'id': id,
791 'isSupported': isSupported(),
792 'targetPlatform': getNameForTargetPlatform(await targetPlatform),
793 'emulator': isLocalEmu,
794 'sdk': await sdkNameAndVersion,
795 'capabilities': <String, Object>{
796 'hotReload': supportsHotReload,
797 'hotRestart': supportsHotRestart,
798 'screenshot': supportsScreenshot,
799 'fastStart': supportsFastStart,
800 'flutterExit': supportsFlutterExit,
801 'hardwareRendering': isLocalEmu && await supportsHardwareRendering,
802 'startPaused': supportsStartPaused,
803 }
804 };
805 }
806
Nolan Scobie43c1b342020-08-06 19:18:52 -0400807 /// Clean up resources allocated by device.
Christopher Fujinoed482c32019-10-09 16:30:27 -0700808 ///
809 /// For example log readers or port forwarders.
Zachary Anderson99684ce2019-12-05 08:48:00 -0800810 Future<void> dispose();
Ian Fischer81746e92015-09-11 12:20:20 -0700811}
812
Jonah Williams831163f2019-11-26 14:49:56 -0800813/// Information about an application's memory usage.
814abstract class MemoryInfo {
815 /// Const constructor to allow subclasses to be const.
816 const MemoryInfo();
817
818 /// Create a [MemoryInfo] object with no information.
819 const factory MemoryInfo.empty() = _NoMemoryInfo;
820
821 /// Convert the object to a JSON representation suitable for serialization.
822 Map<String, Object> toJson();
823}
824
825class _NoMemoryInfo implements MemoryInfo {
826 const _NoMemoryInfo();
827
828 @override
829 Map<String, Object> toJson() => <String, Object>{};
830}
831
Devon Carewb0dca792016-04-27 14:43:42 -0700832class DebuggingOptions {
Alexandre Ardhuinbfa1d252019-03-23 00:02:21 +0100833 DebuggingOptions.enabled(
834 this.buildInfo, {
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200835 this.startPaused = false,
Ben Konyi3764cb82019-04-18 21:01:50 -0700836 this.disableServiceAuthCodes = false,
Ben Konyi3a5a3ea2020-07-29 10:05:40 -0700837 this.disableDds = false,
George Wright2b512782020-11-06 15:34:02 -0800838 this.dartEntrypointArgs = const <String>[],
Ben Konyie59d9a82019-06-11 11:37:47 -0700839 this.dartFlags = '',
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200840 this.enableSoftwareRendering = false,
841 this.skiaDeterministicRendering = false,
842 this.traceSkia = false,
Michael Goderbauere48b7e92020-06-19 13:43:02 -0700843 this.traceAllowlist,
Chinmay Gardee38efc82019-01-28 19:55:01 -0800844 this.traceSystrace = false,
Gityuanb8dd6bd2020-02-07 04:33:01 +0800845 this.endlessTraceBuffer = false,
liyuqianbe5c83b2019-03-15 12:37:53 -0700846 this.dumpSkpOnShaderCompilation = false,
liyuqian31cb4482019-10-15 14:28:55 -0700847 this.cacheSkSL = false,
Yuqian Li22bf19c2020-07-29 12:24:39 -0700848 this.purgePersistentCache = false,
Alexandre Ardhuin09276be2018-06-05 08:50:40 +0200849 this.useTestFonts = false,
Jonah Williams25820ab2019-03-13 22:42:12 -0700850 this.verboseSystemLogs = false,
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800851 this.hostVmServicePort,
Jenn Magderf8b1de32020-10-07 08:52:05 -0700852 this.disablePortPublication = false,
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800853 this.deviceVmServicePort,
Ben Konyia17b3302020-09-16 16:27:42 -0700854 this.ddsPort,
Jonah Williams925a52f2019-09-12 08:58:49 -0700855 this.hostname,
856 this.port,
Danny Tuppeny49446222019-12-12 18:43:58 +0000857 this.webEnableExposeUrl,
Danny Tuppeny589b14d2020-04-08 17:02:03 +0100858 this.webUseSseForDebugProxy = true,
Danny Tuppenyf8135ad2020-07-13 21:31:04 +0100859 this.webUseSseForDebugBackend = true,
Yegorb3404692020-02-13 18:34:08 -0800860 this.webRunHeadless = false,
861 this.webBrowserDebugPort,
Anna Gringauzebc1c1b22020-04-17 10:42:02 -0700862 this.webEnableExpressionEvaluation = false,
Jonah Williamse22d4aa2019-10-15 13:05:47 -0700863 this.vmserviceOutFile,
Jonah Williamsbda9d902019-12-10 10:26:14 -0800864 this.fastStart = false,
Jonah Williamsc86d0902020-08-11 11:36:03 -0700865 this.nullAssertions = false,
Jonah Williams9e5c8772020-12-03 08:59:31 -0800866 this.nativeNullAssertions = false,
Devon Carewb0dca792016-04-27 14:43:42 -0700867 }) : debuggingEnabled = true;
868
Danny Tuppeny49446222019-12-12 18:43:58 +0000869 DebuggingOptions.disabled(this.buildInfo, {
George Wright2b512782020-11-06 15:34:02 -0800870 this.dartEntrypointArgs = const <String>[],
Danny Tuppeny49446222019-12-12 18:43:58 +0000871 this.port,
872 this.hostname,
873 this.webEnableExposeUrl,
Danny Tuppeny589b14d2020-04-08 17:02:03 +0100874 this.webUseSseForDebugProxy = true,
Danny Tuppenyf8135ad2020-07-13 21:31:04 +0100875 this.webUseSseForDebugBackend = true,
Yegorb3404692020-02-13 18:34:08 -0800876 this.webRunHeadless = false,
877 this.webBrowserDebugPort,
Danny Tuppeny49446222019-12-12 18:43:58 +0000878 this.cacheSkSL = false,
Michael Goderbauere48b7e92020-06-19 13:43:02 -0700879 this.traceAllowlist,
Danny Tuppeny49446222019-12-12 18:43:58 +0000880 }) : debuggingEnabled = false,
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100881 useTestFonts = false,
882 startPaused = false,
Ben Konyie59d9a82019-06-11 11:37:47 -0700883 dartFlags = '',
Ben Konyi3764cb82019-04-18 21:01:50 -0700884 disableServiceAuthCodes = false,
Ben Konyi3a5a3ea2020-07-29 10:05:40 -0700885 disableDds = false,
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100886 enableSoftwareRendering = false,
887 skiaDeterministicRendering = false,
888 traceSkia = false,
889 traceSystrace = false,
Gityuanb8dd6bd2020-02-07 04:33:01 +0800890 endlessTraceBuffer = false,
liyuqianbe5c83b2019-03-15 12:37:53 -0700891 dumpSkpOnShaderCompilation = false,
Yuqian Li22bf19c2020-07-29 12:24:39 -0700892 purgePersistentCache = false,
Jonah Williams25820ab2019-03-13 22:42:12 -0700893 verboseSystemLogs = false,
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800894 hostVmServicePort = null,
Jenn Magderf8b1de32020-10-07 08:52:05 -0700895 disablePortPublication = false,
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800896 deviceVmServicePort = null,
Ben Konyia17b3302020-09-16 16:27:42 -0700897 ddsPort = null,
Jonah Williamsbda9d902019-12-10 10:26:14 -0800898 vmserviceOutFile = null,
Anna Gringauzebc1c1b22020-04-17 10:42:02 -0700899 fastStart = false,
Jonah Williamsc86d0902020-08-11 11:36:03 -0700900 webEnableExpressionEvaluation = false,
Jonah Williams9e5c8772020-12-03 08:59:31 -0800901 nullAssertions = false,
902 nativeNullAssertions = false;
Devon Carewb0dca792016-04-27 14:43:42 -0700903
904 final bool debuggingEnabled;
905
Mikkel Nygaard Ravn9496e6d2017-08-23 10:55:35 +0200906 final BuildInfo buildInfo;
Devon Carewb0dca792016-04-27 14:43:42 -0700907 final bool startPaused;
Ben Konyie59d9a82019-06-11 11:37:47 -0700908 final String dartFlags;
George Wright2b512782020-11-06 15:34:02 -0800909 final List<String> dartEntrypointArgs;
Ben Konyi3764cb82019-04-18 21:01:50 -0700910 final bool disableServiceAuthCodes;
Ben Konyi3a5a3ea2020-07-29 10:05:40 -0700911 final bool disableDds;
Gary Qiana367dcb2017-06-02 09:44:36 -0700912 final bool enableSoftwareRendering;
Todd Volkert21c514f2018-02-20 23:22:58 -0800913 final bool skiaDeterministicRendering;
Carlo Bernaschinaba360082017-09-13 12:59:05 -0700914 final bool traceSkia;
Michael Goderbauere48b7e92020-06-19 13:43:02 -0700915 final String traceAllowlist;
Chinmay Gardee38efc82019-01-28 19:55:01 -0800916 final bool traceSystrace;
Gityuanb8dd6bd2020-02-07 04:33:01 +0800917 final bool endlessTraceBuffer;
liyuqianbe5c83b2019-03-15 12:37:53 -0700918 final bool dumpSkpOnShaderCompilation;
liyuqian31cb4482019-10-15 14:28:55 -0700919 final bool cacheSkSL;
Yuqian Li22bf19c2020-07-29 12:24:39 -0700920 final bool purgePersistentCache;
Ian Hickson2a545242017-04-12 13:33:02 -0700921 final bool useTestFonts;
Jonah Williams25820ab2019-03-13 22:42:12 -0700922 final bool verboseSystemLogs;
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800923 final int hostVmServicePort;
924 final int deviceVmServicePort;
Jenn Magderf8b1de32020-10-07 08:52:05 -0700925 final bool disablePortPublication;
Ben Konyia17b3302020-09-16 16:27:42 -0700926 final int ddsPort;
Jonah Williams925a52f2019-09-12 08:58:49 -0700927 final String port;
928 final String hostname;
Danny Tuppeny49446222019-12-12 18:43:58 +0000929 final bool webEnableExposeUrl;
Danny Tuppeny589b14d2020-04-08 17:02:03 +0100930 final bool webUseSseForDebugProxy;
Danny Tuppenyf8135ad2020-07-13 21:31:04 +0100931 final bool webUseSseForDebugBackend;
Yegorb3404692020-02-13 18:34:08 -0800932
933 /// Whether to run the browser in headless mode.
934 ///
935 /// Some CI environments do not provide a display and fail to launch the
936 /// browser with full graphics stack. Some browsers provide a special
937 /// "headless" mode that runs the browser with no graphics.
938 final bool webRunHeadless;
939
940 /// The port the browser should use for its debugging protocol.
941 final int webBrowserDebugPort;
942
Nolan Scobie43c1b342020-08-06 19:18:52 -0400943 /// Enable expression evaluation for web target.
Anna Gringauzebc1c1b22020-04-17 10:42:02 -0700944 final bool webEnableExpressionEvaluation;
945
Jonah Williams08576cb2020-10-12 09:31:02 -0700946 /// A file where the VM Service URL should be written after the application is started.
Jonah Williamse22d4aa2019-10-15 13:05:47 -0700947 final String vmserviceOutFile;
Jonah Williamsbda9d902019-12-10 10:26:14 -0800948 final bool fastStart;
Devon Carewb0dca792016-04-27 14:43:42 -0700949
Jonah Williamsc86d0902020-08-11 11:36:03 -0700950 final bool nullAssertions;
951
Jonah Williams9e5c8772020-12-03 08:59:31 -0800952 /// Additional null runtime checks inserted for web applications.
953 ///
954 /// See also:
955 /// * https://github.com/dart-lang/sdk/blob/master/sdk/lib/html/doc/NATIVE_NULL_ASSERTIONS.md
956 final bool nativeNullAssertions;
957
Jonah Williamse3cb2c32019-11-13 16:02:46 -0800958 bool get hasObservatoryPort => hostVmServicePort != null;
Devon Carewb0dca792016-04-27 14:43:42 -0700959}
960
961class LaunchResult {
Jason Simmons1affb422017-10-30 15:53:04 -0700962 LaunchResult.succeeded({ this.observatoryUri }) : started = true;
Alexandre Ardhuinef276ff2019-01-29 21:47:16 +0100963 LaunchResult.failed()
964 : started = false,
965 observatoryUri = null;
Devon Carewb0dca792016-04-27 14:43:42 -0700966
Dan Rubela9584e12016-11-30 20:29:04 -0500967 bool get hasObservatory => observatoryUri != null;
Devon Carewb0dca792016-04-27 14:43:42 -0700968
969 final bool started;
Dan Rubela9584e12016-11-30 20:29:04 -0500970 final Uri observatoryUri;
Devon Carewb0dca792016-04-27 14:43:42 -0700971
972 @override
973 String toString() {
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200974 final StringBuffer buf = StringBuffer('started=$started');
Zachary Andersone2340c62019-09-13 14:51:35 -0700975 if (observatoryUri != null) {
Dan Rubela9584e12016-11-30 20:29:04 -0500976 buf.write(', observatory=$observatoryUri');
Zachary Andersone2340c62019-09-13 14:51:35 -0700977 }
Devon Carewb0dca792016-04-27 14:43:42 -0700978 return buf.toString();
979 }
980}
981
John McCutchan5e140b72016-03-10 15:48:37 -0800982class ForwardedPort {
Chinmay Garde9782d972016-06-14 11:47:51 -0700983 ForwardedPort(this.hostPort, this.devicePort) : context = null;
984 ForwardedPort.withContext(this.hostPort, this.devicePort, this.context);
John McCutchan5e140b72016-03-10 15:48:37 -0800985
986 final int hostPort;
987 final int devicePort;
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100988 final Process context;
John McCutchan5e140b72016-03-10 15:48:37 -0800989
Hixie797e27e2016-03-14 13:31:43 -0700990 @override
John McCutchan5e140b72016-03-10 15:48:37 -0800991 String toString() => 'ForwardedPort HOST:$hostPort to DEVICE:$devicePort';
Christopher Fujinoed482c32019-10-09 16:30:27 -0700992
993 /// Kill subprocess (if present) used in forwarding.
994 void dispose() {
Alexandre Ardhuinadc73512019-11-19 07:57:42 +0100995 if (context != null) {
996 context.kill();
Christopher Fujinoed482c32019-10-09 16:30:27 -0700997 }
998 }
John McCutchan5e140b72016-03-10 15:48:37 -0800999}
1000
1001/// Forward ports from the host machine to the device.
1002abstract class DevicePortForwarder {
1003 /// Returns a Future that completes with the current list of forwarded
1004 /// ports for this device.
1005 List<ForwardedPort> get forwardedPorts;
1006
1007 /// Forward [hostPort] on the host to [devicePort] on the device.
Ian Hickson35ad2a72018-06-27 16:44:28 -07001008 /// If [hostPort] is null or zero, will auto select a host port.
John McCutchan5e140b72016-03-10 15:48:37 -08001009 /// Returns a Future that completes with the host port.
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001010 Future<int> forward(int devicePort, { int hostPort });
John McCutchan5e140b72016-03-10 15:48:37 -08001011
1012 /// Stops forwarding [forwardedPort].
Alexandre Ardhuin2d3ff102018-10-05 07:54:56 +02001013 Future<void> unforward(ForwardedPort forwardedPort);
Christopher Fujinoed482c32019-10-09 16:30:27 -07001014
Nolan Scobie43c1b342020-08-06 19:18:52 -04001015 /// Cleanup allocated resources, like [forwardedPorts].
Zachary Anderson99684ce2019-12-05 08:48:00 -08001016 Future<void> dispose();
John McCutchan5e140b72016-03-10 15:48:37 -08001017}
1018
Devon Carewb0dca792016-04-27 14:43:42 -07001019/// Read the log for a particular device.
Devon Carew78e05882016-02-01 10:36:09 -08001020abstract class DeviceLogReader {
1021 String get name;
1022
Devon Carewb0dca792016-04-27 14:43:42 -07001023 /// A broadcast stream where each element in the string is a line of log output.
1024 Stream<String> get logLines;
Devon Carew78e05882016-02-01 10:36:09 -08001025
Jenn Magder2e7d9132019-11-01 14:37:17 -07001026 /// Some logs can be obtained from a VM service stream.
1027 /// Set this after the VM services are connected.
Jonah Williams9202e542020-04-20 15:15:54 -07001028 vm_service.VmService connectedVMService;
Jenn Magder2e7d9132019-11-01 14:37:17 -07001029
Hixie797e27e2016-03-14 13:31:43 -07001030 @override
Devon Carew78e05882016-02-01 10:36:09 -08001031 String toString() => name;
Jason Simmons67b38712017-04-07 13:41:29 -07001032
Greg Spencer0259be92017-11-17 10:05:21 -08001033 /// Process ID of the app on the device.
Jason Simmons67b38712017-04-07 13:41:29 -07001034 int appPid;
Christopher Fujinoed482c32019-10-09 16:30:27 -07001035
1036 // Clean up resources allocated by log reader e.g. subprocesses
Zachary Anderson99684ce2019-12-05 08:48:00 -08001037 void dispose();
Devon Carew78e05882016-02-01 10:36:09 -08001038}
Jason Simmons00c77342016-06-02 14:00:27 -07001039
1040/// Describes an app running on the device.
1041class DiscoveredApp {
Jason Simmons1affb422017-10-30 15:53:04 -07001042 DiscoveredApp(this.id, this.observatoryPort);
Jason Simmons00c77342016-06-02 14:00:27 -07001043 final String id;
1044 final int observatoryPort;
1045}
Jonah Williams3a694a62019-01-15 07:45:04 -08001046
1047// An empty device log reader
1048class NoOpDeviceLogReader implements DeviceLogReader {
1049 NoOpDeviceLogReader(this.name);
1050
1051 @override
1052 final String name;
1053
1054 @override
1055 int appPid;
1056
1057 @override
Jonah Williams9202e542020-04-20 15:15:54 -07001058 vm_service.VmService connectedVMService;
Jenn Magder2e7d9132019-11-01 14:37:17 -07001059
1060 @override
Jonah Williams3a694a62019-01-15 07:45:04 -08001061 Stream<String> get logLines => const Stream<String>.empty();
Christopher Fujinoed482c32019-10-09 16:30:27 -07001062
1063 @override
1064 void dispose() { }
Jonah Williams3a694a62019-01-15 07:45:04 -08001065}
1066
Jonah Williams08576cb2020-10-12 09:31:02 -07001067// A port forwarder which does not support forwarding ports.
Jonah Williams3a694a62019-01-15 07:45:04 -08001068class NoOpDevicePortForwarder implements DevicePortForwarder {
1069 const NoOpDevicePortForwarder();
1070
1071 @override
Alexandre Ardhuin5169ab52019-02-21 09:27:07 +01001072 Future<int> forward(int devicePort, { int hostPort }) async => devicePort;
Jonah Williams3a694a62019-01-15 07:45:04 -08001073
1074 @override
1075 List<ForwardedPort> get forwardedPorts => <ForwardedPort>[];
1076
1077 @override
Alexandre Ardhuina0d1f932019-03-09 09:03:11 +01001078 Future<void> unforward(ForwardedPort forwardedPort) async { }
Christopher Fujinoed482c32019-10-09 16:30:27 -07001079
1080 @override
1081 Future<void> dispose() async { }
Jonah Williams3a694a62019-01-15 07:45:04 -08001082}
Jonah Williamsc86d0902020-08-11 11:36:03 -07001083
1084/// Append --null_assertions to any existing Dart VM flags if
1085/// [debuggingOptions.nullAssertions] is true.
1086String computeDartVmFlags(DebuggingOptions debuggingOptions) {
1087 return <String>[
1088 if (debuggingOptions.dartFlags?.isNotEmpty ?? false)
1089 debuggingOptions.dartFlags,
1090 if (debuggingOptions.nullAssertions)
1091 '--null_assertions',
1092 ].join(',');
1093}