fingerprint.dart 4.32 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9
import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';

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

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

  final String fingerprintPath;
  final List<String> _paths;
34 35
  final Logger _logger;
  final FileSystem _fileSystem;
36

37 38
  Fingerprint buildFingerprint() {
    final List<String> paths = _getPaths();
39
    return Fingerprint.fromBuildInputs(paths, _fileSystem);
40 41
  }

42
  bool doesFingerprintMatch() {
43
    try {
44
      final File fingerprintFile = _fileSystem.file(fingerprintPath);
45
      if (!fingerprintFile.existsSync()) {
46
        return false;
47
      }
48

49
      final List<String> paths = _getPaths();
50
      if (!paths.every(_fileSystem.isFileSync)) {
51
        return false;
52
      }
53

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

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

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

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

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

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

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

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

  @override
119
  bool operator==(Object other) {
120
    return other is Fingerprint
121
        && _equalMaps(other._checksums, _checksums);
122 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
  // Ignore map entries here to avoid becoming inconsistent with equals
131 132
  // due to differences in map entry order. This is a really bad hash
  // function and should eventually be deprecated and removed.
133
  int get hashCode => _checksums.length.hashCode;
134 135

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