diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart index ccd227b82dc..729f1d45db8 100644 --- a/packages/flutter_tools/lib/src/base/os.dart +++ b/packages/flutter_tools/lib/src/base/os.dart @@ -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(['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]); } + + @override + File makePipe(String path) { + runSync(['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 findAvailablePort() async { diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index c14f40a575d..fed2bb0eb1e 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -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( diff --git a/packages/flutter_tools/lib/src/hot.dart b/packages/flutter_tools/lib/src/hot.dart index 89ba1337f45..60cc7681553 100644 --- a/packages/flutter_tools/lib/src/hot.dart +++ b/packages/flutter_tools/lib/src/hot.dart @@ -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 _readFromControlPipe() async { + final Stream> stream = pipe.openRead(); + final List bytes = await stream.first; + final String string = new String.fromCharCodes(bytes).trim(); + return string; + } + + Future _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 run({ Completer 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 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(); } } } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 3bbbcbfe67d..0d405d6fb39 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -40,12 +40,12 @@ abstract class ResidentRunner { return stopApp(); } - void _debugDumpApp() { - serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId); + Future _debugDumpApp() async { + await serviceProtocol.flutterDebugDumpApp(serviceProtocol.firstIsolateId); } - void _debugDumpRenderTree() { - serviceProtocol.flutterDebugDumpRenderTree(serviceProtocol.firstIsolateId); + Future _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 _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 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 handleTerminalCommand(String code); } /// Given the value of the --target option, return the path of the Dart file diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart index 9455918ae5b..d921b713ad9 100644 --- a/packages/flutter_tools/lib/src/run.dart +++ b/packages/flutter_tools/lib/src/run.dart @@ -208,12 +208,12 @@ class RunAndStayResident extends ResidentRunner { } @override - void handleTerminalCommand(String code) { + Future handleTerminalCommand(String code) async { String lower = code.toLowerCase(); if (lower == 'r' || code == AnsiTerminal.KEY_F5) { if (device.supportsRestart) { // F5, restart - restart(); + await restart(); } } }