| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import '../base/common.dart'; |
| import '../base/deferred_component.dart'; |
| import '../base/file_system.dart'; |
| import '../base/logger.dart'; |
| import '../base/platform.dart'; |
| import '../base/terminal.dart'; |
| |
| /// A class to configure and run deferred component setup verification checks |
| /// and tasks. |
| /// |
| /// Once constructed, checks and tasks can be executed by calling the respective |
| /// methods. The results of the checks are stored internally and can be |
| /// displayed to the user by calling [displayResults]. |
| /// |
| /// The results of each check are handled internally as they are not meant to |
| /// be run isolated. |
| abstract class DeferredComponentsValidator { |
| DeferredComponentsValidator(this.projectDir, this.logger, this.platform, { |
| this.exitOnFail = true, |
| String? title, |
| }) : outputDir = projectDir |
| .childDirectory('build') |
| .childDirectory(kDeferredComponentsTempDirectory), |
| inputs = <File>[], |
| outputs = <File>[], |
| title = title ?? 'Deferred components setup verification', |
| generatedFiles = <String>[], |
| modifiedFiles = <String>[], |
| invalidFiles = <String, String>{}, |
| diffLines = <String>[]; |
| |
| /// Logger to use for [displayResults] output. |
| final Logger logger; |
| |
| final Platform platform; |
| |
| /// When true, failed checks and tasks will result in [attemptToolExit] |
| /// triggering [throwToolExit]. |
| final bool exitOnFail; |
| |
| /// The name of the golden file that tracks the latest loading units |
| /// generated. |
| static const String kLoadingUnitsCacheFileName = 'deferred_components_loading_units.yaml'; |
| /// The directory in the build folder to generate missing/modified files into. |
| static const String kDeferredComponentsTempDirectory = 'android_deferred_components_setup_files'; |
| |
| /// The title printed at the top of the results of [displayResults] |
| final String title; |
| |
| /// The root directory of the flutter project. |
| final Directory projectDir; |
| |
| /// The temporary directory that the validator writes recommended files into. |
| final Directory outputDir; |
| |
| /// Files that were newly generated by this validator. |
| final List<String> generatedFiles; |
| |
| /// Existing files that were modified by this validator. |
| final List<String> modifiedFiles; |
| |
| /// Files that were invalid and unable to be checked. These files are input |
| /// files that the validator tries to read rather than output files the |
| /// validator generates. The key is the file name and the value is the message |
| /// or reason it was invalid. |
| final Map<String, String> invalidFiles; |
| |
| // TODO(garyq): implement the diff task. |
| /// Output of the diff task. |
| final List<String> diffLines; |
| |
| /// Tracks the new and missing loading units. |
| Map<String, dynamic>? loadingUnitComparisonResults; |
| |
| /// All files read by the validator. |
| final List<File> inputs; |
| /// All files output by the validator. |
| final List<File> outputs; |
| |
| /// Returns true if there were any recommended changes that should |
| /// be applied. |
| /// |
| /// Returns false if no problems or recommendations were detected. |
| /// |
| /// If no checks are run, then this will default to false and will remain so |
| /// until a failing check finishes running. |
| bool get changesNeeded => generatedFiles.isNotEmpty |
| || modifiedFiles.isNotEmpty |
| || invalidFiles.isNotEmpty |
| || (loadingUnitComparisonResults != null && !(loadingUnitComparisonResults!['match'] as bool)); |
| |
| /// Handles the results of all executed checks by calling [displayResults] and |
| /// [attemptToolExit]. |
| /// |
| /// This should be called after all desired checks and tasks are executed. |
| void handleResults() { |
| displayResults(); |
| attemptToolExit(); |
| } |
| |
| static const String _thickDivider = '================================================================================='; |
| static const String _thinDivider = '---------------------------------------------------------------------------------'; |
| |
| /// Displays the results of this validator's executed checks and tasks in a |
| /// human readable format. |
| /// |
| /// All checks that are desired should be run before calling this method. |
| void displayResults() { |
| if (changesNeeded) { |
| logger.printStatus(_thickDivider); |
| logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true); |
| logger.printStatus(_thickDivider); |
| // Log any file reading/existence errors. |
| if (invalidFiles.isNotEmpty) { |
| logger.printStatus('Errors checking the following files:\n', emphasis: true); |
| for (final String key in invalidFiles.keys) { |
| logger.printStatus(' - $key: ${invalidFiles[key]}\n'); |
| } |
| } |
| // Log diff file contents, with color highlighting |
| if (diffLines.isNotEmpty) { |
| logger.printStatus('Diff between `android` and expected files:', emphasis: true); |
| logger.printStatus(''); |
| for (final String line in diffLines) { |
| // We only care about diffs in files that have |
| // counterparts. |
| if (line.startsWith('Only in android')) { |
| continue; |
| } |
| TerminalColor color = TerminalColor.grey; |
| if (line.startsWith('+')) { |
| color = TerminalColor.green; |
| } else if (line.startsWith('-')) { |
| color = TerminalColor.red; |
| } |
| logger.printStatus(line, color: color); |
| } |
| logger.printStatus(''); |
| } |
| // Log any newly generated and modified files. |
| if (generatedFiles.isNotEmpty) { |
| logger.printStatus('Newly generated android files:', emphasis: true); |
| for (final String filePath in generatedFiles) { |
| final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1); |
| logger.printStatus(' - $shortenedPath', color: TerminalColor.grey); |
| } |
| logger.printStatus(''); |
| } |
| if (modifiedFiles.isNotEmpty) { |
| logger.printStatus('Modified android files:', emphasis: true); |
| for (final String filePath in modifiedFiles) { |
| final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1); |
| logger.printStatus(' - $shortenedPath', color: TerminalColor.grey); |
| } |
| logger.printStatus(''); |
| } |
| if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) { |
| logger.printStatus(''' |
| The above files have been placed into `build/$kDeferredComponentsTempDirectory`, |
| a temporary directory. The files should be reviewed and moved into the project's |
| `android` directory.'''); |
| if (diffLines.isNotEmpty && !platform.isWindows) { |
| logger.printStatus(r''' |
| |
| The recommended changes can be quickly applied by running: |
| |
| $ patch -p0 < build/setup_deferred_components.diff |
| '''); |
| } |
| logger.printStatus('$_thinDivider\n'); |
| } |
| // Log loading unit golden changes, if any. |
| if (loadingUnitComparisonResults != null) { |
| if ((loadingUnitComparisonResults!['new'] as List<LoadingUnit>).isNotEmpty) { |
| logger.printStatus('New loading units were found:', emphasis: true); |
| for (final LoadingUnit unit in loadingUnitComparisonResults!['new'] as List<LoadingUnit>) { |
| logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2); |
| } |
| logger.printStatus(''); |
| } |
| if ((loadingUnitComparisonResults!['missing'] as Set<LoadingUnit>).isNotEmpty) { |
| logger.printStatus('Previously existing loading units no longer exist:', emphasis: true); |
| for (final LoadingUnit unit in loadingUnitComparisonResults!['missing'] as Set<LoadingUnit>) { |
| logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2); |
| } |
| logger.printStatus(''); |
| } |
| if (loadingUnitComparisonResults!['match'] as bool) { |
| logger.printStatus('No change in generated loading units.\n'); |
| } else { |
| logger.printStatus(''' |
| It is recommended to verify that the changed loading units are expected |
| and to update the `deferred-components` section in `pubspec.yaml` to |
| incorporate any changes. The full list of generated loading units can be |
| referenced in the $kLoadingUnitsCacheFileName file located alongside |
| pubspec.yaml. |
| |
| This loading unit check will not fail again on the next build attempt |
| if no additional changes to the loading units are detected. |
| $_thinDivider\n'''); |
| } |
| } |
| // TODO(garyq): Add link to web tutorial/guide once it is written. |
| logger.printStatus(''' |
| Setup verification can be skipped by passing the `--no-validate-deferred-components` |
| flag, however, doing so may put your app at risk of not functioning even if the |
| build is successful. |
| $_thickDivider'''); |
| return; |
| } |
| logger.printStatus('$title passed.'); |
| } |
| |
| void attemptToolExit() { |
| if (exitOnFail && changesNeeded) { |
| throwToolExit('Setup for deferred components incomplete. See recommended actions.', exitCode: 1); |
| } |
| } |
| } |