1// Copyright 2016 The Chromium Authors. All rights reserved. 2// 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'; 6 7import 'package:meta/meta.dart'; 8 9import 'base/file_system.dart'; 10import 'device.dart'; 11import 'globals.dart'; 12import 'resident_runner.dart'; 13import 'tracing.dart'; 14import 'vmservice.dart'; 15 16// TODO(mklim): Test this, flutter/flutter#23031. 17class ColdRunner extends ResidentRunner { 18 ColdRunner( 19 List<FlutterDevice> devices, { 20 String target, 21 DebuggingOptions debuggingOptions, 22 this.traceStartup = false, 23 this.awaitFirstFrameWhenTracing = true, 24 this.applicationBinary, 25 bool ipv6 = false, 26 bool usesTerminalUi = false, 27 bool stayResident = true, 28 }) : super(devices, 29 target: target, 30 debuggingOptions: debuggingOptions, 31 hotMode: false, 32 usesTerminalUi: usesTerminalUi, 33 stayResident: stayResident, 34 ipv6: ipv6); 35 36 final bool traceStartup; 37 final bool awaitFirstFrameWhenTracing; 38 final File applicationBinary; 39 bool _didAttach = false; 40 41 @override 42 bool get canHotReload => false; 43 44 @override 45 bool get canHotRestart => false; 46 47 @override 48 Future<int> run({ 49 Completer<DebugConnectionInfo> connectionInfoCompleter, 50 Completer<void> appStartedCompleter, 51 String route, 52 }) async { 53 final bool prebuiltMode = applicationBinary != null; 54 if (!prebuiltMode) { 55 if (!fs.isFileSync(mainPath)) { 56 String message = 'Tried to run $mainPath, but that file does not exist.'; 57 if (target == null) 58 message += '\nConsider using the -t option to specify the Dart file to start.'; 59 printError(message); 60 return 1; 61 } 62 } 63 64 for (FlutterDevice device in flutterDevices) { 65 final int result = await device.runCold( 66 coldRunner: this, 67 route: route, 68 ); 69 if (result != 0) 70 return result; 71 } 72 73 // Connect to observatory. 74 if (debuggingOptions.debuggingEnabled) { 75 try { 76 await connectToServiceProtocol(); 77 } on String catch (message) { 78 printError(message); 79 return 2; 80 } 81 } 82 83 if (flutterDevices.first.observatoryUris != null) { 84 // For now, only support one debugger connection. 85 connectionInfoCompleter?.complete(DebugConnectionInfo( 86 httpUri: flutterDevices.first.observatoryUris.first, 87 wsUri: flutterDevices.first.vmServices.first.wsAddress, 88 )); 89 } 90 91 printTrace('Application running.'); 92 93 for (FlutterDevice device in flutterDevices) { 94 if (device.vmServices == null) 95 continue; 96 device.initLogReader(); 97 await device.refreshViews(); 98 printTrace('Connected to ${device.device.name}'); 99 } 100 101 if (traceStartup) { 102 // Only trace startup for the first device. 103 final FlutterDevice device = flutterDevices.first; 104 if (device.vmServices != null && device.vmServices.isNotEmpty) { 105 printStatus('Tracing startup on ${device.device.name}.'); 106 await downloadStartupTrace( 107 device.vmServices.first, 108 awaitFirstFrame: awaitFirstFrameWhenTracing, 109 ); 110 } 111 appFinished(); 112 } 113 114 appStartedCompleter?.complete(); 115 116 if (stayResident && !traceStartup) 117 return waitForAppToFinish(); 118 await cleanupAtFinish(); 119 return 0; 120 } 121 122 @override 123 Future<int> attach({ 124 Completer<DebugConnectionInfo> connectionInfoCompleter, 125 Completer<void> appStartedCompleter, 126 }) async { 127 _didAttach = true; 128 try { 129 await connectToServiceProtocol(); 130 } catch (error) { 131 printError('Error connecting to the service protocol: $error'); 132 // https://github.com/flutter/flutter/issues/33050 133 // TODO(blasten): Remove this check once https://issuetracker.google.com/issues/132325318 has been fixed. 134 if (await hasDeviceRunningAndroidQ(flutterDevices) && 135 error.toString().contains(kAndroidQHttpConnectionClosedExp)) { 136 printStatus(' If you are using an emulator running Android Q Beta, consider using an emulator running API level 29 or lower.'); 137 printStatus('Learn more about the status of this issue on https://issuetracker.google.com/issues/132325318'); 138 } 139 return 2; 140 } 141 for (FlutterDevice device in flutterDevices) { 142 device.initLogReader(); 143 } 144 await refreshViews(); 145 for (FlutterDevice device in flutterDevices) { 146 for (FlutterView view in device.views) { 147 printTrace('Connected to $view.'); 148 } 149 } 150 appStartedCompleter?.complete(); 151 if (stayResident) { 152 return waitForAppToFinish(); 153 } 154 await cleanupAtFinish(); 155 return 0; 156 } 157 158 @override 159 Future<void> cleanupAfterSignal() async { 160 await stopEchoingDeviceLog(); 161 if (_didAttach) { 162 appFinished(); 163 } 164 await exitApp(); 165 } 166 167 @override 168 Future<void> cleanupAtFinish() async { 169 await stopEchoingDeviceLog(); 170 } 171 172 @override 173 void printHelp({ @required bool details }) { 174 bool haveDetails = false; 175 bool haveAnything = false; 176 for (FlutterDevice device in flutterDevices) { 177 final String dname = device.device.name; 178 if (device.observatoryUris != null) { 179 for (Uri uri in device.observatoryUris) { 180 printStatus('An Observatory debugger and profiler on $dname is available at $uri'); 181 haveAnything = true; 182 } 183 } 184 } 185 if (supportsServiceProtocol) { 186 haveDetails = true; 187 if (details) { 188 printHelpDetails(); 189 haveAnything = true; 190 } 191 } 192 final String quitMessage = _didAttach 193 ? 'To detach, press "d"; to quit, press "q".' 194 : 'To quit, press "q".'; 195 if (haveDetails && !details) { 196 printStatus('For a more detailed help message, press "h". $quitMessage'); 197 } else if (haveAnything) { 198 printStatus('To repeat this help message, press "h". $quitMessage'); 199 } else { 200 printStatus(quitMessage); 201 } 202 } 203 204 @override 205 Future<void> preExit() async { 206 for (FlutterDevice device in flutterDevices) { 207 // If we're running in release mode, stop the app using the device logic. 208 if (device.vmServices == null || device.vmServices.isEmpty) 209 await device.device.stopApp(device.package); 210 } 211 } 212} 213