Use recompile-restart instruction when hot restarting on the web (#162616)

recompile has been split into recompile and recompile-restart in the
frontend server so that DDC can distinguish between hot reload
recompiles and hot restart recompiles, and therefore emit rejection
errors only on hot reload.

https://github.com/dart-lang/webdev/issues/2516

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.
This commit is contained in:
Srujan Gaddam 2025-02-04 13:24:44 -08:00 committed by GitHub
parent f5e8fc35f0
commit 5157d2314b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 107 additions and 39 deletions

View File

@ -410,6 +410,7 @@ class _RecompileRequest extends _CompilationRequest {
this.suppressErrors, {
this.additionalSourceUri,
this.nativeAssetsYamlUri,
required this.recompileRestart,
});
Uri mainUri;
@ -419,6 +420,7 @@ class _RecompileRequest extends _CompilationRequest {
bool suppressErrors;
final Uri? additionalSourceUri;
final Uri? nativeAssetsYamlUri;
final bool recompileRestart;
@override
Future<CompilerOutput?> _run(DefaultResidentCompiler compiler) async => compiler._recompile(this);
@ -533,6 +535,9 @@ abstract class ResidentCompiler {
/// If [checkDartPluginRegistry] is true, it is the caller's responsibility
/// to ensure that the generated registrant file has been updated such that
/// it is wrapping [mainUri].
///
/// If [recompileRestart] is true, uses the `recompile-restart` instruction
/// intended for a hot restart instead.
Future<CompilerOutput?> recompile(
Uri mainUri,
List<Uri>? invalidatedFiles, {
@ -544,6 +549,7 @@ abstract class ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
});
Future<CompilerOutput?> compileExpression(
@ -695,6 +701,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
String? projectRootPath,
FileSystem? fs,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
if (!_controller.hasListener) {
_controller.stream.listen(_handleCompilationRequest);
@ -717,6 +724,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
suppressErrors,
additionalSourceUri: additionalSourceUri,
nativeAssetsYamlUri: nativeAssetsYaml,
recompileRestart: recompileRestart,
),
);
return completer.future;
@ -759,7 +767,11 @@ class DefaultResidentCompiler implements ResidentCompiler {
server.stdin.writeln('native-assets $nativeAssets');
_logger.printTrace('<- native-assets $nativeAssets');
}
server.stdin.writeln('recompile $mainUri $inputKey');
if (request.recompileRestart) {
server.stdin.writeln('recompile-restart $mainUri $inputKey');
} else {
server.stdin.writeln('recompile $mainUri $inputKey');
}
_logger.printTrace('<- recompile $mainUri $inputKey');
final List<Uri>? invalidatedFiles = request.invalidatedFiles;
if (invalidatedFiles != null) {

View File

@ -1138,6 +1138,7 @@ class WebDevFS implements DevFS {
projectRootPath: projectRootPath,
fs: globals.fs,
dartPluginRegistrant: dartPluginRegistrant,
recompileRestart: fullRestart,
);
if (compilerOutput == null || compilerOutput.errorCount > 0) {
return UpdateFSReport();

View File

@ -966,6 +966,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) {
return onRecompile?.call(mainUri, invalidatedFiles) ??
Future<CompilerOutput>.value(const CompilerOutput('', 1, <Uri>[]));

View File

@ -321,6 +321,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
recompileCalled = true;
receivedNativeAssetsYaml = nativeAssetsYaml;

View File

@ -1604,6 +1604,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
return const CompilerOutput('foo.dill', 0, <Uri>[]);
}

View File

@ -311,6 +311,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
if (compilerOutput != null) {
fileSystem!.file(compilerOutput!.outputFilename).createSync(recursive: true);

View File

@ -1386,6 +1386,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
return output;
}

View File

@ -1703,6 +1703,7 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler {
bool checkDartPluginRegistry = false,
File? dartPluginRegistrant,
Uri? nativeAssetsYaml,
bool recompileRestart = false,
}) async {
return output;
}

View File

@ -5,45 +5,9 @@
@Tags(<String>['flutter-test-driver'])
library;
import 'package:file/file.dart';
import '../src/common.dart';
import 'test_data/hot_reload_const_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
import 'test_data/hot_reload_errors_common.dart';
void main() {
late Directory tempDir;
final HotReloadConstProject project = HotReloadConstProject();
late FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_reload_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext(
'hot reload displays a formatted error message when removing a field from a const class',
() async {
await flutter.run();
project.removeFieldFromConstClass();
expect(
flutter.hotReload(),
throwsA(
isA<Exception>().having(
(Exception e) => e.toString(),
'message',
contains('Try performing a hot restart instead.'),
),
),
);
},
);
testAll();
}

View File

@ -0,0 +1,60 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/file.dart';
import '../../src/common.dart';
import '../test_driver.dart';
import '../test_utils.dart';
import 'hot_reload_const_project.dart';
void testAll({
bool chrome = false,
List<String> additionalCommandArgs = const <String>[],
String constClassFieldRemovalErrorMessage = 'Try performing a hot restart instead.',
Object? skip = false,
}) {
group('chrome: $chrome'
'${additionalCommandArgs.isEmpty ? '' : ' with args: $additionalCommandArgs'}', () {
late Directory tempDir;
final HotReloadConstProject project = HotReloadConstProject();
late FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_reload_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext(
'hot reload displays a formatted error message when removing a field from a const class',
() async {
await flutter.run();
project.removeFieldFromConstClass();
expect(
flutter.hotReload(),
throwsA(
isA<Exception>().having(
(Exception e) => e.toString(),
'message',
contains(constClassFieldRemovalErrorMessage),
),
),
);
},
);
testWithoutContext('hot restart succeeds when removing a field from a const class', () async {
await flutter.run(chrome: true, additionalCommandArgs: additionalCommandArgs);
project.removeFieldFromConstClass();
await flutter.hotRestart();
});
});
}

View File

@ -0,0 +1,25 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@Tags(<String>['flutter-test-driver'])
library;
import 'dart:io';
import '../integration.shard/test_data/hot_reload_errors_common.dart';
import '../src/common.dart';
void main() {
testAll(
chrome: true,
additionalCommandArgs: <String>[
'--extra-front-end-options=--dartdevc-canary,--dartdevc-module-format=ddc',
],
// TODO(srujzs): Remove this custom message once we have the delta inspector emitting the same
// string as the VM.
constClassFieldRemovalErrorMessage: 'Const class cannot remove fields',
// https://github.com/flutter/flutter/issues/162567
skip: Platform.isWindows,
);
}