diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index cb8d06fd829..a9ff9c88623 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -480,6 +480,8 @@ class DevFS { final Directory rootDirectory; final Set assetPathsToEvict = {}; + static const String kAssetDirectoryPlaceholderFilename = '.__dummy_flutter_asset'; + List sources = []; DateTime lastCompiled; DateTime _previousCompiled; @@ -631,6 +633,13 @@ class DevFS { assetPathsToEvict.add(archivePath); } }); + + // When the bundle is first uploaded, include a dummy file to make sure + // that the assets directory on DevFS is created. + if (bundleFirstUpload) { + final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, kAssetDirectoryPlaceholderFilename)); + dirtyEntries[deviceUri] = DevFSByteContent([]); + } } final CompilerOutput compilerOutput = await pendingCompilerOutput; if (compilerOutput == null || compilerOutput.errorCount > 0) { diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 22024a236c6..73367585deb 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -10,6 +10,7 @@ import 'dart:convert'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -428,7 +429,6 @@ void main() { testWithoutContext('DevFS correctly records the elapsed time', () async { final FileSystem fileSystem = MemoryFileSystem.test(); - // final FakeDevFSWriter writer = FakeDevFSWriter(); final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: [createDevFSRequest], httpAddress: Uri.parse('http://localhost'), @@ -470,6 +470,115 @@ void main() { expect(report.compileDuration, const Duration(seconds: 3)); expect(report.transferDuration, const Duration(seconds: 5)); }); + + group('DevFS with AssetBundle', () { + FileSystem fileSystem; + FakeDevFSWriter writer; + DevFS devFS; + FakeResidentCompiler residentCompiler; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + writer = FakeDevFSWriter(); + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [createDevFSRequest], + ); + + devFS = DevFS( + fakeVmServiceHost.vmService, + 'test', + fileSystem.currentDirectory, + fileSystem: fileSystem, + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + httpClient: FakeHttpClient.any(), + ); + + residentCompiler = FakeResidentCompiler() + ..onRecompile = (Uri mainUri, List invalidatedFiles) async { + fileSystem.file('example').createSync(); + return const CompilerOutput('lib/foo.txt.dill', 0, []); + }; + }); + + testUsingContext('skip uploading assets when bundleFirstUpload', () async { + final FakeAssetBundle fakeAssetBundle = FakeAssetBundle({ + 'assets/modified.png': FakeDevFSByteContent( + bytes: [0], + isModified: true, + ), + 'assets/not_modified.png': FakeDevFSByteContent( + bytes: [0], + isModified: false, + ), + }); + + await devFS.create(); + + expect(writer.written, false); + + final UpdateFSReport report = await devFS.update( + mainUri: Uri.parse('lib/main.dart'), + generator: residentCompiler, + dillOutputPath: 'lib/foo.dill', + pathToReload: 'lib/foo.txt.dill', + trackWidgetCreation: false, + invalidatedFiles: [], + packageConfig: PackageConfig.empty, + devFSWriter: writer, + bundle: fakeAssetBundle, + bundleFirstUpload: true, + ); + + expect(report.success, true); + expect(writer.written, true); + expect(writer.lastWrittenEntries.keys, [ + Uri.parse('build/flutter_assets/${DevFS.kAssetDirectoryPlaceholderFilename}'), + ]); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('uploads updated assets when bundleFirstUpload', () async { + final FakeAssetBundle fakeAssetBundle = FakeAssetBundle({ + 'assets/modified.png': FakeDevFSByteContent( + bytes: [0], + isModified: true, + ), + 'assets/not_modified.png': FakeDevFSByteContent( + bytes: [0], + isModified: false, + ), + }); + await devFS.create(); + + expect(writer.written, false); + + final UpdateFSReport report = await devFS.update( + mainUri: Uri.parse('lib/main.dart'), + generator: residentCompiler, + dillOutputPath: 'lib/foo.dill', + pathToReload: 'lib/foo.txt.dill', + trackWidgetCreation: false, + invalidatedFiles: [], + packageConfig: PackageConfig.empty, + devFSWriter: writer, + bundle: fakeAssetBundle, + bundleFirstUpload: false, + ); + + expect(report.success, true); + expect(writer.written, true); + expect(writer.lastWrittenEntries.keys, [ + Uri.parse('build/flutter_assets/assets/modified.png'), + Uri.parse('lib/foo.txt.dill'), + ]); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.any(), + }); + }); } class FakeResidentCompiler extends Fake implements ResidentCompiler { @@ -484,9 +593,34 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler { class FakeDevFSWriter implements DevFSWriter { bool written = false; + Map lastWrittenEntries; @override Future write(Map entries, Uri baseUri, DevFSWriter parent) async { written = true; + lastWrittenEntries = entries; } } + +class FakeAssetBundle extends Fake implements AssetBundle { + FakeAssetBundle(this.entries); + + @override + Map entries; +} + +class FakeDevFSByteContent extends Fake implements DevFSByteContent { + FakeDevFSByteContent({ + this.bytes, + this.isModified, + }); + + @override + List bytes; + + @override + bool isModified; + + @override + int get size => bytes.length; +}