Recreate outputFileName completer, handle process launch errors. (#11980)
* Recreate outputFileName completer, handle process launch errors.
* Fix formatting
* Updated comment
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index bf663f1..1052805 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -28,8 +28,12 @@
}
class _StdoutHandler {
+ _StdoutHandler() {
+ reset();
+ }
+
String boundaryKey;
- Completer<String> outputFilename = new Completer<String>();
+ Completer<String> outputFilename;
void handler(String string) {
const String kResultPrefix = 'result ';
@@ -43,6 +47,13 @@
else
printTrace('compile debug message: $string');
}
+
+ // This is needed to get ready to process next compilation result output,
+ // with its own boundary key and new completer.
+ void reset() {
+ boundaryKey = null;
+ outputFilename = new Completer<String>();
+ }
}
Future<String> compile({String sdkRoot, String mainPath}) async {
@@ -59,9 +70,12 @@
'--sdk-root',
sdkRoot,
mainPath
- ]);
+ ]).catchError((dynamic error, StackTrace stack) {
+ printTrace('Failed to start frontend server $error, $stack');
+ });
final _StdoutHandler stdoutHandler = new _StdoutHandler();
+
server.stderr
.transform(UTF8.decoder)
.listen((String s) { printTrace('compile debug message: $s'); });
@@ -97,6 +111,8 @@
/// Binary file name is returned if compilation was successful, otherwise
/// `null` is returned.
Future<String> recompile(String mainPath, List<String> invalidatedFiles) async {
+ stdoutHandler.reset();
+
// First time recompile is called we actually have to compile the app from
// scratch ignoring list of invalidated files.
if (_server == null)
@@ -112,18 +128,16 @@
}
Future<String> _compile(String scriptFilename) async {
- if (_server == null) {
- final String frontendServer = artifacts.getArtifactPath(
- Artifact.frontendServerSnapshotForEngineDartSdk
- );
- _server = await processManager.start(<String>[
- _dartExecutable(),
- frontendServer,
- '--sdk-root',
- _sdkRoot,
- '--incremental'
- ]);
- }
+ final String frontendServer = artifacts.getArtifactPath(
+ Artifact.frontendServerSnapshotForEngineDartSdk
+ );
+ _server = await processManager.start(<String>[
+ _dartExecutable(),
+ frontendServer,
+ '--sdk-root',
+ _sdkRoot,
+ '--incremental'
+ ]);
_server.stdout
.transform(UTF8.decoder)
.transform(const LineSplitter())
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart
index 5a0e550..b95d1ae 100644
--- a/packages/flutter_tools/lib/src/devfs.dart
+++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -429,20 +429,26 @@
assetPathsToEvict.add(archivePath);
}
});
+ if (generator != null) {
+ // We run generator even if [dirtyEntries] was empty because we want
+ // to keep logic of accepting/rejecting generator's output simple:
+ // we must accept/reject generator's output after every [update] call.
+ // Incremental run with no changes is supposed to be fast (considering
+ // that it is initiated by user key press).
+ final List<String> invalidatedFiles = <String>[];
+ for (DevFSContent content in dirtyEntries.values)
+ if (content is DevFSFileContent)
+ invalidatedFiles.add(content.file.uri.toString());
+ printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files');
+ final String compiledBinary = await generator.recompile(mainPath, invalidatedFiles);
+ if (compiledBinary != null && compiledBinary.isNotEmpty)
+ dirtyEntries.putIfAbsent(
+ Uri.parse(target + '.dill'),
+ () => new DevFSFileContent(fs.file(compiledBinary))
+ );
+ }
if (dirtyEntries.isNotEmpty) {
printTrace('Updating files');
- if (generator != null) {
- final List<String> invalidatedFiles = <String>[];
- dirtyEntries.forEach((Uri deviceUri, DevFSContent content) {
- if (content is DevFSFileContent)
- invalidatedFiles.add(content.file.uri.toString());
- });
- final String compiledBinary = await generator.recompile(mainPath, invalidatedFiles);
- if (compiledBinary != null && compiledBinary.isNotEmpty)
- dirtyEntries.putIfAbsent(Uri.parse(target + '.dill'),
- () => new DevFSFileContent(fs.file(compiledBinary)));
- }
-
if (_httpWriter != null) {
try {
await _httpWriter.write(dirtyEntries);
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 464ded4..7215dd8 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -135,6 +135,10 @@
await refreshViews();
for (FlutterDevice device in flutterDevices) {
+ // VM must have accepted the kernel binary, there will be no reload
+ // report, so we let incremental compiler know that source code was accepted.
+ if (device.generator != null)
+ device.generator.accept();
for (FlutterView view in device.views)
printTrace('Connected to $view.');
}
@@ -334,6 +338,10 @@
return new OperationResult(1, 'DevFS synchronization failed');
// Check if the isolate is paused and resume it.
for (FlutterDevice device in flutterDevices) {
+ // VM must have accepted the kernel binary, there will be no reload
+ // report, so we let incremental compiler know that source code was accepted.
+ if (device.generator != null)
+ device.generator.accept();
for (FlutterView view in device.views) {
if (view.uiIsolate != null) {
// Reload the isolate.
@@ -495,6 +503,8 @@
device.updateReloadStatus(validateReloadReport(firstReport,
printErrors: false));
retrieveFirstReloadReport.complete(firstReport);
+ }, onError: (dynamic error, StackTrace stack) {
+ retrieveFirstReloadReport.completeError(error, stack);
});
}
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index 3047c22..d0550b3 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -121,32 +121,70 @@
testUsingContext('compile and recompile', () async {
final BufferLogger logger = context[Logger];
- when(mockFrontendServer.stdout).thenReturn(new Stream<List<int>>.fromFuture(
- new Future<List<int>>.value(UTF8.encode(
- 'result abc\nline1\nline2\nabc /path/to/main.dart.dill'
- ))
- ));
-
+ final StreamController<List<int>> streamController = new StreamController<List<int>>();
+ when(mockFrontendServer.stdout).thenReturn(streamController.stream);
+ streamController.add(UTF8.encode('result abc\nline0\nline1\nabc /path/to/main.dart.dill\n'));
await generator.recompile('/path/to/main.dart', null /* invalidatedFiles */);
verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart'));
- final String output = await generator.recompile(
- null /* mainPath */,
- <String>['/path/to/main.dart']
- );
- final String recompileCommand = verify(mockFrontendServerStdIn.writeln(captureThat(startsWith('recompile ')))).captured[0];
- final String token = recompileCommand.split(' ')[1];
- verify(mockFrontendServerStdIn.writeln('/path/to/main.dart'));
- verify(mockFrontendServerStdIn.writeln(token));
+ await _recompile(streamController, generator, mockFrontendServerStdIn,
+ 'result abc\nline1\nline2\nabc /path/to/main.dart.dill\n');
+
verifyNoMoreInteractions(mockFrontendServerStdIn);
- expect(logger.traceText, equals('compile debug message: line1\ncompile debug message: line2\n'));
- expect(output, equals('/path/to/main.dart.dill'));
+ expect(logger.traceText, equals(
+ 'compile debug message: line0\ncompile debug message: line1\n'
+ 'compile debug message: line1\ncompile debug message: line2\n'
+ ));
+ }, overrides: <Type, Generator>{
+ ProcessManager: () => mockProcessManager,
+ });
+
+ testUsingContext('compile and recompile twice', () async {
+ final BufferLogger logger = context[Logger];
+
+ final StreamController<List<int>> streamController = new StreamController<List<int>>();
+ when(mockFrontendServer.stdout).thenReturn(streamController.stream);
+ streamController.add(UTF8.encode(
+ 'result abc\nline0\nline1\nabc /path/to/main.dart.dill\n'
+ ));
+ await generator.recompile('/path/to/main.dart', null /* invalidatedFiles */);
+ verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart'));
+
+ await _recompile(streamController, generator, mockFrontendServerStdIn,
+ 'result abc\nline1\nline2\nabc /path/to/main.dart.dill\n');
+ await _recompile(streamController, generator, mockFrontendServerStdIn,
+ 'result abc\nline2\nline3\nabc /path/to/main.dart.dill\n');
+
+ verifyNoMoreInteractions(mockFrontendServerStdIn);
+ expect(logger.traceText, equals(
+ 'compile debug message: line0\ncompile debug message: line1\n'
+ 'compile debug message: line1\ncompile debug message: line2\n'
+ 'compile debug message: line2\ncompile debug message: line3\n'
+ ));
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
});
}
+Future<Null> _recompile(StreamController<List<int>> streamController,
+ ResidentCompiler generator, MockStdIn mockFrontendServerStdIn,
+ String mockCompilerOutput) async {
+ // Put content into the output stream after generator.recompile gets
+ // going few lines below, resets completer.
+ new Future<List<int>>(() {
+ streamController.add(UTF8.encode(mockCompilerOutput));
+ });
+ final String output = await generator.recompile(null /* mainPath */, <String>['/path/to/main.dart']);
+ expect(output, equals('/path/to/main.dart.dill'));
+ final String recompileCommand = verify(
+ mockFrontendServerStdIn.writeln(captureThat(startsWith('recompile ')))
+ ).captured[0];
+ final String token1 = recompileCommand.split(' ')[1];
+ verify(mockFrontendServerStdIn.writeln('/path/to/main.dart'));
+ verify(mockFrontendServerStdIn.writeln(token1));
+}
+
class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {}
class MockStream extends Mock implements Stream<List<int>> {}