Skip to content

Commit 1401652

Browse files
authored
Ensure snapshot rebuild logic takes main path into account (flutter#11924)
1 parent 39680eb commit 1401652

File tree

3 files changed

+244
-212
lines changed

3 files changed

+244
-212
lines changed

packages/flutter_tools/lib/src/base/build.dart

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'dart:convert' show JSON;
77

88
import 'package:crypto/crypto.dart' show md5;
99
import 'package:meta/meta.dart';
10-
import 'package:quiver/core.dart' show hash4;
10+
import 'package:quiver/core.dart' show hash2;
1111

1212
import '../artifacts.dart';
1313
import '../build_info.dart';
@@ -21,7 +21,9 @@ GenSnapshot get genSnapshot => context.putIfAbsent(GenSnapshot, () => const GenS
2121

2222
/// A snapshot build configuration.
2323
class 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

138126
final 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
}

packages/flutter_tools/lib/src/commands/build_aot.dart

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -286,25 +286,25 @@ Future<String> _buildAotSnapshot(
286286
genSnapshotCmd.add(mainPath);
287287

288288
final SnapshotType snapshotType = new SnapshotType(platform, buildMode);
289-
final File checksumFile = fs.file('$dependencies.checksum');
290-
final List<File> checksumFiles = <File>[checksumFile, fs.file(dependencies)]
289+
final File fingerprintFile = fs.file('$dependencies.fingerprint');
290+
final List<File> fingerprintFiles = <File>[fingerprintFile, fs.file(dependencies)]
291291
..addAll(inputPaths.map(fs.file))
292292
..addAll(outputPaths.map(fs.file));
293-
if (checksumFiles.every((File file) => file.existsSync())) {
293+
if (fingerprintFiles.every((File file) => file.existsSync())) {
294294
try {
295-
final String json = await checksumFile.readAsString();
296-
final Checksum oldChecksum = new Checksum.fromJson(json);
295+
final String json = await fingerprintFile.readAsString();
296+
final Fingerprint oldFingerprint = new Fingerprint.fromJson(json);
297297
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
298298
..add(mainPath)
299299
..addAll(outputPaths);
300-
final Checksum newChecksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
301-
if (oldChecksum == newChecksum) {
302-
printTrace('Skipping AOT snapshot build. Checksums match.');
300+
final Fingerprint newFingerprint = Snapshotter.createFingerprint(snapshotType, mainPath, snapshotInputPaths);
301+
if (oldFingerprint == newFingerprint) {
302+
printStatus('Skipping AOT snapshot build. Fingerprint match.');
303303
return outputPath;
304304
}
305305
} catch (e) {
306306
// Log exception and continue, this step is a performance improvement only.
307-
printTrace('Rebuilding snapshot due to checksum validation error: $e');
307+
printTrace('Rebuilding snapshot due to fingerprint check error: $e');
308308
}
309309
}
310310

@@ -370,16 +370,16 @@ Future<String> _buildAotSnapshot(
370370
await runCheckedAsync(linkCommand);
371371
}
372372

373-
// Compute and record checksums.
373+
// Compute and record build fingerprint.
374374
try {
375375
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
376376
..add(mainPath)
377377
..addAll(outputPaths);
378-
final Checksum checksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
379-
await checksumFile.writeAsString(checksum.toJson());
378+
final Fingerprint fingerprint = Snapshotter.createFingerprint(snapshotType, mainPath, snapshotInputPaths);
379+
await fingerprintFile.writeAsString(fingerprint.toJson());
380380
} catch (e, s) {
381381
// Log exception and continue, this step is a performance improvement only.
382-
printTrace('Error during AOT snapshot checksum output: $e\n$s');
382+
printStatus('Error during AOT snapshot fingerprinting: $e\n$s');
383383
}
384384

385385
return outputPath;

0 commit comments

Comments
 (0)