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