@@ -7,7 +7,7 @@ import 'dart:convert' show JSON;
77
88import 'package:crypto/crypto.dart' show md5;
99import 'package:meta/meta.dart' ;
10- import 'package:quiver/core.dart' show hash4 ;
10+ import 'package:quiver/core.dart' show hash2 ;
1111
1212import '../artifacts.dart' ;
1313import '../build_info.dart' ;
@@ -21,7 +21,9 @@ GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenS
2121
2222/// A snapshot build configuration.
2323class SnapshotType {
24- const SnapshotType (this .platform, this .mode);
24+ SnapshotType (this .platform, this .mode) {
25+ assert (mode != null );
26+ }
2527
2628 final TargetPlatform platform;
2729 final BuildMode mode;
@@ -55,84 +57,70 @@ class GenSnapshot {
5557 }
5658}
5759
58- /// A collection of checksums for a set of input files.
60+ /// A fingerprint for a set of build input files and properties .
5961///
60- /// This class can be used during build actions to compute a checksum of the
62+ /// This class can be used during build actions to compute a fingerprint of the
6163/// build action inputs, and if unchanged from the previous build, skip the
6264/// build step. This assumes that build outputs are strictly a product of the
63- /// input files .
64- class Checksum {
65- Checksum . fromFiles ( SnapshotType type, this ._mainPath, Set <String > inputPaths) {
65+ /// fingerprint inputs .
66+ class Fingerprint {
67+ Fingerprint . fromBuildInputs ( Map < String , String > properties, Iterable <String > inputPaths) {
6668 final Iterable <File > files = inputPaths.map (fs.file);
6769 final Iterable <File > missingInputs = files.where ((File file) => ! file.existsSync ());
6870 if (missingInputs.isNotEmpty)
6971 throw new ArgumentError ('Missing input files:\n ' + missingInputs.join ('\n ' ));
7072
71- _buildMode = type.mode.toString ();
72- _targetPlatform = type.platform? .toString () ?? '' ;
7373 _checksums = < String , String > {};
7474 for (File file in files) {
7575 final List <int > bytes = file.readAsBytesSync ();
7676 _checksums[file.path] = md5.convert (bytes).toString ();
7777 }
78+ _properties = < String , String > {}..addAll (properties);
7879 }
7980
80- /// Creates a checksum from serialized JSON.
81+ /// Creates a Fingerprint from serialized JSON.
8182 ///
82- /// Throws [ArgumentError] in the following cases:
83- /// * Version mismatch between the serializing framework and this framework.
84- /// * buildMode is unspecified.
85- /// * targetPlatform is unspecified.
86- /// * File checksum map is unspecified.
87- Checksum .fromJson (String json) {
83+ /// Throws [ArgumentError] , if there is a version mismatch between the
84+ /// serializing framework and this framework.
85+ Fingerprint .fromJson (String json) {
8886 final Map <String , dynamic > content = JSON .decode (json);
8987
9088 final String version = content['version' ];
9189 if (version != FlutterVersion .instance.frameworkRevision)
92- throw new ArgumentError ('Incompatible checksum version: $version ' );
93-
94- _buildMode = content['buildMode' ];
95- if (_buildMode == null || _buildMode.isEmpty)
96- throw new ArgumentError ('Build mode unspecified in checksum JSON' );
97-
98- _targetPlatform = content['targetPlatform' ];
99- if (_targetPlatform == null )
100- throw new ArgumentError ('Target platform unspecified in checksum JSON' );
101-
102- _mainPath = content['entrypoint' ];
103- if (_mainPath == null )
104- throw new ArgumentError ('Entrypoint unspecified in checksum JSON' );
105-
106- _checksums = content['files' ];
107- if (_checksums == null )
108- throw new ArgumentError ('File checksums unspecified in checksum JSON' );
90+ throw new ArgumentError ('Incompatible fingerprint version: $version ' );
91+ _checksums = content['files' ] ?? < String , String > {};
92+ _properties = content['properties' ] ?? < String , String > {};
10993 }
11094
111- String _mainPath;
112- String _buildMode;
113- String _targetPlatform;
11495 Map <String , String > _checksums;
96+ Map <String , String > _properties;
11597
11698 String toJson () => JSON .encode (< String , dynamic > {
11799 'version' : FlutterVersion .instance.frameworkRevision,
118- 'buildMode' : _buildMode,
119- 'entrypoint' : _mainPath,
120- 'targetPlatform' : _targetPlatform,
100+ 'properties' : _properties,
121101 'files' : _checksums,
122102 });
123103
124104 @override
125105 bool operator == (dynamic other) {
126- return other is Checksum &&
127- _buildMode == other._buildMode &&
128- _targetPlatform == other._targetPlatform &&
129- _mainPath == other._mainPath &&
130- _checksums.length == other._checksums.length &&
131- _checksums.keys.every ((String key) => _checksums[key] == other._checksums[key]);
106+ if (identical (other, this ))
107+ return true ;
108+ if (other.runtimeType != runtimeType)
109+ return false ;
110+ final Fingerprint typedOther = other;
111+ return _equalMaps (typedOther._checksums, _checksums)
112+ && _equalMaps (typedOther._properties, _properties);
113+ }
114+
115+ bool _equalMaps (Map <String , String > a, Map <String , String > b) {
116+ return a.length == b.length
117+ && a.keys.every ((String key) => a[key] == b[key]);
132118 }
133119
134120 @override
135- int get hashCode => hash4 (_buildMode, _targetPlatform, _mainPath, _checksums);
121+ // Ignore map entries here to avoid becoming inconsistent with equals
122+ // due to differences in map entry order.
123+ int get hashCode => hash2 (_properties.length, _checksums.length);
136124}
137125
138126final RegExp _separatorExpr = new RegExp (r'([^\\]) ' );
@@ -177,26 +165,26 @@ class Snapshotter {
177165 @required String depfilePath,
178166 @required String packagesPath
179167 }) async {
180- const SnapshotType type = const SnapshotType (null , BuildMode .debug);
168+ final SnapshotType type = new SnapshotType (null , BuildMode .debug);
181169 final List <String > args = < String > [
182170 '--snapshot_kind=script' ,
183171 '--script_snapshot=$snapshotPath ' ,
184172 mainPath,
185173 ];
186174
187- final String checksumsPath = '$depfilePath .checksums ' ;
175+ final String fingerprintPath = '$depfilePath .fingerprint ' ;
188176 final int exitCode = await _build (
189177 snapshotType: type,
190178 outputSnapshotPath: snapshotPath,
191179 packagesPath: packagesPath,
192180 snapshotArgs: args,
193181 depfilePath: depfilePath,
194182 mainPath: mainPath,
195- checksumsPath : checksumsPath ,
183+ fingerprintPath : fingerprintPath ,
196184 );
197185 if (exitCode != 0 )
198186 return exitCode;
199- await _writeChecksum (type, snapshotPath, depfilePath, mainPath, checksumsPath );
187+ await _writeFingerprint (type, snapshotPath, depfilePath, mainPath, fingerprintPath );
200188 return exitCode;
201189 }
202190
@@ -212,10 +200,10 @@ class Snapshotter {
212200 @required String packagesPath,
213201 @required String depfilePath,
214202 @required String mainPath,
215- @required String checksumsPath ,
203+ @required String fingerprintPath ,
216204 }) async {
217- if (! await _isBuildRequired (snapshotType, outputSnapshotPath, depfilePath, mainPath, checksumsPath )) {
218- printTrace ('Skipping snapshot build. Checksums match.' );
205+ if (! await _isBuildRequired (snapshotType, outputSnapshotPath, depfilePath, mainPath, fingerprintPath )) {
206+ printTrace ('Skipping snapshot build. Fingerprints match.' );
219207 return 0 ;
220208 }
221209
@@ -229,41 +217,49 @@ class Snapshotter {
229217 if (exitCode != 0 )
230218 return exitCode;
231219
232- _writeChecksum (snapshotType, outputSnapshotPath, depfilePath, mainPath, checksumsPath );
220+ _writeFingerprint (snapshotType, outputSnapshotPath, depfilePath, mainPath, fingerprintPath );
233221 return 0 ;
234222 }
235223
236- Future <bool > _isBuildRequired (SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath ) async {
237- final File checksumFile = fs.file (checksumsPath );
224+ Future <bool > _isBuildRequired (SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String fingerprintPath ) async {
225+ final File fingerprintFile = fs.file (fingerprintPath );
238226 final File outputSnapshotFile = fs.file (outputSnapshotPath);
239227 final File depfile = fs.file (depfilePath);
240- if (! outputSnapshotFile.existsSync () || ! depfile.existsSync () || ! checksumFile .existsSync ())
228+ if (! outputSnapshotFile.existsSync () || ! depfile.existsSync () || ! fingerprintFile .existsSync ())
241229 return true ;
242230
243231 try {
244- if (checksumFile.existsSync ()) {
245- final Checksum oldChecksum = new Checksum .fromJson (await checksumFile.readAsString ());
246- final Set <String > checksumPaths = await readDepfile (depfilePath)
247- ..addAll (< String > [outputSnapshotPath, mainPath]);
248- final Checksum newChecksum = new Checksum .fromFiles (type, mainPath, checksumPaths);
249- return oldChecksum != newChecksum;
232+ if (fingerprintFile.existsSync ()) {
233+ final Fingerprint oldFingerprint = new Fingerprint .fromJson (await fingerprintFile.readAsString ());
234+ final Set <String > inputFilePaths = await readDepfile (depfilePath)..addAll (< String > [outputSnapshotPath, mainPath]);
235+ final Fingerprint newFingerprint = createFingerprint (type, mainPath, inputFilePaths);
236+ return oldFingerprint != newFingerprint;
250237 }
251238 } catch (e) {
252239 // Log exception and continue, this step is a performance improvement only.
253- printTrace ('Rebuilding snapshot due to checksum validation error: $e ' );
240+ printTrace ('Rebuilding snapshot due to fingerprint check error: $e ' );
254241 }
255242 return true ;
256243 }
257244
258- Future <Null > _writeChecksum (SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String checksumsPath ) async {
245+ Future <Null > _writeFingerprint (SnapshotType type, String outputSnapshotPath, String depfilePath, String mainPath, String fingerprintPath ) async {
259246 try {
260- final Set <String > checksumPaths = await readDepfile (depfilePath)
247+ final Set <String > inputFilePaths = await readDepfile (depfilePath)
261248 ..addAll (< String > [outputSnapshotPath, mainPath]);
262- final Checksum checksum = new Checksum . fromFiles (type, mainPath, checksumPaths );
263- await fs.file (checksumsPath ).writeAsString (checksum .toJson ());
249+ final Fingerprint fingerprint = createFingerprint (type, mainPath, inputFilePaths );
250+ await fs.file (fingerprintPath ).writeAsString (fingerprint .toJson ());
264251 } catch (e, s) {
265252 // Log exception and continue, this step is a performance improvement only.
266- printTrace ('Error during snapshot checksum output : $e \n $s ' );
253+ print ('Error during snapshot fingerprinting : $e \n $s ' );
267254 }
268255 }
256+
257+ static Fingerprint createFingerprint (SnapshotType type, String mainPath, Iterable <String > inputFilePaths) {
258+ final Map <String , String > properties = < String , String > {
259+ 'buildMode' : type.mode.toString (),
260+ 'targetPlatform' : type.platform? .toString () ?? '' ,
261+ 'entryPoint' : mainPath,
262+ };
263+ return new Fingerprint .fromBuildInputs (properties, inputFilePaths);
264+ }
269265}
0 commit comments