diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 158c33ab5eb..cfdffcd4ca0 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -346,6 +346,14 @@ class _CompileExpressionRequest extends _CompilationRequest { compiler._compileExpression(this); } +class _RejectRequest extends _CompilationRequest { + _RejectRequest(Completer completer): super(completer); + + @override + Future _run(ResidentCompiler compiler) async => + compiler._reject(); +} + /// Wrapper around incremental frontend server compiler, that communicates with /// server via stdin/stdout. /// @@ -389,6 +397,7 @@ class ResidentCompiler { String _initializeFromDill; bool _unsafePackageSerialization; final List _experimentalFlags; + bool _compileRequestNeedsConfirmation = false; final StreamController<_CompilationRequest> _controller; @@ -428,6 +437,8 @@ class ResidentCompiler { ); } + _compileRequestNeedsConfirmation = true; + if (_server == null) { return _compile( _mapFilename(request.mainPath, packageUriMapper), @@ -576,14 +587,33 @@ class ResidentCompiler { /// /// Either [accept] or [reject] should be called after every [recompile] call. void accept() { - _server.stdin.writeln('accept'); + if (_compileRequestNeedsConfirmation) { + _server.stdin.writeln('accept'); + } + _compileRequestNeedsConfirmation = false; } /// Should be invoked when results of compilation are rejected by the client. /// /// Either [accept] or [reject] should be called after every [recompile] call. - void reject() { + Future reject() { + if (!_controller.hasListener) { + _controller.stream.listen(_handleCompilationRequest); + } + + final Completer completer = Completer(); + _controller.add(_RejectRequest(completer)); + return completer.future; + } + + Future _reject() { + if (!_compileRequestNeedsConfirmation) { + return Future.value(null); + } + _stdoutHandler.reset(); _server.stdin.writeln('reject'); + _compileRequestNeedsConfirmation = false; + return _stdoutHandler.compilerOutput.future; } /// Should be invoked when frontend server compiler should forget what was diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index bba0e4dda21..3a6a9fa23ee 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -428,11 +428,11 @@ class FlutterDevice { return report; } - void updateReloadStatus(bool wasReloadSuccessful) { + Future updateReloadStatus(bool wasReloadSuccessful) async { if (wasReloadSuccessful) generator?.accept(); else - generator?.reject(); + await generator?.reject(); } } diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index a3edabbbba1..b1bd42bafa9 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -483,7 +483,7 @@ class HotRunner extends ResidentRunner { if (!updatedDevFS.success) { for (FlutterDevice device in flutterDevices) { if (device.generator != null) - device.generator.reject(); + await device.generator.reject(); } return OperationResult(1, 'DevFS synchronization failed'); } @@ -681,13 +681,13 @@ class HotRunner extends ResidentRunner { final List>> reportFutures = device.reloadSources( entryPath, pause: pause ); - Future.wait(reportFutures).then((List> reports) { // ignore: unawaited_futures + Future.wait(reportFutures).then((List> reports) async { // ignore: unawaited_futures // TODO(aam): Investigate why we are validating only first reload report, // which seems to be current behavior final Map firstReport = reports.first; // Don't print errors because they will be printed further down when // `validateReloadReport` is called again. - device.updateReloadStatus(validateReloadReport(firstReport, printErrors: false)); + await device.updateReloadStatus(validateReloadReport(firstReport, printErrors: false)); completer.complete(DeviceReloadReport(device, reports)); }); } diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart index f808842de5e..c8580bdcd46 100644 --- a/packages/flutter_tools/test/compile_test.dart +++ b/packages/flutter_tools/test/compile_test.dart @@ -274,14 +274,28 @@ example:org-dartlang-app:///lib/ ); expect(mockFrontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n'); + // No accept or reject commands should be issued until we + // send recompile request. + await _accept(streamController, generator, mockFrontendServerStdIn, ''); + await _reject(streamController, generator, mockFrontendServerStdIn, '', ''); + await _recompile(streamController, generator, mockFrontendServerStdIn, 'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'); + await _accept(streamController, generator, mockFrontendServerStdIn, '^accept\\n\$'); + + await _recompile(streamController, generator, mockFrontendServerStdIn, + 'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'); + + await _reject(streamController, generator, mockFrontendServerStdIn, 'result abc\nabc\n', + '^reject\\n\$'); + verifyNoMoreInteractions(mockFrontendServerStdIn); expect(mockFrontendServerStdIn.getAndClear(), isEmpty); expect(logger.errorText, equals( '\nCompiler message:\nline0\nline1\n' '\nCompiler message:\nline1\nline2\n' + '\nCompiler message:\nline1\nline2\n' )); }, overrides: { ProcessManager: () => mockProcessManager, @@ -504,6 +518,34 @@ Future _recompile(StreamController> streamController, mockFrontendServerStdIn._stdInWrites.clear(); } +Future _accept(StreamController> streamController, + ResidentCompiler generator, MockStdIn mockFrontendServerStdIn, + String expected) async { + // Put content into the output stream after generator.recompile gets + // going few lines below, resets completer. + generator.accept(); + final String commands = mockFrontendServerStdIn.getAndClear(); + final RegExp re = RegExp(expected); + expect(commands, matches(re)); + mockFrontendServerStdIn._stdInWrites.clear(); +} + +Future _reject(StreamController> streamController, + ResidentCompiler generator, MockStdIn mockFrontendServerStdIn, + String mockCompilerOutput, String expected) async { + // Put content into the output stream after generator.recompile gets + // going few lines below, resets completer. + scheduleMicrotask(() { + streamController.add(utf8.encode(mockCompilerOutput)); + }); + final CompilerOutput output = await generator.reject(); + expect(output, isNull); + final String commands = mockFrontendServerStdIn.getAndClear(); + final RegExp re = RegExp(expected); + expect(commands, matches(re)); + mockFrontendServerStdIn._stdInWrites.clear(); +} + class MockProcessManager extends Mock implements ProcessManager {} class MockProcess extends Mock implements Process {} class MockStream extends Mock implements Stream> {} diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index 58ba61167d9..3dcc063b158 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -462,7 +462,7 @@ class MockResidentCompiler extends BasicMock implements ResidentCompiler { void accept() {} @override - void reject() {} + Future reject() async { return null; } @override void reset() {}