mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add a control pipe that can trigger reloads / restarts (#5282)
This commit is contained in:
parent
e1ebc41a14
commit
4c1dde8d67
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user