Commit 81b4e827 authored by John McCutchan's avatar John McCutchan Committed by GitHub

Split hot run mode into a separate file (#5233)

parent ea7d5bf2
...@@ -52,10 +52,27 @@ class AssetBundle { ...@@ -52,10 +52,27 @@ class AssetBundle {
static const String _kFontSetMaterial = 'material'; static const String _kFontSetMaterial = 'material';
static const String _kFontSetRoboto = 'roboto'; static const String _kFontSetRoboto = 'roboto';
DateTime _lastBuildTimestamp;
bool needsBuild({String manifestPath: defaultManifestPath}) {
if (_lastBuildTimestamp == null)
return true;
FileStat stat = new File(manifestPath).statSync();
if (stat.type == FileSystemEntityType.NOT_FOUND)
return true;
return stat.modified.isAfter(_lastBuildTimestamp);
}
Future<int> build({String manifestPath: defaultManifestPath, Future<int> build({String manifestPath: defaultManifestPath,
String workingDirPath: defaultWorkingDirPath, String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true}) async { bool includeRobotoFonts: true}) async {
Object manifest = _loadFlutterYamlManifest(manifestPath); Object manifest = _loadFlutterYamlManifest(manifestPath);
if (manifest == null) {
// No manifest file found for this application.
return 0;
}
if (manifest != null) { if (manifest != null) {
int result = await _validateFlutterYamlManifest(manifest); int result = await _validateFlutterYamlManifest(manifest);
if (result != 0) if (result != 0)
...@@ -65,6 +82,8 @@ class AssetBundle { ...@@ -65,6 +82,8 @@ class AssetBundle {
assert(manifestDescriptor != null); assert(manifestDescriptor != null);
String assetBasePath = path.dirname(path.absolute(manifestPath)); String assetBasePath = path.dirname(path.absolute(manifestPath));
_lastBuildTimestamp = new DateTime.now();
final PackageMap packageMap = final PackageMap packageMap =
new PackageMap(path.join(assetBasePath, '.packages')); new PackageMap(path.join(assetBasePath, '.packages'));
......
...@@ -13,7 +13,7 @@ import '../base/utils.dart'; ...@@ -13,7 +13,7 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../dart/package_map.dart'; import '../dart/package_map.dart';
import '../globals.dart'; import '../globals.dart';
import '../run.dart'; import '../resident_runner.dart';
import 'build.dart'; import 'build.dart';
const String _kDefaultAotOutputDir = 'build/aot'; const String _kDefaultAotOutputDir = 'build/aot';
......
...@@ -18,7 +18,7 @@ import '../base/utils.dart'; ...@@ -18,7 +18,7 @@ import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../flx.dart' as flx; import '../flx.dart' as flx;
import '../globals.dart'; import '../globals.dart';
import '../run.dart'; import '../resident_runner.dart';
import '../services.dart'; import '../services.dart';
import 'build_aot.dart'; import 'build_aot.dart';
import 'build.dart'; import 'build.dart';
......
...@@ -291,7 +291,7 @@ class AppDomain extends Domain { ...@@ -291,7 +291,7 @@ class AppDomain extends Domain {
String route = _getStringArg(args, 'route'); String route = _getStringArg(args, 'route');
String mode = _getStringArg(args, 'mode'); String mode = _getStringArg(args, 'mode');
String target = _getStringArg(args, 'target'); String target = _getStringArg(args, 'target');
bool hotMode = _getBoolArg(args, 'hot'); // TODO(johnmccutchan): Wire up support for hot mode.
Device device = daemon.deviceDomain._getDevice(deviceId); Device device = daemon.deviceDomain._getDevice(deviceId);
if (device == null) if (device == null)
...@@ -323,8 +323,7 @@ class AppDomain extends Domain { ...@@ -323,8 +323,7 @@ class AppDomain extends Domain {
device, device,
target: target, target: target,
debuggingOptions: options, debuggingOptions: options,
usesTerminalUI: false, usesTerminalUI: false
hotMode: hotMode
); );
AppInstance app = new AppInstance(_getNextAppId(), runner); AppInstance app = new AppInstance(_getNextAppId(), runner);
......
...@@ -21,7 +21,7 @@ import '../dart/sdk.dart'; ...@@ -21,7 +21,7 @@ import '../dart/sdk.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils; import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils;
import '../run.dart'; import '../resident_runner.dart';
import 'build_apk.dart' as build_apk; import 'build_apk.dart' as build_apk;
import 'run.dart'; import 'run.dart';
......
...@@ -12,7 +12,9 @@ import '../build_info.dart'; ...@@ -12,7 +12,9 @@ import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../hot.dart' as hot;
import '../observatory.dart'; import '../observatory.dart';
import '../resident_runner.dart';
import '../run.dart'; import '../run.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import 'build_apk.dart'; import 'build_apk.dart';
...@@ -132,18 +134,25 @@ class RunCommand extends RunCommandBase { ...@@ -132,18 +134,25 @@ class RunCommand extends RunCommandBase {
} }
if (argResults['resident']) { if (argResults['resident']) {
RunAndStayResident runner = new RunAndStayResident( if (argResults['hot']) {
deviceForCommand, hot.HotRunner runner = new hot.HotRunner(
target: targetFile, deviceForCommand,
debuggingOptions: options, target: targetFile,
hotMode: argResults['hot'] debuggingOptions: options
); );
return runner.run(route: route);
return runner.run( } else {
traceStartup: traceStartup, RunAndStayResident runner = new RunAndStayResident(
benchmark: argResults['benchmark'], deviceForCommand,
route: route target: targetFile,
); debuggingOptions: options
);
return runner.run(
traceStartup: traceStartup,
benchmark: argResults['benchmark'],
route: route
);
}
} else { } else {
// TODO(devoncarew): Remove this path and support the `--no-resident` option // TODO(devoncarew): Remove this path and support the `--no-resident` option
// using the `RunAndStayResident` class. // using the `RunAndStayResident` class.
......
...@@ -12,7 +12,7 @@ import '../build_info.dart'; ...@@ -12,7 +12,7 @@ import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../flx.dart' as flx; import '../flx.dart' as flx;
import '../globals.dart'; import '../globals.dart';
import '../run.dart'; import '../resident_runner.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
const String _kDefaultBundlePath = 'build/app.flx'; const String _kDefaultBundlePath = 'build/app.flx';
......
...@@ -180,7 +180,9 @@ class DevFS { ...@@ -180,7 +180,9 @@ class DevFS {
return await _operations.destroy(fsName); return await _operations.destroy(fsName);
} }
Future<dynamic> update({ DevFSProgressReporter progressReporter, AssetBundle bundle }) async { Future<dynamic> update({ DevFSProgressReporter progressReporter,
AssetBundle bundle,
bool bundleDirty: false }) async {
_bytes = 0; _bytes = 0;
// Mark all entries as not seen. // Mark all entries as not seen.
_entries.forEach((String path, DevFSEntry entry) { _entries.forEach((String path, DevFSEntry entry) {
...@@ -189,7 +191,8 @@ class DevFS { ...@@ -189,7 +191,8 @@ class DevFS {
printTrace('DevFS: Starting sync from $rootDirectory'); printTrace('DevFS: Starting sync from $rootDirectory');
// Send the root and lib directories. // Send the root and lib directories.
Directory directory = rootDirectory; Directory directory = rootDirectory;
_syncDirectory(directory, recursive: true); await _syncDirectory(directory, recursive: true);
printTrace('DevFS: Syncing of $rootDirectory finished');
String packagesFilePath = path.join(rootDirectory.path, kPackagesFileName); String packagesFilePath = path.join(rootDirectory.path, kPackagesFileName);
StringBuffer sb; StringBuffer sb;
// Send the packages. // Send the packages.
...@@ -202,12 +205,16 @@ class DevFS { ...@@ -202,12 +205,16 @@ class DevFS {
if (uri.toString() == 'lib/') if (uri.toString() == 'lib/')
continue; continue;
Directory directory = new Directory.fromUri(uri); Directory directory = new Directory.fromUri(uri);
if (_syncDirectory(directory, printTrace('DevFS: Syncing package $packageName started');
directoryName: 'packages/$packageName', bool packageWritten =
recursive: true)) { await _syncDirectory(directory,
directoryName: 'packages/$packageName',
recursive: true);
if (packageWritten) {
sb ??= new StringBuffer(); sb ??= new StringBuffer();
sb.writeln('$packageName:packages/$packageName'); sb.writeln('$packageName:packages/$packageName');
} }
printTrace('DevFS: Syncing package $packageName finished');
} }
} }
if (bundle != null) { if (bundle != null) {
...@@ -216,6 +223,11 @@ class DevFS { ...@@ -216,6 +223,11 @@ class DevFS {
// We write the assets into 'build/flx' so that they are in the // We write the assets into 'build/flx' so that they are in the
// same location in DevFS and the iOS simulator. // same location in DevFS and the iOS simulator.
final String devicePath = path.join('build/flx', entry.archivePath); final String devicePath = path.join('build/flx', entry.archivePath);
if (!bundleDirty && entry.isStringEntry) {
// When the bundle isn't dirty, we do not need to sync string
// entries.
continue;
}
_syncBundleEntry(devicePath, entry); _syncBundleEntry(devicePath, entry);
} }
} }
...@@ -309,10 +321,10 @@ class DevFS { ...@@ -309,10 +321,10 @@ class DevFS {
return false; return false;
} }
bool _syncDirectory(Directory directory, Future<bool> _syncDirectory(Directory directory,
{String directoryName, {String directoryName,
bool recursive: false, bool recursive: false,
bool ignoreDotFiles: true}) { bool ignoreDotFiles: true}) async {
String prefix = directoryName; String prefix = directoryName;
if (prefix == null) { if (prefix == null) {
prefix = path.relative(directory.path, from: rootDirectory.path); prefix = path.relative(directory.path, from: rootDirectory.path);
...@@ -320,9 +332,9 @@ class DevFS { ...@@ -320,9 +332,9 @@ class DevFS {
prefix = ''; prefix = '';
} }
try { try {
List<FileSystemEntity> files = Stream<FileSystemEntity> files =
directory.listSync(recursive: recursive, followLinks: false); directory.list(recursive: recursive, followLinks: false);
for (FileSystemEntity file in files) { await for (FileSystemEntity file in files) {
if (file is! File) { if (file is! File) {
// Skip non-files. // Skip non-files.
continue; continue;
......
// 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 'package:path/path.dart' as path;
import 'application_package.dart';
import 'asset.dart';
import 'base/logger.dart';
import 'base/utils.dart';
import 'cache.dart';
import 'commands/build_apk.dart';
import 'commands/install.dart';
import 'device.dart';
import 'globals.dart';
import 'devfs.dart';
import 'observatory.dart';
import 'resident_runner.dart';
String getDevFSLoaderScript() {
return path.absolute(path.join(Cache.flutterRoot,
'packages',
'flutter',
'bin',
'loader',
'loader_app.dart'));
}
class HotRunner extends ResidentRunner {
HotRunner(
Device device, {
String target,
DebuggingOptions debuggingOptions,
bool usesTerminalUI: true
}) : super(device,
target: target,
debuggingOptions: debuggingOptions,
usesTerminalUI: usesTerminalUI);
ApplicationPackage _package;
String _mainPath;
final AssetBundle bundle = new AssetBundle();
/// Start the app and keep the process running during its lifetime.
Future<int> run({
Completer<int> observatoryPortCompleter,
String route
}) {
// Don't let uncaught errors kill the process.
return runZoned(() {
return _run(
observatoryPortCompleter: observatoryPortCompleter,
route: route
);
}, onError: (dynamic error, StackTrace stackTrace) {
printError('Exception from flutter run: $error', stackTrace);
});
}
Future<int> _run({
Completer<int> observatoryPortCompleter,
String route
}) 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);
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;
}
// TODO(devoncarew): We shouldn't have to do type checks here.
if (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}.");
// We don't wait for the stop command to complete.
device.stopApp(_package);
}
// Allow any stop commands from above to start work.
await new Future<Duration>.delayed(Duration.ZERO);
// 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<String, dynamic> platformArgs = new Map<String, dynamic>();
await startEchoingDeviceLog();
if (device.needsDevFS) {
printStatus('Launching loader on ${device.name}...');
} else {
printStatus('Launching ${getDisplayPath(_mainPath)} on ${device.name}...');
}
LaunchResult result = await device.startApp(
_package,
debuggingOptions.buildMode,
mainPath: device.needsDevFS ? getDevFSLoaderScript() : _mainPath,
debuggingOptions: debuggingOptions,
platformArgs: platformArgs,
route: route
);
if (!result.started) {
if (device.needsDevFS) {
printError('Error launching DevFS loader on ${device.name}.');
} else {
printError('Error launching ${getDisplayPath(_mainPath)} on ${device.name}.');
}
await stopEchoingDeviceLog();
return 2;
}
if (observatoryPortCompleter != null && result.hasObservatory)
observatoryPortCompleter.complete(result.observatoryPort);
await connectToServiceProtocol(result.observatoryPort);
if (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);
}
printStatus('Application running.');
setupTerminal();
registerSignalHandlers();
return await waitForAppToFinish();
}
@override
void handleTerminalCommand(String code) {
final String lower = code.toLowerCase();
if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
// F5, restart
if (code == 'r') {
// lower-case 'r'
_reloadSources();
} else {
// upper-case 'R'.
_restartFromSources();
}
}
}
void _loaderShowMessage(String message, { int progress, int max }) {
serviceProtocol.flutterLoaderShowMessage(serviceProtocol.firstIsolateId, message);
if (progress != null) {
serviceProtocol.flutterLoaderSetProgress(serviceProtocol.firstIsolateId, progress.toDouble());
serviceProtocol.flutterLoaderSetProgressMax(serviceProtocol.firstIsolateId, max?.toDouble() ?? 0.0);
} else {
serviceProtocol.flutterLoaderSetProgress(serviceProtocol.firstIsolateId, 0.0);
serviceProtocol.flutterLoaderSetProgressMax(serviceProtocol.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(serviceProtocol, fsName, directory);
try {
await _devFS.create();
} catch (error) {
_devFS = null;
printError('Error initializing DevFS: $error');
return false;
}
}
final bool rebuildBundle = bundle.needsBuild();
if (rebuildBundle) {
Status bundleStatus = logger.startProgress('Updating assets...');
await bundle.build();
bundleStatus.stop(showElapsedTime: true);
}
Status devFSStatus = logger.startProgress('Syncing files on device...');
await _devFS.update(progressReporter: progressReporter,
bundle: bundle,
bundleDirty: rebuildBundle);
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();
String deviceAssetsDirectoryPath =
_devFS.baseUri.resolve('build/flx').toFilePath();
String viewId = await serviceProtocol.getFirstViewId();
// When this completer completes the isolate is running.
// TODO(johnmccutchan): Have the framework send an event after the first
// frame is rendered and use that instead of 'runnable'.
Completer<Null> completer = new Completer<Null>();
StreamSubscription<Event> subscription =
serviceProtocol.onIsolateEvent.listen((Event event) {
if (event.kind == 'IsolateStart') {
printTrace('Isolate is spawned.');
} else if (event.kind == 'IsolateRunnable') {
printTrace('Isolate is runnable.');
completer.complete(null);
}
});
await serviceProtocol.runInView(viewId,
deviceEntryPath,
devicePackagesPath,
deviceAssetsDirectoryPath);
await completer.future;
await subscription.cancel();
}
Future<Null> _restartFromSources() async {
if (_devFS != null)
await _updateDevFS();
Status restartStatus = logger.startProgress('Restarting application...');
await _launchFromDevFS(_package, _mainPath);
restartStatus.stop(showElapsedTime: true);
}
/// Returns [true] if the reload was successful.
bool _printReloadReport(Map<String, dynamic> reloadReport) {
if (!reloadReport['success']) {
printError('Hot reload was rejected:');
for (Map<String, dynamic> notice in reloadReport['details']['notices']) {
printError('${notice['message']}');
}
return false;
}
int loadedLibraryCount = reloadReport['details']['loadedLibraryCount'];
int finalLibraryCount = reloadReport['details']['finalLibraryCount'];
printStatus('Reloaded $loadedLibraryCount out of $finalLibraryCount libraries.');
return true;
}
Future<bool> _reloadSources() async {
if (serviceProtocol.firstIsolateId == null)
throw 'Application isolate not found';
if (_devFS != null)
await _updateDevFS();
Status reloadStatus = logger.startProgress('Performing hot reload');
try {
Map<String, dynamic> reloadReport =
await serviceProtocol.reloadSources(serviceProtocol.firstIsolateId);
reloadStatus.stop(showElapsedTime: true);
if (!_printReloadReport(reloadReport)) {
// Reload failed.
return false;
}
} catch (errorMessage) {
reloadStatus.stop(showElapsedTime: true);
printError('Hot reload failed:\n$errorMessage');
return false;
}
Status reassembleStatus =
logger.startProgress('Reassembling application');
try {
await serviceProtocol.flutterReassemble(serviceProtocol.firstIsolateId);
} catch (_) {
reassembleStatus.stop(showElapsedTime: true);
printError('Reassembling application failed.');
return false;
}
reassembleStatus.stop(showElapsedTime: true);
return true;
}
@override
void printHelp() {
printStatus('Type "h" or F1 for this help message. Type "q", F10, or ctrl-c to quit.', emphasis: true);
printStatus('Type "r" or F5 to perform a hot reload of the app.', emphasis: true);
printStatus('Type "R" to restart the app', emphasis: true);
printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.', emphasis: true);
}
@override
Future<Null> cleanupAfterSignal() async {
await stopEchoingDeviceLog();
await stopApp();
}
@override
Future<Null> cleanupAtFinish() async {
await _cleanupDevFS();
await stopEchoingDeviceLog();
}
}
...@@ -116,16 +116,37 @@ class Observatory { ...@@ -116,16 +116,37 @@ class Observatory {
}); });
} }
Future<Null> reloadSources(String isolateId) async { Future<Map<String, dynamic>> reloadSources(String isolateId) async {
try { try {
await sendRequest('_reloadSources', Response response =
<String, dynamic>{ 'isolateId': isolateId }); await sendRequest('_reloadSources',
return null; <String, dynamic>{ 'isolateId': isolateId });
return response.response;
} catch (e) { } catch (e) {
return new Future<Null>.error(e.data['details']); return new Future<Map<String, dynamic>>.error(e.data['details']);
} }
} }
Future<String> getFirstViewId() async {
Map<String, dynamic> response = await peer.sendRequest('_flutter.listViews');
List<Map<String, String>> views = response['views'];
return views[0]['id'];
}
Future<Null> runInView(String viewId,
String main,
String packages,
String assetsDirectory) async {
await peer.sendRequest('_flutter.runInView',
<String, dynamic> {
'viewId': viewId,
'mainScript': main,
'packagesFile': packages,
'assetDirectory': assetsDirectory
});
return null;
}
Future<Response> clearVMTimeline() => sendRequest('_clearVMTimeline'); Future<Response> clearVMTimeline() => sendRequest('_clearVMTimeline');
Future<Response> setVMTimelineFlags(List<String> recordedStreams) { Future<Response> setVMTimelineFlags(List<String> recordedStreams) {
......
// 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 'package:path/path.dart' as path;
import 'base/logger.dart';
import 'build_info.dart';
import 'device.dart';
import 'globals.dart';
import 'observatory.dart';
// Shared code between different resident application runners.
abstract class ResidentRunner {
ResidentRunner(this.device, {
this.target,
this.debuggingOptions,
this.usesTerminalUI: true
});
final Device device;
final String target;
final DebuggingOptions debuggingOptions;
final bool usesTerminalUI;
final Completer<int> _finished = new Completer<int>();
Observatory serviceProtocol;
StreamSubscription<String> _loggingSubscription;
Future<Null> stop() async {
await stopEchoingDeviceLog();
return stopApp();
}
void _debugDumpApp() {
serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId);
}
void _debugDumpRenderTree() {
serviceProtocol.flutterDebugDumpRenderTree(serviceProtocol.firstIsolateId);
}
void registerSignalHandlers() {
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) async {
_resetTerminal();
await cleanupAfterSignal();
exit(0);
});
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) async {
_resetTerminal();
await cleanupAfterSignal();
exit(0);
});
}
Future<Null> startEchoingDeviceLog() async {
if (_loggingSubscription != null) {
return;
}
_loggingSubscription = device.logReader.logLines.listen((String line) {
if (!line.contains('Observatory listening on http') &&
!line.contains('Diagnostic server listening on http'))
printStatus(line);
});
}
Future<Null> stopEchoingDeviceLog() async {
if (_loggingSubscription != null) {
await _loggingSubscription.cancel();
}
_loggingSubscription = null;
}
Future<Null> connectToServiceProtocol(int port) async {
if (!debuggingOptions.debuggingEnabled) {
return new Future<Null>.error('Error the service protocol is not enabled.');
}
serviceProtocol = await Observatory.connect(port);
printTrace('Connected to service protocol on port $port');
serviceProtocol.populateIsolateInfo();
serviceProtocol.onExtensionEvent.listen((Event event) {
printTrace(event.toString());
});
serviceProtocol.onIsolateEvent.listen((Event event) {
printTrace(event.toString());
});
// Listen for service protocol connection to close.
serviceProtocol.done.whenComplete(() {
appFinished();
});
}
/// Returns [true] if the input has been handled by this function.
bool _commonTerminalInputHandler(String character) {
final String lower = character.toLowerCase();
printStatus(''); // the key the user tapped might be on this line
if (lower == 'h' || lower == '?' || character == AnsiTerminal.KEY_F1) {
// F1, help
printHelp();
return true;
} else if (lower == 'w') {
_debugDumpApp();
return true;
} else if (lower == 't') {
_debugDumpRenderTree();
return true;
} else if (lower == 'q' || character == AnsiTerminal.KEY_F10) {
// F10, exit
stopApp();
return true;
}
return false;
}
void appFinished() {
if (_finished.isCompleted)
return;
printStatus('Application finished.');
_resetTerminal();
_finished.complete(0);
}
void _resetTerminal() {
if (usesTerminalUI)
terminal.singleCharMode = false;
}
void setupTerminal() {
if (usesTerminalUI) {
if (!logger.quiet)
printHelp();
terminal.singleCharMode = true;
terminal.onCharInput.listen((String code) {
if (!_commonTerminalInputHandler(code)) {
handleTerminalCommand(code);
}
});
}
}
Future<int> waitForAppToFinish() async {
int exitCode = await _finished.future;
await cleanupAtFinish();
return exitCode;
}
Future<Null> stopApp() {
if (serviceProtocol != null && !serviceProtocol.isClosed) {
if (serviceProtocol.isolates.isNotEmpty) {
serviceProtocol.flutterExit(serviceProtocol.firstIsolateId);
return new Future<Null>.delayed(new Duration(milliseconds: 100));
}
}
appFinished();
return new Future<Null>.value();
}
/// Called when a signal has requested we exit.
Future<Null> cleanupAfterSignal();
/// Called right before we exit.
Future<Null> cleanupAtFinish();
/// Called to print help to the terminal.
void printHelp();
/// Called when the runner should handle a terminal command.
void handleTerminalCommand(String code);
}
/// 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 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;
}
}
...@@ -5,66 +5,32 @@ ...@@ -5,66 +5,32 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart' as path;
import 'application_package.dart'; import 'application_package.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/utils.dart'; import 'base/utils.dart';
import 'build_info.dart';
import 'cache.dart';
import 'commands/build_apk.dart'; import 'commands/build_apk.dart';
import 'commands/install.dart'; import 'commands/install.dart';
import 'commands/trace.dart'; import 'commands/trace.dart';
import 'device.dart'; import 'device.dart';
import 'globals.dart'; import 'globals.dart';
import 'observatory.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( RunAndStayResident(
this.device, { Device device, {
this.target, String target,
this.debuggingOptions, DebuggingOptions debuggingOptions,
this.usesTerminalUI: true, bool usesTerminalUI: true
this.hotMode: false }) : super(device,
}); target: target,
debuggingOptions: debuggingOptions,
final Device device; usesTerminalUI: usesTerminalUI);
final String target;
final DebuggingOptions debuggingOptions;
final bool usesTerminalUI;
final bool hotMode;
ApplicationPackage _package; ApplicationPackage _package;
String _mainPath; String _mainPath;
LaunchResult _result; 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. /// Start the app and keep the process running during its lifetime.
Future<int> run({ Future<int> run({
bool traceStartup: false, bool traceStartup: false,
...@@ -86,7 +52,7 @@ class RunAndStayResident { ...@@ -86,7 +52,7 @@ class RunAndStayResident {
} }
Future<bool> restart() async { Future<bool> restart() async {
if (observatory == null) { if (serviceProtocol == null) {
printError('Debugging is not enabled.'); printError('Debugging is not enabled.');
return false; return false;
} else { } else {
...@@ -95,7 +61,7 @@ class RunAndStayResident { ...@@ -95,7 +61,7 @@ class RunAndStayResident {
Future<Event> extensionAddedEvent; Future<Event> extensionAddedEvent;
if (device.restartSendsFrameworkInitEvent) { if (device.restartSendsFrameworkInitEvent) {
extensionAddedEvent = observatory.onExtensionEvent extensionAddedEvent = serviceProtocol.onExtensionEvent
.where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization') .where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization')
.first; .first;
} }
...@@ -104,7 +70,7 @@ class RunAndStayResident { ...@@ -104,7 +70,7 @@ class RunAndStayResident {
_package, _package,
_result, _result,
mainPath: _mainPath, mainPath: _mainPath,
observatory: observatory observatory: serviceProtocol
); );
status.stop(showElapsedTime: true); status.stop(showElapsedTime: true);
...@@ -118,11 +84,6 @@ class RunAndStayResident { ...@@ -118,11 +84,6 @@ class RunAndStayResident {
} }
} }
Future<Null> stop() {
_stopLogger();
return _stopApp();
}
Future<int> _run({ Future<int> _run({
bool traceStartup: false, bool traceStartup: false,
bool benchmark: false, bool benchmark: false,
...@@ -186,19 +147,13 @@ class RunAndStayResident { ...@@ -186,19 +147,13 @@ class RunAndStayResident {
if (traceStartup != null) if (traceStartup != null)
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup }; platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
if (!hotMode || (hotMode && !device.needsDevFS)) await startEchoingDeviceLog();
printStatus('Running ${getDisplayPath(_mainPath)} on ${device.name}...'); 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);
});
_result = await device.startApp( _result = await device.startApp(
_package, _package,
debuggingOptions.buildMode, debuggingOptions.buildMode,
mainPath: (hotMode && device.needsDevFS) ? getDevFSLoaderScript() : _mainPath, mainPath: _mainPath,
debuggingOptions: debuggingOptions, debuggingOptions: debuggingOptions,
platformArgs: platformArgs, platformArgs: platformArgs,
route: route route: route
...@@ -206,7 +161,7 @@ class RunAndStayResident { ...@@ -206,7 +161,7 @@ class RunAndStayResident {
if (!_result.started) { if (!_result.started) {
printError('Error running application on ${device.name}.'); printError('Error running application on ${device.name}.');
await _loggingSubscription.cancel(); await stopEchoingDeviceLog();
return 2; return 2;
} }
...@@ -217,101 +172,21 @@ class RunAndStayResident { ...@@ -217,101 +172,21 @@ class RunAndStayResident {
// Connect to observatory. // Connect to observatory.
if (debuggingOptions.debuggingEnabled) { if (debuggingOptions.debuggingEnabled) {
observatory = await Observatory.connect(_result.observatoryPort); await connectToServiceProtocol(_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);
}
if (benchmark) if (benchmark)
await observatory.waitFirstIsolate; await serviceProtocol.waitFirstIsolate;
// Listen for observatory connection close.
observatory.done.whenComplete(() {
if (!_exitCompleter.isCompleted) {
printStatus('Application finished.');
_exitCompleter.complete(0);
}
});
} }
printStatus('Application running.'); printStatus('Application running.');
if (observatory != null && traceStartup) { if (serviceProtocol != null && traceStartup) {
printStatus('Downloading startup trace info...'); printStatus('Downloading startup trace info...');
await downloadStartupTrace(serviceProtocol);
await downloadStartupTrace(observatory); appFinished();
if (!_exitCompleter.isCompleted)
_exitCompleter.complete(0);
} else { } else {
if (usesTerminalUI) { setupTerminal();
if (!logger.quiet) registerSignalHandlers();
_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);
});
} }
if (benchmark) { if (benchmark) {
...@@ -329,159 +204,36 @@ class RunAndStayResident { ...@@ -329,159 +204,36 @@ class RunAndStayResident {
stop(); stop();
} }
return _exitCompleter.future.then((int exitCode) async { return await waitForAppToFinish();
_resetTerminal();
_stopLogger();
return exitCode;
});
}
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; @override
String _devFSProjectRootPath; void handleTerminalCommand(String code) {
Future<bool> _updateDevFS({ DevFSProgressReporter progressReporter }) async { String lower = code.toLowerCase();
if (_devFS == null) { if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
Directory directory = Directory.current; if (device.supportsRestart) {
_devFSProjectRootPath = directory.path; // F5, restart
String fsName = path.basename(directory.path); restart();
_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';
} }
} }
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();
} }
void _resetTerminal() { @override
if (usesTerminalUI) Future<Null> cleanupAfterSignal() async {
terminal.singleCharMode = false; await stopEchoingDeviceLog();
await stopApp();
} }
Future<Null> _stopApp() { @override
if (observatory != null && !observatory.isClosed) { Future<Null> cleanupAtFinish() async {
if (observatory.isolates.isNotEmpty) { await stopEchoingDeviceLog();
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) { @override
switch (platform) { void printHelp() {
case TargetPlatform.android_arm: String restartText = device.supportsRestart ? ', "r" or F5 to restart the app,' : '';
case TargetPlatform.android_x64: printStatus('Type "h" or F1 for help$restartText and "q", F10, or ctrl-c to quit.');
return 'Is your project missing an android/AndroidManifest.xml?\nConsider running "flutter create ." to create one.'; printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.');
case TargetPlatform.ios:
return 'Is your project missing an ios/Runner/Info.plist?\nConsider running "flutter create ." to create one.';
default:
return null;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment