// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:crypto/crypto.dart' show md5; import 'package:meta/meta.dart'; import '../convert.dart' show json; import 'file_system.dart'; import 'logger.dart'; import 'utils.dart'; /// A tool that can be used to compute, compare, and write [Fingerprint]s for a /// set of input files and associated build settings. /// /// This class should only be used in situations where `assemble` is not appropriate, /// such as checking if Cocoapods should be run. class Fingerprinter { Fingerprinter({ @required this.fingerprintPath, @required Iterable paths, @required FileSystem fileSystem, @required Logger logger, }) : _paths = paths.toList(), assert(fingerprintPath != null), assert(paths != null && paths.every((String path) => path != null)), _logger = logger, _fileSystem = fileSystem; final String fingerprintPath; final List _paths; final Logger _logger; final FileSystem _fileSystem; Fingerprint buildFingerprint() { final List paths = _getPaths(); return Fingerprint.fromBuildInputs(paths, _fileSystem); } bool doesFingerprintMatch() { try { final File fingerprintFile = _fileSystem.file(fingerprintPath); if (!fingerprintFile.existsSync()) { return false; } final List paths = _getPaths(); if (!paths.every(_fileSystem.isFileSync)) { return false; } final Fingerprint oldFingerprint = Fingerprint.fromJson(fingerprintFile.readAsStringSync()); final Fingerprint newFingerprint = buildFingerprint(); return oldFingerprint == newFingerprint; } on Exception catch (e) { // Log exception and continue, fingerprinting is only a performance improvement. _logger.printTrace('Fingerprint check error: $e'); } return false; } void writeFingerprint() { try { final Fingerprint fingerprint = buildFingerprint(); _fileSystem.file(fingerprintPath).writeAsStringSync(fingerprint.toJson()); } on Exception catch (e) { // Log exception and continue, fingerprinting is only a performance improvement. _logger.printTrace('Fingerprint write error: $e'); } } List _getPaths() => _paths; } /// A fingerprint that uniquely identifies a set of build input files and /// properties. /// /// See [Fingerprinter]. @immutable class Fingerprint { const Fingerprint._({ Map checksums, }) : _checksums = checksums; factory Fingerprint.fromBuildInputs(Iterable inputPaths, FileSystem fileSystem) { final Iterable files = inputPaths.map(fileSystem.file); final Iterable missingInputs = files.where((File file) => !file.existsSync()); if (missingInputs.isNotEmpty) { throw Exception('Missing input files:\n' + missingInputs.join('\n')); } return Fingerprint._( checksums: { for (final File file in files) file.path: md5.convert(file.readAsBytesSync()).toString(), }, ); } /// Creates a Fingerprint from serialized JSON. /// /// Throws [Exception], if there is a version mismatch between the /// serializing framework and this framework. factory Fingerprint.fromJson(String jsonData) { final Map content = castStringKeyedMap(json.decode(jsonData)); return Fingerprint._( checksums: castStringKeyedMap(content['files'])?.cast() ?? {}, ); } final Map _checksums; String toJson() => json.encode({ 'files': _checksums, }); @override bool operator==(Object other) { return other is Fingerprint && _equalMaps(other._checksums, _checksums); } bool _equalMaps(Map a, Map b) { return a.length == b.length && a.keys.every((String key) => a[key] == b[key]); } @override // Ignore map entries here to avoid becoming inconsistent with equals // due to differences in map entry order. This is a really bad hash // function and should eventually be deprecated and removed. int get hashCode => _checksums.length.hashCode; @override String toString() => '{checksums: $_checksums}'; }