1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';
import '../convert.dart' show json;
import 'file_system.dart';
import 'logger.dart';
import 'utils.dart';
/// A tool that can be used to compute, compare, and write [Fingerprint]s for a
/// set of input files and associated build settings.
///
/// This class should only be used in situations where `assemble` is not appropriate,
/// such as checking if Cocoapods should be run.
class Fingerprinter {
Fingerprinter({
required this.fingerprintPath,
required Iterable<String> paths,
required FileSystem fileSystem,
required Logger logger,
}) : _paths = paths.toList(),
assert(fingerprintPath != null),
assert(paths != null && paths.every((String path) => path != null)),
_logger = logger,
_fileSystem = fileSystem;
final String fingerprintPath;
final List<String> _paths;
final Logger _logger;
final FileSystem _fileSystem;
Fingerprint buildFingerprint() {
final List<String> paths = _getPaths();
return Fingerprint.fromBuildInputs(paths, _fileSystem);
}
bool doesFingerprintMatch() {
try {
final File fingerprintFile = _fileSystem.file(fingerprintPath);
if (!fingerprintFile.existsSync()) {
return false;
}
final List<String> paths = _getPaths();
if (!paths.every(_fileSystem.isFileSync)) {
return false;
}
final Fingerprint oldFingerprint = Fingerprint.fromJson(fingerprintFile.readAsStringSync());
final Fingerprint newFingerprint = buildFingerprint();
return oldFingerprint == newFingerprint;
} on Exception catch (e) {
// Log exception and continue, fingerprinting is only a performance improvement.
_logger.printTrace('Fingerprint check error: $e');
}
return false;
}
void writeFingerprint() {
try {
final Fingerprint fingerprint = buildFingerprint();
final File fingerprintFile = _fileSystem.file(fingerprintPath);
fingerprintFile.createSync(recursive: true);
fingerprintFile.writeAsStringSync(fingerprint.toJson());
} on Exception catch (e) {
// Log exception and continue, fingerprinting is only a performance improvement.
_logger.printTrace('Fingerprint write error: $e');
}
}
List<String> _getPaths() => _paths;
}
/// A fingerprint that uniquely identifies a set of build input files and
/// properties.
///
/// See [Fingerprinter].
@immutable
class Fingerprint {
const Fingerprint._({
Map<String, String>? checksums,
}) : _checksums = checksums ?? const <String, String>{};
factory Fingerprint.fromBuildInputs(Iterable<String> inputPaths, FileSystem fileSystem) {
final Iterable<File> files = inputPaths.map<File>(fileSystem.file);
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
if (missingInputs.isNotEmpty) {
throw Exception('Missing input files:\n${missingInputs.join('\n')}');
}
return Fingerprint._(
checksums: <String, String>{
for (final File file in files)
file.path: md5.convert(file.readAsBytesSync()).toString(),
},
);
}
/// Creates a Fingerprint from serialized JSON.
///
/// Throws [Exception], if there is a version mismatch between the
/// serializing framework and this framework.
factory Fingerprint.fromJson(String jsonData) {
final Map<String, dynamic>? content = castStringKeyedMap(json.decode(jsonData));
final Map<String, String>? files = content == null
? null
: castStringKeyedMap(content['files'])?.cast<String, String>();
return Fingerprint._(
checksums: files ?? <String, String>{},
);
}
final Map<String, String> _checksums;
String toJson() => json.encode(<String, dynamic>{
'files': _checksums,
});
@override
bool operator==(Object other) {
return other is Fingerprint
&& _equalMaps(other._checksums, _checksums);
}
bool _equalMaps(Map<String, String> a, Map<String, String> b) {
return a.length == b.length
&& a.keys.every((String key) => a[key] == b[key]);
}
@override
int get hashCode => Object.hash(Object.hashAllUnordered(_checksums.keys), Object.hashAllUnordered(_checksums.values));
@override
String toString() => '{checksums: $_checksums}';
}