Split hot run mode into a separate file (#5233)
diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart index 1ee4eab..098f4b5 100644 --- a/packages/flutter_tools/lib/src/run.dart +++ b/packages/flutter_tools/lib/src/run.dart
@@ -5,66 +5,32 @@ import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as path; - import 'application_package.dart'; import 'base/logger.dart'; import 'base/utils.dart'; -import 'build_info.dart'; -import 'cache.dart'; import 'commands/build_apk.dart'; import 'commands/install.dart'; import 'commands/trace.dart'; import 'device.dart'; import 'globals.dart'; import 'observatory.dart'; -import 'devfs.dart'; +import 'resident_runner.dart'; -/// Given the value of the --target option, return the path of the Dart file -/// where the app's main function should be. -String findMainDartFile([String target]) { - if (target == null) - target = ''; - String targetPath = path.absolute(target); - if (FileSystemEntity.isDirectorySync(targetPath)) - return path.join(targetPath, 'lib', 'main.dart'); - else - return targetPath; -} - -String getDevFSLoaderScript() { - return path.absolute(path.join(Cache.flutterRoot, - 'packages', - 'flutter', - 'bin', - 'loader', - 'loader_app.dart')); -} - -class RunAndStayResident { +class RunAndStayResident extends ResidentRunner { RunAndStayResident( - this.device, { - this.target, - this.debuggingOptions, - this.usesTerminalUI: true, - this.hotMode: false - }); - - final Device device; - final String target; - final DebuggingOptions debuggingOptions; - final bool usesTerminalUI; - final bool hotMode; + Device device, { + String target, + DebuggingOptions debuggingOptions, + bool usesTerminalUI: true + }) : super(device, + target: target, + debuggingOptions: debuggingOptions, + usesTerminalUI: usesTerminalUI); ApplicationPackage _package; String _mainPath; LaunchResult _result; - final Completer<int> _exitCompleter = new Completer<int>(); - StreamSubscription<String> _loggingSubscription; - - Observatory observatory; - /// Start the app and keep the process running during its lifetime. Future<int> run({ bool traceStartup: false, @@ -86,7 +52,7 @@ } Future<bool> restart() async { - if (observatory == null) { + if (serviceProtocol == null) { printError('Debugging is not enabled.'); return false; } else { @@ -95,7 +61,7 @@ Future<Event> extensionAddedEvent; if (device.restartSendsFrameworkInitEvent) { - extensionAddedEvent = observatory.onExtensionEvent + extensionAddedEvent = serviceProtocol.onExtensionEvent .where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization') .first; } @@ -104,7 +70,7 @@ _package, _result, mainPath: _mainPath, - observatory: observatory + observatory: serviceProtocol ); status.stop(showElapsedTime: true); @@ -118,11 +84,6 @@ } } - Future<Null> stop() { - _stopLogger(); - return _stopApp(); - } - Future<int> _run({ bool traceStartup: false, bool benchmark: false, @@ -186,19 +147,13 @@ if (traceStartup != null) platformArgs = <String, dynamic>{ 'trace-startup': traceStartup }; - if (!hotMode || (hotMode && !device.needsDevFS)) - printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); - - - _loggingSubscription = device.logReader.logLines.listen((String line) { - if (!line.contains('Observatory listening on http') && !line.contains('Diagnostic server listening on http')) - printStatus(line); - }); + await startEchoingDeviceLog(); + printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); _result = await device.startApp( _package, debuggingOptions.buildMode, - mainPath: (hotMode && device.needsDevFS) ? getDevFSLoaderScript() : _mainPath, + mainPath: _mainPath, debuggingOptions: debuggingOptions, platformArgs: platformArgs, route: route @@ -206,7 +161,7 @@ if (!_result.started) { printError('Error running application on ${device.name}.'); - await _loggingSubscription.cancel(); + await stopEchoingDeviceLog(); return 2; } @@ -217,101 +172,21 @@ // Connect to observatory. if (debuggingOptions.debuggingEnabled) { - observatory = await Observatory.connect(_result.observatoryPort); - printTrace('Connected to observatory port: ${_result.observatoryPort}.'); - observatory.populateIsolateInfo(); - observatory.onExtensionEvent.listen((Event event) { - printTrace(event.toString()); - }); - observatory.onIsolateEvent.listen((Event event) { - printTrace(event.toString()); - }); - - if (hotMode && device.needsDevFS) { - _loaderShowMessage('Connecting...', progress: 0); - bool result = await _updateDevFS( - progressReporter: (int progress, int max) { - _loaderShowMessage('Syncing files to device...', progress: progress, max: max); - } - ); - if (!result) { - _loaderShowMessage('Failed.'); - printError('Could not perform initial file synchronization.'); - return 3; - } - printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); - _loaderShowMessage('Launching...'); - await _launchFromDevFS(_package, _mainPath); - } + await connectToServiceProtocol(_result.observatoryPort); if (benchmark) - await observatory.waitFirstIsolate; - - // Listen for observatory connection close. - observatory.done.whenComplete(() { - if (!_exitCompleter.isCompleted) { - printStatus('Application finished.'); - _exitCompleter.complete(0); - } - }); + await serviceProtocol.waitFirstIsolate; } printStatus('Application running.'); - if (observatory != null && traceStartup) { + if (serviceProtocol != null && traceStartup) { printStatus('Downloading startup trace info...'); - - await downloadStartupTrace(observatory); - - if (!_exitCompleter.isCompleted) - _exitCompleter.complete(0); + await downloadStartupTrace(serviceProtocol); + appFinished(); } else { - if (usesTerminalUI) { - if (!logger.quiet) - _printHelp(); - - terminal.singleCharMode = true; - terminal.onCharInput.listen((String code) { - printStatus(''); // the key the user tapped might be on this line - final String lower = code.toLowerCase(); - if (lower == 'h' || lower == '?' || code == AnsiTerminal.KEY_F1) { - // F1, help - _printHelp(); - } else if (lower == 'r' || code == AnsiTerminal.KEY_F5) { - // F5, restart - if (hotMode && code == 'r') { - // lower-case 'r' - _reloadSources(); - } else { - // upper-case 'r', or hot restart disabled - if (device.supportsRestart) - restart(); - } - } else if (lower == 'q' || code == AnsiTerminal.KEY_F10) { - // F10, exit - _stopApp(); - } else if (lower == 'w') { - _debugDumpApp(); - } else if (lower == 't') { - _debugDumpRenderTree(); - } - }); - } - - ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) async { - _resetTerminal(); - await _cleanupDevFS(); - await _stopLogger(); - await _stopApp(); - exit(0); - }); - ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) async { - _resetTerminal(); - await _cleanupDevFS(); - await _stopLogger(); - await _stopApp(); - exit(0); - }); + setupTerminal(); + registerSignalHandlers(); } if (benchmark) { @@ -329,159 +204,36 @@ stop(); } - return _exitCompleter.future.then((int exitCode) async { - _resetTerminal(); - _stopLogger(); - return exitCode; - }); + return await waitForAppToFinish(); } - void _debugDumpApp() { - observatory.flutterDebugDumpApp(observatory.firstIsolateId); - } - - void _debugDumpRenderTree() { - observatory.flutterDebugDumpRenderTree(observatory.firstIsolateId); - } - - void _loaderShowMessage(String message, { int progress, int max }) { - observatory.flutterLoaderShowMessage(observatory.firstIsolateId, message); - if (progress != null) { - observatory.flutterLoaderSetProgress(observatory.firstIsolateId, progress.toDouble()); - observatory.flutterLoaderSetProgressMax(observatory.firstIsolateId, max?.toDouble() ?? 0.0); - } else { - observatory.flutterLoaderSetProgress(observatory.firstIsolateId, 0.0); - observatory.flutterLoaderSetProgressMax(observatory.firstIsolateId, -1.0); - } - } - - DevFS _devFS; - String _devFSProjectRootPath; - Future<bool> _updateDevFS({ DevFSProgressReporter progressReporter }) async { - if (_devFS == null) { - Directory directory = Directory.current; - _devFSProjectRootPath = directory.path; - String fsName = path.basename(directory.path); - _devFS = new DevFS(observatory, fsName, directory); - - try { - await _devFS.create(); - } catch (error) { - _devFS = null; - printError('Error initializing DevFS: $error'); - return false; - } - - _exitCompleter.future.then((_) async { - await _cleanupDevFS(); - }); - } - - Status devFSStatus = logger.startProgress('Syncing files on device...'); - await _devFS.update(progressReporter: progressReporter); - devFSStatus.stop(showElapsedTime: true); - printStatus('Synced ${getSizeAsMB(_devFS.bytes)} MB'); - return true; - } - - Future<Null> _cleanupDevFS() async { - if (_devFS != null) { - // Cleanup the devFS. - await _devFS.destroy(); - } - _devFS = null; - } - - Future<Null> _launchFromDevFS(ApplicationPackage package, - String mainScript) async { - String entryPath = path.relative(mainScript, from: _devFSProjectRootPath); - String deviceEntryPath = - _devFS.baseUri.resolve(entryPath).toFilePath(); - String devicePackagesPath = - _devFS.baseUri.resolve('.packages').toFilePath(); - await device.runFromFile(package, - deviceEntryPath, - devicePackagesPath); - } - - Future<bool> _reloadSources() async { - if (observatory.firstIsolateId == null) - throw 'Application isolate not found'; - if (_devFS != null) - await _updateDevFS(); - Status reloadStatus = logger.startProgress('Performing hot reload'); - try { - await observatory.reloadSources(observatory.firstIsolateId); - } catch (errorMessage) { - reloadStatus.stop(showElapsedTime: true); - printError('Hot reload was rejected:\n$errorMessage'); - return false; - } - reloadStatus.stop(showElapsedTime: true); - Status reassembleStatus = - logger.startProgress('Reassembling application'); - try { - await observatory.flutterReassemble(observatory.firstIsolateId); - } catch (_) { - reassembleStatus.stop(showElapsedTime: true); - printError('Reassembling application failed.'); - return false; - } - reassembleStatus.stop(showElapsedTime: true); - return true; - } - - void _printHelp() { - printStatus('Type "h" or F1 for this help message. Type "q", F10, or ctrl-c to quit.', emphasis: true); - String hot = ''; - String cold = ''; - if (hotMode) - hot = 'Type "r" or F5 to perform a hot reload of the app'; - if (device.supportsRestart) { - if (hotMode) { - cold = ', and "R" to cold restart the app'; - } else { - cold = 'Type "r" or F5 to restart the app'; + @override + void handleTerminalCommand(String code) { + String lower = code.toLowerCase(); + if (lower == 'r' || code == AnsiTerminal.KEY_F5) { + if (device.supportsRestart) { + // F5, restart + restart(); } } - if (hot != '' || cold != '') - printStatus('$hot$cold.', emphasis: true); - printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.', emphasis: true); } - Future<dynamic> _stopLogger() { - return _loggingSubscription?.cancel(); + @override + Future<Null> cleanupAfterSignal() async { + await stopEchoingDeviceLog(); + await stopApp(); } - void _resetTerminal() { - if (usesTerminalUI) - terminal.singleCharMode = false; + @override + Future<Null> cleanupAtFinish() async { + await stopEchoingDeviceLog(); } - Future<Null> _stopApp() { - if (observatory != null && !observatory.isClosed) { - if (observatory.isolates.isNotEmpty) { - observatory.flutterExit(observatory.firstIsolateId); - return new Future<Null>.delayed(new Duration(milliseconds: 100)); - } - } - - if (!_exitCompleter.isCompleted) - _exitCompleter.complete(0); - - return new Future<Null>.value(); - } -} - -String getMissingPackageHintForPlatform(TargetPlatform platform) { - switch (platform) { - case TargetPlatform.android_arm: - case TargetPlatform.android_x64: - return 'Is your project missing an android/AndroidManifest.xml?\nConsider running "flutter create ." to create one.'; - case TargetPlatform.ios: - return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.'; - default: - return null; + @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.'); } }