Recreate outputFileName completer, handle process launch errors. (#11980)
* Recreate outputFileName completer, handle process launch errors. * Fix formatting * Updated comment
This commit is contained in:
parent
b91e8f97ef
commit
35c47611b9
@ -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 {
|
|||||||
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 {
|
|||||||
'--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 {
|
|||||||
/// 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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
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 {
|
|||||||
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() {
|
|||||||
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 */);
|
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']
|
|
||||||
);
|
|
||||||
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'
|
||||||
|
));
|
||||||
|
}, 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>{
|
}, 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>> {}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user