Commit 35c47611 authored by Alexander Aprelev's avatar Alexander Aprelev Committed by GitHub

Recreate outputFileName completer, handle process launch errors. (#11980)

* Recreate outputFileName completer, handle process launch errors.

* Fix formatting

* Updated comment
parent b91e8f97
...@@ -28,8 +28,12 @@ String _dartExecutable() { ...@@ -28,8 +28,12 @@ String _dartExecutable() {
} }
class _StdoutHandler { class _StdoutHandler {
_StdoutHandler() {
reset();
}
String boundaryKey; String boundaryKey;
Completer<String> outputFilename = new Completer<String>(); Completer<String> outputFilename;
void handler(String string) { void handler(String string) {
const String kResultPrefix = 'result '; const String kResultPrefix = 'result ';
...@@ -43,6 +47,13 @@ class _StdoutHandler { ...@@ -43,6 +47,13 @@ class _StdoutHandler {
else else
printTrace('compile debug message: $string'); 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 { Future<String> compile({String sdkRoot, String mainPath}) async {
...@@ -59,9 +70,12 @@ Future<String> compile({String sdkRoot, String mainPath}) async { ...@@ -59,9 +70,12 @@ Future<String> compile({String sdkRoot, String mainPath}) async {
'--sdk-root', '--sdk-root',
sdkRoot, sdkRoot,
mainPath mainPath
]); ]).catchError((dynamic error, StackTrace stack) {
printTrace('Failed to start frontend server $error, $stack');
});
final _StdoutHandler stdoutHandler = new _StdoutHandler(); final _StdoutHandler stdoutHandler = new _StdoutHandler();
server.stderr server.stderr
.transform(UTF8.decoder) .transform(UTF8.decoder)
.listen((String s) { printTrace('compile debug message: $s'); }); .listen((String s) { printTrace('compile debug message: $s'); });
...@@ -97,6 +111,8 @@ class ResidentCompiler { ...@@ -97,6 +111,8 @@ class ResidentCompiler {
/// Binary file name is returned if compilation was successful, otherwise /// Binary file name is returned if compilation was successful, otherwise
/// `null` is returned. /// `null` is returned.
Future<String> recompile(String mainPath, List<String> invalidatedFiles) async { Future<String> recompile(String mainPath, List<String> invalidatedFiles) async {
stdoutHandler.reset();
// First time recompile is called we actually have to compile the app from // First time recompile is called we actually have to compile the app from
// scratch ignoring list of invalidated files. // scratch ignoring list of invalidated files.
if (_server == null) if (_server == null)
...@@ -112,18 +128,16 @@ class ResidentCompiler { ...@@ -112,18 +128,16 @@ class ResidentCompiler {
} }
Future<String> _compile(String scriptFilename) async { Future<String> _compile(String scriptFilename) async {
if (_server == null) { final String frontendServer = artifacts.getArtifactPath(
final String frontendServer = artifacts.getArtifactPath( Artifact.frontendServerSnapshotForEngineDartSdk
Artifact.frontendServerSnapshotForEngineDartSdk );
); _server = await processManager.start(<String>[
_server = await processManager.start(<String>[ _dartExecutable(),
_dartExecutable(), frontendServer,
frontendServer, '--sdk-root',
'--sdk-root', _sdkRoot,
_sdkRoot, '--incremental'
'--incremental' ]);
]);
}
_server.stdout _server.stdout
.transform(UTF8.decoder) .transform(UTF8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
......
...@@ -429,20 +429,26 @@ class DevFS { ...@@ -429,20 +429,26 @@ class DevFS {
assetPathsToEvict.add(archivePath); 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) { if (dirtyEntries.isNotEmpty) {
printTrace('Updating files'); 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) { if (_httpWriter != null) {
try { try {
await _httpWriter.write(dirtyEntries); await _httpWriter.write(dirtyEntries);
......
...@@ -135,6 +135,10 @@ class HotRunner extends ResidentRunner { ...@@ -135,6 +135,10 @@ class HotRunner extends ResidentRunner {
await refreshViews(); await refreshViews();
for (FlutterDevice device in flutterDevices) { 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) for (FlutterView view in device.views)
printTrace('Connected to $view.'); printTrace('Connected to $view.');
} }
...@@ -334,6 +338,10 @@ class HotRunner extends ResidentRunner { ...@@ -334,6 +338,10 @@ class HotRunner extends ResidentRunner {
return new OperationResult(1, 'DevFS synchronization failed'); return new OperationResult(1, 'DevFS synchronization failed');
// Check if the isolate is paused and resume it. // Check if the isolate is paused and resume it.
for (FlutterDevice device in flutterDevices) { 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) { for (FlutterView view in device.views) {
if (view.uiIsolate != null) { if (view.uiIsolate != null) {
// Reload the isolate. // Reload the isolate.
...@@ -495,6 +503,8 @@ class HotRunner extends ResidentRunner { ...@@ -495,6 +503,8 @@ class HotRunner extends ResidentRunner {
device.updateReloadStatus(validateReloadReport(firstReport, device.updateReloadStatus(validateReloadReport(firstReport,
printErrors: false)); printErrors: false));
retrieveFirstReloadReport.complete(firstReport); retrieveFirstReloadReport.complete(firstReport);
}, onError: (dynamic error, StackTrace stack) {
retrieveFirstReloadReport.completeError(error, stack);
}); });
} }
......
...@@ -121,32 +121,70 @@ void main() { ...@@ -121,32 +121,70 @@ void main() {
testUsingContext('compile and recompile', () async { testUsingContext('compile and recompile', () async {
final BufferLogger logger = context[Logger]; final BufferLogger logger = context[Logger];
when(mockFrontendServer.stdout).thenReturn(new Stream<List<int>>.fromFuture( final StreamController<List<int>> streamController = new StreamController<List<int>>();
new Future<List<int>>.value(UTF8.encode( when(mockFrontendServer.stdout).thenReturn(streamController.stream);
'result abc\nline1\nline2\nabc /path/to/main.dart.dill' 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');
verifyNoMoreInteractions(mockFrontendServerStdIn);
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 */); await generator.recompile('/path/to/main.dart', null /* invalidatedFiles */);
verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart')); verify(mockFrontendServerStdIn.writeln('compile /path/to/main.dart'));
final String output = await generator.recompile( await _recompile(streamController, generator, mockFrontendServerStdIn,
null /* mainPath */, 'result abc\nline1\nline2\nabc /path/to/main.dart.dill\n');
<String>['/path/to/main.dart'] await _recompile(streamController, generator, mockFrontendServerStdIn,
); 'result abc\nline2\nline3\nabc /path/to/main.dart.dill\n');
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));
verifyNoMoreInteractions(mockFrontendServerStdIn); verifyNoMoreInteractions(mockFrontendServerStdIn);
expect(logger.traceText, equals('compile debug message: line1\ncompile debug message: line2\n')); expect(logger.traceText, equals(
expect(output, equals('/path/to/main.dart.dill')); '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>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, 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 MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {} class MockProcess extends Mock implements Process {}
class MockStream extends Mock implements Stream<List<int>> {} class MockStream extends Mock implements Stream<List<int>> {}
......
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