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

Add version, build mode to the snapshot checksums (#11787)

This change ensures that snapshot build checksums used to avoid
duplicate builds are invalidated by a change to framework revision
(in case gen_snapshot is updated), as well as by build mode.

Currently, only FLX snapshotting uses checksums to avoid duplicate
builds. FLX snapshotting is always done with BuildMode.debug, so didn't
include build mode in the checksum file.
parent e960ba0b
......@@ -5,7 +5,10 @@
import 'dart:convert' show JSON;
import 'package:crypto/crypto.dart' show md5;
import 'package:quiver/core.dart' show hash2;
import '../build_info.dart';
import '../version.dart';
import 'file_system.dart';
/// A collection of checksums for a set of input files.
......@@ -15,30 +18,59 @@ import 'file_system.dart';
/// build step. This assumes that build outputs are strictly a product of the
/// input files.
class Checksum {
Checksum.fromFiles(Set<String> inputPaths) : _checksums = <String, String>{} {
Checksum.fromFiles(BuildMode buildMode, Set<String> inputPaths) {
final Iterable<File> files = inputPaths.map(fs.file);
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
if (missingInputs.isNotEmpty)
throw new ArgumentError('Missing input files:\n' + missingInputs.join('\n'));
_buildMode = buildMode.toString();
_checksums = <String, String>{};
for (File file in files) {
final List<int> bytes = file.readAsBytesSync();
_checksums[file.path] = md5.convert(bytes).toString();
}
}
Checksum.fromJson(String json) : _checksums = JSON.decode(json);
/// Creates a checksum from serialized JSON.
///
/// Throws [ArgumentError] in the following cases:
/// * Version mismatch between the serializing framework and this framework.
/// * BuildMode is unspecified.
/// * File checksum map is unspecified.
Checksum.fromJson(String json) {
final Map<String, dynamic> content = JSON.decode(json);
final String version = content['version'];
if (version != FlutterVersion.instance.frameworkRevision)
throw new ArgumentError('Incompatible checksum version: $version');
_buildMode = content['buildMode'];
if (_buildMode == null || _buildMode.isEmpty)
throw new ArgumentError('BuildMode unspecified in checksum JSON');
_checksums = content['files'];
if (_checksums == null)
throw new ArgumentError('File checksums unspecified in checksum JSON');
}
final Map<String, String> _checksums;
String _buildMode;
Map<String, String> _checksums;
String toJson() => JSON.encode(_checksums);
String toJson() => JSON.encode(<String, dynamic>{
'version': FlutterVersion.instance.frameworkRevision,
'buildMode': _buildMode,
'files': _checksums,
});
@override
bool operator==(dynamic other) {
return other is Checksum &&
_buildMode == other._buildMode &&
_checksums.length == other._checksums.length &&
_checksums.keys.every((String key) => _checksums[key] == other._checksums[key]);
}
@override
int get hashCode => _checksums.hashCode;
int get hashCode => hash2(_buildMode, _checksums);
}
......@@ -40,7 +40,8 @@ Future<int> _createSnapshot({
assert(snapshotPath != null);
assert(depfilePath != null);
assert(packages != null);
final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, null, BuildMode.debug);
final BuildMode buildMode = BuildMode.debug;
final String snapshotterPath = artifacts.getArtifactPath(Artifact.genSnapshot, null, buildMode);
final String vmSnapshotData = artifacts.getArtifactPath(Artifact.vmSnapshotData);
final String isolateSnapshotData = artifacts.getArtifactPath(Artifact.isolateSnapshotData);
......@@ -68,7 +69,7 @@ Future<int> _createSnapshot({
final Set<String> inputPaths = await _readDepfile(depfilePath);
inputPaths.add(snapshotPath);
inputPaths.add(mainPath);
final Checksum newChecksum = new Checksum.fromFiles(inputPaths);
final Checksum newChecksum = new Checksum.fromFiles(buildMode, inputPaths);
if (oldChecksum == newChecksum) {
printTrace('Skipping snapshot build. Checksums match.');
return 0;
......@@ -89,7 +90,7 @@ Future<int> _createSnapshot({
final Set<String> inputPaths = await _readDepfile(depfilePath);
inputPaths.add(snapshotPath);
inputPaths.add(mainPath);
final Checksum checksum = new Checksum.fromFiles(inputPaths);
final Checksum checksum = new Checksum.fromFiles(buildMode, inputPaths);
await checksumFile.writeAsString(checksum.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
......
......@@ -2,15 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/base/build.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import '../src/context.dart';
class MockFlutterVersion extends Mock implements FlutterVersion {}
void main() {
group('Checksum', () {
MockFlutterVersion mockVersion;
const String kVersion = '123456abcdef';
setUp(() {
mockVersion = new MockFlutterVersion();
when(mockVersion.frameworkRevision).thenReturn(kVersion);
});
group('fromFiles', () {
MemoryFileSystem fs;
......@@ -20,47 +35,79 @@ void main() {
testUsingContext('throws if any input file does not exist', () async {
await fs.file('a.dart').create();
expect(() => new Checksum.fromFiles(<String>['a.dart', 'b.dart'].toSet()), throwsA(anything));
expect(() => new Checksum.fromFiles(BuildMode.debug, <String>['a.dart', 'b.dart'].toSet()), throwsA(anything));
}, overrides: <Type, Generator>{ FileSystem: () => fs});
testUsingContext('populates checksums for valid files', () async {
await fs.file('a.dart').writeAsString('This is a');
await fs.file('b.dart').writeAsString('This is b');
final Checksum checksum = new Checksum.fromFiles(<String>['a.dart', 'b.dart'].toSet());
final String json = checksum.toJson();
expect(json, '{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
}, overrides: <Type, Generator>{ FileSystem: () => fs});
final Checksum checksum = new Checksum.fromFiles(BuildMode.debug, <String>['a.dart', 'b.dart'].toSet());
final Map<String, dynamic> json = JSON.decode(checksum.toJson());
expect(json, hasLength(3));
expect(json['version'], mockVersion.frameworkRevision);
expect(json['buildMode'], BuildMode.debug.toString());
expect(json['files'], hasLength(2));
expect(json['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(json['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
}, overrides: <Type, Generator>{
FileSystem: () => fs,
FlutterVersion: () => mockVersion,
});
});
group('fromJson', () {
test('throws if JSON is invalid', () async {
testUsingContext('throws if JSON is invalid', () async {
expect(() => new Checksum.fromJson('<xml></xml>'), throwsA(anything));
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
test('populates checksums for valid JSON', () async {
final String json = '{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}';
testUsingContext('populates checksums for valid JSON', () async {
final String json = '{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}';
final Checksum checksum = new Checksum.fromJson(json);
expect(checksum.toJson(), '{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
final Map<String, dynamic> content = JSON.decode(checksum.toJson());
expect(content, hasLength(3));
expect(content['version'], mockVersion.frameworkRevision);
expect(content['buildMode'], BuildMode.release.toString());
expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('throws ArgumentError for unknown versions', () async {
final String json = '{"version":"bad","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}';
expect(() => new Checksum.fromJson(json), throwsArgumentError);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
group('operator ==', () {
test('reports not equal if checksums do not match', () async {
final Checksum a = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
final Checksum b = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07d"}');
testUsingContext('reports not equal if checksums do not match', () async {
final Checksum a = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}');
final Checksum b = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07d"}}');
expect(a == b, isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
test('reports not equal if keys do not match', () async {
final Checksum a = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
final Checksum b = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","c.dart":"6f144e08b58cd0925328610fad7ac07c"}');
testUsingContext('reports not equal if keys do not match', () async {
final Checksum a = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}');
final Checksum b = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","c.dart":"6f144e08b58cd0925328610fad7ac07c"}}');
expect(a == b, isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
test('reports equal if all checksums match', () async {
final Checksum a = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
final Checksum b = new Checksum.fromJson('{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}');
testUsingContext('reports equal if all checksums match', () async {
final Checksum a = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}');
final Checksum b = new Checksum.fromJson('{"version":"$kVersion","buildMode":"BuildMode.release","files":{"a.dart":"8a21a15fad560b799f6731d436c1b698","b.dart":"6f144e08b58cd0925328610fad7ac07c"}}');
expect(a == b, isTrue);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
});
......
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