@@ -25,6 +25,84 @@ import 'flutter_manifest.dart';
25
25
import 'license_collector.dart' ;
26
26
import 'project.dart' ;
27
27
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
+
28
106
const String defaultManifestPath = 'pubspec.yaml' ;
29
107
30
108
const String kFontManifestJson = 'FontManifest.json' ;
@@ -113,6 +191,7 @@ abstract class AssetBundle {
113
191
114
192
/// Returns 0 for success; non-zero for failure.
115
193
Future <int > build ({
194
+ FlutterHookResult ? flutterHookResult,
116
195
String manifestPath = defaultManifestPath,
117
196
required String packageConfigPath,
118
197
bool deferredComponentsEnabled = false ,
@@ -163,7 +242,8 @@ class ManifestAssetBundle implements AssetBundle {
163
242
_platform = platform,
164
243
_flutterRoot = flutterRoot,
165
244
_splitDeferredAssets = splitDeferredAssets,
166
- _licenseCollector = LicenseCollector (fileSystem: fileSystem);
245
+ _licenseCollector = LicenseCollector (fileSystem: fileSystem),
246
+ _lastHookResult = FlutterHookResult .empty ();
167
247
168
248
final Logger _logger;
169
249
final FileSystem _fileSystem;
@@ -189,6 +269,8 @@ class ManifestAssetBundle implements AssetBundle {
189
269
190
270
DateTime ? _lastBuildTimestamp;
191
271
272
+ FlutterHookResult _lastHookResult;
273
+
192
274
// We assume the main asset is designed for a device pixel ratio of 1.0.
193
275
static const String _kAssetManifestJsonFilename = 'AssetManifest.json' ;
194
276
static const String _kAssetManifestBinFilename = 'AssetManifest.bin' ;
@@ -208,13 +290,19 @@ class ManifestAssetBundle implements AssetBundle {
208
290
209
291
@override
210
292
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)) {
213
299
return true ;
214
300
}
301
+ final DateTime lastBuildTimestamp = _lastBuildTimestamp! ;
215
302
216
303
final FileStat manifestStat = _fileSystem.file (manifestPath).statSync ();
217
- if (manifestStat.type == FileSystemEntityType .notFound) {
304
+ if (manifestStat.type == FileSystemEntityType .notFound ||
305
+ manifestStat.modified.isAfter (lastBuildTimestamp)) {
218
306
return true ;
219
307
}
220
308
@@ -223,18 +311,19 @@ class ManifestAssetBundle implements AssetBundle {
223
311
return true ; // directory was deleted.
224
312
}
225
313
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)) {
228
316
return true ;
229
317
}
230
318
}
231
319
}
232
320
233
- return manifestStat.modified. isAfter (lastBuildTimestamp) ;
321
+ return false ;
234
322
}
235
323
236
324
@override
237
325
Future <int > build ({
326
+ FlutterHookResult ? flutterHookResult,
238
327
String manifestPath = defaultManifestPath,
239
328
FlutterProject ? flutterProject,
240
329
required String packageConfigPath,
@@ -258,6 +347,7 @@ class ManifestAssetBundle implements AssetBundle {
258
347
// hang on hot reload, as the incremental dill files will never be copied to the
259
348
// device.
260
349
_lastBuildTimestamp = DateTime .now ();
350
+ _lastHookResult = flutterHookResult ?? FlutterHookResult .empty ();
261
351
if (flutterManifest.isEmpty) {
262
352
entries[_kAssetManifestJsonFilename] = AssetBundleEntry (
263
353
DevFSStringContent ('{}' ),
@@ -425,9 +515,38 @@ class ManifestAssetBundle implements AssetBundle {
425
515
);
426
516
}
427
517
}
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
+ }
428
547
429
548
// Save the contents of each image, image variant, and font
430
- // asset in entries.
549
+ // asset in [ entries] .
431
550
for (final _Asset asset in assetVariants.keys) {
432
551
final File assetFile = asset.lookupAssetFile (_fileSystem);
433
552
final List <_Asset > variants = assetVariants[asset]! ;
0 commit comments