Skip to content

Commit c896fe2

Browse files
authored
Invalidate snapshot when entrypoint changes (flutter#11913)
Adds the app entrypoint as a key in the checksum file. This change eliminates the assumption that checksummed files change when the main entrypoint changes. In the case where there are two entrypoints, a.dart and b.dart and a.dart imports b.dart and b.dart imports a.dart, building the app with entrypoint a.dart followed by a build of the app with entrypoint b.dart would result in the same files list and checksums, but should invalidate the build.
1 parent 59bc0a0 commit c896fe2

File tree

3 files changed

+57
-19
lines changed

3 files changed

+57
-19
lines changed

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

Lines changed: 12 additions & 5 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 hash3;
10+
import 'package:quiver/core.dart' show hash4;
1111

1212
import '../artifacts.dart';
1313
import '../build_info.dart';
@@ -62,7 +62,7 @@ class GenSnapshot {
6262
/// build step. This assumes that build outputs are strictly a product of the
6363
/// input files.
6464
class Checksum {
65-
Checksum.fromFiles(SnapshotType type, Set<String> inputPaths) {
65+
Checksum.fromFiles(SnapshotType type, this._mainPath, Set<String> inputPaths) {
6666
final Iterable<File> files = inputPaths.map(fs.file);
6767
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
6868
if (missingInputs.isNotEmpty)
@@ -99,18 +99,24 @@ class Checksum {
9999
if (_targetPlatform == null)
100100
throw new ArgumentError('Target platform unspecified in checksum JSON');
101101

102+
_mainPath = content['entrypoint'];
103+
if (_mainPath == null)
104+
throw new ArgumentError('Entrypoint unspecified in checksum JSON');
105+
102106
_checksums = content['files'];
103107
if (_checksums == null)
104108
throw new ArgumentError('File checksums unspecified in checksum JSON');
105109
}
106110

111+
String _mainPath;
107112
String _buildMode;
108113
String _targetPlatform;
109114
Map<String, String> _checksums;
110115

111116
String toJson() => JSON.encode(<String, dynamic>{
112117
'version': FlutterVersion.instance.frameworkRevision,
113118
'buildMode': _buildMode,
119+
'entrypoint': _mainPath,
114120
'targetPlatform': _targetPlatform,
115121
'files': _checksums,
116122
});
@@ -120,12 +126,13 @@ class Checksum {
120126
return other is Checksum &&
121127
_buildMode == other._buildMode &&
122128
_targetPlatform == other._targetPlatform &&
129+
_mainPath == other._mainPath &&
123130
_checksums.length == other._checksums.length &&
124131
_checksums.keys.every((String key) => _checksums[key] == other._checksums[key]);
125132
}
126133

127134
@override
128-
int get hashCode => hash3(_buildMode, _targetPlatform, _checksums);
135+
int get hashCode => hash4(_buildMode, _targetPlatform, _mainPath, _checksums);
129136
}
130137

131138
final RegExp _separatorExpr = new RegExp(r'([^\\]) ');
@@ -238,7 +245,7 @@ class Snapshotter {
238245
final Checksum oldChecksum = new Checksum.fromJson(await checksumFile.readAsString());
239246
final Set<String> checksumPaths = await readDepfile(depfilePath)
240247
..addAll(<String>[outputSnapshotPath, mainPath]);
241-
final Checksum newChecksum = new Checksum.fromFiles(type, checksumPaths);
248+
final Checksum newChecksum = new Checksum.fromFiles(type, mainPath, checksumPaths);
242249
return oldChecksum != newChecksum;
243250
}
244251
} catch (e, s) {
@@ -252,7 +259,7 @@ class Snapshotter {
252259
try {
253260
final Set<String> checksumPaths = await readDepfile(depfilePath)
254261
..addAll(<String>[outputSnapshotPath, mainPath]);
255-
final Checksum checksum = new Checksum.fromFiles(type, checksumPaths);
262+
final Checksum checksum = new Checksum.fromFiles(type, mainPath, checksumPaths);
256263
await fs.file(checksumsPath).writeAsString(checksum.toJson());
257264
} catch (e, s) {
258265
// Log exception and continue, this step is a performance improvement only.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ Future<String> _buildAotSnapshot(
297297
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
298298
..add(mainPath)
299299
..addAll(outputPaths);
300-
final Checksum newChecksum = new Checksum.fromFiles(snapshotType, snapshotInputPaths);
300+
final Checksum newChecksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
301301
if (oldChecksum == newChecksum) {
302302
printStatus('Skipping AOT snapshot build. Checksums match.');
303303
return outputPath;
@@ -375,7 +375,7 @@ Future<String> _buildAotSnapshot(
375375
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
376376
..add(mainPath)
377377
..addAll(outputPaths);
378-
final Checksum checksum = new Checksum.fromFiles(snapshotType, snapshotInputPaths);
378+
final Checksum checksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
379379
await checksumFile.writeAsString(checksum.toJson());
380380
} catch (e, s) {
381381
// Log exception and continue, this step is a performance improvement only.

packages/flutter_tools/test/base/build_test.dart

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ void main() {
7575
await fs.file('a.dart').create();
7676
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, BuildMode.debug);
7777
expect(
78-
() => new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet()),
78+
() => new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet()),
7979
throwsA(anything),
8080
);
8181
}, overrides: <Type, Generator>{ FileSystem: () => fs });
@@ -84,7 +84,7 @@ void main() {
8484
await fs.file('a.dart').create();
8585
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, null);
8686
expect(
87-
() => new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet()),
87+
() => new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet()),
8888
throwsA(anything),
8989
);
9090
}, overrides: <Type, Generator>{ FileSystem: () => fs });
@@ -93,7 +93,7 @@ void main() {
9393
await fs.file('a.dart').create();
9494
const SnapshotType snapshotType = const SnapshotType(null, BuildMode.debug);
9595
expect(
96-
new Checksum.fromFiles(snapshotType, <String>['a.dart'].toSet()),
96+
new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart'].toSet()),
9797
isNotNull,
9898
);
9999
}, overrides: <Type, Generator>{ FileSystem: () => fs });
@@ -102,13 +102,14 @@ void main() {
102102
await fs.file('a.dart').writeAsString('This is a');
103103
await fs.file('b.dart').writeAsString('This is b');
104104
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, BuildMode.debug);
105-
final Checksum checksum = new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet());
105+
final Checksum checksum = new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet());
106106

