@@ -25,6 +25,84 @@ import 'flutter_manifest.dart';
2525import 'license_collector.dart' ;
2626import 'project.dart' ;
2727
28+ class FlutterHookResult {
29+ const FlutterHookResult ({
30+ required this .buildStart,
31+ required this .buildEnd,
32+ required this .dataAssets,
33+ required this .dependencies,
34+ });
35+
36+ FlutterHookResult .empty ()
37+ : this (
38+ buildStart: DateTime .fromMillisecondsSinceEpoch (0 ),
39+ buildEnd: DateTime .fromMillisecondsSinceEpoch (0 ),
40+ dataAssets: < HookAsset > [],
41+ dependencies: < Uri > [],
42+ );
43+
44+ final List <HookAsset > dataAssets;
45+
46+ /// The timestamp at which we start a build - so the timestamp of the inputs.
47+ final DateTime buildStart;
48+
49+ /// The timestamp at which we finish a build - so the timestamp of the
50+ /// outputs.
51+ final DateTime buildEnd;
52+
53+ /// The dependencies of the build are used to check if the build needs to be
54+ /// rerun.
55+ final List <Uri > dependencies;
56+
57+ /// Whether caller may need to re-run the Dart build.
58+ bool hasAnyModifiedFiles (FileSystem fileSystem) =>
59+ _wasAnyFileModifiedSince (fileSystem, buildStart, dependencies);
60+
61+ /// Whether the files produced by the build are up-to-date.
62+ ///
63+ /// NOTICE: The build itself may be up-to-date but the output may not be (as
64+ /// the output may be existing on disk and not be produced by the build
65+ /// itself - in which case we may not need to re-build if the file changes,
66+ /// but we may need to make a new asset bundle with the modified file).
67+ bool isOutputDirty (FileSystem fileSystem) => _wasAnyFileModifiedSince (
68+ fileSystem,
69+ buildEnd,
70+ dataAssets.map ((HookAsset e) => e.file).toList (),
71+ );
72+
73+ static bool _wasAnyFileModifiedSince (FileSystem fileSystem, DateTime since, List <Uri > uris) {
74+ for (final Uri uri in uris) {
75+ final DateTime modified = fileSystem.statSync (uri.toFilePath ()).modified;
76+ if (modified.isAfter (since)) {
77+ return true ;
78+ }
79+ }
80+ return false ;
81+ }
82+
83+ @override
84+ String toString () {
85+ return dataAssets.toString ();
86+ }
87+ }
88+
89+ /// A convenience class to wrap native assets
90+ ///
91+ /// When translating from a `DartHooksResult` to a [FlutterHookResult] , where we
92+ /// need to have different classes to not import `isolated/` stuff.
93+ class HookAsset {
94+ const HookAsset ({required this .file, required this .name, required this .package});
95+
96+ final Uri file;
97+ final String name;
98+ final String package;
99+
100+ @override
101+ String toString () {
102+ return 'HookAsset(file: $file , name: $name , package: $package )' ;
103+ }
104+ }
105+
28106const String defaultManifestPath = 'pubspec.yaml' ;
29107
30108const String kFontManifestJson = 'FontManifest.json' ;
@@ -113,6 +191,7 @@ abstract class AssetBundle {
113191
114192 /// Returns 0 for success; non-zero for failure.
115193 Future <int > build ({
194+ FlutterHookResult ? flutterHookResult,
116195 String manifestPath = defaultManifestPath,
117196 required String packageConfigPath,
118197 bool deferredComponentsEnabled = false ,
@@ -163,7 +242,8 @@ class ManifestAssetBundle implements AssetBundle {
163242 _platform = platform,
164243 _flutterRoot = flutterRoot,
165244 _splitDeferredAssets = splitDeferredAssets,
166- _licenseCollector = LicenseCollector (fileSystem: fileSystem);
245+ _licenseCollector = LicenseCollector (fileSystem: fileSystem),
246+ _lastHookResult = FlutterHookResult .empty ();
167247
168248 final Logger _logger;
169249 final FileSystem _fileSystem;
@@ -189,6 +269,8 @@ class ManifestAssetBundle implements AssetBundle {
189269
190270 DateTime ? _lastBuildTimestamp;
191271
272+ FlutterHookResult _lastHookResult;
273+
192274 // We assume the main asset is designed for a device pixel ratio of 1.0.
193275 static const String _kAssetManifestJsonFilename = 'AssetManifest.json' ;
194276 static const String _kAssetManifestBinFilename = 'AssetManifest.bin' ;
@@ -208,13 +290,19 @@ class ManifestAssetBundle implements AssetBundle {
208290
209291 @override
210292 bool needsBuild ({String manifestPath = defaultManifestPath}) {
211- final DateTime ? lastBuildTimestamp = _lastBuildTimestamp;
212- if (lastBuildTimestamp == null ) {
293+ if (! wasBuiltOnce () ||
294+ // We need to re-run the Dart build.
295+ _lastHookResult.hasAnyModifiedFiles (_fileSystem) ||
296+ // We don't have to re-run the Dart build, but some files the Dart build
297+ // wants us to bundle have changed contents.
298+ _lastHookResult.isOutputDirty (_fileSystem)) {
213299 return true ;
214300 }
301+ final DateTime lastBuildTimestamp = _lastBuildTimestamp! ;
215302
216303 final FileStat manifestStat = _fileSystem.file (manifestPath).statSync ();
217- if (manifestStat.type == FileSystemEntityType .notFound) {
304+ if (manifestStat.type == FileSystemEntityType .notFound ||
305+ manifestStat.modified.isAfter (lastBuildTimestamp)) {
218306 return true ;
219307 }
220308
@@ -223,18 +311,19 @@ class ManifestAssetBundle implements AssetBundle {
223311 return true ; // directory was deleted.
224312 }
225313 for (final File file in directory.listSync ().whereType <File >()) {
226- final DateTime dateTime = file.statSync ().modified;
227- if (dateTime .isAfter (lastBuildTimestamp)) {
314+ final DateTime lastModified = file.statSync ().modified;
315+ if (lastModified .isAfter (lastBuildTimestamp)) {
228316 return true ;
229317 }
230318 }
231319 }
232320
233- return manifestStat.modified. isAfter (lastBuildTimestamp) ;
321+ return false ;
234322 }
235323
236324 @override
237325 Future <int > build ({
326+ FlutterHookResult ? flutterHookResult,
238327 String manifestPath = defaultManifestPath,
239328 FlutterProject ? flutterProject,
240329 required String packageConfigPath,
@@ -258,6 +347,7 @@ class ManifestAssetBundle implements AssetBundle {
258347 // hang on hot reload, as the incremental dill files will never be copied to the
259348 // device.
260349 _lastBuildTimestamp = DateTime .now ();
350+ _lastHookResult = flutterHookResult ?? FlutterHookResult .empty ();
261351 if (flutterManifest.isEmpty) {
262352 entries[_kAssetManifestJsonFilename] = AssetBundleEntry (
263353 DevFSStringContent ('{}' ),
@@ -425,9 +515,38 @@ class ManifestAssetBundle implements AssetBundle {
425515 );
426516 }
427517 }
518+ for (final HookAsset dataAsset in flutterHookResult? .dataAssets ?? < HookAsset > []) {
519+ final Package package = packageConfig[dataAsset.package]! ;
520+ final Uri fileUri = dataAsset.file;
521+
522+ final String baseDir;
523+ final Uri relativeUri;
524+ if (fileUri.isAbsolute) {
525+ final String filePath = fileUri.toFilePath ();
526+ baseDir = _fileSystem.path.dirname (filePath);
527+ relativeUri = Uri (path: _fileSystem.path.basename (filePath));
528+ } else {
529+ baseDir = package.root.toFilePath ();
530+ relativeUri = fileUri;
531+ }
532+
533+ final _Asset asset = _Asset (
534+ baseDir: baseDir,
535+ relativeUri: relativeUri,
536+ entryUri: Uri .parse (_fileSystem.path.join ('packages' , dataAsset.package, dataAsset.name)),
537+ package: package,
538+ );
539+ if (assetVariants.containsKey (asset)) {
540+ _logger.printError (
541+ 'Conflicting assets: The asset "$asset " was declared in the pubspec and the hook.' ,
542+ );
543+ return 1 ;
544+ }
545+ assetVariants[asset] = < _Asset > [asset];
546+ }
428547
429548 // Save the contents of each image, image variant, and font
430- // asset in entries.
549+ // asset in [ entries] .
431550 for (final _Asset asset in assetVariants.keys) {
432551 final File assetFile = asset.lookupAssetFile (_fileSystem);
433552 final List <_Asset > variants = assetVariants[asset]! ;
0 commit comments