From 0bdb4d68b5b3dfc4ea51c606a77a27cc328e8a3a Mon Sep 17 00:00:00 2001 From: Srujan Gaddam <58529443+srujzs@users.noreply.github.com> Date: Wed, 12 Mar 2025 08:16:49 -0700 Subject: [PATCH] [flutter_tools] Call reassemble with DWDS 24.3.7 and update hot reload and restart analytics (#165006) https://github.com/dart-lang/webdev/issues/2584 Reassemble was being called in DWDS in the injected client until v24.3.7. Flutter tools should now instead be the one to call the service extension. Now that it's in Flutter tools, we can also report how long it took. Similarly, we should update analytics on various things like, whether there was a reload rejection, how long the compile took, and more. Adds test to check that these analytics are being reported correctly. ## 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. - [x] All existing and new tests are passing. --- .../lib/src/commands/daemon.dart | 2 +- packages/flutter_tools/lib/src/devfs.dart | 14 +- .../lib/src/isolated/devfs_web.dart | 9 +- .../lib/src/isolated/resident_web_runner.dart | 81 ++++++- .../lib/src/resident_devtools_handler.dart | 2 +- packages/flutter_tools/lib/src/run_hot.dart | 2 +- packages/flutter_tools/lib/src/vmservice.dart | 6 +- packages/flutter_tools/pubspec.yaml | 4 +- .../resident_web_runner_cold_test.dart | 5 + .../resident_web_runner_test.dart | 213 ++++++++++++++---- 10 files changed, 280 insertions(+), 58 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index eeac6d4871..811cfd0d7e 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -927,7 +927,7 @@ class AppDomain extends Domain { final Map? result = await device.vmService!.invokeFlutterExtensionRpcRaw( methodName, args: params, - isolateId: views.first.uiIsolate!.id!, + isolateId: views.first.uiIsolate!.id, ); if (result == null) { throw DaemonException('method not available: $methodName'); diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index ed6c83f9da..b779d18877 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -380,13 +380,15 @@ class UpdateFSReport { Duration compileDuration = Duration.zero, Duration transferDuration = Duration.zero, Duration findInvalidatedDuration = Duration.zero, + bool hotReloadRejected = false, }) : _success = success, _invalidatedSourcesCount = invalidatedSourcesCount, _syncedBytes = syncedBytes, _scannedSourcesCount = scannedSourcesCount, _compileDuration = compileDuration, _transferDuration = transferDuration, - _findInvalidatedDuration = findInvalidatedDuration; + _findInvalidatedDuration = findInvalidatedDuration, + _hotReloadRejected = hotReloadRejected; bool get success => _success; int get invalidatedSourcesCount => _invalidatedSourcesCount; @@ -396,6 +398,12 @@ class UpdateFSReport { Duration get transferDuration => _transferDuration; Duration get findInvalidatedDuration => _findInvalidatedDuration; + /// Whether there was a hot reload rejection in this compile. + /// + /// On the web, hot reload can be rejected during compile time instead of at + /// runtime. + bool get hotReloadRejected => _hotReloadRejected; + bool _success; int _invalidatedSourcesCount; int _syncedBytes; @@ -403,6 +411,7 @@ class UpdateFSReport { Duration _compileDuration; Duration _transferDuration; Duration _findInvalidatedDuration; + bool _hotReloadRejected; void incorporateResults(UpdateFSReport report) { if (!report._success) { @@ -414,6 +423,9 @@ class UpdateFSReport { _compileDuration += report._compileDuration; _transferDuration += report._transferDuration; _findInvalidatedDuration += report._findInvalidatedDuration; + if (report._hotReloadRejected) { + _hotReloadRejected = true; + } } } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 3d03483639..dd1cb3fd84 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -1139,7 +1139,14 @@ class WebDevFS implements DevFS { recompileRestart: fullRestart, ); if (compilerOutput == null || compilerOutput.errorCount > 0) { - return UpdateFSReport(); + return UpdateFSReport( + // TODO(srujzs): We're currently reliant on compile error string parsing + // as hot reload rejections are sent to stderr just like other + // compilation errors. Ideally, we should have some shared parsing + // functionality, but that would require a shared package. + // See https://github.com/dart-lang/sdk/issues/60275. + hotReloadRejected: compilerOutput?.errorMessage?.contains('Hot reload rejected') ?? false, + ); } // Only update the last compiled time if we successfully compiled. diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 48075324ca..a4ef4e4df7 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -432,19 +432,40 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). status = _logger.startProgress('Performing hot reload...', progressId: 'hot.reload'); } + final String targetPlatform = getNameForTargetPlatform(TargetPlatform.web_javascript); + final String sdkName = await device!.device!.sdkNameAndVersion; + + late UpdateFSReport report; if (debuggingOptions.buildInfo.isDebug && !debuggingOptions.webUseWasm) { await runSourceGenerators(); // Don't reset the resident compiler for web, since the extra recompile is // wasteful. - final UpdateFSReport report = await _updateDevFS( - fullRestart: fullRestart, - resetCompiler: false, - ); + report = await _updateDevFS(fullRestart: fullRestart, resetCompiler: false); if (report.success) { device!.generator!.accept(); } else { status.stop(); await device!.generator!.reject(); + if (report.hotReloadRejected) { + // We cannot capture the reason why the reload was rejected as it may + // contain user information. + HotEvent( + 'reload-reject', + targetPlatform: targetPlatform, + sdkName: sdkName, + emulator: false, + fullRestart: fullRestart, + ).send(); + _analytics.send( + Event.hotRunnerInfo( + label: 'reload-reject', + targetPlatform: targetPlatform, + sdkName: sdkName, + emulator: false, + fullRestart: fullRestart, + ), + ); + } return OperationResult(1, 'Failed to recompile application.'); } } else { @@ -469,6 +490,8 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). } } + late Duration reloadDuration; + late Duration reassembleDuration; try { if (!deviceIsDebuggable) { _logger.printStatus('Recompile complete. Page requires refresh.'); @@ -482,7 +505,9 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). } else { // Isolates don't work on web. For lack of a better value, pass an // empty string for the isolate id. + final DateTime reloadStart = _systemClock.now(); final vmservice.ReloadReport report = await _vmService.service.reloadSources(''); + reloadDuration = _systemClock.now().difference(reloadStart); final ReloadReportContents contents = ReloadReportContents.fromReloadReport(report); final bool success = contents.success ?? false; if (!success) { @@ -498,6 +523,21 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). } return OperationResult(1, reloadFailedMessage); } + String? failedReassemble; + final DateTime reassembleStart = _systemClock.now(); + await _vmService + .flutterReassemble(isolateId: null) + .then( + (Object? o) => o, + onError: (Object error, StackTrace stackTrace) { + failedReassemble = 'Reassembling failed: $error\n$stackTrace'; + _logger.printError(failedReassemble!); + }, + ); + reassembleDuration = _systemClock.now().difference(reassembleStart); + if (failedReassemble != null) { + return OperationResult(1, failedReassemble!); + } } } else { // On non-debug builds, a hard refresh is required to ensure the @@ -522,11 +562,6 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). // Don't track restart times for dart2js builds or web-server devices. if (debuggingOptions.buildInfo.isDebug && deviceIsDebuggable) { - // TODO(srujzs): There are a number of fields that the VM tracks in the - // analytics that we do not for both hot restart and reload. We should - // unify that. - final String targetPlatform = getNameForTargetPlatform(TargetPlatform.web_javascript); - final String sdkName = await device!.device!.sdkNameAndVersion; if (fullRestart) { _analytics.send( Event.timing( @@ -543,6 +578,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: true, reason: reason, overallTimeInMs: elapsed.inMilliseconds, + syncedBytes: report.syncedBytes, + invalidatedSourcesCount: report.invalidatedSourcesCount, + transferTimeInMs: report.transferDuration.inMilliseconds, + compileTimeInMs: report.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report.scannedSourcesCount, ).send(); _analytics.send( Event.hotRunnerInfo( @@ -553,6 +594,12 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: true, reason: reason, overallTimeInMs: elapsed.inMilliseconds, + syncedBytes: report.syncedBytes, + invalidatedSourcesCount: report.invalidatedSourcesCount, + transferTimeInMs: report.transferDuration.inMilliseconds, + compileTimeInMs: report.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report.scannedSourcesCount, ), ); } else { @@ -571,6 +618,14 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: false, reason: reason, overallTimeInMs: elapsed.inMilliseconds, + syncedBytes: report.syncedBytes, + invalidatedSourcesCount: report.invalidatedSourcesCount, + transferTimeInMs: report.transferDuration.inMilliseconds, + compileTimeInMs: report.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report.scannedSourcesCount, + reassembleTimeInMs: reassembleDuration.inMilliseconds, + reloadVMTimeInMs: reloadDuration.inMilliseconds, ).send(); _analytics.send( Event.hotRunnerInfo( @@ -581,6 +636,14 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). fullRestart: false, reason: reason, overallTimeInMs: elapsed.inMilliseconds, + syncedBytes: report.syncedBytes, + invalidatedSourcesCount: report.invalidatedSourcesCount, + transferTimeInMs: report.transferDuration.inMilliseconds, + compileTimeInMs: report.compileDuration.inMilliseconds, + findInvalidatedTimeInMs: report.findInvalidatedDuration.inMilliseconds, + scannedSourcesCount: report.scannedSourcesCount, + reassembleTimeInMs: reassembleDuration.inMilliseconds, + reloadVMTimeInMs: reloadDuration.inMilliseconds, ), ); } diff --git a/packages/flutter_tools/lib/src/resident_devtools_handler.dart b/packages/flutter_tools/lib/src/resident_devtools_handler.dart index a48fcd9768..19140deb77 100644 --- a/packages/flutter_tools/lib/src/resident_devtools_handler.dart +++ b/packages/flutter_tools/lib/src/resident_devtools_handler.dart @@ -298,7 +298,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { await device.vmService!.invokeFlutterExtensionRpcRaw( method, args: params, - isolateId: views.first.uiIsolate!.id!, + isolateId: views.first.uiIsolate!.id, ); } diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 8e26c31087..efda6de35f 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -1416,7 +1416,7 @@ Future _defaultReassembleHelper( // If the tool identified a change in a single widget, do a fast instead // of a full reassemble. final Future reassembleWork = device.vmService!.flutterReassemble( - isolateId: view.uiIsolate!.id!, + isolateId: view.uiIsolate!.id, ); reassembleFutures.add( reassembleWork.then( diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 29f80086f4..9d23256a43 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -684,7 +684,7 @@ class FlutterVmService { ); } - Future?> flutterReassemble({required String isolateId}) { + Future?> flutterReassemble({required String? isolateId}) { return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble', isolateId: isolateId); } @@ -803,12 +803,12 @@ class FlutterVmService { /// available, returns null. Future?> invokeFlutterExtensionRpcRaw( String method, { - required String isolateId, + required String? isolateId, Map? args, }) async { final vm_service.Response? response = await _checkedCallServiceExtension( method, - args: {'isolateId': isolateId, ...?args}, + args: {if (isolateId != null) 'isolateId': isolateId, ...?args}, ); return response?.json; } diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 21df2fbaa3..fe0ae13483 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: archive: 3.6.1 args: 2.6.0 dds: 5.0.0 - dwds: 24.3.6 + dwds: 24.3.7 code_builder: 4.10.1 completion: 1.0.1 coverage: 1.11.1 @@ -122,4 +122,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 5a6c +# PUBSPEC CHECKSUM: 776d diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart index 360c02d044..9d196bf558 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_cold_test.dart @@ -260,6 +260,11 @@ class FakeWebDevice extends Fake implements Device { return true; } + @override + Future get sdkNameAndVersion async { + return 'Flutter Tools'; + } + @override Future startApp( ApplicationPackage? package, { diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index cf87025692..93fc2c05ae 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -75,6 +75,11 @@ const List kAttachExpectations = [ ...kAttachIsolateExpectations, ]; +const List kDdcLibraryBundleFlags = [ + '--dartdevc-module-format=ddc', + '--dartdevc-canary', +]; + void main() { late FakeDebugConnection debugConnection; late FakeChromeDevice chromeDevice; @@ -725,13 +730,29 @@ name: my_app flutterDevice, logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), + debuggingOptions: DebuggingOptions.enabled( + const BuildInfo( + BuildMode.debug, + null, + trackWidgetCreation: true, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + // Hot reload only supported with these flags for now. + extraFrontEndOptions: kDdcLibraryBundleFlags, + ), + ), ); fakeVmServiceHost = FakeVmServiceHost( requests: [ ...kAttachExpectations, const FakeVmServiceRequest( - method: kHotRestartServiceName, - jsonResponse: {'type': 'Success'}, + method: kReloadSourcesServiceName, + args: {'isolateId': ''}, + jsonResponse: {'type': 'ReloadReport', 'success': true}, + ), + const FakeVmServiceRequest( + method: 'ext.flutter.reassemble', + jsonResponse: {'type': 'ReloadReport', 'success': true}, ), const FakeVmServiceRequest( method: 'streamListen', @@ -769,7 +790,7 @@ name: my_app final OperationResult result = await residentWebRunner.restart(); - expect(logger.statusText, contains('Restarted application in')); + expect(logger.statusText, contains('Reloaded application in')); expect(result.code, 0); expect(webDevFS.mainUri.toString(), contains('entrypoint.dart')); @@ -777,24 +798,26 @@ name: my_app fakeAnalytics.sentEvents, contains( Event.hotRunnerInfo( - label: 'restart', + label: 'reload', targetPlatform: 'web-javascript', sdkName: '', emulator: false, - fullRestart: true, + fullRestart: false, overallTimeInMs: 0, + syncedBytes: 0, + invalidatedSourcesCount: 0, + transferTimeInMs: 0, + compileTimeInMs: 0, + findInvalidatedTimeInMs: 0, + scannedSourcesCount: 0, + reassembleTimeInMs: 0, + reloadVMTimeInMs: 0, ), ), ); expect( fakeAnalytics.sentEvents, - contains( - Event.timing( - workflow: 'hot', - variableName: 'web-incremental-restart', - elapsedMilliseconds: 0, - ), - ), + contains(Event.timing(workflow: 'hot', variableName: 'reload', elapsedMilliseconds: 0)), ); }, overrides: { @@ -807,20 +830,31 @@ name: my_app ); testUsingContext( - 'Can hot restart after attaching', + 'Hot reload reject reports correct analytics', () async { final BufferLogger logger = BufferLogger.test(); final ResidentRunner residentWebRunner = setUpResidentRunner( flutterDevice, logger: logger, systemClock: SystemClock.fixed(DateTime(2001)), + debuggingOptions: DebuggingOptions.enabled( + const BuildInfo( + BuildMode.debug, + null, + trackWidgetCreation: true, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + // Hot reload only supported with these flags for now. + extraFrontEndOptions: kDdcLibraryBundleFlags, + ), + ), ); fakeVmServiceHost = FakeVmServiceHost( requests: [ ...kAttachExpectations, const FakeVmServiceRequest( - method: kHotRestartServiceName, - jsonResponse: {'type': 'Success'}, + method: 'streamListen', + args: {'streamId': 'Isolate'}, ), ], ); @@ -848,39 +882,25 @@ name: my_app final Completer connectionInfoCompleter = Completer(); unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter)); - await connectionInfoCompleter.future; - final OperationResult result = await residentWebRunner.restart(fullRestart: true); + final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; - // Ensure that generated entrypoint is generated correctly. - expect(webDevFS.mainUri, isNotNull); - final String entrypointContents = fileSystem.file(webDevFS.mainUri).readAsStringSync(); - expect(entrypointContents, contains('// Flutter web bootstrap script')); - expect(entrypointContents, contains("import 'dart:ui_web' as ui_web;")); - expect(entrypointContents, contains('await ui_web.bootstrapEngine(')); + expect(debugConnectionInfo, isNotNull); - expect(logger.statusText, contains('Restarted application in')); - expect(result.code, 0); + webDevFS.report = UpdateFSReport(hotReloadRejected: true); + final OperationResult result = await residentWebRunner.restart(); + + expect(result.code, 1); + expect(webDevFS.mainUri.toString(), contains('entrypoint.dart')); expect( fakeAnalytics.sentEvents, contains( Event.hotRunnerInfo( - label: 'restart', + label: 'reload-reject', targetPlatform: 'web-javascript', sdkName: '', emulator: false, - fullRestart: true, - overallTimeInMs: 0, - ), - ), - ); - expect( - fakeAnalytics.sentEvents, - contains( - Event.timing( - workflow: 'hot', - variableName: 'web-incremental-restart', - elapsedMilliseconds: 0, + fullRestart: false, ), ), ); @@ -894,6 +914,120 @@ name: my_app }, ); + // Hot restart is available with and without the DDC library bundle format. + // Test one extra config where `fullRestart` is false without the DDC library + // bundle format - we should do a hot restart in this case because hot reload + // is not available. + for (final (List flags, bool fullRestart) in <(List, bool)>[ + (kDdcLibraryBundleFlags, true), + ([], true), + ([], false), + ]) { + testUsingContext( + 'Can hot restart after attaching with flags: $flags fullRestart: $fullRestart', + () async { + final BufferLogger logger = BufferLogger.test(); + final ResidentRunner residentWebRunner = setUpResidentRunner( + flutterDevice, + logger: logger, + systemClock: SystemClock.fixed(DateTime(2001)), + debuggingOptions: DebuggingOptions.enabled( + BuildInfo( + BuildMode.debug, + null, + trackWidgetCreation: true, + treeShakeIcons: false, + packageConfigPath: '.dart_tool/package_config.json', + extraFrontEndOptions: flags, + ), + ), + ); + fakeVmServiceHost = FakeVmServiceHost( + requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: kHotRestartServiceName, + jsonResponse: {'type': 'Success'}, + ), + ], + ); + setupMocks(); + final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher(); + final FakeProcess process = FakeProcess(); + final Chromium chrome = Chromium( + 1, + chromeConnection, + chromiumLauncher: chromiumLauncher, + process: process, + logger: logger, + ); + chromiumLauncher.setInstance(chrome); + + flutterDevice.device = GoogleChromeDevice( + fileSystem: fileSystem, + chromiumLauncher: chromiumLauncher, + logger: BufferLogger.test(), + platform: FakePlatform(), + processManager: FakeProcessManager.any(), + ); + webDevFS.report = UpdateFSReport(success: true); + + final Completer connectionInfoCompleter = + Completer(); + unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter)); + await connectionInfoCompleter.future; + final OperationResult result = await residentWebRunner.restart(fullRestart: fullRestart); + + // Ensure that generated entrypoint is generated correctly. + expect(webDevFS.mainUri, isNotNull); + final String entrypointContents = fileSystem.file(webDevFS.mainUri).readAsStringSync(); + expect(entrypointContents, contains('// Flutter web bootstrap script')); + expect(entrypointContents, contains("import 'dart:ui_web' as ui_web;")); + expect(entrypointContents, contains('await ui_web.bootstrapEngine(')); + + expect(logger.statusText, contains('Restarted application in')); + expect(result.code, 0); + + expect( + fakeAnalytics.sentEvents, + contains( + Event.hotRunnerInfo( + label: 'restart', + targetPlatform: 'web-javascript', + sdkName: '', + emulator: false, + fullRestart: true, + overallTimeInMs: 0, + syncedBytes: 0, + invalidatedSourcesCount: 0, + transferTimeInMs: 0, + compileTimeInMs: 0, + findInvalidatedTimeInMs: 0, + scannedSourcesCount: 0, + ), + ), + ); + expect( + fakeAnalytics.sentEvents, + contains( + Event.timing( + workflow: 'hot', + variableName: 'web-incremental-restart', + elapsedMilliseconds: 0, + ), + ), + ); + }, + overrides: { + Analytics: () => fakeAnalytics, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + FeatureFlags: enableExplicitPackageDependencies, + Pub: FakePubWithPrimedDeps.new, + }, + ); + } + testUsingContext( 'Can hot restart after attaching with web-server device', () async { @@ -1130,7 +1264,8 @@ name: my_app trackWidgetCreation: true, treeShakeIcons: false, packageConfigPath: '.dart_tool/package_config.json', - extraFrontEndOptions: ['--dartdevc-module-format=ddc', '--dartdevc-canary'], + // Hot reload only supported with these flags for now. + extraFrontEndOptions: kDdcLibraryBundleFlags, ), ), );