Commit c896fe2f authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Invalidate snapshot when entrypoint changes (#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.
parent 59bc0a0f
......@@ -7,7 +7,7 @@ import 'dart:convert' show JSON;
import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';
import 'package:quiver/core.dart' show hash3;
import 'package:quiver/core.dart' show hash4;
import '../artifacts.dart';
import '../build_info.dart';
......@@ -62,7 +62,7 @@ class GenSnapshot {
/// build step. This assumes that build outputs are strictly a product of the
/// input files.
class Checksum {
Checksum.fromFiles(SnapshotType type, Set<String> inputPaths) {
Checksum.fromFiles(SnapshotType type, this._mainPath, Set<String> inputPaths) {
final Iterable<File> files = inputPaths.map(fs.file);
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
if (missingInputs.isNotEmpty)
......@@ -99,11 +99,16 @@ class Checksum {
if (_targetPlatform == null)
throw new ArgumentError('Target platform unspecified in checksum JSON');
_mainPath = content['entrypoint'];
if (_mainPath == null)
throw new ArgumentError('Entrypoint unspecified in checksum JSON');
_checksums = content['files'];
if (_checksums == null)
throw new ArgumentError('File checksums unspecified in checksum JSON');
}
String _mainPath;
String _buildMode;
String _targetPlatform;
Map<String, String> _checksums;
......@@ -111,6 +116,7 @@ class Checksum {
String toJson() => JSON.encode(<String, dynamic>{
'version': FlutterVersion.instance.frameworkRevision,
'buildMode': _buildMode,
'entrypoint': _mainPath,
'targetPlatform': _targetPlatform,
'files': _checksums,
});
......@@ -120,12 +126,13 @@ class Checksum {
return other is Checksum &&
_buildMode == other._buildMode &&
_targetPlatform == other._targetPlatform &&
_mainPath == other._mainPath &&
_checksums.length == other._checksums.length &&
_checksums.keys.every((String key) => _checksums[key] == other._checksums[key]);
}
@override
int get hashCode => hash3(_buildMode, _targetPlatform, _checksums);
int get hashCode => hash4(_buildMode, _targetPlatform, _mainPath, _checksums);
}
final RegExp _separatorExpr = new RegExp(r'([^\\]) ');
......@@ -238,7 +245,7 @@ class Snapshotter {
final Checksum oldChecksum = new Checksum.fromJson(await checksumFile.readAsString());
final Set<String> checksumPaths = await readDepfile(depfilePath)
..addAll(<String>[outputSnapshotPath, mainPath]);
final Checksum newChecksum = new Checksum.fromFiles(type, checksumPaths);
final Checksum newChecksum = new Checksum.fromFiles(type, mainPath, checksumPaths);
return oldChecksum != newChecksum;
}
} catch (e, s) {
......@@ -252,7 +259,7 @@ class Snapshotter {
try {
final Set<String> checksumPaths = await readDepfile(depfilePath)
..addAll(<String>[outputSnapshotPath, mainPath]);
final Checksum checksum = new Checksum.fromFiles(type, checksumPaths);
final Checksum checksum = new Checksum.fromFiles(type, mainPath, checksumPaths);
await fs.file(checksumsPath).writeAsString(checksum.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
......
......@@ -297,7 +297,7 @@ Future<String> _buildAotSnapshot(
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
..add(mainPath)
..addAll(outputPaths);
final Checksum newChecksum = new Checksum.fromFiles(snapshotType, snapshotInputPaths);
final Checksum newChecksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
if (oldChecksum == newChecksum) {
printStatus('Skipping AOT snapshot build. Checksums match.');
return outputPath;
......@@ -375,7 +375,7 @@ Future<String> _buildAotSnapshot(
final Set<String> snapshotInputPaths = await readDepfile(dependencies)
..add(mainPath)
..addAll(outputPaths);
final Checksum checksum = new Checksum.fromFiles(snapshotType, snapshotInputPaths);
final Checksum checksum = new Checksum.fromFiles(snapshotType, mainPath, snapshotInputPaths);
await checksumFile.writeAsString(checksum.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
......
......@@ -75,7 +75,7 @@ void main() {
await fs.file('a.dart').create();
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, BuildMode.debug);
expect(
() => new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet()),
() => new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet()),
throwsA(anything),
);
}, overrides: <Type, Generator>{ FileSystem: () => fs });
......@@ -84,7 +84,7 @@ void main() {
await fs.file('a.dart').create();
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, null);
expect(
() => new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet()),
() => new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet()),
throwsA(anything),
);
}, overrides: <Type, Generator>{ FileSystem: () => fs });
......@@ -93,7 +93,7 @@ void main() {
await fs.file('a.dart').create();
const SnapshotType snapshotType = const SnapshotType(null, BuildMode.debug);
expect(
new Checksum.fromFiles(snapshotType, <String>['a.dart'].toSet()),
new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart'].toSet()),
isNotNull,
);
}, overrides: <Type, Generator>{ FileSystem: () => fs });
......@@ -102,13 +102,14 @@ void main() {
await fs.file('a.dart').writeAsString('This is a');
await fs.file('b.dart').writeAsString('This is b');
const SnapshotType snapshotType = const SnapshotType(TargetPlatform.ios, BuildMode.debug);
final Checksum checksum = new Checksum.fromFiles(snapshotType, <String>['a.dart', 'b.dart'].toSet());
final Checksum checksum = new Checksum.fromFiles(snapshotType, 'a.dart', <String>['a.dart', 'b.dart'].toSet());
final Map<String, dynamic> json = JSON.decode(checksum.toJson());
expect(json, hasLength(4));
expect(json, hasLength(5));
expect(json['version'], mockVersion.frameworkRevision);
expect(json['buildMode'], BuildMode.debug.toString());
expect(json['targetPlatform'], TargetPlatform.ios.toString());
expect(json['entrypoint'], 'a.dart');
expect(json['files'], hasLength(2));
expect(json['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(json['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
......@@ -130,6 +131,7 @@ void main() {
'version': kVersion,
'buildMode': BuildMode.release.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -138,10 +140,11 @@ void main() {
final Checksum checksum = new Checksum.fromJson(json);
final Map<String, dynamic> content = JSON.decode(checksum.toJson());
expect(content, hasLength(4));
expect(content, hasLength(5));
expect(content['version'], mockVersion.frameworkRevision);
expect(content['buildMode'], BuildMode.release.toString());
expect(content['targetPlatform'], TargetPlatform.ios.toString());
expect(content['entrypoint'], 'a.dart');
expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
}, overrides: <Type, Generator>{
......@@ -152,6 +155,8 @@ void main() {
final String json = JSON.encode(<String, dynamic>{
'version': 'bad',
'buildMode': BuildMode.release.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -169,6 +174,7 @@ void main() {
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -186,6 +192,7 @@ void main() {
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -198,11 +205,30 @@ void main() {
FlutterVersion: () => mockVersion,
});
testUsingContext('reports not equal if entrypoints do not match', () async {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
},
};
final Map<String, dynamic> b = new Map<String, dynamic>.from(a);
b['entrypoint'] = 'b.dart';
expect(new Checksum.fromJson(JSON.encode(a)) == new Checksum.fromJson(JSON.encode(b)), isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('reports not equal if checksums do not match', () async {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -223,6 +249,7 @@ void main() {
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -243,6 +270,7 @@ void main() {
'version': kVersion,
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entrypoint': 'a.dart',
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -398,12 +426,12 @@ void main() {
final _FakeGenSnapshot genSnapshot = new _FakeGenSnapshot(
snapshotPath: 'output.snapshot',
depfilePath: 'output.snapshot.d',
depfileContent: 'output.snapshot : other.dart',
depfileContent: 'output.snapshot : main.dart other.dart',
);
context.setVariable(GenSnapshot, genSnapshot);
await fs.file('main.dart').writeAsString('void main() {}');
await fs.file('other.dart').writeAsString('void main() { print("Kanpai ima kimi wa jinsei no ookina ookina butai ni tachi"); }');
await fs.file('main.dart').writeAsString('import "other.dart";\nvoid main() {}');
await fs.file('other.dart').writeAsString('import "main.dart";\nvoid main() {}');
await fs.file('output.snapshot').create();
await fs.file('output.snapshot.d').writeAsString('output.snapshot : main.dart');
await fs.file('output.snapshot.d.checksums').writeAsString(JSON.encode(<String, dynamic>{
......@@ -411,7 +439,8 @@ void main() {
'buildMode': BuildMode.debug.toString(),
'targetPlatform': '',
'files': <String, dynamic>{
'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c',
'other.dart': 'e0c35f083f0ad76b2d87100ec678b516',
'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
},
}));
......@@ -424,8 +453,9 @@ void main() {
expect(genSnapshot.callCount, 1);
final Map<String, dynamic> json = JSON.decode(await fs.file('output.snapshot.d.checksums').readAsString());
expect(json['files'], hasLength(2));
expect(json['files']['other.dart'], '3238d0ae341339b1731d3c2e195ad177');
expect(json['files'], hasLength(3));
expect(json['files']['main.dart'], 'bc096b33f14dde5e0ffaf93a1d03395c');
expect(json['files']['other.dart'], 'e0c35f083f0ad76b2d87100ec678b516');
expect(json['files']['output.snapshot'], 'd41d8cd98f00b204e9800998ecf8427e');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
......@@ -440,6 +470,7 @@ void main() {
'version': '$kVersion',
'buildMode': BuildMode.debug.toString(),
'targetPlatform': '',
'entrypoint': 'main.dart',
'files': <String, dynamic>{
'main.dart': '27f5ebf0f8c559b2af9419d190299a5e',
'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment