fingerprint.dart 4.27 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// 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';

8
import '../convert.dart' show json;
9
import 'file_system.dart';
10
import 'logger.dart';
11
import 'utils.dart';
12 13 14 15

/// A tool that can be used to compute, compare, and write [Fingerprint]s for a
/// set of input files and associated build settings.
///
16 17
/// This class should only be used in situations where `assemble` is not appropriate,
/// such as checking if Cocoapods should be run.
18 19
class Fingerprinter {
  Fingerprinter({
20 21 22 23
    required this.fingerprintPath,
    required Iterable<String> paths,
    required FileSystem fileSystem,
    required Logger logger,
24
  }) : _paths = paths.toList(),
25 26
       _logger = logger,
       _fileSystem = fileSystem;
27 28 29

  final String fingerprintPath;
  final List<String> _paths;
30 31
  final Logger _logger;
  final FileSystem _fileSystem;
32

33 34
  Fingerprint buildFingerprint() {
    final List<String> paths = _getPaths();
35
    return Fingerprint.fromBuildInputs(paths, _fileSystem);
36 37
  }

38
  bool doesFingerprintMatch() {
39
    try {
40
      final File fingerprintFile = _fileSystem.file(fingerprintPath);
41
      if (!fingerprintFile.existsSync()) {
42
        return false;
43
      }
44

45
      final List<String> paths = _getPaths();
46
      if (!paths.every(_fileSystem.isFileSync)) {
47
        return false;
48
      }
49

50 51
      final Fingerprint oldFingerprint = Fingerprint.fromJson(fingerprintFile.readAsStringSync());
      final Fingerprint newFingerprint = buildFingerprint();
52
      return oldFingerprint == newFingerprint;
53
    } on Exception catch (e) {
54
      // Log exception and continue, fingerprinting is only a performance improvement.
55
      _logger.printTrace('Fingerprint check error: $e');
56 57 58 59
    }
    return false;
  }

60
  void writeFingerprint() {
61
    try {
62
      final Fingerprint fingerprint = buildFingerprint();
63 64 65
      final File fingerprintFile = _fileSystem.file(fingerprintPath);
      fingerprintFile.createSync(recursive: true);
      fingerprintFile.writeAsStringSync(fingerprint.toJson());
66
    } on Exception catch (e) {
67
      // Log exception and continue, fingerprinting is only a performance improvement.
68
      _logger.printTrace('Fingerprint write error: $e');
69 70 71
    }
  }

72
  List<String> _getPaths() => _paths;
73 74 75 76 77 78
}

/// A fingerprint that uniquely identifies a set of build input files and
/// properties.
///
/// See [Fingerprinter].
79
@immutable
80
class Fingerprint {
81
  const Fingerprint._({
82 83
    Map<String, String>? checksums,
  })  : _checksums = checksums ?? const <String, String>{};
84

85 86
  factory Fingerprint.fromBuildInputs(Iterable<String> inputPaths, FileSystem fileSystem) {
    final Iterable<File> files = inputPaths.map<File>(fileSystem.file);
87
    final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
88
    if (missingInputs.isNotEmpty) {
89
      throw Exception('Missing input files:\n${missingInputs.join('\n')}');
90
    }
91 92 93 94 95 96
    return Fingerprint._(
      checksums: <String, String>{
        for (final File file in files)
          file.path: md5.convert(file.readAsBytesSync()).toString(),
      },
    );
97 98 99 100
  }

  /// Creates a Fingerprint from serialized JSON.
  ///
101
  /// Throws [Exception], if there is a version mismatch between the
102
  /// serializing framework and this framework.
103
  factory Fingerprint.fromJson(String jsonData) {
104 105 106 107
    final Map<String, dynamic>? content = castStringKeyedMap(json.decode(jsonData));
    final Map<String, String>? files = content == null
        ? null
        : castStringKeyedMap(content['files'])?.cast<String, String>();
108
    return Fingerprint._(
109
      checksums: files ?? <String, String>{},
110
    );
111 112
  }

113
  final Map<String, String> _checksums;
114 115 116 117 118 119

  String toJson() => json.encode(<String, dynamic>{
    'files': _checksums,
  });

  @override
120
  bool operator==(Object other) {
121
    return other is Fingerprint
122
        && _equalMaps(other._checksums, _checksums);
123 124 125 126 127 128 129 130
  }

  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
131
  int get hashCode => Object.hash(Object.hashAllUnordered(_checksums.keys), Object.hashAllUnordered(_checksums.values));
132 133

  @override
134
  String toString() => '{checksums: $_checksums}';
135
}