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() {
}
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 @@ class _StdoutHandler {
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 @@ Future<String> compile({String sdkRoot, String mainPath}) async {
'--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 @@ class ResidentCompiler {
/// 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 @@ class ResidentCompiler {
}
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())
......
......@@ -429,20 +429,26 @@ class DevFS {
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);
......
......@@ -135,6 +135,10 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
device.updateReloadStatus(validateReloadReport(firstReport,
printErrors: false));
retrieveFirstReloadReport.complete(firstReport);
}, onError: (dynamic error, StackTrace stack) {
retrieveFirstReloadReport.completeError(error, stack);
});
}
......
......@@ -121,32 +121,70 @@ void main() {
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'));
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 */);
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');
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: 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'
'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>> {}
......
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