blob: 131fb1423235b3c69a90e7938ae55b60665041f3 [file] [log] [blame]
Ian Hickson449f4a62019-11-27 15:04:02 -08001// Copyright 2014 The Flutter Authors. All rights reserved.
Alexander Aprelev391e91c2018-08-30 07:30:25 -07002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Jonah Williamsf6f59c52021-04-16 14:29:32 -07005import 'dart:async';
Alexander Aprelev391e91c2018-08-30 07:30:25 -07006import 'dart:convert';
Ian Hicksoneae05c72019-11-14 13:19:40 -08007import 'dart:core' hide print;
Ian Hicksoneae05c72019-11-14 13:19:40 -08008import 'dart:io' hide exit;
Dan Field24f39d42020-01-02 11:47:28 -08009import 'dart:typed_data';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070010
Darren Austin807ca682021-08-25 14:56:03 -070011import 'package:analyzer/dart/analysis/features.dart';
12import 'package:analyzer/dart/analysis/results.dart';
13import 'package:analyzer/dart/analysis/utilities.dart';
14import 'package:analyzer/dart/ast/ast.dart';
15import 'package:analyzer/dart/ast/visitor.dart';
Dan Field24f39d42020-01-02 11:47:28 -080016import 'package:crypto/crypto.dart';
Francisco Magdaleno04ea3182020-01-02 09:25:59 -080017import 'package:meta/meta.dart';
Dan Field24f39d42020-01-02 11:47:28 -080018import 'package:path/path.dart' as path;
Alexander Aprelev391e91c2018-08-30 07:30:25 -070019
Jonah Williamsf6f59c52021-04-16 14:29:32 -070020import 'allowlist.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070021import 'run_command.dart';
Dan Field24f39d42020-01-02 11:47:28 -080022import 'utils.dart';
Alexander Aprelev391e91c2018-08-30 07:30:25 -070023
Alexander Aprelev391e91c2018-08-30 07:30:25 -070024final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script))));
25final String flutter = path.join(flutterRoot, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
Kate Lovett616f9bc2021-08-30 17:41:02 -050026final String flutterPackages = path.join(flutterRoot, 'packages');
Dan Fieldab0a3352021-12-12 13:05:03 -080027final String flutterExamples = path.join(flutterRoot, 'examples');
Paul Berryf3a389c2021-12-15 13:39:16 -080028
29/// The path to the `dart` executable; set at the top of `main`
30late final String dart;
31
32/// The path to the `pub` executable; set at the top of `main`
33late final String pub;
34
Alexander Aprelev391e91c2018-08-30 07:30:25 -070035final String pubCache = path.join(flutterRoot, '.pub-cache');
36
37/// When you call this, you can pass additional arguments to pass custom
38/// arguments to flutter analyze. For example, you might want to call this
39/// script with the parameter --dart-sdk to use custom dart sdk.
40///
41/// For example:
42/// bin/cache/dart-sdk/bin/dart dev/bots/analyze.dart --dart-sdk=/tmp/dart-sdk
Ian Hicksoneae05c72019-11-14 13:19:40 -080043Future<void> main(List<String> arguments) async {
Paul Berryf3a389c2021-12-15 13:39:16 -080044 final String dartSdk = path.join(
45 Directory.current.absolute.path,
46 _getDartSdkFromArguments(arguments) ?? path.join(flutterRoot, 'bin', 'cache', 'dart-sdk'),
47 );
48 dart = path.join(dartSdk, 'bin', Platform.isWindows ? 'dart.exe' : 'dart');
49 pub = path.join(dartSdk, 'bin', Platform.isWindows ? 'pub.bat' : 'pub');
Ian Hickson449f4a62019-11-27 15:04:02 -080050 print('$clock STARTING ANALYSIS');
Ian Hicksoneae05c72019-11-14 13:19:40 -080051 try {
52 await run(arguments);
53 } on ExitException catch (error) {
54 error.apply();
55 }
Dan Field24f39d42020-01-02 11:47:28 -080056 print('$clock ${bold}Analysis successful.$reset');
Ian Hicksoneae05c72019-11-14 13:19:40 -080057}
58
Paul Berryf3a389c2021-12-15 13:39:16 -080059/// Scans [arguments] for an argument of the form `--dart-sdk` or
60/// `--dart-sdk=...` and returns the configured SDK, if any.
61String? _getDartSdkFromArguments(List<String> arguments) {
62 String? result;
63 for (int i = 0; i < arguments.length; i += 1) {
64 if (arguments[i] == '--dart-sdk') {
65 if (result != null) {
66 exitWithError(<String>['The --dart-sdk argument must not be used more than once.']);
67 }
68 if (i + 1 < arguments.length) {
69 result = arguments[i + 1];
70 } else {
71 exitWithError(<String>['--dart-sdk must be followed by a path.']);
72 }
73 }
74 if (arguments[i].startsWith('--dart-sdk=')) {
75 if (result != null) {
76 exitWithError(<String>['The --dart-sdk argument must not be used more than once.']);
77 }
78 result = arguments[i].substring('--dart-sdk='.length);
79 }
80 }
81 return result;
82}
83
Ian Hicksoneae05c72019-11-14 13:19:40 -080084Future<void> run(List<String> arguments) async {
Ian Hickson58939b72019-02-12 12:29:36 -080085 bool assertsEnabled = false;
86 assert(() { assertsEnabled = true; return true; }());
87 if (!assertsEnabled) {
Dan Field24f39d42020-01-02 11:47:28 -080088 exitWithError(<String>['The analyze.dart script must be run with --enable-asserts.']);
Ian Hickson58939b72019-02-12 12:29:36 -080089 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -070090
Dan Fieldab0a3352021-12-12 13:05:03 -080091 print('$clock No sync*/async*');
92 await verifyNoSyncAsyncStar(flutterPackages);
93 await verifyNoSyncAsyncStar(flutterExamples, minimumMatches: 200);
94
Ian Hicksonbde9f112021-11-19 13:13:05 -080095 print('$clock No runtimeType in toString...');
Dan Field3e634112020-01-14 16:43:01 -080096 await verifyNoRuntimeTypeInToString(flutterRoot);
97
Ian Hicksonbde9f112021-11-19 13:13:05 -080098 print('$clock Debug mode instead of checked mode...');
Jonah Williams0d3b44e2021-08-03 13:25:05 -070099 await verifyNoCheckedMode(flutterRoot);
100
Ian Hicksonbde9f112021-11-19 13:13:05 -0800101 print('$clock Links for creating GitHub issues');
102 await verifyIssueLinks(flutterRoot);
103
Dan Field24f39d42020-01-02 11:47:28 -0800104 print('$clock Unexpected binaries...');
105 await verifyNoBinaries(flutterRoot);
106
107 print('$clock Trailing spaces...');
108 await verifyNoTrailingSpaces(flutterRoot); // assumes no unexpected binaries, so should be after verifyNoBinaries
109
Ian Hickson449f4a62019-11-27 15:04:02 -0800110 print('$clock Deprecations...');
Ian Hickson62e4ab82019-11-15 19:21:53 -0800111 await verifyDeprecations(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -0800112
Kate Lovett616f9bc2021-08-30 17:41:02 -0500113 print('$clock Goldens...');
114 await verifyGoldenTags(flutterPackages);
115
Darren Austin41ff30c2021-08-10 23:03:48 -0700116 print('$clock Skip test comments...');
117 await verifySkipTestComments(flutterRoot);
118
Ian Hickson449f4a62019-11-27 15:04:02 -0800119 print('$clock Licenses...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800120 await verifyNoMissingLicense(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -0800121
122 print('$clock Test imports...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800123 await verifyNoTestImports(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -0800124
Ian Hickson449f4a62019-11-27 15:04:02 -0800125 print('$clock Bad imports (framework)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800126 await verifyNoBadImportsInFlutter(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -0800127
128 print('$clock Bad imports (tools)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800129 await verifyNoBadImportsInFlutterTools(flutterRoot);
Ian Hickson449f4a62019-11-27 15:04:02 -0800130
131 print('$clock Internationalization...');
Greg Spencer57224f82021-07-26 15:31:37 -0700132 await verifyInternationalizations(flutterRoot, dart);
Ian Hickson449f4a62019-11-27 15:04:02 -0800133
Dan Field9c0bd182021-06-23 13:08:51 -0700134 print('$clock Integration test timeouts...');
135 await verifyIntegrationTestTimeouts(flutterRoot);
136
Jonah Williamsfda40942021-12-14 10:51:30 -0800137 print('$clock null initialized debug fields...');
138 await verifyNullInitializedDebugExpensiveFields(flutterRoot);
139
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700140 // Ensure that all package dependencies are in sync.
Ian Hickson449f4a62019-11-27 15:04:02 -0800141 print('$clock Package dependencies...');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700142 await runCommand(flutter, <String>['update-packages', '--verify-only'],
143 workingDirectory: flutterRoot,
144 );
145
Jonah Williamsf6f59c52021-04-16 14:29:32 -0700146 /// Ensure that no new dependencies have been accidentally
147 /// added to core packages.
148 print('$clock Package Allowlist...');
149 await _checkConsumerDependencies();
150
Ian Hicksoneae05c72019-11-14 13:19:40 -0800151 // Analyze all the Dart code in the repo.
Ian Hickson449f4a62019-11-27 15:04:02 -0800152 print('$clock Dart analysis...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800153 await _runFlutterAnalyze(flutterRoot, options: <String>[
154 '--flutter-repo',
155 ...arguments,
156 ]);
157
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700158 // Try with the --watch analyzer, to make sure it returns success also.
159 // The --benchmark argument exits after one run.
Ian Hickson449f4a62019-11-27 15:04:02 -0800160 print('$clock Dart analysis (with --watch)...');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800161 await _runFlutterAnalyze(flutterRoot, options: <String>[
162 '--flutter-repo',
163 '--watch',
164 '--benchmark',
165 ...arguments,
166 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700167
Daco Harkes67fdf932021-09-27 21:38:07 +0200168 // Analyze all the sample code in the repo.
Dan Field24f39d42020-01-02 11:47:28 -0800169 print('$clock Sample code...');
170 await runCommand(dart,
Daco Harkes67fdf932021-09-27 21:38:07 +0200171 <String>[path.join(flutterRoot, 'dev', 'bots', 'analyze_sample_code.dart'), '--verbose'],
Dan Field24f39d42020-01-02 11:47:28 -0800172 workingDirectory: flutterRoot,
173 );
174
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700175 // Try analysis against a big version of the gallery; generate into a temporary directory.
Ian Hickson449f4a62019-11-27 15:04:02 -0800176 print('$clock Dart analysis (mega gallery)...');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700177 final Directory outDir = Directory.systemTemp.createTempSync('flutter_mega_gallery.');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700178 try {
179 await runCommand(dart,
180 <String>[
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700181 path.join(flutterRoot, 'dev', 'tools', 'mega_gallery.dart'),
182 '--out',
183 outDir.path,
184 ],
185 workingDirectory: flutterRoot,
186 );
Ian Hickson449f4a62019-11-27 15:04:02 -0800187 await _runFlutterAnalyze(outDir.path, options: <String>[
188 '--watch',
189 '--benchmark',
190 ...arguments,
191 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700192 } finally {
193 outDir.deleteSync(recursive: true);
194 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700195}
196
Ian Hickson62e4ab82019-11-15 19:21:53 -0800197
198// TESTS
199
Dan Fieldab0a3352021-12-12 13:05:03 -0800200Future<void> verifyNoSyncAsyncStar(String workingDirectory, {int minimumMatches = 2000 }) async {
201 final RegExp syncPattern = RegExp(r'\s*?a?sync\*\s*?{');
Dan Fieldbf602912021-12-14 19:00:52 -0800202 final RegExp ignorePattern = RegExp(r'^\s*?// The following uses a?sync\* because:? ');
203 final RegExp commentPattern = RegExp(r'^\s*?//');
Dan Fieldab0a3352021-12-12 13:05:03 -0800204 final List<String> errors = <String>[];
205 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
206 if (file.path.contains('test')) {
207 continue;
208 }
209 final List<String> lines = file.readAsLinesSync();
210 for (int index = 0; index < lines.length; index += 1) {
211 final String line = lines[index];
212 if (line.startsWith(commentPattern)) {
213 continue;
214 }
Dan Fieldbf602912021-12-14 19:00:52 -0800215 if (line.contains(syncPattern)) {
216 int lookBehindIndex = index - 1;
217 bool hasExplanation = false;
218 while (lookBehindIndex >= 0 && lines[lookBehindIndex].startsWith(commentPattern)) {
219 if (lines[lookBehindIndex].startsWith(ignorePattern)) {
220 hasExplanation = true;
221 break;
222 }
223 lookBehindIndex -= 1;
224 }
225 if (!hasExplanation) {
226 errors.add('${file.path}:$index: sync*/async* without an explanation.');
227 }
Dan Fieldab0a3352021-12-12 13:05:03 -0800228 }
229 }
230 }
231 if (errors.isNotEmpty) {
232 exitWithError(<String>[
233 '${bold}Do not use sync*/async* methods. See https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#avoid-syncasync for details.$reset',
234 ...errors,
235 ]);
236 }
237}
238
Kate Lovett616f9bc2021-08-30 17:41:02 -0500239final RegExp _findGoldenTestPattern = RegExp(r'matchesGoldenFile\(');
240final RegExp _findGoldenDefinitionPattern = RegExp(r'matchesGoldenFile\(Object');
241final RegExp _leadingComment = RegExp(r'\/\/');
242final RegExp _goldenTagPattern1 = RegExp(r'@Tags\(');
243final RegExp _goldenTagPattern2 = RegExp(r"'reduced-test-set'");
244
245/// Only golden file tests in the flutter package are subject to reduced testing,
246/// for example, invocations in flutter_test to validate comparator
247/// functionality do not require tagging.
248const String _ignoreGoldenTag = '// flutter_ignore: golden_tag (see analyze.dart)';
249const String _ignoreGoldenTagForFile = '// flutter_ignore_for_file: golden_tag (see analyze.dart)';
250
251Future<void> verifyGoldenTags(String workingDirectory, { int minimumMatches = 2000 }) async {
252 final List<String> errors = <String>[];
253 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
254 bool needsTag = false;
255 bool hasTagNotation = false;
256 bool hasReducedTag = false;
257 bool ignoreForFile = false;
258 final List<String> lines = file.readAsLinesSync();
259 for (final String line in lines) {
260 if (line.contains(_goldenTagPattern1)) {
261 hasTagNotation = true;
262 }
263 if (line.contains(_goldenTagPattern2)) {
264 hasReducedTag = true;
265 }
266 if (line.contains(_findGoldenTestPattern)
267 && !line.contains(_findGoldenDefinitionPattern)
268 && !line.contains(_leadingComment)
269 && !line.contains(_ignoreGoldenTag)) {
270 needsTag = true;
271 }
272 if (line.contains(_ignoreGoldenTagForFile)) {
273 ignoreForFile = true;
274 }
275 // If the file is being ignored or a reduced test tag is already accounted
276 // for, skip parsing the rest of the lines for golden file tests.
277 if (ignoreForFile || (hasTagNotation && hasReducedTag)) {
278 break;
279 }
280 }
281 // If a reduced test tag is already accounted for, move on to the next file.
282 if (ignoreForFile || (hasTagNotation && hasReducedTag)) {
283 continue;
284 }
285 // If there are golden file tests, ensure they are tagged for all reduced
286 // test environments.
287 if (needsTag) {
288 if (!hasTagNotation) {
289 errors.add('${file.path}: Files containing golden tests must be tagged using '
290 '`@Tags(...)` at the top of the file before import statements.');
291 } else if (!hasReducedTag) {
292 errors.add('${file.path}: Files containing golden tests must be tagged with '
293 "'reduced-test-set'.");
294 }
295 }
296 }
297 if (errors.isNotEmpty) {
298 exitWithError(<String>[
299 ...errors,
300 '${bold}See: https://github.com/flutter/flutter/wiki/Writing-a-golden-file-test-for-package:flutter$reset',
301 ]);
302 }
303}
304
Ian Hickson62e4ab82019-11-15 19:21:53 -0800305final RegExp _findDeprecationPattern = RegExp(r'@[Dd]eprecated');
Michael Goderbauer197b4402021-03-19 15:33:05 -0700306final RegExp _deprecationPattern1 = RegExp(r'^( *)@Deprecated\($'); // flutter_ignore: deprecation_syntax (see analyze.dart)
Ian Hickson62e4ab82019-11-15 19:21:53 -0800307final RegExp _deprecationPattern2 = RegExp(r"^ *'(.+) '$");
Alexandre Ardhuina6832d42021-03-30 06:29:02 +0200308final RegExp _deprecationPattern3 = RegExp(r"^ *'This feature was deprecated after v([0-9]+)\.([0-9]+)\.([0-9]+)(\-[0-9]+\.[0-9]+\.pre)?\.',?$");
Ian Hickson62e4ab82019-11-15 19:21:53 -0800309final RegExp _deprecationPattern4 = RegExp(r'^ *\)$');
310
311/// Some deprecation notices are special, for example they're used to annotate members that
312/// will never go away and were never allowed but which we are trying to show messages for.
313/// (One example would be a library that intentionally conflicts with a member in another
314/// library to indicate that it is incompatible with that other library. Another would be
315/// the regexp just above...)
Michael Goderbauer197b4402021-03-19 15:33:05 -0700316const String _ignoreDeprecation = ' // flutter_ignore: deprecation_syntax (see analyze.dart)';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800317
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700318/// Some deprecation notices are exempt for historical reasons. They must have an issue listed.
Michael Goderbauer197b4402021-03-19 15:33:05 -0700319final RegExp _legacyDeprecation = RegExp(r' // flutter_ignore: deprecation_syntax, https://github.com/flutter/flutter/issues/[0-9]+$');
Ian Hickson62e4ab82019-11-15 19:21:53 -0800320
Dan Field24f39d42020-01-02 11:47:28 -0800321Future<void> verifyDeprecations(String workingDirectory, { int minimumMatches = 2000 }) async {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800322 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800323 await for (final File file in _allFiles(workingDirectory, 'dart', minimumMatches: minimumMatches)) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800324 int lineNumber = 0;
325 final List<String> lines = file.readAsLinesSync();
326 final List<int> linesWithDeprecations = <int>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100327 for (final String line in lines) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800328 if (line.contains(_findDeprecationPattern) &&
329 !line.endsWith(_ignoreDeprecation) &&
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700330 !line.contains(_legacyDeprecation)) {
Ian Hickson62e4ab82019-11-15 19:21:53 -0800331 linesWithDeprecations.add(lineNumber);
332 }
333 lineNumber += 1;
334 }
335 for (int lineNumber in linesWithDeprecations) {
336 try {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700337 final Match? match1 = _deprecationPattern1.firstMatch(lines[lineNumber]);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800338 if (match1 == null)
339 throw 'Deprecation notice does not match required pattern.';
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700340 final String indent = match1[1]!;
Ian Hickson62e4ab82019-11-15 19:21:53 -0800341 lineNumber += 1;
342 if (lineNumber >= lines.length)
343 throw 'Incomplete deprecation notice.';
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700344 Match? match3;
345 String? message;
Ian Hickson62e4ab82019-11-15 19:21:53 -0800346 do {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700347 final Match? match2 = _deprecationPattern2.firstMatch(lines[lineNumber]);
Tong Mu7abee112021-03-09 13:21:45 -0800348 if (match2 == null) {
349 String possibleReason = '';
350 if (lines[lineNumber].trimLeft().startsWith('"')) {
351 possibleReason = ' You might have used double quotes (") for the string instead of single quotes (\').';
352 }
353 throw 'Deprecation notice does not match required pattern.$possibleReason';
354 }
Ian Hickson62e4ab82019-11-15 19:21:53 -0800355 if (!lines[lineNumber].startsWith("$indent '"))
356 throw 'Unexpected deprecation notice indent.';
357 if (message == null) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700358 final String firstChar = String.fromCharCode(match2[1]!.runes.first);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800359 if (firstChar.toUpperCase() != firstChar)
Christopher Fujino5cfb16b2020-06-19 12:03:38 -0700360 throw 'Deprecation notice should be a grammatically correct sentence and start with a capital letter; see style guide: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800361 }
362 message = match2[1];
363 lineNumber += 1;
364 if (lineNumber >= lines.length)
365 throw 'Incomplete deprecation notice.';
366 match3 = _deprecationPattern3.firstMatch(lines[lineNumber]);
367 } while (match3 == null);
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700368 final int v1 = int.parse(match3[1]!);
369 final int v2 = int.parse(match3[2]!);
Christopher Fujino5cfb16b2020-06-19 12:03:38 -0700370 final bool hasV4 = match3[4] != null;
371 if (v1 > 1 || (v1 == 1 && v2 >= 20)) {
372 if (!hasV4)
373 throw 'Deprecation notice does not accurately indicate a dev branch version number; please see https://flutter.dev/docs/development/tools/sdk/releases to find the latest dev build version number.';
374 }
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700375 if (!message!.endsWith('.') && !message.endsWith('!') && !message.endsWith('?'))
Ian Hickson62e4ab82019-11-15 19:21:53 -0800376 throw 'Deprecation notice should be a grammatically correct sentence and end with a period.';
377 if (!lines[lineNumber].startsWith("$indent '"))
378 throw 'Unexpected deprecation notice indent.';
Ian Hickson62e4ab82019-11-15 19:21:53 -0800379 lineNumber += 1;
380 if (lineNumber >= lines.length)
381 throw 'Incomplete deprecation notice.';
382 if (!lines[lineNumber].contains(_deprecationPattern4))
383 throw 'End of deprecation notice does not match required pattern.';
384 if (!lines[lineNumber].startsWith('$indent)'))
385 throw 'Unexpected deprecation notice indent.';
386 } catch (error) {
387 errors.add('${file.path}:${lineNumber + 1}: $error');
388 }
389 }
390 }
391 // Fail if any errors
392 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800393 exitWithError(<String>[
394 ...errors,
395 '${bold}See: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes$reset',
396 ]);
Ian Hickson62e4ab82019-11-15 19:21:53 -0800397 }
398}
399
Ian Hickson449f4a62019-11-27 15:04:02 -0800400String _generateLicense(String prefix) {
401 assert(prefix != null);
402 return '${prefix}Copyright 2014 The Flutter Authors. All rights reserved.\n'
403 '${prefix}Use of this source code is governed by a BSD-style license that can be\n'
404 '${prefix}found in the LICENSE file.';
405}
406
Dan Field24f39d42020-01-02 11:47:28 -0800407Future<void> verifyNoMissingLicense(String workingDirectory, { bool checkMinimums = true }) async {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700408 final int? overrideMinimumMatches = checkMinimums ? null : 0;
Zachary Anderson366648a2021-11-09 08:44:56 -0800409 await _verifyNoMissingLicenseForExtension(workingDirectory, 'dart', overrideMinimumMatches ?? 2000, _generateLicense('// '));
410 await _verifyNoMissingLicenseForExtension(workingDirectory, 'java', overrideMinimumMatches ?? 39, _generateLicense('// '));
411 await _verifyNoMissingLicenseForExtension(workingDirectory, 'h', overrideMinimumMatches ?? 30, _generateLicense('// '));
412 await _verifyNoMissingLicenseForExtension(workingDirectory, 'm', overrideMinimumMatches ?? 30, _generateLicense('// '));
413 await _verifyNoMissingLicenseForExtension(workingDirectory, 'swift', overrideMinimumMatches ?? 10, _generateLicense('// '));
414 await _verifyNoMissingLicenseForExtension(workingDirectory, 'gradle', overrideMinimumMatches ?? 80, _generateLicense('// '));
415 await _verifyNoMissingLicenseForExtension(workingDirectory, 'gn', overrideMinimumMatches ?? 0, _generateLicense('# '));
416 await _verifyNoMissingLicenseForExtension(workingDirectory, 'sh', overrideMinimumMatches ?? 1, '#!/usr/bin/env bash\n${_generateLicense('# ')}');
417 await _verifyNoMissingLicenseForExtension(workingDirectory, 'bat', overrideMinimumMatches ?? 1, '@ECHO off\n${_generateLicense('REM ')}');
418 await _verifyNoMissingLicenseForExtension(workingDirectory, 'ps1', overrideMinimumMatches ?? 1, _generateLicense('# '));
419 await _verifyNoMissingLicenseForExtension(workingDirectory, 'html', overrideMinimumMatches ?? 1, '<!DOCTYPE HTML>\n<!-- ${_generateLicense('')} -->', trailingBlank: false);
420 await _verifyNoMissingLicenseForExtension(workingDirectory, 'xml', overrideMinimumMatches ?? 1, '<!-- ${_generateLicense('')} -->');
Ian Hickson449f4a62019-11-27 15:04:02 -0800421}
422
Zachary Anderson366648a2021-11-09 08:44:56 -0800423Future<void> _verifyNoMissingLicenseForExtension(String workingDirectory, String extension, int minimumMatches, String license, { bool trailingBlank = true }) async {
Ian Hickson449f4a62019-11-27 15:04:02 -0800424 assert(!license.endsWith('\n'));
Zachary Anderson366648a2021-11-09 08:44:56 -0800425 final String licensePattern = '$license\n${trailingBlank ? '\n' : ''}';
Ian Hicksoneae05c72019-11-14 13:19:40 -0800426 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800427 await for (final File file in _allFiles(workingDirectory, extension, minimumMatches: minimumMatches)) {
Ian Hickson449f4a62019-11-27 15:04:02 -0800428 final String contents = file.readAsStringSync().replaceAll('\r\n', '\n');
429 if (contents.isEmpty)
430 continue; // let's not go down the /bin/true rabbit hole
Zachary Anderson366648a2021-11-09 08:44:56 -0800431 if (!contents.startsWith(licensePattern))
Ian Hicksoneae05c72019-11-14 13:19:40 -0800432 errors.add(file.path);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700433 }
Ian Hicksoneae05c72019-11-14 13:19:40 -0800434 // Fail if any errors
435 if (errors.isNotEmpty) {
Zachary Anderson366648a2021-11-09 08:44:56 -0800436 final String s = errors.length == 1 ? ' does' : 's do';
437 exitWithError(<String>[
438 '${bold}The following ${errors.length} file$s not have the right license header:$reset',
439 ...errors,
Dan Field24f39d42020-01-02 11:47:28 -0800440 'The expected license header is:',
441 license,
442 if (trailingBlank) '...followed by a blank line.',
Zachary Anderson366648a2021-11-09 08:44:56 -0800443 ]);
xsterc7a09a42019-04-24 12:40:17 -0700444 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700445}
446
Darren Austin807ca682021-08-25 14:56:03 -0700447class _TestSkip {
448 _TestSkip(this.line, this.content);
449
450 final int line;
451 final String content;
452}
453
454Iterable<_TestSkip> _getTestSkips(File file) {
455 final ParseStringResult parseResult = parseFile(
456 featureSet: FeatureSet.latestLanguageVersion(),
457 path: file.absolute.path,
458 );
459 final _TestSkipLinesVisitor<CompilationUnit> visitor = _TestSkipLinesVisitor<CompilationUnit>(parseResult);
460 visitor.visitCompilationUnit(parseResult.unit);
461 return visitor.skips;
462}
463
464class _TestSkipLinesVisitor<T> extends RecursiveAstVisitor<T> {
465 _TestSkipLinesVisitor(this.parseResult) : skips = <_TestSkip>{};
466
467 final ParseStringResult parseResult;
468 final Set<_TestSkip> skips;
469
470 static bool isTestMethod(String name) {
471 return name.startsWith('test') || name == 'group' || name == 'expect';
472 }
473
474 @override
475 T? visitMethodInvocation(MethodInvocation node) {
476 if (isTestMethod(node.methodName.toString())) {
477 for (final Expression argument in node.argumentList.arguments) {
478 if (argument is NamedExpression && argument.name.label.name == 'skip') {
479 final int lineNumber = parseResult.lineInfo.getLocation(argument.beginToken.charOffset).lineNumber;
480 final String content = parseResult.content.substring(parseResult.lineInfo.getOffsetOfLine(lineNumber - 1),
481 parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1);
482 skips.add(_TestSkip(lineNumber, content));
483 }
484 }
485 }
486 return super.visitMethodInvocation(node);
487 }
488}
489
490final RegExp _skipTestCommentPattern = RegExp(r'//(.*)$');
Darren Austin41ff30c2021-08-10 23:03:48 -0700491const Pattern _skipTestIntentionalPattern = '[intended]';
492final Pattern _skipTestTrackingBugPattern = RegExp(r'https+?://github.com/.*/issues/[0-9]+');
493
494Future<void> verifySkipTestComments(String workingDirectory) async {
495 final List<String> errors = <String>[];
Darren Austin807ca682021-08-25 14:56:03 -0700496 final Stream<File> testFiles =_allFiles(workingDirectory, 'dart', minimumMatches: 1500)
Darren Austin41ff30c2021-08-10 23:03:48 -0700497 .where((File f) => f.path.endsWith('_test.dart'));
498
499 await for (final File file in testFiles) {
Darren Austin807ca682021-08-25 14:56:03 -0700500 for (final _TestSkip skip in _getTestSkips(file)) {
501 final Match? match = _skipTestCommentPattern.firstMatch(skip.content);
Darren Austin41ff30c2021-08-10 23:03:48 -0700502 final String? skipComment = match?.group(1);
Darren Austin807ca682021-08-25 14:56:03 -0700503 if (skipComment == null ||
504 !(skipComment.contains(_skipTestIntentionalPattern) ||
505 skipComment.contains(_skipTestTrackingBugPattern))) {
506 errors.add('${file.path}:${skip.line}: skip test without a justification comment.');
Darren Austin41ff30c2021-08-10 23:03:48 -0700507 }
508 }
509 }
510
511 // Fail if any errors
512 if (errors.isNotEmpty) {
513 exitWithError(<String>[
514 ...errors,
515 '\n${bold}See: https://github.com/flutter/flutter/wiki/Tree-hygiene#skipped-tests$reset',
516 ]);
517 }
518}
519
Ian Hicksoneae05c72019-11-14 13:19:40 -0800520final RegExp _testImportPattern = RegExp(r'''import (['"])([^'"]+_test\.dart)\1''');
521const Set<String> _exemptTestImports = <String>{
522 'package:flutter_test/flutter_test.dart',
523 'hit_test.dart',
524 'package:test_api/src/backend/live_test.dart',
Dan Field76784652020-11-05 17:28:47 -0800525 'package:integration_test/integration_test.dart',
Ian Hicksoneae05c72019-11-14 13:19:40 -0800526};
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700527
Ian Hicksoneae05c72019-11-14 13:19:40 -0800528Future<void> verifyNoTestImports(String workingDirectory) async {
529 final List<String> errors = <String>[];
530 assert("// foo\nimport 'binding_test.dart' as binding;\n'".contains(_testImportPattern));
Yuqian Lifb552ed2020-11-07 05:19:02 -0800531 final List<File> dartFiles = await _allFiles(path.join(workingDirectory, 'packages'), 'dart', minimumMatches: 1500).toList();
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100532 for (final File file in dartFiles) {
533 for (final String line in file.readAsLinesSync()) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700534 final Match? match = _testImportPattern.firstMatch(line);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800535 if (match != null && !_exemptTestImports.contains(match.group(2)))
536 errors.add(file.path);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700537 }
538 }
Ian Hicksoneae05c72019-11-14 13:19:40 -0800539 // Fail if any errors
540 if (errors.isNotEmpty) {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800541 final String s = errors.length == 1 ? '' : 's';
Dan Field24f39d42020-01-02 11:47:28 -0800542 exitWithError(<String>[
543 '${bold}The following file$s import a test directly. Test utilities should be in their own file.$reset',
544 ...errors,
545 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700546 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700547}
548
Ian Hicksoneae05c72019-11-14 13:19:40 -0800549Future<void> verifyNoBadImportsInFlutter(String workingDirectory) async {
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700550 final List<String> errors = <String>[];
551 final String libPath = path.join(workingDirectory, 'packages', 'flutter', 'lib');
552 final String srcPath = path.join(workingDirectory, 'packages', 'flutter', 'lib', 'src');
553 // Verify there's one libPath/*.dart for each srcPath/*/.
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200554 final List<String> packages = Directory(libPath).listSync()
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700555 .where((FileSystemEntity entity) => entity is File && path.extension(entity.path) == '.dart')
556 .map<String>((FileSystemEntity entity) => path.basenameWithoutExtension(entity.path))
557 .toList()..sort();
Alexandre Ardhuind927c932018-09-12 08:29:29 +0200558 final List<String> directories = Directory(srcPath).listSync()
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700559 .whereType<Directory>()
560 .map<String>((Directory entity) => path.basename(entity.path))
561 .toList()..sort();
Ian Hicksoneae05c72019-11-14 13:19:40 -0800562 if (!_listEquals<String>(packages, directories)) {
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200563 errors.add(<String>[
564 'flutter/lib/*.dart does not match flutter/lib/src/*/:',
565 'These are the exported packages:',
566 ...packages.map<String>((String path) => ' lib/$path.dart'),
567 'These are the directories:',
568 ...directories.map<String>((String path) => ' lib/src/$path/')
569 ].join('\n'));
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700570 }
571 // Verify that the imports are well-ordered.
572 final Map<String, Set<String>> dependencyMap = <String, Set<String>>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100573 for (final String directory in directories) {
Yuqian Lifb552ed2020-11-07 05:19:02 -0800574 dependencyMap[directory] = await _findFlutterDependencies(path.join(srcPath, directory), errors, checkForMeta: directory != 'foundation');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700575 }
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700576 assert(dependencyMap['material']!.contains('widgets') &&
577 dependencyMap['widgets']!.contains('rendering') &&
578 dependencyMap['rendering']!.contains('painting')); // to make sure we're convinced _findFlutterDependencies is finding some
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100579 for (final String package in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700580 if (dependencyMap[package]!.contains(package)) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700581 errors.add(
582 'One of the files in the $yellow$package$reset package imports that package recursively.'
583 );
584 }
585 }
LongCatIsLooongd291de02020-01-09 10:25:58 -0800586
587 for (final String key in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700588 for (final String dependency in dependencyMap[key]!) {
LongCatIsLooongd291de02020-01-09 10:25:58 -0800589 if (dependencyMap[dependency] != null)
590 continue;
591 // Sanity check before performing _deepSearch, to ensure there's no rogue
592 // dependencies.
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200593 final String validFilenames = dependencyMap.keys.map((String name) => '$name.dart').join(', ');
LongCatIsLooongd291de02020-01-09 10:25:58 -0800594 errors.add(
595 '$key imported package:flutter/$dependency.dart '
596 'which is not one of the valid exports { $validFilenames }.\n'
597 'Consider changing $dependency.dart to one of them.'
598 );
599 }
600 }
601
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100602 for (final String package in dependencyMap.keys) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -0700603 final List<String>? loop = _deepSearch<String>(dependencyMap, package);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700604 if (loop != null) {
Alexandre Ardhuin34059ee2021-06-01 20:14:06 +0200605 errors.add('${yellow}Dependency loop:$reset ${loop.join(' depends on ')}');
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700606 }
607 }
608 // Fail if any errors
609 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800610 exitWithError(<String>[
611 if (errors.length == 1)
612 '${bold}An error was detected when looking at import dependencies within the Flutter package:$reset'
613 else
614 '${bold}Multiple errors were detected when looking at import dependencies within the Flutter package:$reset',
615 ...errors,
616 ]);
Alexander Aprelev391e91c2018-08-30 07:30:25 -0700617 }
618}
619
Ian Hicksoneae05c72019-11-14 13:19:40 -0800620Future<void> verifyNoBadImportsInFlutterTools(String workingDirectory) async {
621 final List<String> errors = <String>[];
Yuqian Lifb552ed2020-11-07 05:19:02 -0800622 final List<File> files = await _allFiles(path.join(workingDirectory, 'packages', 'flutter_tools', 'lib'), 'dart', minimumMatches: 200).toList();
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100623 for (final File file in files) {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800624 if (file.readAsStringSync().contains('package:flutter_tools/')) {
625 errors.add('$yellow${file.path}$reset imports flutter_tools.');
626 }
627 }
628 // Fail if any errors
629 if (errors.isNotEmpty) {
Dan Field24f39d42020-01-02 11:47:28 -0800630 exitWithError(<String>[
631 if (errors.length == 1)
632 '${bold}An error was detected when looking at import dependencies within the flutter_tools package:$reset'
633 else
634 '${bold}Multiple errors were detected when looking at import dependencies within the flutter_tools package:$reset',
635 ...errors.map((String paragraph) => '$paragraph\n'),
636 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800637 }
638}
639
Dan Field9c0bd182021-06-23 13:08:51 -0700640Future<void> verifyIntegrationTestTimeouts(String workingDirectory) async {
641 final List<String> errors = <String>[];
642 final String dev = path.join(workingDirectory, 'dev');
643 final List<File> files = await _allFiles(dev, 'dart', minimumMatches: 1)
644 .where((File file) => file.path.contains('test_driver') && (file.path.endsWith('_test.dart') || file.path.endsWith('util.dart')))
645 .toList();
646 for (final File file in files) {
647 final String contents = file.readAsStringSync();
648 final int testCount = ' test('.allMatches(contents).length;
649 final int timeoutNoneCount = 'timeout: Timeout.none'.allMatches(contents).length;
650 if (testCount != timeoutNoneCount) {
651 errors.add('$yellow${file.path}$reset has at least $testCount test(s) but only $timeoutNoneCount `Timeout.none`(s).');
652 }
653 }
654 if (errors.isNotEmpty) {
655 exitWithError(<String>[
656 if (errors.length == 1)
Ian Hickson24207182021-09-15 09:42:05 -0700657 '${bold}An error was detected when looking at integration test timeouts:$reset'
Dan Field9c0bd182021-06-23 13:08:51 -0700658 else
Ian Hickson24207182021-09-15 09:42:05 -0700659 '${bold}Multiple errors were detected when looking at integration test timeouts:$reset',
Dan Field9c0bd182021-06-23 13:08:51 -0700660 ...errors.map((String paragraph) => '$paragraph\n'),
661 ]);
662 }
663}
664
Greg Spencer57224f82021-07-26 15:31:37 -0700665Future<void> verifyInternationalizations(String workingDirectory, String dartExecutable) async {
Ian Hicksoneae05c72019-11-14 13:19:40 -0800666 final EvalResult materialGenResult = await _evalCommand(
Greg Spencer57224f82021-07-26 15:31:37 -0700667 dartExecutable,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800668 <String>[
Shi-Hao Hong7874bca2019-12-16 17:30:57 -0800669 path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
Ian Hicksoneae05c72019-11-14 13:19:40 -0800670 '--material',
671 ],
Greg Spencer57224f82021-07-26 15:31:37 -0700672 workingDirectory: workingDirectory,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800673 );
674 final EvalResult cupertinoGenResult = await _evalCommand(
Greg Spencer57224f82021-07-26 15:31:37 -0700675 dartExecutable,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800676 <String>[
Shi-Hao Hong7874bca2019-12-16 17:30:57 -0800677 path.join('dev', 'tools', 'localization', 'bin', 'gen_localizations.dart'),
Ian Hicksoneae05c72019-11-14 13:19:40 -0800678 '--cupertino',
679 ],
Greg Spencer57224f82021-07-26 15:31:37 -0700680 workingDirectory: workingDirectory,
Ian Hicksoneae05c72019-11-14 13:19:40 -0800681 );
682
Greg Spencer57224f82021-07-26 15:31:37 -0700683 final String materialLocalizationsFile = path.join(workingDirectory, 'packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_material_localizations.dart');
684 final String cupertinoLocalizationsFile = path.join(workingDirectory, 'packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_cupertino_localizations.dart');
Ian Hicksoneae05c72019-11-14 13:19:40 -0800685 final String expectedMaterialResult = await File(materialLocalizationsFile).readAsString();
686 final String expectedCupertinoResult = await File(cupertinoLocalizationsFile).readAsString();
687
688 if (materialGenResult.stdout.trim() != expectedMaterialResult.trim()) {
Dan Field24f39d42020-01-02 11:47:28 -0800689 exitWithError(<String>[
690 '<<<<<<< $materialLocalizationsFile',
691 expectedMaterialResult.trim(),
692 '=======',
693 materialGenResult.stdout.trim(),
694 '>>>>>>> gen_localizations',
695 'The contents of $materialLocalizationsFile are different from that produced by gen_localizations.',
696 '',
697 'Did you forget to run gen_localizations.dart after updating a .arb file?',
698 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800699 }
700 if (cupertinoGenResult.stdout.trim() != expectedCupertinoResult.trim()) {
Dan Field24f39d42020-01-02 11:47:28 -0800701 exitWithError(<String>[
702 '<<<<<<< $cupertinoLocalizationsFile',
703 expectedCupertinoResult.trim(),
704 '=======',
705 cupertinoGenResult.stdout.trim(),
706 '>>>>>>> gen_localizations',
707 'The contents of $cupertinoLocalizationsFile are different from that produced by gen_localizations.',
708 '',
709 'Did you forget to run gen_localizations.dart after updating a .arb file?',
710 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -0800711 }
712}
713
Jonah Williams0d3b44e2021-08-03 13:25:05 -0700714
715/// Verifies that all instances of "checked mode" have been migrated to "debug mode".
716Future<void> verifyNoCheckedMode(String workingDirectory) async {
717 final String flutterPackages = path.join(workingDirectory, 'packages');
718 final List<File> files = await _allFiles(flutterPackages, 'dart', minimumMatches: 400)
719 .where((File file) => path.extension(file.path) == '.dart')
720 .toList();
721 final List<String> problems = <String>[];
722 for (final File file in files) {
723 int lineCount = 0;
724 for (final String line in file.readAsLinesSync()) {
725 if (line.toLowerCase().contains('checked mode')) {
726 problems.add('${file.path}:$lineCount uses deprecated "checked mode" instead of "debug mode".');
727 }
728 lineCount += 1;
729 }
730 }
731 if (problems.isNotEmpty) {
732 exitWithError(problems);
733 }
734}
735
736
Dan Field3e634112020-01-14 16:43:01 -0800737Future<void> verifyNoRuntimeTypeInToString(String workingDirectory) async {
738 final String flutterLib = path.join(workingDirectory, 'packages', 'flutter', 'lib');
739 final Set<String> excludedFiles = <String>{
740 path.join(flutterLib, 'src', 'foundation', 'object.dart'), // Calls this from within an assert.
741 };
Yuqian Lifb552ed2020-11-07 05:19:02 -0800742 final List<File> files = await _allFiles(flutterLib, 'dart', minimumMatches: 400)
Dan Field3e634112020-01-14 16:43:01 -0800743 .where((File file) => !excludedFiles.contains(file.path))
744 .toList();
745 final RegExp toStringRegExp = RegExp(r'^\s+String\s+to(.+?)?String(.+?)?\(\)\s+(\{|=>)');
746 final List<String> problems = <String>[];
747 for (final File file in files) {
748 final List<String> lines = file.readAsLinesSync();
749 for (int index = 0; index < lines.length; index++) {
750 if (toStringRegExp.hasMatch(lines[index])) {
751 final int sourceLine = index + 1;
752 bool _checkForRuntimeType(String line) {
753 if (line.contains(r'$runtimeType') || line.contains('runtimeType.toString()')) {
754 problems.add('${file.path}:$sourceLine}: toString calls runtimeType.toString');
755 return true;
756 }
757 return false;
758 }
759 if (_checkForRuntimeType(lines[index])) {
760 continue;
761 }
762 if (lines[index].contains('=>')) {
763 while (!lines[index].contains(';')) {
764 index++;
765 assert(index < lines.length, 'Source file $file has unterminated toString method.');
766 if (_checkForRuntimeType(lines[index])) {
767 break;
768 }
769 }
770 } else {
771 int openBraceCount = '{'.allMatches(lines[index]).length - '}'.allMatches(lines[index]).length;
772 while (!lines[index].contains('}') && openBraceCount > 0) {
773 index++;
774 assert(index < lines.length, 'Source file $file has unbalanced braces in a toString method.');
775 if (_checkForRuntimeType(lines[index])) {
776 break;
777 }
778 openBraceCount += '{'.allMatches(lines[index]).length;
779 openBraceCount -= '}'.allMatches(lines[index]).length;
780 }
781 }
782 }
783 }
784 }
785 if (problems.isNotEmpty)
786 exitWithError(problems);
787}
788
Dan Field24f39d42020-01-02 11:47:28 -0800789Future<void> verifyNoTrailingSpaces(String workingDirectory, { int minimumMatches = 4000 }) async {
Yuqian Lifb552ed2020-11-07 05:19:02 -0800790 final List<File> files = await _allFiles(workingDirectory, null, minimumMatches: minimumMatches)
Dan Field24f39d42020-01-02 11:47:28 -0800791 .where((File file) => path.basename(file.path) != 'serviceaccount.enc')
792 .where((File file) => path.basename(file.path) != 'Ahem.ttf')
793 .where((File file) => path.extension(file.path) != '.snapshot')
794 .where((File file) => path.extension(file.path) != '.png')
795 .where((File file) => path.extension(file.path) != '.jpg')
stuartmorgan685e9d12020-03-23 10:42:26 -0700796 .where((File file) => path.extension(file.path) != '.ico')
Dan Field24f39d42020-01-02 11:47:28 -0800797 .where((File file) => path.extension(file.path) != '.jar')
Jonah Williams0b3f5cf2020-04-21 20:39:36 -0700798 .where((File file) => path.extension(file.path) != '.swp')
Dan Field24f39d42020-01-02 11:47:28 -0800799 .toList();
800 final List<String> problems = <String>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +0100801 for (final File file in files) {
Dan Field24f39d42020-01-02 11:47:28 -0800802 final List<String> lines = file.readAsLinesSync();
803 for (int index = 0; index < lines.length; index += 1) {
804 if (lines[index].endsWith(' ')) {
805 problems.add('${file.path}:${index + 1}: trailing U+0020 space character');
806 } else if (lines[index].endsWith('\t')) {
807 problems.add('${file.path}:${index + 1}: trailing U+0009 tab character');
808 }
Ian Hicksone768c922019-12-30 17:12:19 -0800809 }
Dan Field24f39d42020-01-02 11:47:28 -0800810 if (lines.isNotEmpty && lines.last == '')
811 problems.add('${file.path}:${lines.length}: trailing blank line');
812 }
813 if (problems.isNotEmpty)
814 exitWithError(problems);
815}
816
Ian Hicksonbde9f112021-11-19 13:13:05 -0800817String _bullets(String value) => ' * $value';
818
819Future<void> verifyIssueLinks(String workingDirectory) async {
820 const String issueLinkPrefix = 'https://github.com/flutter/flutter/issues/new';
821 const Set<String> stops = <String>{ '\n', ' ', "'", '"', r'\', ')', '>' };
822 assert(!stops.contains('.')); // instead of "visit https://foo." say "visit: https://", it copy-pastes better
823 const String kGiveTemplates =
824 'Prefer to provide a link either to $issueLinkPrefix/choose (the list of issue '
825 'templates) or to a specific template directly ($issueLinkPrefix?template=...).\n';
826 final Set<String> templateNames =
827 Directory(path.join(workingDirectory, '.github', 'ISSUE_TEMPLATE'))
828 .listSync()
829 .whereType<File>()
830 .where((File file) => path.extension(file.path) == '.md')
831 .map<String>((File file) => path.basename(file.path))
832 .toSet();
833 final String kTemplates = 'The available templates are:\n${templateNames.map(_bullets).join("\n")}';
834 final List<String> problems = <String>[];
835 final Set<String> suggestions = <String>{};
836 final List<File> files = await _gitFiles(workingDirectory);
837 for (final File file in files) {
Jonah Williamsfda40942021-12-14 10:51:30 -0800838 if (path.basename(file.path).endsWith('_test.dart') || path.basename(file.path) == 'analyze.dart')
Ian Hicksonbde9f112021-11-19 13:13:05 -0800839 continue; // Skip tests, they're not public-facing.
840 final Uint8List bytes = file.readAsBytesSync();
841 // We allow invalid UTF-8 here so that binaries don't trip us up.
842 // There's a separate test in this file that verifies that all text
843 // files are actually valid UTF-8 (see verifyNoBinaries below).
844 final String contents = utf8.decode(bytes, allowMalformed: true);
845 int start = 0;
846 while ((start = contents.indexOf(issueLinkPrefix, start)) >= 0) {
847 int end = start + issueLinkPrefix.length;
848 while (end < contents.length && !stops.contains(contents[end])) {
849 end += 1;
850 }
851 final String url = contents.substring(start, end);
852 if (url == issueLinkPrefix) {
853 if (file.path != path.join(workingDirectory, 'dev', 'bots', 'analyze.dart')) {
854 problems.add('${file.path} contains a direct link to $issueLinkPrefix.');
855 suggestions.add(kGiveTemplates);
856 suggestions.add(kTemplates);
857 }
858 } else if (url.startsWith('$issueLinkPrefix?')) {
859 final Uri parsedUrl = Uri.parse(url);
860 final List<String>? templates = parsedUrl.queryParametersAll['template'];
861 if (templates == null) {
862 problems.add('${file.path} contains $url, which has no "template" argument specified.');
863 suggestions.add(kGiveTemplates);
864 suggestions.add(kTemplates);
865 } else if (templates.length != 1) {
866 problems.add('${file.path} contains $url, which has ${templates.length} templates specified.');
867 suggestions.add(kGiveTemplates);
868 suggestions.add(kTemplates);
869 } else if (!templateNames.contains(templates.single)) {
870 problems.add('${file.path} contains $url, which specifies a non-existent template ("${templates.single}").');
871 suggestions.add(kTemplates);
872 } else if (parsedUrl.queryParametersAll.keys.length > 1) {
873 problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.');
874 suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.');
875 }
876 } else if (url != '$issueLinkPrefix/choose') {
877 problems.add('${file.path} contains $url, which the analyze.dart script is not sure how to handle.');
878 suggestions.add('Update analyze.dart to handle the URLs above, or change them to the expected pattern.');
879 }
880 start = end;
881 }
882 }
883 assert(problems.isEmpty == suggestions.isEmpty);
884 if (problems.isNotEmpty) {
885 exitWithError(<String>[
886 ...problems,
887 ...suggestions,
888 ]);
889 }
890}
891
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200892@immutable
Dan Field24f39d42020-01-02 11:47:28 -0800893class Hash256 {
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200894 const Hash256(this.a, this.b, this.c, this.d);
Dan Field24f39d42020-01-02 11:47:28 -0800895
896 factory Hash256.fromDigest(Digest digest) {
897 assert(digest.bytes.length == 32);
898 return Hash256(
899 digest.bytes[ 0] << 56 |
900 digest.bytes[ 1] << 48 |
901 digest.bytes[ 2] << 40 |
902 digest.bytes[ 3] << 32 |
903 digest.bytes[ 4] << 24 |
904 digest.bytes[ 5] << 16 |
905 digest.bytes[ 6] << 8 |
906 digest.bytes[ 7] << 0,
907 digest.bytes[ 8] << 56 |
908 digest.bytes[ 9] << 48 |
909 digest.bytes[10] << 40 |
910 digest.bytes[11] << 32 |
911 digest.bytes[12] << 24 |
912 digest.bytes[13] << 16 |
913 digest.bytes[14] << 8 |
914 digest.bytes[15] << 0,
915 digest.bytes[16] << 56 |
916 digest.bytes[17] << 48 |
917 digest.bytes[18] << 40 |
918 digest.bytes[19] << 32 |
919 digest.bytes[20] << 24 |
920 digest.bytes[21] << 16 |
921 digest.bytes[22] << 8 |
922 digest.bytes[23] << 0,
923 digest.bytes[24] << 56 |
924 digest.bytes[25] << 48 |
925 digest.bytes[26] << 40 |
926 digest.bytes[27] << 32 |
927 digest.bytes[28] << 24 |
928 digest.bytes[29] << 16 |
929 digest.bytes[30] << 8 |
930 digest.bytes[31] << 0,
931 );
932 }
933
934 final int a;
935 final int b;
936 final int c;
937 final int d;
938
939 @override
940 bool operator ==(Object other) {
941 if (other.runtimeType != runtimeType)
942 return false;
943 return other is Hash256
944 && other.a == a
945 && other.b == b
946 && other.c == c
947 && other.d == d;
948 }
949
950 @override
Dan Fielde36e62e2021-06-30 09:46:54 -0700951 int get hashCode => Object.hash(a, b, c, d);
Dan Field24f39d42020-01-02 11:47:28 -0800952}
953
954// DO NOT ADD ANY ENTRIES TO THIS LIST.
955// We have a policy of not checking in binaries into this repository.
stuartmorgan685e9d12020-03-23 10:42:26 -0700956// If you are adding/changing template images, use the flutter_template_images
957// package and a .img.tmpl placeholder instead.
958// If you have other binaries to add, please consult Hixie for advice.
Michael Goderbauer584fd5f2020-06-16 09:15:43 -0700959final Set<Hash256> _legacyBinaries = <Hash256>{
Dan Field24f39d42020-01-02 11:47:28 -0800960 // DEFAULT ICON IMAGES
961
962 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-hdpi/ic_launcher.png
963 // packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/res/mipmap-hdpi/ic_launcher.png
964 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200965 const Hash256(0x6A7C8F0D703E3682, 0x108F9662F8133022, 0x36240D3F8F638BB3, 0x91E32BFB96055FEF),
Dan Field24f39d42020-01-02 11:47:28 -0800966
967 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-mdpi/ic_launcher.png
968 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200969 const Hash256(0xC7C0C0189145E4E3, 0x2A401C61C9BDC615, 0x754B0264E7AFAE24, 0xE834BB81049EAF81),
Dan Field24f39d42020-01-02 11:47:28 -0800970
971 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xhdpi/ic_launcher.png
972 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200973 const Hash256(0xE14AA40904929BF3, 0x13FDED22CF7E7FFC, 0xBF1D1AAC4263B5EF, 0x1BE8BFCE650397AA),
Dan Field24f39d42020-01-02 11:47:28 -0800974
975 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
976 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200977 const Hash256(0x4D470BF22D5C17D8, 0x4EDC5F82516D1BA8, 0xA1C09559CD761CEF, 0xB792F86D9F52B540),
Dan Field24f39d42020-01-02 11:47:28 -0800978
979 // packages/flutter_tools/templates/app/android.tmpl/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
980 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200981 const Hash256(0x3C34E1F298D0C9EA, 0x3455D46DB6B7759C, 0x8211A49E9EC6E44B, 0x635FC5C87DFB4180),
Dan Field24f39d42020-01-02 11:47:28 -0800982
983 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
984 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
985 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200986 const Hash256(0x7770183009E91411, 0x2DE7D8EF1D235A6A, 0x30C5834424858E0D, 0x2F8253F6B8D31926),
Dan Field24f39d42020-01-02 11:47:28 -0800987
988 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
989 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
990 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200991 const Hash256(0x5925DAB509451F9E, 0xCBB12CE8A625F9D4, 0xC104718EE20CAFF8, 0xB1B51032D1CD8946),
Dan Field24f39d42020-01-02 11:47:28 -0800992
993 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
994 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
995 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
996 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
997 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +0200998 const Hash256(0xC4D9A284C12301D0, 0xF50E248EC53ED51A, 0x19A10147B774B233, 0x08399250B0D44C55),
Dan Field24f39d42020-01-02 11:47:28 -0800999
1000 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
1001 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
1002 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001003 const Hash256(0xBF97F9D3233F33E1, 0x389B09F7B8ADD537, 0x41300CB834D6C7A5, 0xCA32CBED363A4FB2),
Dan Field24f39d42020-01-02 11:47:28 -08001004
1005 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
1006 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
1007 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001008 const Hash256(0x285442F69A06B45D, 0x9D79DF80321815B5, 0x46473548A37B7881, 0x9B68959C7B8ED237),
Dan Field24f39d42020-01-02 11:47:28 -08001009
1010 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
1011 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
1012 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001013 const Hash256(0x2AB64AF8AC727EA9, 0x9C6AB9EAFF847F46, 0xFBF2A9A0A78A0ABC, 0xBF3180F3851645B4),
Dan Field24f39d42020-01-02 11:47:28 -08001014
1015 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
1016 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
1017 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001018 const Hash256(0x9DCA09F4E5ED5684, 0xD3C4DFF41F4E8B7C, 0xB864B438172D72BE, 0x069315FA362930F9),
Dan Field24f39d42020-01-02 11:47:28 -08001019
1020 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
1021 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
1022 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001023 const Hash256(0xD5AD04DE321EF37C, 0xACC5A7B960AFCCE7, 0x1BDCB96FA020C482, 0x49C1545DD1A0F497),
Dan Field24f39d42020-01-02 11:47:28 -08001024
1025 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
1026 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
1027 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
1028 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
1029 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001030 const Hash256(0x809ABFE75C440770, 0xC13C4E2E46D09603, 0xC22053E9D4E0E227, 0x5DCB9C1DCFBB2C75),
Dan Field24f39d42020-01-02 11:47:28 -08001031
1032 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
1033 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
1034 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001035 const Hash256(0x3DB08CB79E7B01B9, 0xE81F956E3A0AE101, 0x48D0FAFDE3EA7AA7, 0x0048DF905AA52CFD),
Dan Field24f39d42020-01-02 11:47:28 -08001036
1037 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
1038 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
1039 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001040 const Hash256(0x23C13D463F5DCA5C, 0x1F14A14934003601, 0xC29F1218FD461016, 0xD8A22CEF579A665F),
Dan Field24f39d42020-01-02 11:47:28 -08001041
1042 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
1043 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
1044 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001045 const Hash256(0x6DB7726530D71D3F, 0x52CB59793EB69131, 0x3BAA04796E129E1E, 0x043C0A58A1BFFD2F),
Dan Field24f39d42020-01-02 11:47:28 -08001046
1047 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
1048 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
1049 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001050 const Hash256(0xCEE565F5E6211656, 0x9B64980B209FD5CA, 0x4B3D3739011F5343, 0x250B33A1A2C6EB65),
Dan Field24f39d42020-01-02 11:47:28 -08001051
1052 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
1053 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
1054 // packages/flutter_tools/templates/app/ios.tmpl/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
1055 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
1056 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
1057 // packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
1058 // (also used by many examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001059 const Hash256(0x93AE7D494FAD0FB3, 0x0CBF3AE746A39C4B, 0xC7A0F8BBF87FBB58, 0x7A3F3C01F3C5CE20),
Dan Field24f39d42020-01-02 11:47:28 -08001060
1061 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
1062 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001063 const Hash256(0xB18BEBAAD1AD6724, 0xE48BCDF699BA3927, 0xDF3F258FEBE646A3, 0xAB5C62767C6BAB40),
Dan Field24f39d42020-01-02 11:47:28 -08001064
1065 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
1066 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001067 const Hash256(0xF90D839A289ECADB, 0xF2B0B3400DA43EB8, 0x08B84908335AE4A0, 0x07457C4D5A56A57C),
Dan Field24f39d42020-01-02 11:47:28 -08001068
1069 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
1070 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001071 const Hash256(0x592C2ABF84ADB2D3, 0x91AED8B634D3233E, 0x2C65369F06018DCD, 0x8A4B27BA755EDCBE),
Dan Field24f39d42020-01-02 11:47:28 -08001072
1073 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
1074 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001075 const Hash256(0x75D9A0C034113CA8, 0xA1EC11C24B81F208, 0x6630A5A5C65C7D26, 0xA5DC03A1C0A4478C),
Dan Field24f39d42020-01-02 11:47:28 -08001076
1077 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
1078 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001079 const Hash256(0xA896E65745557732, 0xC72BD4EE3A10782F, 0xE2AA95590B5AF659, 0x869E5808DB9C01C1),
Dan Field24f39d42020-01-02 11:47:28 -08001080
1081 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
1082 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001083 const Hash256(0x3A69A8A1AAC5D9A8, 0x374492AF4B6D07A4, 0xCE637659EB24A784, 0x9C4DFB261D75C6A3),
Dan Field24f39d42020-01-02 11:47:28 -08001084
1085 // packages/flutter_tools/templates/app/macos.tmpl/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
1086 // (also used by a few examples)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001087 const Hash256(0xD29D4E0AF9256DC9, 0x2D0A8F8810608A5E, 0x64A132AD8B397CA2, 0xC4DDC0B1C26A68C3),
Dan Field24f39d42020-01-02 11:47:28 -08001088
Jonah Williams5d30c092020-01-10 09:37:20 -08001089 // packages/flutter_tools/templates/app/web/icons/Icon-192.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001090 // dev/integration_tests/flutter_gallery/web/icons/Icon-192.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001091 const Hash256(0x3DCE99077602F704, 0x21C1C6B2A240BC9B, 0x83D64D86681D45F2, 0x154143310C980BE3),
Jonah Williams5d30c092020-01-10 09:37:20 -08001092
1093 // packages/flutter_tools/templates/app/web/icons/Icon-512.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001094 // dev/integration_tests/flutter_gallery/web/icons/Icon-512.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001095 const Hash256(0xBACCB205AE45f0B4, 0x21BE1657259B4943, 0xAC40C95094AB877F, 0x3BCBE12CD544DCBE),
Dan Field24f39d42020-01-02 11:47:28 -08001096
Jonah Williamsab426852020-01-28 13:09:18 -08001097 // packages/flutter_tools/templates/app/web/favicon.png.copy.tmpl
Michael Thomsene1671812020-03-16 02:31:42 -07001098 // dev/integration_tests/flutter_gallery/web/favicon.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001099 const Hash256(0x7AB2525F4B86B65D, 0x3E4C70358A17E5A1, 0xAAF6F437f99CBCC0, 0x46DAD73d59BB9015),
Jonah Williamsab426852020-01-28 13:09:18 -08001100
Dan Field24f39d42020-01-02 11:47:28 -08001101 // GALLERY ICONS
1102
Michael Thomsene1671812020-03-16 02:31:42 -07001103 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001104 const Hash256(0x03CFDE53C249475C, 0x277E8B8E90AC8A13, 0xE5FC13C358A94CCB, 0x67CA866C9862A0DD),
Dan Field24f39d42020-01-02 11:47:28 -08001105
Michael Thomsene1671812020-03-16 02:31:42 -07001106 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001107 const Hash256(0x86A83E23A505EFCC, 0x39C358B699EDE12F, 0xC088EE516A1D0C73, 0xF3B5D74DDAD164B1),
Dan Field24f39d42020-01-02 11:47:28 -08001108
Michael Thomsene1671812020-03-16 02:31:42 -07001109 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001110 const Hash256(0xD813B1A77320355E, 0xB68C485CD47D0F0F, 0x3C7E1910DCD46F08, 0x60A6401B8DC13647),
Dan Field24f39d42020-01-02 11:47:28 -08001111
Michael Thomsene1671812020-03-16 02:31:42 -07001112 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001113 const Hash256(0x35AFA76BD5D6053F, 0xEE927436C78A8794, 0xA8BA5F5D9FC9653B, 0xE5B96567BB7215ED),
Dan Field24f39d42020-01-02 11:47:28 -08001114
Michael Thomsene1671812020-03-16 02:31:42 -07001115 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001116 const Hash256(0x263CE9B4F1F69B43, 0xEBB08AE9FE8F80E7, 0x95647A59EF2C040B, 0xA8AEB246861A7DFF),
Dan Field24f39d42020-01-02 11:47:28 -08001117
Michael Thomsene1671812020-03-16 02:31:42 -07001118 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001119 const Hash256(0x5E1A93C3653BAAFF, 0x1AAC6BCEB8DCBC2F, 0x2AE7D68ECB07E507, 0xCB1FA8354B28313A),
Dan Field24f39d42020-01-02 11:47:28 -08001120
Michael Thomsene1671812020-03-16 02:31:42 -07001121 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001122 const Hash256(0xA5C77499151DDEC6, 0xDB40D0AC7321FD74, 0x0646C0C0F786743F, 0x8F3C3C408CAC5E8C),
Dan Field24f39d42020-01-02 11:47:28 -08001123
Michael Thomsene1671812020-03-16 02:31:42 -07001124 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001125 const Hash256(0x33DE450980A2A16B, 0x1982AC7CDC1E7B01, 0x919E07E0289C2139, 0x65F85BCED8895FEF),
Dan Field24f39d42020-01-02 11:47:28 -08001126
Michael Thomsene1671812020-03-16 02:31:42 -07001127 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001128 const Hash256(0xC3B8577F4A89BA03, 0x830944FB06C3566B, 0x4C99140A2CA52958, 0x089BFDC3079C59B7),
Dan Field24f39d42020-01-02 11:47:28 -08001129
Michael Thomsene1671812020-03-16 02:31:42 -07001130 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_background.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001131 const Hash256(0xDEBC241D6F9C5767, 0x8980FDD46FA7ED0C, 0x5B8ACD26BCC5E1BC, 0x473C89B432D467AD),
Dan Field24f39d42020-01-02 11:47:28 -08001132
Michael Thomsene1671812020-03-16 02:31:42 -07001133 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_foreground.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001134 const Hash256(0xBEFE5F7E82BF8B64, 0x148D869E3742004B, 0xF821A9F5A1BCDC00, 0x357D246DCC659DC2),
Dan Field24f39d42020-01-02 11:47:28 -08001135
Michael Thomsene1671812020-03-16 02:31:42 -07001136 // dev/integration_tests/flutter_gallery/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001137 const Hash256(0xC385404341FF9EDD, 0x30FBE76F0EC99155, 0x8EA4F4AFE8CC0C60, 0x1CA3EDEF177E1DA8),
Dan Field24f39d42020-01-02 11:47:28 -08001138
Michael Thomsene1671812020-03-16 02:31:42 -07001139 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001140 const Hash256(0x6BE5751A29F57A80, 0x36A4B31CC542C749, 0x984E49B22BD65CAA, 0x75AE8B2440848719),
Dan Field24f39d42020-01-02 11:47:28 -08001141
Michael Thomsene1671812020-03-16 02:31:42 -07001142 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-120.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001143 const Hash256(0x9972A2264BFA8F8D, 0x964AFE799EADC1FA, 0x2247FB31097F994A, 0x1495DC32DF071793),
Dan Field24f39d42020-01-02 11:47:28 -08001144
Michael Thomsene1671812020-03-16 02:31:42 -07001145 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-152.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001146 const Hash256(0x4C7CC9B09BEEDA24, 0x45F57D6967753910, 0x57D68E1A6B883D2C, 0x8C52701A74F1400F),
Dan Field24f39d42020-01-02 11:47:28 -08001147
Michael Thomsene1671812020-03-16 02:31:42 -07001148 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-167.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001149 const Hash256(0x66DACAC1CFE4D349, 0xDBE994CB9125FFD7, 0x2D795CFC9CF9F739, 0xEDBB06CE25082E9C),
Dan Field24f39d42020-01-02 11:47:28 -08001150
Michael Thomsene1671812020-03-16 02:31:42 -07001151 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-180.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001152 const Hash256(0x5188621015EBC327, 0xC9EF63AD76E60ECE, 0xE82BDC3E4ABF09E2, 0xEE0139FA7C0A2BE5),
Dan Field24f39d42020-01-02 11:47:28 -08001153
Michael Thomsene1671812020-03-16 02:31:42 -07001154 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001155 const Hash256(0x27D2752D04EE9A6B, 0x78410E208F74A6CD, 0xC90D9E03B73B8C60, 0xD05F7D623E790487),
Dan Field24f39d42020-01-02 11:47:28 -08001156
Michael Thomsene1671812020-03-16 02:31:42 -07001157 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001158 const Hash256(0xBB20556B2826CF85, 0xD5BAC73AA69C2AC3, 0x8E71DAD64F15B855, 0xB30CB73E0AF89307),
Dan Field24f39d42020-01-02 11:47:28 -08001159
Michael Thomsene1671812020-03-16 02:31:42 -07001160 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001161 const Hash256(0x623820FA45CDB0AC, 0x808403E34AD6A53E, 0xA3E9FDAE83EE0931, 0xB020A3A4EF2CDDE7),
Dan Field24f39d42020-01-02 11:47:28 -08001162
Michael Thomsene1671812020-03-16 02:31:42 -07001163 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-58.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001164 const Hash256(0xC6D631D1E107215E, 0xD4A58FEC5F3AA4B5, 0x0AE9724E07114C0C, 0x453E5D87C2CAD3B3),
Dan Field24f39d42020-01-02 11:47:28 -08001165
Michael Thomsene1671812020-03-16 02:31:42 -07001166 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001167 const Hash256(0x4B6F58D1EB8723C6, 0xE717A0D09FEC8806, 0x90C6D1EF4F71836E, 0x618672827979B1A2),
Dan Field24f39d42020-01-02 11:47:28 -08001168
Michael Thomsene1671812020-03-16 02:31:42 -07001169 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001170 const Hash256(0x0A1744CC7634D508, 0xE85DD793331F0C8A, 0x0B7C6DDFE0975D8F, 0x29E91C905BBB1BED),
Dan Field24f39d42020-01-02 11:47:28 -08001171
Michael Thomsene1671812020-03-16 02:31:42 -07001172 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-80.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001173 const Hash256(0x24032FBD1E6519D6, 0x0BA93C0D5C189554, 0xF50EAE23756518A2, 0x3FABACF4BD5DAF08),
Dan Field24f39d42020-01-02 11:47:28 -08001174
Michael Thomsene1671812020-03-16 02:31:42 -07001175 // dev/integration_tests/flutter_gallery/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-87.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001176 const Hash256(0xC17BAE6DF6BB234A, 0xE0AF4BEB0B805F12, 0x14E74EB7AA9A30F1, 0x5763689165DA7DDF),
Dan Field24f39d42020-01-02 11:47:28 -08001177
1178
1179 // STOCKS ICONS
1180
Greg Spencer4b4cff92020-01-30 09:31:07 -08001181 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001182 const Hash256(0x74052AB5241D4418, 0x7085180608BC3114, 0xD12493C50CD8BBC7, 0x56DED186C37ACE84),
Dan Field24f39d42020-01-02 11:47:28 -08001183
Greg Spencer4b4cff92020-01-30 09:31:07 -08001184 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001185 const Hash256(0xE37947332E3491CB, 0x82920EE86A086FEA, 0xE1E0A70B3700A7DA, 0xDCAFBDD8F40E2E19),
Dan Field24f39d42020-01-02 11:47:28 -08001186
Greg Spencer4b4cff92020-01-30 09:31:07 -08001187 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001188 const Hash256(0xE608CDFC0C8579FB, 0xE38873BAAF7BC944, 0x9C9D2EE3685A4FAE, 0x671EF0C8BC41D17C),
Dan Field24f39d42020-01-02 11:47:28 -08001189
Greg Spencer4b4cff92020-01-30 09:31:07 -08001190 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001191 const Hash256(0xBD53D86977DF9C54, 0xF605743C5ABA114C, 0x9D51D1A8BB917E1A, 0x14CAA26C335CAEBD),
Dan Field24f39d42020-01-02 11:47:28 -08001192
Greg Spencer4b4cff92020-01-30 09:31:07 -08001193 // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001194 const Hash256(0x64E4D02262C4F3D0, 0xBB4FDC21CD0A816C, 0x4CD2A0194E00FB0F, 0x1C3AE4142FAC0D15),
Dan Field24f39d42020-01-02 11:47:28 -08001195
Greg Spencer4b4cff92020-01-30 09:31:07 -08001196 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
1197 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001198 const Hash256(0x5BA3283A76918FC0, 0xEE127D0F22D7A0B6, 0xDF03DAED61669427, 0x93D89DDD87A08117),
Dan Field24f39d42020-01-02 11:47:28 -08001199
Greg Spencer4b4cff92020-01-30 09:31:07 -08001200 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001201 const Hash256(0xCD7F26ED31DEA42A, 0x535D155EC6261499, 0x34E6738255FDB2C4, 0xBD8D4BDDE9A99B05),
Dan Field24f39d42020-01-02 11:47:28 -08001202
Greg Spencer4b4cff92020-01-30 09:31:07 -08001203 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001204 const Hash256(0x3FA1225FC9A96A7E, 0xCD071BC42881AB0E, 0x7747EB72FFB72459, 0xA37971BBAD27EE24),
Dan Field24f39d42020-01-02 11:47:28 -08001205
Greg Spencer4b4cff92020-01-30 09:31:07 -08001206 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001207 const Hash256(0xCD867001ACD7BBDB, 0x25CDFD452AE89FA2, 0x8C2DC980CAF55F48, 0x0B16C246CFB389BC),
Dan Field24f39d42020-01-02 11:47:28 -08001208
Greg Spencer4b4cff92020-01-30 09:31:07 -08001209 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001210 const Hash256(0x848E9736E5C4915A, 0x7945BCF6B32FD56B, 0x1F1E7CDDD914352E, 0xC9681D38EF2A70DA),
Dan Field24f39d42020-01-02 11:47:28 -08001211
Greg Spencer4b4cff92020-01-30 09:31:07 -08001212 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001213 const Hash256(0x654BA7D6C4E05CA0, 0x7799878884EF8F11, 0xA383E1F24CEF5568, 0x3C47604A966983C8),
Dan Field24f39d42020-01-02 11:47:28 -08001214
Greg Spencer4b4cff92020-01-30 09:31:07 -08001215 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
1216 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001217 const Hash256(0x743056FE7D83FE42, 0xA2990825B6AD0415, 0x1AF73D0D43B227AA, 0x07EBEA9B767381D9),
Dan Field24f39d42020-01-02 11:47:28 -08001218
Greg Spencer4b4cff92020-01-30 09:31:07 -08001219 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001220 const Hash256(0xA7E1570812D119CF, 0xEF4B602EF28DD0A4, 0x100D066E66F5B9B9, 0x881765DC9303343B),
Dan Field24f39d42020-01-02 11:47:28 -08001221
Greg Spencer4b4cff92020-01-30 09:31:07 -08001222 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001223 const Hash256(0xB4102839A1E41671, 0x62DACBDEFA471953, 0xB1EE89A0AB7594BE, 0x1D9AC1E67DC2B2CE),
Dan Field24f39d42020-01-02 11:47:28 -08001224
Greg Spencer4b4cff92020-01-30 09:31:07 -08001225 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001226 const Hash256(0x70AC6571B593A967, 0xF1CBAEC9BC02D02D, 0x93AD766D8290ADE6, 0x840139BF9F219019),
Dan Field24f39d42020-01-02 11:47:28 -08001227
Greg Spencer4b4cff92020-01-30 09:31:07 -08001228 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001229 const Hash256(0x5D87A78386DA2C43, 0xDDA8FEF2CA51438C, 0xE5A276FE28C6CF0A, 0xEBE89085B56665B6),
Dan Field24f39d42020-01-02 11:47:28 -08001230
Greg Spencer4b4cff92020-01-30 09:31:07 -08001231 // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001232 const Hash256(0x4D9F5E81F668DA44, 0xB20A77F8BF7BA2E1, 0xF384533B5AD58F07, 0xB3A2F93F8635CD96),
Dan Field24f39d42020-01-02 11:47:28 -08001233
1234
1235 // LEGACY ICONS
1236
1237 // dev/benchmarks/complex_layout/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1238 // dev/benchmarks/microbenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1239 // examples/flutter_view/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png
1240 // (not really sure where this came from, or why neither the template nor most examples use them)
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001241 const Hash256(0x6E645DC9ED913AAD, 0xB50ED29EEB16830D, 0xB32CA12F39121DB9, 0xB7BC1449DDDBF8B8),
Dan Field24f39d42020-01-02 11:47:28 -08001242
1243 // dev/benchmarks/macrobenchmarks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1244 // dev/integration_tests/codegen/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1245 // dev/integration_tests/ios_add2app/ios_add2app/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
1246 // dev/integration_tests/release_smoke_test/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001247 const Hash256(0xDEFAC77E08EC71EC, 0xA04CCA3C95D1FC33, 0xB9F26E1CB15CB051, 0x47DEFC79CDD7C158),
Dan Field24f39d42020-01-02 11:47:28 -08001248
1249 // examples/flutter_view/ios/Runner/ic_add.png
1250 // examples/platform_view/ios/Runner/ic_add.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001251 const Hash256(0x3CCE7450334675E2, 0xE3AABCA20B028993, 0x127BE82FE0EB3DFF, 0x8B027B3BAF052F2F),
Dan Field24f39d42020-01-02 11:47:28 -08001252
1253 // examples/image_list/images/coast.jpg
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001254 const Hash256(0xDA957FD30C51B8D2, 0x7D74C2C918692DC4, 0xD3C5C99BB00F0D6B, 0x5EBB30395A6EDE82),
Dan Field24f39d42020-01-02 11:47:28 -08001255
1256 // examples/image_list/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001257 const Hash256(0xB5792CA06F48A431, 0xD4379ABA2160BD5D, 0xE92339FC64C6A0D3, 0x417AA359634CD905),
Dan Field24f39d42020-01-02 11:47:28 -08001258
1259
1260 // TEST ASSETS
1261
1262 // dev/benchmarks/macrobenchmarks/assets/999x1000.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001263 const Hash256(0x553E9C36DFF3E610, 0x6A608BDE822A0019, 0xDE4F1769B6FBDB97, 0xBC3C20E26B839F59),
Dan Field24f39d42020-01-02 11:47:28 -08001264
1265 // dev/bots/test/analyze-test-input/root/packages/foo/serviceaccount.enc
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001266 const Hash256(0xA8100AE6AA1940D0, 0xB663BB31CD466142, 0xEBBDBD5187131B92, 0xD93818987832EB89),
Dan Field24f39d42020-01-02 11:47:28 -08001267
1268 // dev/automated_tests/icon/test.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001269 const Hash256(0xE214B4A0FEEEC6FA, 0x8E7AA8CC9BFBEC40, 0xBCDAC2F2DEBC950F, 0x75AF8EBF02BCE459),
Dan Field24f39d42020-01-02 11:47:28 -08001270
1271 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/drawable-land-xxhdpi/flutter_splash_screen.png
1272 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/mipmap-land-xxhdpi/flutter_splash_screen.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001273 const Hash256(0x2D4F8D7A3DFEF9D3, 0xA0C66938E169AB58, 0x8C6BBBBD1973E34E, 0x03C428416D010182),
Dan Field24f39d42020-01-02 11:47:28 -08001274
1275 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/drawable-xxhdpi/flutter_splash_screen.png
1276 // dev/integration_tests/android_splash_screens/splash_screen_kitchen_sink/android/app/src/main/res/mipmap-xxhdpi/flutter_splash_screen.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001277 const Hash256(0xCD46C01BAFA3B243, 0xA6AA1645EEDDE481, 0x143AC8ABAB1A0996, 0x22CAA9D41F74649A),
Dan Field24f39d42020-01-02 11:47:28 -08001278
1279 // dev/integration_tests/flutter_driver_screenshot_test/assets/red_square.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001280 const Hash256(0x40054377E1E084F4, 0x4F4410CE8F44C210, 0xABA945DFC55ED0EF, 0x23BDF9469E32F8D3),
Dan Field24f39d42020-01-02 11:47:28 -08001281
1282 // dev/integration_tests/flutter_driver_screenshot_test/test_driver/goldens/red_square_image/iPhone7,2.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001283 const Hash256(0x7F9D27C7BC418284, 0x01214E21CA886B2F, 0x40D9DA2B31AE7754, 0x71D68375F9C8A824),
Dan Field24f39d42020-01-02 11:47:28 -08001284
1285 // examples/flutter_view/assets/flutter-mark-square-64.png
1286 // examples/platform_view/assets/flutter-mark-square-64.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001287 const Hash256(0xF416B0D8AC552EC8, 0x819D1F492D1AB5E6, 0xD4F20CF45DB47C22, 0x7BB431FEFB5B67B2),
Dan Field24f39d42020-01-02 11:47:28 -08001288
1289 // packages/flutter_tools/test/data/intellij/plugins/Dart/lib/Dart.jar
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001290 const Hash256(0x576E489D788A13DB, 0xBF40E4A39A3DAB37, 0x15CCF0002032E79C, 0xD260C69B29E06646),
Dan Field24f39d42020-01-02 11:47:28 -08001291
1292 // packages/flutter_tools/test/data/intellij/plugins/flutter-intellij.jar
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001293 const Hash256(0x4C67221E25626CB2, 0x3F94E1F49D34E4CF, 0x3A9787A514924FC5, 0x9EF1E143E5BC5690),
Dan Field24f39d42020-01-02 11:47:28 -08001294
1295
Dan Field24f39d42020-01-02 11:47:28 -08001296 // MISCELLANEOUS
1297
1298 // dev/bots/serviceaccount.enc
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001299 const Hash256(0x1F19ADB4D80AFE8C, 0xE61899BA776B1A8D, 0xCA398C75F5F7050D, 0xFB0E72D7FBBBA69B),
Dan Field24f39d42020-01-02 11:47:28 -08001300
1301 // dev/docs/favicon.ico
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001302 const Hash256(0x67368CA1733E933A, 0xCA3BC56EF0695012, 0xE862C371AD4412F0, 0x3EC396039C609965),
Dan Field24f39d42020-01-02 11:47:28 -08001303
1304 // dev/snippets/assets/code_sample.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001305 const Hash256(0xAB2211A47BDA001D, 0x173A52FD9C75EBC7, 0xE158942FFA8243AD, 0x2A148871990D4297),
Dan Field24f39d42020-01-02 11:47:28 -08001306
1307 // dev/snippets/assets/code_snippet.png
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001308 const Hash256(0xDEC70574DA46DFBB, 0xFA657A771F3E1FBD, 0xB265CFC6B2AA5FE3, 0x93BA4F325D1520BA),
Dan Field24f39d42020-01-02 11:47:28 -08001309
1310 // packages/flutter_tools/static/Ahem.ttf
Alexandre Ardhuinf5a99022020-04-06 22:36:01 +02001311 const Hash256(0x63D2ABD0041C3E3B, 0x4B52AD8D382353B5, 0x3C51C6785E76CE56, 0xED9DACAD2D2E31C4),
Dan Field24f39d42020-01-02 11:47:28 -08001312};
1313
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001314Future<void> verifyNoBinaries(String workingDirectory, { Set<Hash256>? legacyBinaries }) async {
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001315 // Please do not add anything to the _legacyBinaries set above.
Dan Field24f39d42020-01-02 11:47:28 -08001316 // We have a policy of not checking in binaries into this repository.
stuartmorgan685e9d12020-03-23 10:42:26 -07001317 // If you are adding/changing template images, use the flutter_template_images
1318 // package and a .img.tmpl placeholder instead.
1319 // If you have other binaries to add, please consult Hixie for advice.
Dan Field24f39d42020-01-02 11:47:28 -08001320 assert(
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001321 _legacyBinaries
Dan Field24f39d42020-01-02 11:47:28 -08001322 .expand<int>((Hash256 hash) => <int>[hash.a, hash.b, hash.c, hash.d])
Casey Hillers781cd4d2020-02-11 18:25:17 -08001323 .reduce((int value, int element) => value ^ element) == 0x606B51C908B40BFA // Please do not modify this line.
Dan Field24f39d42020-01-02 11:47:28 -08001324 );
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001325 legacyBinaries ??= _legacyBinaries;
Dan Field24f39d42020-01-02 11:47:28 -08001326 if (!Platform.isWindows) { // TODO(ianh): Port this to Windows
Ian Hicksonbde9f112021-11-19 13:13:05 -08001327 final List<File> files = await _gitFiles(workingDirectory);
Dan Field24f39d42020-01-02 11:47:28 -08001328 final List<String> problems = <String>[];
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +01001329 for (final File file in files) {
Dan Field24f39d42020-01-02 11:47:28 -08001330 final Uint8List bytes = file.readAsBytesSync();
1331 try {
1332 utf8.decode(bytes);
1333 } on FormatException catch (error) {
Paul Berryc32d1382020-05-04 09:10:20 -07001334 final Digest digest = sha256.convert(bytes);
Michael Goderbauer584fd5f2020-06-16 09:15:43 -07001335 if (!legacyBinaries.contains(Hash256.fromDigest(digest)))
Paul Berryc32d1382020-05-04 09:10:20 -07001336 problems.add('${file.path}:${error.offset}: file is not valid UTF-8');
Dan Field24f39d42020-01-02 11:47:28 -08001337 }
1338 }
1339 if (problems.isNotEmpty) {
1340 exitWithError(<String>[
1341 ...problems,
1342 'All files in this repository must be UTF-8. In particular, images and other binaries',
1343 'must not be checked into this repository. This is because we are very sensitive to the',
1344 'size of the repository as it is distributed to all our developers. If you have a binary',
1345 'to which you need access, you should consider how to fetch it from another repository;',
1346 'for example, the "assets-for-api-docs" repository is used for images in API docs.',
1347 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -08001348 }
1349 }
1350}
1351
1352
1353// UTILITY FUNCTIONS
1354
1355bool _listEquals<T>(List<T> a, List<T> b) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001356 assert(a != null);
1357 assert(b != null);
1358 if (a.length != b.length)
1359 return false;
1360 for (int index = 0; index < a.length; index += 1) {
1361 if (a[index] != b[index])
1362 return false;
1363 }
1364 return true;
1365}
1366
Yuqian Lifb552ed2020-11-07 05:19:02 -08001367Future<List<File>> _gitFiles(String workingDirectory, {bool runSilently = true}) async {
1368 final EvalResult evalResult = await _evalCommand(
1369 'git', <String>['ls-files', '-z'],
1370 workingDirectory: workingDirectory,
1371 runSilently: runSilently,
1372 );
1373 if (evalResult.exitCode != 0) {
1374 exitWithError(<String>[
Dan Field9c0bd182021-06-23 13:08:51 -07001375 'git ls-files failed with exit code ${evalResult.exitCode}',
Yuqian Lifb552ed2020-11-07 05:19:02 -08001376 '${bold}stdout:$reset',
1377 evalResult.stdout,
1378 '${bold}stderr:$reset',
1379 evalResult.stderr,
1380 ]);
1381 }
1382 final List<String> filenames = evalResult
1383 .stdout
1384 .split('\x00');
1385 assert(filenames.last.isEmpty); // git ls-files gives a trailing blank 0x00
1386 filenames.removeLast();
1387 return filenames
1388 .map<File>((String filename) => File(path.join(workingDirectory, filename)))
1389 .toList();
1390}
1391
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001392Stream<File> _allFiles(String workingDirectory, String? extension, { required int minimumMatches }) async* {
Yuqian Lifb552ed2020-11-07 05:19:02 -08001393 final Set<String> gitFileNamesSet = <String>{};
1394 gitFileNamesSet.addAll((await _gitFiles(workingDirectory)).map((File f) => path.canonicalize(f.absolute.path)));
1395
Dan Field24f39d42020-01-02 11:47:28 -08001396 assert(extension == null || !extension.startsWith('.'), 'Extension argument should not start with a period.');
Ian Hickson62e4ab82019-11-15 19:21:53 -08001397 final Set<FileSystemEntity> pending = <FileSystemEntity>{ Directory(workingDirectory) };
Dan Field24f39d42020-01-02 11:47:28 -08001398 int matches = 0;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001399 while (pending.isNotEmpty) {
1400 final FileSystemEntity entity = pending.first;
1401 pending.remove(entity);
Ian Hickson449f4a62019-11-27 15:04:02 -08001402 if (path.extension(entity.path) == '.tmpl')
1403 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001404 if (entity is File) {
Yuqian Lifb552ed2020-11-07 05:19:02 -08001405 if (!gitFileNamesSet.contains(path.canonicalize(entity.absolute.path)))
1406 continue;
Ian Hickson449f4a62019-11-27 15:04:02 -08001407 if (_isGeneratedPluginRegistrant(entity))
1408 continue;
1409 if (path.basename(entity.path) == 'flutter_export_environment.sh')
1410 continue;
1411 if (path.basename(entity.path) == 'gradlew.bat')
1412 continue;
Darren Austin4253e422020-09-18 14:37:04 -07001413 if (path.basename(entity.path) == '.DS_Store')
1414 continue;
Dan Field24f39d42020-01-02 11:47:28 -08001415 if (extension == null || path.extension(entity.path) == '.$extension') {
1416 matches += 1;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001417 yield entity;
Dan Field24f39d42020-01-02 11:47:28 -08001418 }
Ian Hickson62e4ab82019-11-15 19:21:53 -08001419 } else if (entity is Directory) {
1420 if (File(path.join(entity.path, '.dartignore')).existsSync())
1421 continue;
1422 if (path.basename(entity.path) == '.git')
1423 continue;
Jim Graham210f7682020-07-29 17:16:26 -07001424 if (path.basename(entity.path) == '.idea')
1425 continue;
Dan Field24f39d42020-01-02 11:47:28 -08001426 if (path.basename(entity.path) == '.gradle')
1427 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001428 if (path.basename(entity.path) == '.dart_tool')
1429 continue;
Darren Austin753b8482020-07-20 18:51:04 -07001430 if (path.basename(entity.path) == '.idea')
1431 continue;
Ian Hickson449f4a62019-11-27 15:04:02 -08001432 if (path.basename(entity.path) == 'build')
1433 continue;
Ian Hickson62e4ab82019-11-15 19:21:53 -08001434 pending.addAll(entity.listSync());
1435 }
1436 }
Dan Field24f39d42020-01-02 11:47:28 -08001437 assert(matches >= minimumMatches, 'Expected to find at least $minimumMatches files with extension ".$extension" in "$workingDirectory", but only found $matches.');
Ian Hicksoneae05c72019-11-14 13:19:40 -08001438}
1439
1440class EvalResult {
1441 EvalResult({
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001442 required this.stdout,
1443 required this.stderr,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001444 this.exitCode = 0,
1445 });
1446
1447 final String stdout;
1448 final String stderr;
1449 final int exitCode;
1450}
1451
Dan Field24f39d42020-01-02 11:47:28 -08001452// TODO(ianh): Refactor this to reuse the code in run_command.dart
Ian Hicksoneae05c72019-11-14 13:19:40 -08001453Future<EvalResult> _evalCommand(String executable, List<String> arguments, {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001454 required String workingDirectory,
1455 Map<String, String>? environment,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001456 bool allowNonZeroExit = false,
Yuqian Lifb552ed2020-11-07 05:19:02 -08001457 bool runSilently = false,
Ian Hicksoneae05c72019-11-14 13:19:40 -08001458}) async {
1459 final String commandDescription = '${path.relative(executable, from: workingDirectory)} ${arguments.join(' ')}';
1460 final String relativeWorkingDir = path.relative(workingDirectory);
Yuqian Lifb552ed2020-11-07 05:19:02 -08001461
1462 if (!runSilently) {
1463 printProgress('RUNNING', relativeWorkingDir, commandDescription);
1464 }
Ian Hicksoneae05c72019-11-14 13:19:40 -08001465
1466 final Stopwatch time = Stopwatch()..start();
1467 final Process process = await Process.start(executable, arguments,
1468 workingDirectory: workingDirectory,
1469 environment: environment,
1470 );
1471
1472 final Future<List<List<int>>> savedStdout = process.stdout.toList();
1473 final Future<List<List<int>>> savedStderr = process.stderr.toList();
1474 final int exitCode = await process.exitCode;
1475 final EvalResult result = EvalResult(
1476 stdout: utf8.decode((await savedStdout).expand<int>((List<int> ints) => ints).toList()),
1477 stderr: utf8.decode((await savedStderr).expand<int>((List<int> ints) => ints).toList()),
1478 exitCode: exitCode,
1479 );
1480
Yuqian Lifb552ed2020-11-07 05:19:02 -08001481 if (!runSilently) {
1482 print('$clock ELAPSED TIME: $bold${prettyPrintDuration(time.elapsed)}$reset for $commandDescription in $relativeWorkingDir');
1483 }
Ian Hicksoneae05c72019-11-14 13:19:40 -08001484
1485 if (exitCode != 0 && !allowNonZeroExit) {
1486 stderr.write(result.stderr);
Dan Field24f39d42020-01-02 11:47:28 -08001487 exitWithError(<String>[
1488 '${bold}ERROR:$red Last command exited with $exitCode.$reset',
1489 '${bold}Command:$red $commandDescription$reset',
1490 '${bold}Relative working directory:$red $relativeWorkingDir$reset',
1491 ]);
Ian Hicksoneae05c72019-11-14 13:19:40 -08001492 }
1493
1494 return result;
1495}
1496
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001497Future<void> _checkConsumerDependencies() async {
1498 final ProcessResult result = await Process.run(flutter, <String>[
1499 'update-packages',
1500 '--transitive-closure',
1501 '--consumer-only',
1502 ]);
1503 if (result.exitCode != 0) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001504 print(result.stdout as Object);
1505 print(result.stderr as Object);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001506 exit(result.exitCode);
1507 }
Ian Hickson126cd732021-10-04 10:28:03 -07001508 final Set<String> dependencies = <String>{};
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001509 for (final String line in result.stdout.toString().split('\n')) {
1510 if (!line.contains('->')) {
1511 continue;
1512 }
1513 final List<String> parts = line.split('->');
1514 final String name = parts[0].trim();
Ian Hickson126cd732021-10-04 10:28:03 -07001515 dependencies.add(name);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001516 }
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001517
Ian Hickson126cd732021-10-04 10:28:03 -07001518 final Set<String> removed = kCorePackageAllowList.difference(dependencies);
1519 final Set<String> added = dependencies.difference(kCorePackageAllowList);
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001520
Ian Hickson126cd732021-10-04 10:28:03 -07001521 String plural(int n, String s, String p) => n == 1 ? s : p;
1522
1523 if (added.isNotEmpty) {
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001524 exitWithError(<String>[
Ian Hickson126cd732021-10-04 10:28:03 -07001525 'The transitive closure of package dependencies contains ${plural(added.length, "a non-allowlisted package", "non-allowlisted packages")}:',
1526 ' ${added.join(', ')}',
1527 'We strongly desire to keep the number of dependencies to a minimum and',
1528 'therefore would much prefer not to add new dependencies.',
1529 'See dev/bots/allowlist.dart for instructions on how to update the package',
1530 'allowlist if you nonetheless believe this is a necessary addition.',
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001531 ]);
1532 }
1533
Ian Hickson126cd732021-10-04 10:28:03 -07001534 if (removed.isNotEmpty) {
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001535 exitWithError(<String>[
Ian Hickson126cd732021-10-04 10:28:03 -07001536 'Excellent news! ${plural(removed.length, "A package dependency has been removed!", "Multiple package dependencies have been removed!")}',
1537 ' ${removed.join(', ')}',
1538 'To make sure we do not accidentally add ${plural(removed.length, "this dependency", "these dependencies")} back in the future,',
1539 'please remove ${plural(removed.length, "this", "these")} packages from the allow-list in dev/bots/allowlist.dart.',
1540 'Thanks!',
Jonah Williamsf6f59c52021-04-16 14:29:32 -07001541 ]);
1542 }
1543}
1544
Jonah Williamsfda40942021-12-14 10:51:30 -08001545const String _kDebugOnlyAnnotation = '@_debugOnly';
1546final RegExp _nullInitializedField = RegExp(r'kDebugMode \? [\w\<\> ,{}()]+ : null;');
1547
1548Future<void> verifyNullInitializedDebugExpensiveFields(String workingDirectory, {int minimumMatches = 400}) async {
1549 final String flutterLib = path.join(workingDirectory, 'packages', 'flutter', 'lib');
1550 final List<File> files = await _allFiles(flutterLib, 'dart', minimumMatches: minimumMatches)
1551 .toList();
1552 final List<String> errors = <String>[];
1553 for (final File file in files) {
1554 final List<String> lines = file.readAsLinesSync();
1555 for (int i = 0; i < lines.length; i += 1) {
1556 final String line = lines[i];
1557 if (!line.contains(_kDebugOnlyAnnotation)) {
1558 continue;
1559 }
1560 final String nextLine = lines[i + 1];
1561 if (_nullInitializedField.firstMatch(nextLine) == null) {
1562 errors.add('${file.path} L$i');
1563 }
1564 }
1565 }
1566
1567 if (errors.isNotEmpty) {
1568 exitWithError(<String>[
1569 '${bold}ERROR: ${red}fields annotated with @_debugOnly must null initialize.$reset',
1570 'to ensure both the field and initializer are removed from profile/release mode.',
1571 'These fields should be written as:\n',
1572 'field = kDebugMode ? <DebugValue> : null;\n',
1573 'Errors were found in the following files:',
1574 ...errors,
1575 ]);
1576 }
1577}
1578
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001579Future<CommandResult> _runFlutterAnalyze(String workingDirectory, {
Ian Hicksoneae05c72019-11-14 13:19:40 -08001580 List<String> options = const <String>[],
Dan Field24f39d42020-01-02 11:47:28 -08001581}) async {
Michael Goderbauercb867bb2021-03-05 18:38:15 -08001582 return runCommand(
Ian Hicksoneae05c72019-11-14 13:19:40 -08001583 flutter,
Jenn Magder95d7d672021-04-18 09:29:02 -07001584 <String>['analyze', ...options],
Ian Hicksoneae05c72019-11-14 13:19:40 -08001585 workingDirectory: workingDirectory,
1586 );
1587}
1588
Greg Spencer35fcd902019-01-14 13:49:50 -08001589final RegExp _importPattern = RegExp(r'''^\s*import (['"])package:flutter/([^.]+)\.dart\1''');
Ian Hickson58939b72019-02-12 12:29:36 -08001590final RegExp _importMetaPattern = RegExp(r'''^\s*import (['"])package:meta/meta\.dart\1''');
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001591
Yuqian Lifb552ed2020-11-07 05:19:02 -08001592Future<Set<String>> _findFlutterDependencies(String srcPath, List<String> errors, { bool checkForMeta = false }) async {
Michael Goderbauercb867bb2021-03-05 18:38:15 -08001593 return _allFiles(srcPath, 'dart', minimumMatches: 1)
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001594 .map<Set<String>>((File file) {
1595 final Set<String> result = <String>{};
Alexandre Ardhuin4f9b6cf2020-01-07 16:32:04 +01001596 for (final String line in file.readAsLinesSync()) {
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001597 Match? match = _importPattern.firstMatch(line);
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001598 if (match != null)
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001599 result.add(match.group(2)!);
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001600 if (checkForMeta) {
1601 match = _importMetaPattern.firstMatch(line);
1602 if (match != null) {
1603 errors.add(
1604 '${file.path}\nThis package imports the ${yellow}meta$reset package.\n'
1605 'You should instead import the "foundation.dart" library.'
1606 );
1607 }
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001608 }
1609 }
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001610 return result;
1611 })
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001612 .reduce((Set<String>? value, Set<String> element) {
Alexandre Ardhuinec1a0152019-12-05 22:34:06 +01001613 value ??= <String>{};
1614 value.addAll(element);
1615 return value;
1616 });
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001617}
1618
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001619List<T>? _deepSearch<T>(Map<T, Set<T>> map, T start, [ Set<T>? seen ]) {
LongCatIsLooongd291de02020-01-09 10:25:58 -08001620 if (map[start] == null)
1621 return null; // We catch these separately.
1622
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001623 for (final T key in map[start]!) {
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001624 if (key == start)
1625 continue; // we catch these separately
1626 if (seen != null && seen.contains(key))
1627 return <T>[start, key];
Michael Goderbauer045ba2b2021-07-19 13:59:15 -07001628 final List<T>? result = _deepSearch<T>(
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001629 map,
1630 key,
Alexandre Ardhuin919dcf52019-06-27 21:23:16 +02001631 <T>{
1632 if (seen == null) start else ...seen,
1633 key,
1634 },
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001635 );
1636 if (result != null) {
1637 result.insert(0, start);
1638 // Only report the shortest chains.
1639 // For example a->b->a, rather than c->a->b->a.
1640 // Since we visit every node, we know the shortest chains are those
1641 // that start and end on the loop.
1642 if (result.first == result.last)
1643 return result;
1644 }
1645 }
1646 return null;
1647}
1648
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001649bool _isGeneratedPluginRegistrant(File file) {
1650 final String filename = path.basename(file.path);
1651 return !file.path.contains('.pub-cache')
1652 && (filename == 'GeneratedPluginRegistrant.java' ||
1653 filename == 'GeneratedPluginRegistrant.h' ||
Jonah Williams0b3f5cf2020-04-21 20:39:36 -07001654 filename == 'GeneratedPluginRegistrant.m' ||
Jonah Williams14722d32020-09-28 10:07:35 -07001655 filename == 'generated_plugin_registrant.dart' ||
1656 filename == 'generated_plugin_registrant.h');
Alexander Aprelev391e91c2018-08-30 07:30:25 -07001657}