diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 606d5b2d9492e..469f73ef295c3 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -469,7 +469,6 @@ abstract class ResidentCompiler { // See: https://github.com/flutter/flutter/issues/50494 void addFileSystemRoot(String root); - /// If invoked for the first time, it compiles Dart script identified by /// [mainPath], [invalidatedFiles] list is ignored. /// On successive runs [invalidatedFiles] indicates which files need to be diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 3400c82689ce2..b985bff325b00 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -488,6 +488,9 @@ class DevFS { if (fullRestart) { generator.reset(); } + // On a full restart, or on an initial compile for the attach based workflow, + // this will produce a full dill. Subsequent invocations will produce incremental + // dill files that depend on the invalidated files. globals.printTrace('Compiling dart to kernel with ${invalidatedFiles.length} updated files'); final CompilerOutput compilerOutput = await generator.recompile( mainPath, diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index b58487e1f334b..d24e3c7e6eea1 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -629,16 +629,6 @@ abstract class ResidentRunner { if (!artifactDirectory.existsSync()) { artifactDirectory.createSync(recursive: true); } - // TODO(jonahwilliams): this is a temporary work around to regain some of - // the initialize from dill performance. Longer term, we should have a - // better way to determine where the appropriate dill file is, as this - // doesn't work for Android or macOS builds.} - if (dillOutputPath == null) { - final File existingDill = globals.fs.file(globals.fs.path.join('build', 'app.dill')); - if (existingDill.existsSync()) { - existingDill.copySync(globals.fs.path.join(artifactDirectory.path, 'app.dill')); - } - } } @protected diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 67a93d3ad9e93..32f98a6166fbd 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -16,6 +16,7 @@ import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/utils.dart'; import 'build_info.dart'; +import 'bundle.dart'; import 'compile.dart'; import 'convert.dart'; import 'devfs.dart'; @@ -329,14 +330,36 @@ class HotRunner extends ResidentRunner { firstBuildTime = DateTime.now(); + final List> startupTasks = >[]; for (final FlutterDevice device in flutterDevices) { - final int result = await device.runHot( + // Here we initialize the frontend_server concurrently with the platform + // build, reducing overall initialization time. This is safe because the first + // invocation of the frontend server produces a full dill file that the + // subsequent invocation in devfs will not overwrite. + if (device.generator != null) { + startupTasks.add( + device.generator.recompile( + mainPath, + [], + outputPath: dillOutputPath ?? + getDefaultApplicationKernelPath(trackWidgetCreation: device.trackWidgetCreation), + packagesFilePath : packagesFilePath, + ).then((CompilerOutput output) => output?.errorCount == 0) + ); + } + startupTasks.add(device.runHot( hotRunner: this, route: route, - ); - if (result != 0) { - return result; + ).then((int result) => result == 0)); + } + try { + final List results = await Future.wait(startupTasks); + if (!results.every((bool passed) => passed)) { + return 1; } + } on Exception catch (err) { + globals.printError(err.toString()); + return 1; } return attach( diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 77f60ad9f4e04..9a9535e2cbe63 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -84,47 +84,6 @@ void main() { ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('Forces fast start off for devices that do not support it', () async { - final MockDevice mockDevice = MockDevice(TargetPlatform.android_arm); - when(mockDevice.name).thenReturn('mockdevice'); - when(mockDevice.supportsFastStart).thenReturn(false); - when(mockDevice.supportsHotReload).thenReturn(true); - when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async => false); - when(deviceManager.hasSpecifiedAllDevices).thenReturn(false); - when(deviceManager.findTargetDevices(any)).thenAnswer((Invocation invocation) { - return Future>.value([mockDevice]); - }); - when(deviceManager.getDevices()).thenAnswer((Invocation invocation) { - return Future>.value([mockDevice]); - }); - globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); - globals.fs.file('pubspec.yaml').createSync(); - globals.fs.file('.packages').createSync(); - - final RunCommand command = RunCommand(); - applyMocksToCommand(command); - try { - await createTestCommandRunner(command).run([ - 'run', - '--fast-start', - '--no-pub', - ]); - fail('Expect exception'); - } catch (e) { - expect(e, isA()); - } - - final BufferLogger bufferLogger = globals.logger as BufferLogger; - expect(bufferLogger.statusText, isNot(contains( - 'Using --fast-start option with device mockdevice, but this device ' - 'does not support it. Overriding the setting to false.' - ))); - }, overrides: { - FileSystem: () => MemoryFileSystem(), - ProcessManager: () => FakeProcessManager.any(), - DeviceManager: () => MockDeviceManager(), - }); - testUsingContext('Walks upward looking for a pubspec.yaml and succeeds if found', () async { globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages') diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index f4431157fbf59..d570b08c995d2 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -234,10 +234,6 @@ void main() { Usage: () => MockUsage(), })); - test('ResidentRunner copies dill file from build output into temp directory', () => testbed.run(() async { - expect(residentRunner.artifactDirectory.childFile('app.dill').readAsStringSync(), 'ABC'); - })); - test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; diff --git a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart index 455e7563f588f..612de2f71bf86 100644 --- a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart +++ b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart @@ -36,10 +36,10 @@ void main() { }); test('newly added code executes during hot reload', () async { - await _flutter.run(); - _project.uncommentHotReloadPrint(); final StringBuffer stdout = StringBuffer(); final StreamSubscription subscription = _flutter.stdout.listen(stdout.writeln); + await _flutter.run(); + _project.uncommentHotReloadPrint(); try { await _flutter.hotReload(); expect(stdout.toString(), contains('(((((RELOAD WORKED)))))')); @@ -49,10 +49,10 @@ void main() { }); test('reloadMethod triggers hot reload behavior', () async { - await _flutter.run(); - _project.uncommentHotReloadPrint(); final StringBuffer stdout = StringBuffer(); final StreamSubscription subscription = _flutter.stdout.listen(stdout.writeln); + await _flutter.run(); + _project.uncommentHotReloadPrint(); try { final String libraryId = _project.buildBreakpointUri.toString(); await _flutter.reloadMethod(libraryId: libraryId, classId: 'MyApp'); @@ -72,7 +72,6 @@ void main() { test('breakpoints are hit after hot reload', () async { Isolate isolate; - await _flutter.run(withDebugger: true, startPaused: true); final Completer sawTick1 = Completer(); final Completer sawTick3 = Completer(); final Completer sawDebuggerPausedMessage = Completer(); @@ -92,6 +91,7 @@ void main() { } }, ); + await _flutter.run(withDebugger: true, startPaused: true); await _flutter.resume(); // we start paused so we can set up our TICK 1 listener before the app starts unawaited(sawTick1.future.timeout( const Duration(seconds: 5), @@ -125,16 +125,15 @@ void main() { }); test("hot reload doesn't reassemble if paused", () async { - await _flutter.run(withDebugger: true); - final Completer sawTick2 = Completer(); + final Completer sawTick1 = Completer(); final Completer sawTick3 = Completer(); final Completer sawDebuggerPausedMessage1 = Completer(); final Completer sawDebuggerPausedMessage2 = Completer(); final StreamSubscription subscription = _flutter.stdout.listen( (String line) { - if (line.contains('((((TICK 2))))')) { - expect(sawTick2.isCompleted, isFalse); - sawTick2.complete(); + if (line.contains('(((TICK 1)))')) { + expect(sawTick1.isCompleted, isFalse); + sawTick1.complete(); } if (line.contains('The application is paused in the debugger on a breakpoint.')) { expect(sawDebuggerPausedMessage1.isCompleted, isFalse); @@ -146,13 +145,14 @@ void main() { } }, ); + await _flutter.run(withDebugger: true); + await sawTick1.future; await _flutter.addBreakpoint( _project.buildBreakpointUri, _project.buildBreakpointLine, ); bool reloaded = false; final Future reloadFuture = _flutter.hotReload().then((void value) { reloaded = true; }); - await sawTick2.future; // this should happen before it pauses final Isolate isolate = await _flutter.waitForPause(); expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); expect(reloaded, isFalse);