// 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<String> 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<String> _paths; final Logger _logger; final FileSystem _fileSystem; Fingerprint buildFingerprint() { final List<String> paths = _getPaths(); return Fingerprint.fromBuildInputs(paths, _fileSystem); } bool doesFingerprintMatch() { try { final File fingerprintFile = _fileSystem.file(fingerprintPath); if (!fingerprintFile.existsSync()) { return false; } final List<String> 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<String> _getPaths() => _paths; } /// A fingerprint that uniquely identifies a set of build input files and /// properties. /// /// See [Fingerprinter]. @immutable class Fingerprint { const Fingerprint._({ Map<String, String>? checksums, }) : _checksums = checksums ?? const <String, String>{}; factory Fingerprint.fromBuildInputs(Iterable<String> inputPaths, FileSystem fileSystem) { final Iterable<File> files = inputPaths.map<File>(fileSystem.file); final Iterable<File> missingInputs = files.where((File file) => !file.existsSync()); if (missingInputs.isNotEmpty) { throw Exception('Missing input files:\n${missingInputs.join('\n')}'); } return Fingerprint._( checksums: <String, String>{ 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<String, dynamic>? content = castStringKeyedMap(json.decode(jsonData)); final Map<String, String>? files = content == null ? null : castStringKeyedMap(content['files'])?.cast<String, String>(); return Fingerprint._( checksums: files ?? <String, String>{}, ); } final Map<String, String> _checksums; String toJson() => json.encode(<String, dynamic>{ 'files': _checksums, }); @override bool operator==(Object other) { return other is Fingerprint && _equalMaps(other._checksums, _checksums); } bool _equalMaps(Map<String, String> a, Map<String, String> b) { return a.length == b.length && a.keys.every((String key) => a[key] == b[key]); } @override int get hashCode => Object.hash(Object.hashAllUnordered(_checksums.keys), Object.hashAllUnordered(_checksums.values)); @override String toString() => '{checksums: $_checksums}'; }