mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* Reland: [flutter_tool] Where possible, catch only subtypes of Exception * Add armv7f to getIOSArchForName
196 lines
6.8 KiB
Dart
196 lines
6.8 KiB
Dart
// 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 'package:quiver/core.dart' show hash2;
|
|
|
|
import '../convert.dart' show json;
|
|
import '../globals.dart' as globals;
|
|
import 'file_system.dart';
|
|
import 'utils.dart';
|
|
|
|
typedef FingerprintPathFilter = bool Function(String path);
|
|
|
|
/// 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 can be used during build actions to compute a fingerprint of the
|
|
/// build action inputs and options, and if unchanged from the previous build,
|
|
/// skip the build step. This assumes that build outputs are strictly a product
|
|
/// of the fingerprint inputs.
|
|
class Fingerprinter {
|
|
Fingerprinter({
|
|
@required this.fingerprintPath,
|
|
@required Iterable<String> paths,
|
|
@required Map<String, String> properties,
|
|
Iterable<String> depfilePaths = const <String>[],
|
|
FingerprintPathFilter pathFilter,
|
|
}) : _paths = paths.toList(),
|
|
_properties = Map<String, String>.from(properties),
|
|
_depfilePaths = depfilePaths.toList(),
|
|
_pathFilter = pathFilter,
|
|
assert(fingerprintPath != null),
|
|
assert(paths != null && paths.every((String path) => path != null)),
|
|
assert(properties != null),
|
|
assert(depfilePaths != null && depfilePaths.every((String path) => path != null));
|
|
|
|
final String fingerprintPath;
|
|
final List<String> _paths;
|
|
final Map<String, String> _properties;
|
|
final List<String> _depfilePaths;
|
|
final FingerprintPathFilter _pathFilter;
|
|
|
|
Fingerprint buildFingerprint() {
|
|
final List<String> paths = _getPaths();
|
|
return Fingerprint.fromBuildInputs(_properties, paths);
|
|
}
|
|
|
|
bool doesFingerprintMatch() {
|
|
try {
|
|
final File fingerprintFile = globals.fs.file(fingerprintPath);
|
|
if (!fingerprintFile.existsSync()) {
|
|
return false;
|
|
}
|
|
|
|
if (!_depfilePaths.every(globals.fs.isFileSync)) {
|
|
return false;
|
|
}
|
|
|
|
final List<String> paths = _getPaths();
|
|
if (!paths.every(globals.fs.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.
|
|
globals.printTrace('Fingerprint check error: $e');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void writeFingerprint() {
|
|
try {
|
|
final Fingerprint fingerprint = buildFingerprint();
|
|
globals.fs.file(fingerprintPath).writeAsStringSync(fingerprint.toJson());
|
|
} on Exception catch (e) {
|
|
// Log exception and continue, fingerprinting is only a performance improvement.
|
|
globals.printTrace('Fingerprint write error: $e');
|
|
}
|
|
}
|
|
|
|
List<String> _getPaths() {
|
|
final Set<String> paths = <String>{
|
|
..._paths,
|
|
for (final String depfilePath in _depfilePaths)
|
|
...readDepfile(depfilePath),
|
|
};
|
|
final FingerprintPathFilter filter = _pathFilter ?? (String path) => true;
|
|
return paths.where(filter).toList()..sort();
|
|
}
|
|
}
|
|
|
|
/// A fingerprint that uniquely identifies a set of build input files and
|
|
/// properties.
|
|
///
|
|
/// See [Fingerprinter].
|
|
class Fingerprint {
|
|
Fingerprint.fromBuildInputs(Map<String, String> properties, Iterable<String> inputPaths) {
|
|
final Iterable<File> files = inputPaths.map<File>(globals.fs.file);
|
|
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
|
|
if (missingInputs.isNotEmpty) {
|
|
throw Exception('Missing input files:\n' + missingInputs.join('\n'));
|
|
}
|
|
|
|
_checksums = <String, String>{};
|
|
for (final File file in files) {
|
|
final List<int> bytes = file.readAsBytesSync();
|
|
_checksums[file.path] = md5.convert(bytes).toString();
|
|
}
|
|
_properties = <String, String>{...properties};
|
|
}
|
|
|
|
/// Creates a Fingerprint from serialized JSON.
|
|
///
|
|
/// Throws [Exception], if there is a version mismatch between the
|
|
/// serializing framework and this framework.
|
|
Fingerprint.fromJson(String jsonData) {
|
|
final Map<String, dynamic> content = castStringKeyedMap(json.decode(jsonData));
|
|
|
|
final String version = content['version'] as String;
|
|
if (version != globals.flutterVersion.frameworkRevision) {
|
|
throw Exception('Incompatible fingerprint version: $version');
|
|
}
|
|
_checksums = castStringKeyedMap(content['files'])?.cast<String,String>() ?? <String, String>{};
|
|
_properties = castStringKeyedMap(content['properties'])?.cast<String,String>() ?? <String, String>{};
|
|
}
|
|
|
|
Map<String, String> _checksums;
|
|
Map<String, String> _properties;
|
|
|
|
String toJson() => json.encode(<String, dynamic>{
|
|
'version': globals.flutterVersion.frameworkRevision,
|
|
'properties': _properties,
|
|
'files': _checksums,
|
|
});
|
|
|
|
@override
|
|
bool operator==(Object other) {
|
|
if (identical(other, this)) {
|
|
return true;
|
|
}
|
|
if (other.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
return other is Fingerprint
|
|
&& _equalMaps(other._checksums, _checksums)
|
|
&& _equalMaps(other._properties, _properties);
|
|
}
|
|
|
|
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
|
|
// due to differences in map entry order.
|
|
int get hashCode => hash2(_properties.length, _checksums.length);
|
|
|
|
@override
|
|
String toString() => '{checksums: $_checksums, properties: $_properties}';
|
|
}
|
|
|
|
final RegExp _separatorExpr = RegExp(r'([^\\]) ');
|
|
final RegExp _escapeExpr = RegExp(r'\\(.)');
|
|
|
|
/// Parses a VM snapshot dependency file.
|
|
///
|
|
/// Snapshot dependency files are a single line mapping the output snapshot to a
|
|
/// space-separated list of input files used to generate that output. Spaces and
|
|
/// backslashes are escaped with a backslash. e.g,
|
|
///
|
|
/// outfile : file1.dart fil\\e2.dart fil\ e3.dart
|
|
///
|
|
/// will return a set containing: 'file1.dart', 'fil\e2.dart', 'fil e3.dart'.
|
|
Set<String> readDepfile(String depfilePath) {
|
|
// Depfile format:
|
|
// outfile1 outfile2 : file1.dart file2.dart file3.dart
|
|
final String contents = globals.fs.file(depfilePath).readAsStringSync();
|
|
|
|
final List<String> dependencies = contents.split(': ');
|
|
if (dependencies.length < 2) {
|
|
throw Exception('malformed depfile');
|
|
}
|
|
return dependencies[1]
|
|
.replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
|
|
.split('\n')
|
|
.map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
|
|
.where((String path) => path.isNotEmpty)
|
|
.toSet();
|
|
}
|