107107
final Map<String, dynamic> json = JSON.decode(checksum.toJson());
108-
expect(json, hasLength(4));
108+
expect(json, hasLength(5));
109109
expect(json['version'], mockVersion.frameworkRevision);
110110
expect(json['buildMode'], BuildMode.debug.toString());
111111
expect(json['targetPlatform'], TargetPlatform.ios.toString());
112+
expect(json['entrypoint'], 'a.dart');
112113
expect(json['files'], hasLength(2));
113114
expect(json['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
114115
expect(json['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
@@ -130,6 +131,7 @@ void main() {
130131
'version': kVersion,
131132
'buildMode': BuildMode.release.toString(),
132133
'targetPlatform': TargetPlatform.ios.toString(),
134+
'entrypoint': 'a.dart',
133135
'files': <String, dynamic>{
134136
'a.dart': '8a21a15fad560b799f6731d436c1b698',
135137
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -138,10 +140,11 @@ void main() {
138140
final Checksum checksum = new Checksum.fromJson(json);
139141

140142
final Map<String, dynamic> content = JSON.decode(checksum.toJson());
141-
expect(content, hasLength(4));
143+
expect(content, hasLength(5));
142144
expect(content['version'], mockVersion.frameworkRevision);
143145
expect(content['buildMode'], BuildMode.release.toString());
144146
expect(content['targetPlatform'], TargetPlatform.ios.toString());
147+
expect(content['entrypoint'], 'a.dart');
145148
expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
146149
expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
147150
}, overrides: <Type, Generator>{
@@ -152,6 +155,8 @@ void main() {
152155
final String json = JSON.encode(<String, dynamic>{
153156
'version': 'bad',
154157
'buildMode': BuildMode.release.toString(),
158+
'targetPlatform': TargetPlatform.ios.toString(),
159+
'entrypoint': 'a.dart',
155160
'files': <String, dynamic>{
156161
'a.dart': '8a21a15fad560b799f6731d436c1b698',
157162
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -169,6 +174,7 @@ void main() {
169174
'version': kVersion,
170175
'buildMode': BuildMode.debug.toString(),
171176
'targetPlatform': TargetPlatform.ios.toString(),
177+
'entrypoint': 'a.dart',
172178
'files': <String, dynamic>{
173179
'a.dart': '8a21a15fad560b799f6731d436c1b698',
174180
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -186,6 +192,7 @@ void main() {
186192
'version': kVersion,
187193
'buildMode': BuildMode.debug.toString(),
188194
'targetPlatform': TargetPlatform.ios.toString(),
195+
'entrypoint': 'a.dart',
189196
'files': <String, dynamic>{
190197
'a.dart': '8a21a15fad560b799f6731d436c1b698',
191198
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -198,11 +205,30 @@ void main() {
198205
FlutterVersion: () => mockVersion,
199206
});
200207

208+
testUsingContext('reports not equal if entrypoints do not match', () async {
209+
final Map<String, dynamic> a = <String, dynamic>{
210+
'version': kVersion,
211+
'buildMode': BuildMode.debug.toString(),
212+
'targetPlatform': TargetPlatform.ios.toString(),
213+
'entrypoint': 'a.dart',
214+
'files': <String, dynamic>{
215+
'a.dart': '8a21a15fad560b799f6731d436c1b698',
216+
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
217+
},
218+
};
219+
final Map<String, dynamic> b = new Map<String, dynamic>.from(a);
220+
b['entrypoint'] = 'b.dart';
221+
expect(new Checksum.fromJson(JSON.encode(a)) == new Checksum.fromJson(JSON.encode(b)), isFalse);
222+
}, overrides: <Type, Generator>{
223+
FlutterVersion: () => mockVersion,
224+
});
225+
201226
testUsingContext('reports not equal if checksums do not match', () async {
202227
final Map<String, dynamic> a = <String, dynamic>{
203228
'version': kVersion,
204229
'buildMode': BuildMode.debug.toString(),
205230
'targetPlatform': TargetPlatform.ios.toString(),
231+
'entrypoint': 'a.dart',
206232
'files': <String, dynamic>{
207233
'a.dart': '8a21a15fad560b799f6731d436c1b698',
208234
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -223,6 +249,7 @@ void main() {
223249
'version': kVersion,
224250
'buildMode': BuildMode.debug.toString(),
225251
'targetPlatform': TargetPlatform.ios.toString(),
252+
'entrypoint': 'a.dart',
226253
'files': <String, dynamic>{
227254
'a.dart': '8a21a15fad560b799f6731d436c1b698',
228255
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -243,6 +270,7 @@ void main() {
243270
'version': kVersion,
244271
'buildMode': BuildMode.debug.toString(),
245272
'targetPlatform': TargetPlatform.ios.toString(),
273+
'entrypoint': 'a.dart',
246274
'files': <String, dynamic>{
247275
'a.dart': '8a21a15fad560b799f6731d436c1b698',
248276
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
@@ -398,20 +426,21 @@ void main() {
398426
final _FakeGenSnapshot genSnapshot = new _FakeGenSnapshot(
399427
snapshotPath: 'output.snapshot',
400428
depfilePath: 'output.snapshot.d',
401-
depfileContent: 'output.snapshot : other.dart',
429+
depfileContent: 'output.snapshot : main.dart other.dart',
402430
);
403431
context.setVariable(GenSnapshot, genSnapshot);
404432

405-
await fs.file('main.dart').writeAsString('void main() {}');
406-
await fs.file('other.dart').writeAsString('void main() { print("Kanpai ima kimi wa jinsei no ookina ookina butai ni tachi"); }');
433+
await fs.file('main.dart').writeAsString('import "other.dart";\nvoid main() {}');
434+
await fs.file('other.dart').writeAsString('import "main.dart";\nvoid main() {}');
407435
await fs.file('output.snapshot').create();
408436
await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
409437
await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
410438
'version': '$kVersion',
411439
'buildMode': BuildMode.debug.toString(),
412440
'targetPlatform': '',
413441
'files': <String, dynamic>{
414-
'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
442+
'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
443+
'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
415444
'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
416445
},
417446
}));
@@ -424,8 +453,9 @@ void main() {
424453

425454
expect(genSnapshot.callCount, 1);
426455
final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
427-
expect(json['files'], hasLength(2));
428-
expect(json['files']['other.dart'], '3238d0ae341339b1731d3c2e195ad177');
456+
expect(json['files'], hasLength(3));
457+
expect(json['files']['main.dart'], 'bc096b33f14dde5e0ffaf93a1d03395c');
458+
expect(json['files']['other.dart'], 'e0c35f083f0ad76b2d87100ec678b516');
429459
expect(json['files']['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
430460
}, overrides: <Type, Generator>{
431461
FileSystem: () => fs,
@@ -440,6 +470,7 @@ void main() {
440470
'version': '$kVersion',
441471
'buildMode': BuildMode.debug.toString(),
442472
'targetPlatform': '',
473+
'entrypoint': 'main.dart',
443474
'files': <String, dynamic>{
444475
'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
445476
'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',

0 commit comments

Comments
 (0)