Send exit for flutter run --resident (#3829)
* send ext.flutter.exit * listen for help restart, quit * refactor into a separate class
This commit is contained in:
parent
810b3e32a6
commit
067715e302
@ -3,15 +3,16 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert' show ASCII;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
final _AnsiTerminal _terminal = new _AnsiTerminal();
|
final AnsiTerminal terminal = new AnsiTerminal();
|
||||||
|
|
||||||
abstract class Logger {
|
abstract class Logger {
|
||||||
bool get isVerbose => false;
|
bool get isVerbose => false;
|
||||||
|
|
||||||
set supportsColor(bool value) {
|
set supportsColor(bool value) {
|
||||||
_terminal.supportsColor = value;
|
terminal.supportsColor = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display an error level message to the user. Commands should use this if they
|
/// Display an error level message to the user. Commands should use this if they
|
||||||
@ -59,7 +60,7 @@ class StdoutLogger extends Logger {
|
|||||||
_status?.cancel();
|
_status?.cancel();
|
||||||
_status = null;
|
_status = null;
|
||||||
|
|
||||||
print(emphasis ? _terminal.writeBold(message) : message);
|
print(emphasis ? terminal.writeBold(message) : message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,7 +71,7 @@ class StdoutLogger extends Logger {
|
|||||||
_status?.cancel();
|
_status?.cancel();
|
||||||
_status = null;
|
_status = null;
|
||||||
|
|
||||||
if (_terminal.supportsColor) {
|
if (terminal.supportsColor) {
|
||||||
_status = new _AnsiStatus(message);
|
_status = new _AnsiStatus(message);
|
||||||
return _status;
|
return _status;
|
||||||
} else {
|
} else {
|
||||||
@ -177,34 +178,48 @@ class _LogMessage {
|
|||||||
String prefix = '${millis.toString().padLeft(4)} ms • ';
|
String prefix = '${millis.toString().padLeft(4)} ms • ';
|
||||||
String indent = ''.padLeft(prefix.length);
|
String indent = ''.padLeft(prefix.length);
|
||||||
if (millis >= 100)
|
if (millis >= 100)
|
||||||
prefix = _terminal.writeBold(prefix.substring(0, prefix.length - 3)) + ' • ';
|
prefix = terminal.writeBold(prefix.substring(0, prefix.length - 3)) + ' • ';
|
||||||
String indentMessage = message.replaceAll('\n', '\n$indent');
|
String indentMessage = message.replaceAll('\n', '\n$indent');
|
||||||
|
|
||||||
if (type == _LogType.error) {
|
if (type == _LogType.error) {
|
||||||
stderr.writeln(prefix + _terminal.writeBold(indentMessage));
|
stderr.writeln(prefix + terminal.writeBold(indentMessage));
|
||||||
if (stackTrace != null)
|
if (stackTrace != null)
|
||||||
stderr.writeln(indent + stackTrace.toString().replaceAll('\n', '\n$indent'));
|
stderr.writeln(indent + stackTrace.toString().replaceAll('\n', '\n$indent'));
|
||||||
} else if (type == _LogType.status) {
|
} else if (type == _LogType.status) {
|
||||||
print(prefix + _terminal.writeBold(indentMessage));
|
print(prefix + terminal.writeBold(indentMessage));
|
||||||
} else {
|
} else {
|
||||||
print(prefix + indentMessage);
|
print(prefix + indentMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnsiTerminal {
|
class AnsiTerminal {
|
||||||
_AnsiTerminal() {
|
AnsiTerminal() {
|
||||||
// TODO(devoncarew): This detection does not work for Windows.
|
// TODO(devoncarew): This detection does not work for Windows.
|
||||||
String term = Platform.environment['TERM'];
|
String term = Platform.environment['TERM'];
|
||||||
supportsColor = term != null && term != 'dumb';
|
supportsColor = term != null && term != 'dumb';
|
||||||
}
|
}
|
||||||
|
|
||||||
static const String _bold = '\u001B[1m';
|
static const String KEY_F1 = '\u001BOP';
|
||||||
|
static const String KEY_F5 = '\u001B[15~';
|
||||||
|
static const String KEY_F10 = '\u001B[21~';
|
||||||
|
|
||||||
|
static const String _bold = '\u001B[1m';
|
||||||
static const String _reset = '\u001B[0m';
|
static const String _reset = '\u001B[0m';
|
||||||
|
|
||||||
bool supportsColor;
|
bool supportsColor;
|
||||||
|
|
||||||
String writeBold(String str) => supportsColor ? '$_bold$str$_reset' : str;
|
String writeBold(String str) => supportsColor ? '$_bold$str$_reset' : str;
|
||||||
|
|
||||||
|
set singleCharMode(bool value) {
|
||||||
|
stdin.echoMode = !value;
|
||||||
|
stdin.lineMode = !value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return keystrokes from the console.
|
||||||
|
///
|
||||||
|
/// Useful when the console is in [singleCharMode].
|
||||||
|
Stream<String> get onCharInput => stdin.transform(ASCII.decoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnsiStatus extends Status {
|
class _AnsiStatus extends Status {
|
||||||
|
@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
|
|||||||
|
|
||||||
import '../application_package.dart';
|
import '../application_package.dart';
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
|
import '../base/logger.dart';
|
||||||
import '../build_configuration.dart';
|
import '../build_configuration.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
@ -98,7 +99,6 @@ class RunCommand extends RunCommandBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int result;
|
|
||||||
DebuggingOptions options;
|
DebuggingOptions options;
|
||||||
|
|
||||||
if (getBuildMode() != BuildMode.debug) {
|
if (getBuildMode() != BuildMode.debug) {
|
||||||
@ -112,7 +112,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (argResults['resident']) {
|
if (argResults['resident']) {
|
||||||
result = await startAppStayResident(
|
_RunAndStayResident runner = new _RunAndStayResident(
|
||||||
deviceForCommand,
|
deviceForCommand,
|
||||||
toolchain,
|
toolchain,
|
||||||
target: target,
|
target: target,
|
||||||
@ -120,8 +120,10 @@ class RunCommand extends RunCommandBase {
|
|||||||
traceStartup: traceStartup,
|
traceStartup: traceStartup,
|
||||||
buildMode: getBuildMode()
|
buildMode: getBuildMode()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return runner.run();
|
||||||
} else {
|
} else {
|
||||||
result = await startApp(
|
return startApp(
|
||||||
deviceForCommand,
|
deviceForCommand,
|
||||||
toolchain,
|
toolchain,
|
||||||
target: target,
|
target: target,
|
||||||
@ -133,8 +135,6 @@ class RunCommand extends RunCommandBase {
|
|||||||
buildMode: getBuildMode()
|
buildMode: getBuildMode()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,141 +231,6 @@ Future<int> startApp(
|
|||||||
return result.started ? 0 : 2;
|
return result.started ? 0 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start logging
|
|
||||||
// start the app
|
|
||||||
// scrape obs. port
|
|
||||||
// connect via obs.
|
|
||||||
// stay alive as long as obs. is alive
|
|
||||||
// intercept SIG_QUIT; kill the launched app
|
|
||||||
|
|
||||||
Future<int> startAppStayResident(
|
|
||||||
Device device,
|
|
||||||
Toolchain toolchain, {
|
|
||||||
String target,
|
|
||||||
DebuggingOptions debuggingOptions,
|
|
||||||
bool traceStartup: false,
|
|
||||||
BuildMode buildMode: BuildMode.debug
|
|
||||||
}) async {
|
|
||||||
String 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationPackage 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,
|
|
||||||
toolchain,
|
|
||||||
target: target,
|
|
||||||
buildMode: 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 (!(await installApp(device, package)))
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> platformArgs;
|
|
||||||
if (traceStartup != null)
|
|
||||||
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
|
|
||||||
|
|
||||||
printStatus('Running ${_getDisplayPath(mainPath)} on ${device.name}...');
|
|
||||||
|
|
||||||
StreamSubscription<String> loggingSubscription = device.logReader.logLines.listen((String line) {
|
|
||||||
if (!line.contains('Observatory listening on http') && !line.contains('Diagnostic server listening on http'))
|
|
||||||
printStatus(line);
|
|
||||||
});
|
|
||||||
|
|
||||||
LaunchResult result = await device.startApp(
|
|
||||||
package,
|
|
||||||
toolchain,
|
|
||||||
mainPath: mainPath,
|
|
||||||
debuggingOptions: debuggingOptions,
|
|
||||||
platformArgs: platformArgs
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result.started) {
|
|
||||||
printError('Error running application on ${device.name}.');
|
|
||||||
await loggingSubscription.cancel();
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
Completer<int> exitCompleter = new Completer<int>();
|
|
||||||
|
|
||||||
void complete(int exitCode) {
|
|
||||||
if (!exitCompleter.isCompleted)
|
|
||||||
exitCompleter.complete(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Connect to observatory.
|
|
||||||
WebSocket observatoryConnection;
|
|
||||||
|
|
||||||
if (debuggingOptions.debuggingEnabled) {
|
|
||||||
final String localhost = InternetAddress.LOOPBACK_IP_V4.address;
|
|
||||||
final String url = 'ws://$localhost:${result.observatoryPort}/ws';
|
|
||||||
|
|
||||||
observatoryConnection = await WebSocket.connect(url);
|
|
||||||
printTrace('Connected to observatory port: ${result.observatoryPort}.');
|
|
||||||
|
|
||||||
// Listen for observatory connection close.
|
|
||||||
observatoryConnection.listen((dynamic data) {
|
|
||||||
// Ignore observatory messages.
|
|
||||||
}, onDone: () {
|
|
||||||
loggingSubscription.cancel();
|
|
||||||
printStatus('Application finished.');
|
|
||||||
complete(0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
printStatus('Application running.');
|
|
||||||
|
|
||||||
// When terminating, close down the log reader.
|
|
||||||
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
|
|
||||||
loggingSubscription.cancel();
|
|
||||||
printStatus('');
|
|
||||||
complete(0);
|
|
||||||
});
|
|
||||||
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
|
|
||||||
loggingSubscription.cancel();
|
|
||||||
complete(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
return exitCompleter.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given the value of the --target option, return the path of the Dart file
|
/// Given the value of the --target option, return the path of the Dart file
|
||||||
/// where the app's main function should be.
|
/// where the app's main function should be.
|
||||||
String findMainDartFile([String target]) {
|
String findMainDartFile([String target]) {
|
||||||
@ -477,3 +342,227 @@ String _getDisplayPath(String fullPath) {
|
|||||||
String cwd = Directory.current.path + Platform.pathSeparator;
|
String cwd = Directory.current.path + Platform.pathSeparator;
|
||||||
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
|
return fullPath.startsWith(cwd) ? fullPath.substring(cwd.length) : fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _RunAndStayResident {
|
||||||
|
_RunAndStayResident(
|
||||||
|
this.device,
|
||||||
|
this.toolchain, {
|
||||||
|
this.target,
|
||||||
|
this.debuggingOptions,
|
||||||
|
this.traceStartup : false,
|
||||||
|
this.buildMode : BuildMode.debug
|
||||||
|
});
|
||||||
|
|
||||||
|
final Device device;
|
||||||
|
final Toolchain toolchain;
|
||||||
|
final String target;
|
||||||
|
final DebuggingOptions debuggingOptions;
|
||||||
|
final bool traceStartup;
|
||||||
|
final BuildMode buildMode;
|
||||||
|
|
||||||
|
Completer<int> _exitCompleter;
|
||||||
|
StreamSubscription<String> _loggingSubscription;
|
||||||
|
|
||||||
|
WebSocket _observatoryConnection;
|
||||||
|
String _isolateId;
|
||||||
|
int _messageId = 0;
|
||||||
|
|
||||||
|
/// Start the app and keep the process running during its lifetime.
|
||||||
|
Future<int> run() async {
|
||||||
|
String 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationPackage 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,
|
||||||
|
toolchain,
|
||||||
|
target: target,
|
||||||
|
buildMode: 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 (!(await installApp(device, package)))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> platformArgs;
|
||||||
|
if (traceStartup != null)
|
||||||
|
platformArgs = <String, dynamic>{ 'trace-startup': traceStartup };
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
LaunchResult result = await device.startApp(
|
||||||
|
package,
|
||||||
|
toolchain,
|
||||||
|
mainPath: mainPath,
|
||||||
|
debuggingOptions: debuggingOptions,
|
||||||
|
platformArgs: platformArgs
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result.started) {
|
||||||
|
printError('Error running application on ${device.name}.');
|
||||||
|
await _loggingSubscription.cancel();
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exitCompleter = new Completer<int>();
|
||||||
|
|
||||||
|
// Connect to observatory.
|
||||||
|
if (debuggingOptions.debuggingEnabled) {
|
||||||
|
final String localhost = InternetAddress.LOOPBACK_IP_V4.address;
|
||||||
|
final String url = 'ws://$localhost:${result.observatoryPort}/ws';
|
||||||
|
|
||||||
|
_observatoryConnection = await WebSocket.connect(url);
|
||||||
|
printTrace('Connected to observatory port: ${result.observatoryPort}.');
|
||||||
|
|
||||||
|
// Listen for observatory connection close.
|
||||||
|
_observatoryConnection.listen((dynamic data) {
|
||||||
|
if (data is String) {
|
||||||
|
Map<String, dynamic> json = JSON.decode(data);
|
||||||
|
|
||||||
|
if (json['method'] == 'streamNotify') {
|
||||||
|
Map<String, dynamic> event = json['params']['event'];
|
||||||
|
if (event['isolate'] != null && _isolateId == null)
|
||||||
|
_isolateId = event['isolate']['id'];
|
||||||
|
} else if (json['result'] != null && json['result']['type'] == 'VM') {
|
||||||
|
// isolates: [{
|
||||||
|
// type: @Isolate, fixedId: true, id: isolates/724543296, name: dev.flx$main, number: 724543296
|
||||||
|
// }]
|
||||||
|
List<dynamic> isolates = json['result']['isolates'];
|
||||||
|
if (isolates.isNotEmpty)
|
||||||
|
_isolateId = isolates.first['id'];
|
||||||
|
} else if (json['error'] != null) {
|
||||||
|
printError('Error: ${json['error']['message']}.');
|
||||||
|
printTrace(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, onDone: () {
|
||||||
|
_handleExit();
|
||||||
|
});
|
||||||
|
|
||||||
|
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
||||||
|
'method': 'streamListen',
|
||||||
|
'params': <String, dynamic>{ 'streamId': 'Isolate' },
|
||||||
|
'id': _messageId++
|
||||||
|
}));
|
||||||
|
|
||||||
|
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
||||||
|
'method': 'getVM',
|
||||||
|
'id': _messageId++
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
printStatus('Application running.');
|
||||||
|
_printHelp();
|
||||||
|
|
||||||
|
terminal.singleCharMode = true;
|
||||||
|
|
||||||
|
terminal.onCharInput.listen((String code) {
|
||||||
|
String lower = code.toLowerCase();
|
||||||
|
|
||||||
|
if (lower == 'h' || code == AnsiTerminal.KEY_F1) {
|
||||||
|
// F1, help
|
||||||
|
_printHelp();
|
||||||
|
} else if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
|
||||||
|
// F5, refresh
|
||||||
|
_handleRefresh();
|
||||||
|
} else if (lower == 'q' || code == AnsiTerminal.KEY_F10) {
|
||||||
|
// F10, exit
|
||||||
|
_handleExit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ProcessSignal.SIGINT.watch().listen((ProcessSignal signal) {
|
||||||
|
_handleExit();
|
||||||
|
});
|
||||||
|
ProcessSignal.SIGTERM.watch().listen((ProcessSignal signal) {
|
||||||
|
_handleExit();
|
||||||
|
});
|
||||||
|
|
||||||
|
return _exitCompleter.future.then((int exitCode) async {
|
||||||
|
if (_observatoryConnection != null &&
|
||||||
|
_observatoryConnection.readyState == WebSocket.OPEN &&
|
||||||
|
_isolateId != null) {
|
||||||
|
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
||||||
|
'method': 'ext.flutter.exit',
|
||||||
|
'params': <String, dynamic>{ 'isolateId': _isolateId },
|
||||||
|
'id': _messageId++
|
||||||
|
}));
|
||||||
|
// WebSockets do not have a flush() method.
|
||||||
|
await new Future<Null>.delayed(new Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return exitCode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _printHelp() {
|
||||||
|
printStatus('Type "h" or F1 for help, "r" or F5 to restart the app, and "q", F10, or ctrl-c to quit.');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleRefresh() {
|
||||||
|
if (_observatoryConnection == null) {
|
||||||
|
printError('Debugging is not enabled.');
|
||||||
|
} else {
|
||||||
|
printStatus('Re-starting application...');
|
||||||
|
|
||||||
|
// TODO(devoncarew): Show an error if the isolate reload fails.
|
||||||
|
_observatoryConnection.add(JSON.encode(<String, dynamic>{
|
||||||
|
'method': 'isolateReload',
|
||||||
|
'params': <String, dynamic>{ 'isolateId': _isolateId },
|
||||||
|
'id': _messageId++
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleExit() {
|
||||||
|
if (!_exitCompleter.isCompleted) {
|
||||||
|
_loggingSubscription?.cancel();
|
||||||
|
printStatus('');
|
||||||
|
printStatus('Application finished.');
|
||||||
|
terminal.singleCharMode = false;
|
||||||
|
_exitCompleter.complete(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/utils.dart';
|
import '../base/utils.dart';
|
||||||
|
@ -12,8 +12,8 @@ import 'package:web_socket_channel/io.dart';
|
|||||||
import 'android/android_device.dart';
|
import 'android/android_device.dart';
|
||||||
import 'application_package.dart';
|
import 'application_package.dart';
|
||||||
import 'base/common.dart';
|
import 'base/common.dart';
|
||||||
import 'base/utils.dart';
|
|
||||||
import 'base/os.dart';
|
import 'base/os.dart';
|
||||||
|
import 'base/utils.dart';
|
||||||
import 'build_configuration.dart';
|
import 'build_configuration.dart';
|
||||||
import 'globals.dart';
|
import 'globals.dart';
|
||||||
import 'ios/devices.dart';
|
import 'ios/devices.dart';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user