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