Commit 4c1dde8d authored by John McCutchan's avatar John McCutchan Committed by GitHub

Add a control pipe that can trigger reloads / restarts (#5282)

parent e1ebc41a
......@@ -40,6 +40,9 @@ abstract class OperatingSystemUtils {
/// if `which` was not able to locate the binary.
File which(String execName);
/// Return the File representing a new pipe.
File makePipe(String path);
void unzip(File file, Directory targetDirectory);
}
......@@ -67,6 +70,12 @@ class _PosixUtils extends OperatingSystemUtils {
void unzip(File file, Directory targetDirectory) {
runSync(<String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]);
}
@override
File makePipe(String path) {
runSync(<String>['mkfifo', path]);
return new File(path);
}
}
class _WindowsUtils extends OperatingSystemUtils {
......@@ -101,6 +110,11 @@ class _WindowsUtils extends OperatingSystemUtils {
destFile.writeAsBytesSync(archiveFile.content);
}
}
@override
File makePipe(String path) {
throw new UnsupportedError('makePipe is not implemented on Windows.');
}
}
Future<int> findAvailablePort() async {
......
......@@ -20,6 +20,7 @@ import '../runner/flutter_command.dart';
import 'build_apk.dart';
import 'install.dart';
import 'trace.dart';
import '../base/os.dart';
abstract class RunCommandBase extends FlutterCommand {
RunCommandBase() {
......@@ -63,6 +64,12 @@ class RunCommand extends RunCommandBase {
defaultsTo: false,
help: 'Run with support for hot reloading.');
// Option to enable control over a named pipe.
argParser.addOption('control-pipe',
hide: true,
help: 'Specify a named pipe to receive commands on.');
// Hidden option to enable a benchmarking mode. This will run the given
// application, measure the startup time and the app restart time, write the
// results out to 'refresh_benchmark.json', and exit. This flag is intended
......@@ -118,6 +125,7 @@ class RunCommand extends RunCommandBase {
// Do some early error checks for hot mode.
bool hotMode = argResults['hot'];
if (hotMode) {
if (getBuildMode() != BuildMode.debug) {
printError('Hot mode only works with debug builds.');
......@@ -127,6 +135,21 @@ class RunCommand extends RunCommandBase {
printError('Hot mode is not supported by this device.');
return 1;
}
} else {
if (argResults['control-pipe']) {
printError('--control-pipe requires --hot');
return 1;
}
}
String pipePath = argResults['control-pipe'];
File pipe;
if (pipePath != null) {
try {
// Attempt to create the pipe.
os.makePipe(pipePath);
} catch (_) { /* ignore */ }
pipe = new File(pipePath);
}
ResidentRunner runner;
......@@ -135,7 +158,8 @@ class RunCommand extends RunCommandBase {
runner = new HotRunner(
deviceForCommand,
target: targetFile,
debuggingOptions: options
debuggingOptions: options,
pipe: pipe
);
} else {
runner = new RunAndStayResident(
......
......@@ -34,7 +34,8 @@ class HotRunner extends ResidentRunner {
Device device, {
String target,
DebuggingOptions debuggingOptions,
bool usesTerminalUI: true
bool usesTerminalUI: true,
this.pipe
}) : super(device,
target: target,
debuggingOptions: debuggingOptions,
......@@ -46,6 +47,33 @@ class HotRunner extends ResidentRunner {
String _mainPath;
String _projectRootPath;
final AssetBundle bundle = new AssetBundle();
final File pipe;
Future<String> _readFromControlPipe() async {
final Stream<List<int>> stream = pipe.openRead();
final List<int> bytes = await stream.first;
final String string = new String.fromCharCodes(bytes).trim();
return string;
}
Future<Null> _startReadingFromControlPipe() async {
if (pipe == null)
return;
while (true) {
// This loop will only exit if _readFromControlPipe throws an exception.
// If no exception is thrown this will keep the flutter command running
// until it is explicitly stopped via some other mechanism, for example,
// ctrl+c or sending "q" to the control pipe.
String result = await _readFromControlPipe();
printStatus('Control pipe received "$result"');
await processTerminalInput(result);
if (result.toLowerCase() == 'q') {
printStatus("Finished reading from control pipe");
break;
}
}
}
@override
Future<int> run({ Completer<int> observatoryPortCompleter, String route }) {
......@@ -166,6 +194,8 @@ class HotRunner extends ResidentRunner {
await _launchFromDevFS(_package, _mainPath);
}
_startReadingFromControlPipe();
printStatus('Application running.');
setupTerminal();
......@@ -176,16 +206,16 @@ class HotRunner extends ResidentRunner {
}
@override
void handleTerminalCommand(String code) {
Future<Null> handleTerminalCommand(String code) async {
final String lower = code.toLowerCase();
if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
if ((lower == 'r') || (code == AnsiTerminal.KEY_F5)) {
// F5, restart
if (code == 'r') {
if ((code == 'r') || (code == AnsiTerminal.KEY_F5)) {
// lower-case 'r'
_reloadSources();
await _reloadSources();
} else {
// upper-case 'R'.
_restartFromSources();
await _restartFromSources();
}
}
}
......
......@@ -40,12 +40,12 @@ abstract class ResidentRunner {
return stopApp();
}
void _debugDumpApp() {
serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId);
Future<Null> _debugDumpApp() async {
await serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId);
}
void _debugDumpRenderTree() {
serviceProtocol.flutterDebugDumpRenderTree(serviceProtocol.firstIsolateId);
Future<Null> _debugDumpRenderTree() async {
await serviceProtocol.flutterDebugDumpRenderTree(serviceProtocol.firstIsolateId);
}
void registerSignalHandlers() {
......@@ -99,7 +99,7 @@ abstract class ResidentRunner {
}
/// Returns [true] if the input has been handled by this function.
bool _commonTerminalInputHandler(String character) {
Future<bool> _commonTerminalInputHandler(String character) async {
final String lower = character.toLowerCase();
printStatus(''); // the key the user tapped might be on this line
......@@ -109,20 +109,26 @@ abstract class ResidentRunner {
printHelp();
return true;
} else if (lower == 'w') {
_debugDumpApp();
await _debugDumpApp();
return true;
} else if (lower == 't') {
_debugDumpRenderTree();
await _debugDumpRenderTree();
return true;
} else if (lower == 'q' || character == AnsiTerminal.KEY_F10) {
// F10, exit
stopApp();
await stopApp();
return true;
}
return false;
}
Future<Null> processTerminalInput(String command) async {
bool handled = await _commonTerminalInputHandler(command);
if (!handled)
await handleTerminalCommand(command);
}
void appFinished() {
if (_finished.isCompleted)
return;
......@@ -143,9 +149,7 @@ abstract class ResidentRunner {
terminal.singleCharMode = true;
terminal.onCharInput.listen((String code) {
if (!_commonTerminalInputHandler(code)) {
handleTerminalCommand(code);
}
processTerminalInput(code);
});
}
}
......@@ -174,7 +178,7 @@ abstract class ResidentRunner {
/// Called to print help to the terminal.
void printHelp();
/// Called when the runner should handle a terminal command.
void handleTerminalCommand(String code);
Future<Null> handleTerminalCommand(String code);
}
/// Given the value of the --target option, return the path of the Dart file
......
......@@ -208,12 +208,12 @@ class RunAndStayResident extends ResidentRunner {
}
@override
void handleTerminalCommand(String code) {
Future<Null> handleTerminalCommand(String code) async {
String lower = code.toLowerCase();
if (lower == 'r' || code == AnsiTerminal.KEY_F5) {
if (device.supportsRestart) {
// F5, restart
restart();
await restart();
}
}
}
......
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