diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index b7c78cbf540..07adbbafe1c 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -350,6 +350,32 @@ class _DevFSHttpWriter { } } +// Basic statistics for DevFS update operation. +class UpdateFSReport { + UpdateFSReport({bool success = false, + int invalidatedSourcesCount = 0, int syncedBytes = 0}) { + _success = success; + _invalidatedSourcesCount = invalidatedSourcesCount; + _syncedBytes = syncedBytes; + } + + bool get success => _success; + int get invalidatedSourcesCount => _invalidatedSourcesCount; + int get syncedBytes => _syncedBytes; + + void incorporateResults(UpdateFSReport report) { + if (!report._success) { + _success = false; + } + _invalidatedSourcesCount += report._invalidatedSourcesCount; + _syncedBytes += report._syncedBytes; + } + + bool _success; + int _invalidatedSourcesCount; + int _syncedBytes; +} + class DevFS { /// Create a [DevFS] named [fsName] for the local files in [rootDirectory]. DevFS(VMService serviceProtocol, @@ -422,7 +448,7 @@ class DevFS { /// Updates files on the device. /// /// Returns the number of bytes synced. - Future update({ + Future update({ @required String mainPath, String target, AssetBundle bundle, @@ -487,7 +513,7 @@ class DevFS { } // Update modified files - int numBytes = 0; + int syncedBytes = 0; final Map dirtyEntries = {}; _entries.forEach((Uri deviceUri, DevFSContent content) { String archivePath; @@ -498,7 +524,7 @@ class DevFS { // files to incremental compiler next time user does hot reload. if (content.isModified || ((bundleDirty || bundleFirstUpload) && archivePath != null)) { dirtyEntries[deviceUri] = content; - numBytes += content.size; + syncedBytes += content.size; if (archivePath != null && (!bundleFirstUpload || content.isModifiedAfter(firstBuildTime))) assetPathsToEvict.add(archivePath); } @@ -516,7 +542,7 @@ class DevFS { if (content is DevFSFileContent) { filesUris.add(uri); invalidatedFiles.add(content.file.uri.toString()); - numBytes -= content.size; + syncedBytes -= content.size; } } } @@ -545,7 +571,7 @@ class DevFS { if (!dirtyEntries.containsKey(entryUri)) { final DevFSFileContent content = DevFSFileContent(fs.file(compiledBinary)); dirtyEntries[entryUri] = content; - numBytes += content.size; + syncedBytes += content.size; } } } @@ -576,7 +602,8 @@ class DevFS { } printTrace('DevFS: Sync finished'); - return numBytes; + return UpdateFSReport(success: true, syncedBytes: syncedBytes, + invalidatedSourcesCount: invalidatedFiles.length); } void _scanFile(Uri deviceUri, FileSystemEntity file) { diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index f18f1450d2f..0f3ce7d6d9d 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -368,7 +368,7 @@ class FlutterDevice { return 0; } - Future updateDevFS({ + Future updateDevFS({ String mainPath, String target, AssetBundle bundle, @@ -384,9 +384,9 @@ class FlutterDevice { 'Syncing files to device ${device.name}...', expectSlowOperation: true, ); - int bytes = 0; + UpdateFSReport report; try { - bytes = await devFS.update( + report = await devFS.update( mainPath: mainPath, target: target, bundle: bundle, @@ -403,11 +403,11 @@ class FlutterDevice { ); } on DevFSException { devFSStatus.cancel(); - return false; + return UpdateFSReport(success: false); } devFSStatus.stop(); - printTrace('Synced ${getSizeAsMB(bytes)}.'); - return true; + printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.'); + return report; } void updateReloadStatus(bool wasReloadSuccessful) { diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index d03e02fa3cc..f2575179f8a 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -19,6 +19,7 @@ import 'build_info.dart'; import 'compile.dart'; import 'dart/dependencies.dart'; import 'dart/pub.dart'; +import 'devfs.dart'; import 'device.dart'; import 'globals.dart'; import 'resident_runner.dart'; @@ -193,12 +194,12 @@ class HotRunner extends ResidentRunner { return 3; } final Stopwatch initialUpdateDevFSsTimer = Stopwatch()..start(); - final bool devfsResult = await _updateDevFS(fullRestart: true); + final UpdateFSReport devfsResult = await _updateDevFS(fullRestart: true); _addBenchmarkData( 'hotReloadInitialDevFSSyncMilliseconds', initialUpdateDevFSsTimer.elapsed.inMilliseconds, ); - if (!devfsResult) + if (!devfsResult.success) return 3; await refreshViews(); @@ -329,10 +330,10 @@ class HotRunner extends ResidentRunner { return devFSUris; } - Future _updateDevFS({ bool fullRestart = false }) async { + Future _updateDevFS({ bool fullRestart = false }) async { if (!await _refreshDartDependencies()) { // Did not update DevFS because of a Dart source error. - return false; + return UpdateFSReport(success: false); } final bool isFirstUpload = assetBundle.wasBuiltOnce() == false; final bool rebuildBundle = assetBundle.needsBuild(); @@ -340,12 +341,12 @@ class HotRunner extends ResidentRunner { printTrace('Updating assets'); final int result = await assetBundle.build(); if (result != 0) - return false; + return UpdateFSReport(success: false); } - final List results = []; + final UpdateFSReport results = UpdateFSReport(success: true); for (FlutterDevice device in flutterDevices) { - results.add(await device.updateDevFS( + results.incorporateResults(await device.updateDevFS( mainPath: mainPath, target: target, bundle: assetBundle, @@ -358,16 +359,15 @@ class HotRunner extends ResidentRunner { pathToReload: getReloadPath(fullRestart: fullRestart), )); } - // If there any failures reported, bail out. - if (results.any((bool result) => !result)) { - return false; + if (!results.success) { + return results; } if (!hotRunnerConfig.stableDartDependencies) { // Clear the set after the sync so they are recomputed next time. _dartDependencies = null; } - return true; + return results; } Future _evictDirtyAssets() { @@ -460,8 +460,8 @@ class HotRunner extends ResidentRunner { final Stopwatch restartTimer = Stopwatch()..start(); // TODO(aam): Add generator reset logic once we switch to using incremental // compiler for full application recompilation on restart. - final bool updatedDevFS = await _updateDevFS(fullRestart: true); - if (!updatedDevFS) { + final UpdateFSReport updatedDevFS = await _updateDevFS(fullRestart: true); + if (!updatedDevFS.success) { for (FlutterDevice device in flutterDevices) { if (device.generator != null) device.generator.reject(); @@ -618,11 +618,11 @@ class HotRunner extends ResidentRunner { final Stopwatch reloadTimer = Stopwatch()..start(); final Stopwatch devFSTimer = Stopwatch()..start(); - final bool updatedDevFS = await _updateDevFS(); + final UpdateFSReport updatedDevFS = await _updateDevFS(); // Record time it took to synchronize to DevFS. _addBenchmarkData('hotReloadDevFSSyncMilliseconds', devFSTimer.elapsed.inMilliseconds); - if (!updatedDevFS) + if (!updatedDevFS.success) return OperationResult(1, 'DevFS synchronization failed'); String reloadMessage; final Stopwatch vmReloadTimer = Stopwatch()..start(); diff --git a/packages/flutter_tools/test/devfs_test.dart b/packages/flutter_tools/test/devfs_test.dart index 3adca1929e9..5a96c7da949 100644 --- a/packages/flutter_tools/test/devfs_test.dart +++ b/packages/flutter_tools/test/devfs_test.dart @@ -119,7 +119,7 @@ void main() { devFSOperations.expectMessages(['create test']); expect(devFS.assetPathsToEvict, isEmpty); - int bytes = await devFS.update( + UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -130,7 +130,7 @@ void main() { ]); expect(devFS.assetPathsToEvict, isEmpty); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -141,7 +141,8 @@ void main() { ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); @@ -150,7 +151,7 @@ void main() { final File file = fs.file(fs.path.join(basePath, filePath2)); await file.parent.create(recursive: true); file.writeAsBytesSync([1, 2, 3, 4, 5, 6, 7]); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -160,13 +161,14 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); testUsingContext('modify existing file on local file system', () async { - int bytes = await devFS.update( + UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -176,12 +178,13 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); final File file = fs.file(fs.path.join(basePath, filePath)); // Set the last modified time to 5 seconds in the past. updateFileModificationTime(file.path, DateTime.now(), -5); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -191,10 +194,11 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); await file.writeAsBytes([1, 2, 3, 4, 5, 6]); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -204,11 +208,12 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); // Set the last modified time to 5 seconds in the past. updateFileModificationTime(file.path, DateTime.now(), -5); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -218,10 +223,11 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill.track.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); await file.writeAsBytes([1, 2, 3, 4, 5, 6]); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -231,7 +237,8 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill.track.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, @@ -240,7 +247,7 @@ void main() { testUsingContext('delete a file from the local file system', () async { final File file = fs.file(fs.path.join(basePath, filePath)); await file.delete(); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -251,14 +258,15 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); testUsingContext('add new package', () async { await _createPackage(fs, 'newpkg', 'anotherfile.txt'); - int bytes = await devFS.update( + UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -268,9 +276,10 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); - bytes = await devFS.update( + report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -280,7 +289,8 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill.track.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, @@ -302,7 +312,7 @@ void main() { .map((File file) => canonicalizePath(file.path)) .toList()); } - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', fileFilter: fileFilter, generator: residentCompiler, @@ -313,14 +323,15 @@ void main() { 'writeFile test lib/foo.txt.dill build/app.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); testUsingContext('add an asset bundle', () async { assetBundle.entries['a.txt'] = DevFSStringContent('abc'); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', bundle: assetBundle, bundleDirty: true, @@ -334,14 +345,14 @@ void main() { ]); expect(devFS.assetPathsToEvict, unorderedMatches(['a.txt'])); devFS.assetPathsToEvict.clear(); - expect(bytes, 25); + expect(report.syncedBytes, 25); }, overrides: { FileSystem: () => fs, }); testUsingContext('add a file to the asset bundle - bundleDirty', () async { assetBundle.entries['b.txt'] = DevFSStringContent('abcd'); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', bundle: assetBundle, bundleDirty: true, @@ -358,14 +369,14 @@ void main() { expect(devFS.assetPathsToEvict, unorderedMatches([ 'a.txt', 'b.txt'])); devFS.assetPathsToEvict.clear(); - expect(bytes, 29); + expect(report.syncedBytes, 29); }, overrides: { FileSystem: () => fs, }); testUsingContext('add a file to the asset bundle', () async { assetBundle.entries['c.txt'] = DevFSStringContent('12'); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', bundle: assetBundle, generator: residentCompiler, @@ -379,14 +390,14 @@ void main() { expect(devFS.assetPathsToEvict, unorderedMatches([ 'c.txt'])); devFS.assetPathsToEvict.clear(); - expect(bytes, 24); + expect(report.syncedBytes, 24); }, overrides: { FileSystem: () => fs, }); testUsingContext('delete a file from the asset bundle', () async { assetBundle.entries.remove('c.txt'); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', bundle: assetBundle, generator: residentCompiler, @@ -399,14 +410,15 @@ void main() { ]); expect(devFS.assetPathsToEvict, unorderedMatches(['c.txt'])); devFS.assetPathsToEvict.clear(); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); testUsingContext('delete all files from the asset bundle', () async { assetBundle.entries.clear(); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', bundle: assetBundle, bundleDirty: true, @@ -423,7 +435,8 @@ void main() { 'a.txt', 'b.txt' ])); devFS.assetPathsToEvict.clear(); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); @@ -466,7 +479,7 @@ void main() { vmService.expectMessages(['create test']); expect(devFS.assetPathsToEvict, isEmpty); - final int bytes = await devFS.update( + final UpdateFSReport report = await devFS.update( mainPath: 'lib/foo.txt', generator: residentCompiler, pathToReload: 'lib/foo.txt.dill', @@ -476,7 +489,8 @@ void main() { 'writeFile test lib/foo.txt.dill', ]); expect(devFS.assetPathsToEvict, isEmpty); - expect(bytes, 22); + expect(report.syncedBytes, 22); + expect(report.success, true); }, overrides: { FileSystem: () => fs, }); diff --git a/packages/flutter_tools/test/hot_test.dart b/packages/flutter_tools/test/hot_test.dart index a6f70b90ba1..dce058d1b14 100644 --- a/packages/flutter_tools/test/hot_test.dart +++ b/packages/flutter_tools/test/hot_test.dart @@ -112,7 +112,8 @@ void main() { trackWidgetCreation: anyNamed('trackWidgetCreation'), projectRootPath: anyNamed('projectRootPath'), pathToReload: anyNamed('pathToReload'), - )).thenAnswer((Invocation _) => Future.value(1000)); + )).thenAnswer((Invocation _) => Future.value( + UpdateFSReport(success: true, syncedBytes: 1000, invalidatedSourcesCount: 1))); when(mockDevFs.assetPathsToEvict).thenReturn(Set()); when(mockDevFs.baseUri).thenReturn(Uri.file('test'));