From c896fe2f6ec36826b76e42bb4d9bb73c6df21c98 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Tue, 5 Sep 2017 11:19:09 -0700 Subject: [PATCH] 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. --- .../flutter_tools/lib/src/base/build.dart | 17 ++++-- .../lib/src/commands/build_aot.dart | 4 +- .../flutter_tools/test/base/build_test.dart | 55 +++++++++++++++---- 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 81aacb90609..ef00578ec10 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -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 inputPaths) { + Checksum.fromFiles(SnapshotType type, this._mainPath, Set inputPaths) { final Iterable files = inputPaths.map(fs.file); final Iterable 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 _checksums; @@ -111,6 +116,7 @@ class Checksum { String toJson() => JSON.encode({ '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 checksumPaths = await readDepfile(depfilePath) ..addAll([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 checksumPaths = await readDepfile(depfilePath) ..addAll([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. diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart index 4409374dfb9..215d4d58d36 100644 --- a/packages/flutter_tools/lib/src/commands/build_aot.dart +++ b/packages/flutter_tools/lib/src/commands/build_aot.dart @@ -297,7 +297,7 @@ Future _buildAotSnapshot( final Set 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 _buildAotSnapshot( final Set 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. diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart index de3e28ce342..e8788784ebb 100644 --- a/packages/flutter_tools/test/base/build_test.dart +++ b/packages/flutter_tools/test/base/build_test.dart @@ -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, ['a.dart', 'b.dart'].toSet()), + () => new Checksum.fromFiles(snapshotType, 'a.dart', ['a.dart', 'b.dart'].toSet()), throwsA(anything), ); }, overrides: { 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, ['a.dart', 'b.dart'].toSet()), + () => new Checksum.fromFiles(snapshotType, 'a.dart', ['a.dart', 'b.dart'].toSet()), throwsA(anything), ); }, overrides: { 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, ['a.dart'].toSet()), + new Checksum.fromFiles(snapshotType, 'a.dart', ['a.dart'].toSet()), isNotNull, ); }, overrides: { 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, ['a.dart', 'b.dart'].toSet()); + final Checksum checksum = new Checksum.fromFiles(snapshotType, 'a.dart', ['a.dart', 'b.dart'].toSet()); final Map 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': { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', @@ -138,10 +140,11 @@ void main() { final Checksum checksum = new Checksum.fromJson(json); final Map 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: { @@ -152,6 +155,8 @@ void main() { final String json = JSON.encode({ 'version': 'bad', 'buildMode': BuildMode.release.toString(), + 'targetPlatform': TargetPlatform.ios.toString(), + 'entrypoint': 'a.dart', 'files': { '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': { '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': { '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 a = { + 'version': kVersion, + 'buildMode': BuildMode.debug.toString(), + 'targetPlatform': TargetPlatform.ios.toString(), + 'entrypoint': 'a.dart', + 'files': { + 'a.dart': '8a21a15fad560b799f6731d436c1b698', + 'b.dart': '6f144e08b58cd0925328610fad7ac07c', + }, + }; + final Map b = new Map.from(a); + b['entrypoint'] = 'b.dart'; + expect(new Checksum.fromJson(JSON.encode(a)) == new Checksum.fromJson(JSON.encode(b)), isFalse); + }, overrides: { + FlutterVersion: () => mockVersion, + }); + testUsingContext('reports not equal if checksums do not match', () async { final Map a = { 'version': kVersion, 'buildMode': BuildMode.debug.toString(), 'targetPlatform': TargetPlatform.ios.toString(), + 'entrypoint': 'a.dart', 'files': { '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': { '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': { '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({ @@ -411,7 +439,8 @@ void main() { 'buildMode': BuildMode.debug.toString(), 'targetPlatform': '', 'files': { - 'main.dart': '27f5ebf0f8c559b2af9419d190299a5e', + 'main.dart': 'bc096b33f14dde5e0ffaf93a1d03395c', + 'other.dart': 'e0c35f083f0ad76b2d87100ec678b516', 'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e', }, })); @@ -424,8 +453,9 @@ void main() { expect(genSnapshot.callCount, 1); final Map 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: { FileSystem: () => fs, @@ -440,6 +470,7 @@ void main() { 'version': '$kVersion', 'buildMode': BuildMode.debug.toString(), 'targetPlatform': '', + 'entrypoint': 'main.dart', 'files': { 'main.dart': '27f5ebf0f8c559b2af9419d190299a5e', 'output.snapshot': 'd41d8cd98f00b204e9800998ecf8427e',