// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:io'; import 'application_package.dart'; import 'base/logger.dart'; import 'base/utils.dart'; import 'commands/build_apk.dart'; import 'commands/install.dart'; import 'commands/trace.dart'; import 'device.dart'; import 'globals.dart'; import 'vmservice.dart'; import 'resident_runner.dart'; class RunAndStayResident extends ResidentRunner { RunAndStayResident( Device device, { String target, DebuggingOptions debuggingOptions, bool usesTerminalUI: true, this.traceStartup: false, this.benchmark: false, this.applicationBinary }) : super(device, target: target, debuggingOptions: debuggingOptions, usesTerminalUI: usesTerminalUI); ApplicationPackage _package; String _mainPath; LaunchResult _result; final bool traceStartup; final bool benchmark; final String applicationBinary; @override Future run({ Completer connectionInfoCompleter, String route, bool shouldBuild: true }) { // Don't let uncaught errors kill the process. return runZoned(() { return _run( traceStartup: traceStartup, benchmark: benchmark, connectionInfoCompleter: connectionInfoCompleter, route: route, shouldBuild: shouldBuild ); }, onError: (dynamic error, StackTrace stackTrace) { printError('Exception from flutter run: $error', stackTrace); }); } @override Future restart({ bool fullRestart: false }) async { if (vmService == null) { printError('Debugging is not enabled.'); return false; } else { Status status = logger.startProgress('Re-starting application...'); Future extensionAddedEvent; if (device.restartSendsFrameworkInitEvent) { extensionAddedEvent = vmService.onExtensionEvent .where((ServiceEvent event) => event.extensionKind == 'Flutter.FrameworkInitialization') .first; } bool restartResult = await device.restartApp( _package, _result, mainPath: _mainPath, observatory: vmService ); status.stop(showElapsedTime: true); if (restartResult && extensionAddedEvent != null) { // TODO(devoncarew): We should restore the route here. await extensionAddedEvent; } return restartResult; } } Future _run({ bool traceStartup: false, bool benchmark: false, Completer connectionInfoCompleter, String route, bool shouldBuild: true }) async { _mainPath = findMainDartFile(target); if (!FileSystemEntity.isFileSync(_mainPath)) { String message = 'Tried to run $_mainPath, but that file does not exist.'; if (target == null) message += '\nConsider using the -t option to specify the Dart file to start.'; printError(message); return 1; } _package = getApplicationPackageForPlatform(device.platform, applicationBinary: applicationBinary); if (_package == null) { String message = 'No application found for ${device.platform}.'; String hint = getMissingPackageHintForPlatform(device.platform); if (hint != null) message += '\n$hint'; printError(message); return 1; } Stopwatch startTime = new Stopwatch()..start(); // TODO(devoncarew): We shouldn't have to do type checks here. if (shouldBuild && device is AndroidDevice) { printTrace('Running build command.'); int result = await buildApk( device.platform, target: target, buildMode: debuggingOptions.buildMode ); if (result != 0) return result; } // TODO(devoncarew): Move this into the device.startApp() impls. if (_package != null) { printTrace("Stopping app '${_package.name}' on ${device.name}."); await device.stopApp(_package); } // TODO(devoncarew): This fails for ios devices - we haven't built yet. if (device is AndroidDevice) { printTrace('Running install command.'); if (!(installApp(device, _package, uninstall: false))) return 1; } Map platformArgs; if (traceStartup != null) platformArgs = { 'trace-startup': traceStartup }; await startEchoingDeviceLog(); printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); _result = await device.startApp( _package, debuggingOptions.buildMode, mainPath: _mainPath, debuggingOptions: debuggingOptions, platformArgs: platformArgs, route: route ); if (!_result.started) { printError('Error running application on ${device.name}.'); await stopEchoingDeviceLog(); return 2; } startTime.stop(); if (connectionInfoCompleter != null && _result.hasObservatory) connectionInfoCompleter.complete(new DebugConnectionInfo(_result.observatoryPort)); // Connect to observatory. if (debuggingOptions.debuggingEnabled) { await connectToServiceProtocol(_result.observatoryPort); if (benchmark) { await vmService.getVM(); } } printStatus('Application running.'); if (vmService != null) { await vmService.vm.refreshViews(); printStatus('Connected to view \'${vmService.vm.mainView}\'.'); } if (vmService != null && traceStartup) { printStatus('Downloading startup trace info...'); try { await downloadStartupTrace(vmService); } catch(error) { printError(error); return 2; } appFinished(); } else { setupTerminal(); registerSignalHandlers(); } if (benchmark) { await new Future.delayed(new Duration(seconds: 4)); // Touch the file. File mainFile = new File(_mainPath); mainFile.writeAsBytesSync(mainFile.readAsBytesSync()); Stopwatch restartTime = new Stopwatch()..start(); bool restarted = await restart(); restartTime.stop(); writeRunBenchmarkFile(startTime, restarted ? restartTime : null); await new Future.delayed(new Duration(seconds: 2)); stop(); } return waitForAppToFinish(); } @override Future handleTerminalCommand(String code) async { String lower = code.toLowerCase(); if (lower == 'r' || code == AnsiTerminal.KEY_F5) { if (device.supportsRestart) { // F5, restart await restart(); } } } @override Future cleanupAfterSignal() async { await stopEchoingDeviceLog(); await stopApp(); } @override Future cleanupAtFinish() async { await stopEchoingDeviceLog(); } @override void printHelp() { String restartText = device.supportsRestart ? ', "r" or F5 to restart the app,' : ''; printStatus('Type "h" or F1 for help$restartText and "q", F10, or ctrl-c to quit.'); printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.'); } } void writeRunBenchmarkFile(Stopwatch startTime, [Stopwatch restartTime]) { final String benchmarkOut = 'refresh_benchmark.json'; Map data = { 'start': startTime.elapsedMilliseconds, 'time': (restartTime ?? startTime).elapsedMilliseconds // time and restart are the same }; if (restartTime != null) data['restart'] = restartTime.elapsedMilliseconds; new File(benchmarkOut).writeAsStringSync(toPrettyJson(data)); printStatus('Run benchmark written to $benchmarkOut ($data).'); }