[flutter_tools] Refactor DeferredComponents validator and add build target (#76400)
This commit is contained in:
parent
d60e9de75d
commit
9df362a73f
@ -0,0 +1,328 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:xml/xml.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../base/deferred_component.dart';
|
||||
import '../base/error_handling_io.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import 'deferred_components_validator.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].
|
||||
class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator {
|
||||
/// Constructs a validator instance.
|
||||
///
|
||||
/// The [env] property is used to locate the project files that are checked.
|
||||
///
|
||||
/// The [templatesDir] parameter is optional. If null, the tool's default
|
||||
/// templates directory will be used.
|
||||
///
|
||||
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
|
||||
/// methods will exit the tool when this validator detects a recommended
|
||||
/// change. This defaults to true.
|
||||
DeferredComponentsGenSnapshotValidator(Environment env, {
|
||||
bool exitOnFail = true,
|
||||
String title,
|
||||
}) : super(env, exitOnFail: exitOnFail, title: title);
|
||||
|
||||
// The key used to identify the metadata element as the loading unit id to
|
||||
// deferred component mapping.
|
||||
static const String _mappingKey = 'io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping';
|
||||
|
||||
/// Checks if the base module `app`'s `AndroidManifest.xml` contains the
|
||||
/// required meta-data that maps loading units to deferred components.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// Flutter engine uses a manifest meta-data mapping to determine which
|
||||
/// deferred component includes a particular loading unit id. This method
|
||||
/// checks if `app`'s `AndroidManifest.xml` contains this metadata. If not, it
|
||||
/// will generate a modified AndroidManifest.xml with the correct metadata
|
||||
/// entry.
|
||||
///
|
||||
/// An example mapping:
|
||||
///
|
||||
/// 2:componentA,3:componentB,4:componentC
|
||||
///
|
||||
/// Where loading unit 2 is included in componentA, loading unit 3 is included
|
||||
/// in componentB, and loading unit 4 is included in componentC.
|
||||
bool checkAppAndroidManifestComponentLoadingUnitMapping(List<DeferredComponent> components, List<LoadingUnit> generatedLoadingUnits) {
|
||||
final Directory androidDir = env.projectDir.childDirectory('android');
|
||||
inputs.add(env.projectDir.childFile('pubspec.yaml'));
|
||||
|
||||
// We do not use the Xml package to handle the writing, as we do not want to
|
||||
// erase any user applied formatting and comments. The changes can be
|
||||
// applied with dart io and custom parsing.
|
||||
final File appManifestFile = androidDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
inputs.add(appManifestFile);
|
||||
if (!appManifestFile.existsSync()) {
|
||||
invalidFiles[appManifestFile.path] = 'Error: $appManifestFile does not '
|
||||
'exist or could not be found. Please ensure an AndroidManifest.xml '
|
||||
'exists for the app\'s base module.';
|
||||
return false;
|
||||
}
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(appManifestFile.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
invalidFiles[appManifestFile.path] = 'Error parsing $appManifestFile '
|
||||
'Please ensure that the android manifest is a valid XML document and '
|
||||
'try again.';
|
||||
return false;
|
||||
} on FileSystemException {
|
||||
invalidFiles[appManifestFile.path] = 'Error reading $appManifestFile '
|
||||
'even though it exists. Please ensure that you have read permission for '
|
||||
'this file and try again.';
|
||||
return false;
|
||||
}
|
||||
// Create loading unit mapping.
|
||||
final Map<int, String> mapping = <int, String>{};
|
||||
for (final DeferredComponent component in components) {
|
||||
component.assignLoadingUnits(generatedLoadingUnits);
|
||||
for (final LoadingUnit unit in component.loadingUnits) {
|
||||
if (!mapping.containsKey(unit.id)) {
|
||||
mapping[unit.id] = component.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Encode the mapping as a string.
|
||||
final StringBuffer mappingBuffer = StringBuffer();
|
||||
for (final int key in mapping.keys) {
|
||||
mappingBuffer.write('$key:${mapping[key]},');
|
||||
}
|
||||
String encodedMapping = mappingBuffer.toString();
|
||||
// remove trailing comma.
|
||||
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
|
||||
// Check for existing metadata entry and see if needs changes.
|
||||
bool exists = false;
|
||||
bool modified = false;
|
||||
for (final XmlElement metaData in document.findAllElements('meta-data')) {
|
||||
final String name = metaData.getAttribute('android:name');
|
||||
if (name == _mappingKey) {
|
||||
exists = true;
|
||||
final String storedMappingString = metaData.getAttribute('android:value');
|
||||
if (storedMappingString != encodedMapping) {
|
||||
metaData.setAttribute('android:value', encodedMapping);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
// Create an meta-data XmlElement that contains the mapping.
|
||||
final XmlElement mappingMetadataElement = XmlElement(XmlName.fromString('meta-data'),
|
||||
<XmlAttribute>[
|
||||
XmlAttribute(XmlName.fromString('android:name'), _mappingKey),
|
||||
XmlAttribute(XmlName.fromString('android:value'), encodedMapping),
|
||||
],
|
||||
);
|
||||
for (final XmlElement application in document.findAllElements('application')) {
|
||||
application.children.add(mappingMetadataElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists || modified) {
|
||||
final File manifestOutput = outputDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
ErrorHandlingFileSystem.deleteIfExists(manifestOutput);
|
||||
manifestOutput.createSync(recursive: true);
|
||||
manifestOutput.writeAsStringSync(document.toXmlString(pretty: true), flush: true);
|
||||
modifiedFiles.add(manifestOutput.path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Compares the provided loading units against the contents of the
|
||||
/// `deferred_components_loading_units.yaml` file.
|
||||
///
|
||||
/// Returns true if a loading unit cache file exists and all loading units
|
||||
/// match, and false otherwise.
|
||||
///
|
||||
/// This method will parse the cached loading units file if it exists and
|
||||
/// compare it to the provided generatedLoadingUnits. It will distinguish
|
||||
/// between newly added loading units and no longer existing loading units. If
|
||||
/// the cache file does not exist, then all generatedLoadingUnits will be
|
||||
/// considered new.
|
||||
bool checkAgainstLoadingUnitsCache(
|
||||
List<LoadingUnit> generatedLoadingUnits) {
|
||||
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
|
||||
loadingUnitComparisonResults = <String, dynamic>{};
|
||||
final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{};
|
||||
final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[];
|
||||
if (generatedLoadingUnits == null || cachedLoadingUnits == null) {
|
||||
loadingUnitComparisonResults['new'] = newLoadingUnits;
|
||||
loadingUnitComparisonResults['missing'] = unmatchedLoadingUnits;
|
||||
loadingUnitComparisonResults['match'] = false;
|
||||
return false;
|
||||
}
|
||||
unmatchedLoadingUnits.addAll(cachedLoadingUnits);
|
||||
final Set<int> addedNewIds = <int>{};
|
||||
for (final LoadingUnit genUnit in generatedLoadingUnits) {
|
||||
bool matched = false;
|
||||
for (final LoadingUnit cacheUnit in cachedLoadingUnits) {
|
||||
if (genUnit.equalsIgnoringPath(cacheUnit)) {
|
||||
matched = true;
|
||||
unmatchedLoadingUnits.remove(cacheUnit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched && !addedNewIds.contains(genUnit.id)) {
|
||||
newLoadingUnits.add(genUnit);
|
||||
addedNewIds.add(genUnit.id);
|
||||
}
|
||||
}
|
||||
loadingUnitComparisonResults['new'] = newLoadingUnits;
|
||||
loadingUnitComparisonResults['missing'] = unmatchedLoadingUnits;
|
||||
loadingUnitComparisonResults['match'] = newLoadingUnits.isEmpty && unmatchedLoadingUnits.isEmpty;
|
||||
return loadingUnitComparisonResults['match'] as bool;
|
||||
}
|
||||
|
||||
List<LoadingUnit> _parseLodingUnitsCache(File cacheFile) {
|
||||
final List<LoadingUnit> loadingUnits = <LoadingUnit>[];
|
||||
inputs.add(cacheFile);
|
||||
if (!cacheFile.existsSync()) {
|
||||
return loadingUnits;
|
||||
}
|
||||
final YamlMap data = loadYaml(cacheFile.readAsStringSync()) as YamlMap;
|
||||
// validate yaml format.
|
||||
if (!data.containsKey('loading-units')) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, \'loading-units\' '
|
||||
'entry did not exist.';
|
||||
return loadingUnits;
|
||||
} else {
|
||||
if (data['loading-units'] is! YamlList && data['loading-units'] != null) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, \'loading-units\' '
|
||||
'is not a list.';
|
||||
return loadingUnits;
|
||||
}
|
||||
if (data['loading-units'] != null) {
|
||||
for (final dynamic loadingUnitData in data['loading-units']) {
|
||||
if (loadingUnitData is! YamlMap) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, \'loading-units\' '
|
||||
'is not a list of maps.';
|
||||
return loadingUnits;
|
||||
}
|
||||
final YamlMap loadingUnitDataMap = loadingUnitData as YamlMap;
|
||||
if (loadingUnitDataMap['id'] == null) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, all '
|
||||
'loading units must have an \'id\'';
|
||||
return loadingUnits;
|
||||
}
|
||||
if (loadingUnitDataMap['libraries'] != null) {
|
||||
if (loadingUnitDataMap['libraries'] is! YamlList) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, \'libraries\' '
|
||||
'is not a list.';
|
||||
return loadingUnits;
|
||||
}
|
||||
for (final dynamic node in loadingUnitDataMap['libraries'] as YamlList) {
|
||||
if (node is! String) {
|
||||
invalidFiles[cacheFile.path] = 'Invalid loading units yaml file, \'libraries\' '
|
||||
'is not a list of strings.';
|
||||
return loadingUnits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse out validated yaml.
|
||||
if (data.containsKey('loading-units')) {
|
||||
if (data['loading-units'] != null) {
|
||||
for (final dynamic loadingUnitData in data['loading-units']) {
|
||||
final YamlMap loadingUnitDataMap = loadingUnitData as YamlMap;
|
||||
final List<String> libraries = <String>[];
|
||||
if (loadingUnitDataMap['libraries'] != null) {
|
||||
for (final dynamic node in loadingUnitDataMap['libraries'] as YamlList) {
|
||||
libraries.add(node as String);
|
||||
}
|
||||
}
|
||||
loadingUnits.add(
|
||||
LoadingUnit(
|
||||
id: loadingUnitDataMap['id'] as int,
|
||||
path: null,
|
||||
libraries: libraries,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadingUnits;
|
||||
}
|
||||
|
||||
/// Writes the provided generatedLoadingUnits as `deferred_components_loading_units.yaml`
|
||||
///
|
||||
/// This cache file is used to detect any changes in the loading units
|
||||
/// produced by gen_snapshot. Running [checkAgainstLoadingUnitCache] with a
|
||||
/// mismatching or missing cache will result in a failed validation. This
|
||||
/// prevents unexpected changes in loading units causing misconfigured
|
||||
/// deferred components.
|
||||
void writeLoadingUnitsCache(List<LoadingUnit> generatedLoadingUnits) {
|
||||
generatedLoadingUnits ??= <LoadingUnit>[];
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
outputs.add(cacheFile);
|
||||
ErrorHandlingFileSystem.deleteIfExists(cacheFile);
|
||||
cacheFile.createSync(recursive: true);
|
||||
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.write('''
|
||||
# ==============================================================================
|
||||
# The contents of this file are automatically generated and it is not
|
||||
# recommended to modify this file manually.
|
||||
# ==============================================================================
|
||||
#
|
||||
# In order to prevent unexpected splitting of deferred apps, this file records
|
||||
# the last generated set of loading units. It only possible to obtain the final
|
||||
# configuration of loading units after compilation is complete. This means
|
||||
# improperly setup deferred imports can only be detected after compilation.
|
||||
#
|
||||
# This file allows the build tool to detect any changes in the generated
|
||||
# loading units. During the next build attempt, loading units in this file are
|
||||
# compared against the newly generated loading units to check for any new or
|
||||
# removed loading units. In the case where loading units do not match, the build
|
||||
# will fail and ask the developer to verify that the `deferred-components`
|
||||
# configuration in `pubspec.yaml` is correct. Developers should make any
|
||||
# necessary changes to integrate new and changed loading units or remove no
|
||||
# longer existing loading units from the configuration. The build command should
|
||||
# then be re-run to continue the build process.
|
||||
#
|
||||
# Sometimes, changes to the generated loading units may be unintentional. If
|
||||
# the list of loading units in this file is not what is expected, the app's
|
||||
# deferred imports should be reviewed. Third party plugins and packages may
|
||||
# also introduce deferred imports that result in unexpected loading units.
|
||||
loading-units:
|
||||
''');
|
||||
final Set<int> usedIds = <int>{};
|
||||
for (final LoadingUnit unit in generatedLoadingUnits) {
|
||||
if (usedIds.contains(unit.id)) {
|
||||
continue;
|
||||
}
|
||||
buffer.write(' - id: ${unit.id}\n');
|
||||
if (unit.libraries != null && unit.libraries.isNotEmpty) {
|
||||
buffer.write(' libraries:\n');
|
||||
for (final String lib in unit.libraries) {
|
||||
buffer.write(' - $lib\n');
|
||||
}
|
||||
}
|
||||
usedIds.add(unit.id);
|
||||
}
|
||||
cacheFile.writeAsStringSync(buffer.toString(), flush: true);
|
||||
}
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import '../base/deferred_component.dart';
|
||||
import '../base/error_handling_io.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../template.dart';
|
||||
import 'deferred_components_validator.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].
|
||||
class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
|
||||
/// Constructs a validator instance.
|
||||
///
|
||||
/// The [env] property is used to locate the project files that are checked.
|
||||
///
|
||||
/// The [templatesDir] parameter is optional. If null, the tool's default
|
||||
/// templates directory will be used.
|
||||
///
|
||||
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
|
||||
/// methods will exit the tool when this validator detects a recommended
|
||||
/// change. This defaults to true.
|
||||
DeferredComponentsPrebuildValidator(Environment env, {
|
||||
bool exitOnFail = true,
|
||||
String title,
|
||||
Directory templatesDir,
|
||||
}) : _templatesDir = templatesDir,
|
||||
super(env, exitOnFail: exitOnFail, title: title);
|
||||
|
||||
final Directory _templatesDir;
|
||||
|
||||
/// Checks if an android dynamic feature module exists for each deferred
|
||||
/// component.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// This method looks for the existence of `android/<componentname>/build.gradle`
|
||||
/// and `android/<componentname>/src/main/AndroidManifest.xml`. If either of
|
||||
/// these files does not exist, it will generate it in the validator output
|
||||
/// directory based off of a template.
|
||||
///
|
||||
/// This method does not check if the contents of either of the files are
|
||||
/// valid, as there are many ways that they can be validly configured.
|
||||
Future<bool> checkAndroidDynamicFeature(List<DeferredComponent> components) async {
|
||||
inputs.add(env.projectDir.childFile('pubspec.yaml'));
|
||||
if (components == null || components.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
bool changesMade = false;
|
||||
for (final DeferredComponent component in components) {
|
||||
final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles(
|
||||
name: component.name,
|
||||
env: env,
|
||||
templatesDir: _templatesDir
|
||||
);
|
||||
if (!androidFiles.verifyFilesExist()) {
|
||||
// generate into temp directory
|
||||
final Map<String, List<File>> results =
|
||||
await androidFiles.generateFiles(
|
||||
alternateAndroidDir: outputDir,
|
||||
clearAlternateOutputDir: true,
|
||||
);
|
||||
for (final File file in results['outputs']) {
|
||||
generatedFiles.add(file.path);
|
||||
changesMade = true;
|
||||
}
|
||||
outputs.addAll(results['outputs']);
|
||||
inputs.addAll(results['inputs']);
|
||||
}
|
||||
}
|
||||
return !changesMade;
|
||||
}
|
||||
|
||||
/// Checks if the base module `app`'s `strings.xml` contain string
|
||||
/// resources for each component's name.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// In each dynamic feature module's AndroidManifest.xml, the
|
||||
/// name of the module is a string resource. This checks if
|
||||
/// the needed string resources are in the base module `strings.xml`.
|
||||
/// If not, this method will generate a modified `strings.xml` (or a
|
||||
/// completely new one if the original file did not exist) in the
|
||||
/// validator's output directory.
|
||||
///
|
||||
/// For example, if there is a deferred component named `component1`,
|
||||
/// there should be the following string resource:
|
||||
///
|
||||
/// <string name="component1Name">component1</string>
|
||||
///
|
||||
/// The string element's name attribute should be the component name with
|
||||
/// `Name` as a suffix, and the text contents should be the component name.
|
||||
bool checkAndroidResourcesStrings(List<DeferredComponent> components) {
|
||||
final Directory androidDir = env.projectDir.childDirectory('android');
|
||||
inputs.add(env.projectDir.childFile('pubspec.yaml'));
|
||||
|
||||
// Add component name mapping to strings.xml
|
||||
final File stringRes = androidDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
inputs.add(stringRes);
|
||||
final File stringResOutput = outputDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
ErrorHandlingFileSystem.deleteIfExists(stringResOutput);
|
||||
if (components == null || components.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
final Map<String, String> requiredEntriesMap = <String, String>{};
|
||||
for (final DeferredComponent component in components) {
|
||||
requiredEntriesMap['${component.name}Name'] = component.name;
|
||||
}
|
||||
if (stringRes.existsSync()) {
|
||||
bool modified = false;
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(stringRes.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
invalidFiles[stringRes.path] = 'Error parsing $stringRes '
|
||||
'Please ensure that the strings.xml is a valid XML document and '
|
||||
'try again.';
|
||||
return false;
|
||||
}
|
||||
// Check if all required lines are present, and fix if name exists, but
|
||||
// wrong string stored.
|
||||
for (final XmlElement resources in document.findAllElements('resources')) {
|
||||
for (final XmlElement element in resources.findElements('string')) {
|
||||
final String name = element.getAttribute('name');
|
||||
if (requiredEntriesMap.containsKey(name)) {
|
||||
if (element.text != null && element.text != requiredEntriesMap[name]) {
|
||||
element.innerText = requiredEntriesMap[name];
|
||||
modified = true;
|
||||
}
|
||||
requiredEntriesMap.remove(name);
|
||||
}
|
||||
}
|
||||
for (final String key in requiredEntriesMap.keys) {
|
||||
modified = true;
|
||||
final XmlElement newStringElement = XmlElement(
|
||||
XmlName.fromString('string'),
|
||||
<XmlAttribute>[
|
||||
XmlAttribute(XmlName.fromString('name'), key),
|
||||
],
|
||||
<XmlNode>[
|
||||
XmlText(requiredEntriesMap[key]),
|
||||
],
|
||||
);
|
||||
resources.children.add(newStringElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (modified) {
|
||||
stringResOutput.createSync(recursive: true);
|
||||
stringResOutput.writeAsStringSync(document.toXmlString(pretty: true));
|
||||
modifiedFiles.add(stringResOutput.path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// strings.xml does not exist, generate completely new file.
|
||||
stringResOutput.createSync(recursive: true);
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.writeln('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
''');
|
||||
for (final String key in requiredEntriesMap.keys) {
|
||||
buffer.write(' <string name="$key">${requiredEntriesMap[key]}</string>\n');
|
||||
}
|
||||
buffer.write(
|
||||
'''
|
||||
</resources>
|
||||
|
||||
''');
|
||||
stringResOutput.writeAsStringSync(buffer.toString(), flush: true, mode: FileMode.append);
|
||||
generatedFiles.add(stringResOutput.path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Deletes all files inside of the validator's output directory.
|
||||
void clearOutputDir() {
|
||||
final Directory dir = env.projectDir.childDirectory('build').childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory);
|
||||
ErrorHandlingFileSystem.deleteIfExists(dir, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Handles a single deferred component's android dynamic feature module
|
||||
// directory.
|
||||
class _DeferredComponentAndroidFiles {
|
||||
_DeferredComponentAndroidFiles({
|
||||
@required this.name,
|
||||
@required this.env,
|
||||
Directory templatesDir,
|
||||
}) : _templatesDir = templatesDir;
|
||||
|
||||
// The name of the deferred component.
|
||||
final String name;
|
||||
final Environment env;
|
||||
final Directory _templatesDir;
|
||||
|
||||
Directory get androidDir => env.projectDir.childDirectory('android');
|
||||
Directory get componentDir => androidDir.childDirectory(name);
|
||||
|
||||
File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
File get buildGradleFile => componentDir.childFile('build.gradle');
|
||||
|
||||
// True when AndroidManifest.xml and build.gradle exist for the android dynamic feature.
|
||||
bool verifyFilesExist() {
|
||||
return androidManifestFile.existsSync() && buildGradleFile.existsSync();
|
||||
}
|
||||
|
||||
// Generates any missing basic files for the dynamic feature into a temporary directory.
|
||||
Future<Map<String, List<File>>> generateFiles({Directory alternateAndroidDir, bool clearAlternateOutputDir = false}) async {
|
||||
final Directory outputDir = alternateAndroidDir?.childDirectory(name) ?? componentDir;
|
||||
if (clearAlternateOutputDir && alternateAndroidDir != null) {
|
||||
ErrorHandlingFileSystem.deleteIfExists(outputDir);
|
||||
}
|
||||
final List<File> inputs = <File>[];
|
||||
inputs.add(androidManifestFile);
|
||||
inputs.add(buildGradleFile);
|
||||
final Map<String, List<File>> results = <String, List<File>>{'inputs': inputs};
|
||||
results['outputs'] = await _setupComponentFiles(outputDir);
|
||||
return results;
|
||||
}
|
||||
|
||||
// generates default build.gradle and AndroidManifest.xml for the deferred component.
|
||||
Future<List<File>> _setupComponentFiles(Directory outputDir) async {
|
||||
Template template;
|
||||
if (_templatesDir != null) {
|
||||
final Directory templateComponentDir = _templatesDir.childDirectory('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component');
|
||||
template = Template(templateComponentDir, templateComponentDir, _templatesDir,
|
||||
fileSystem: env.fileSystem,
|
||||
templateManifest: null,
|
||||
logger: env.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
} else {
|
||||
template = await Template.fromName('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component',
|
||||
fileSystem: env.fileSystem,
|
||||
templateManifest: null,
|
||||
logger: env.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
}
|
||||
final Map<String, dynamic> context = <String, dynamic>{
|
||||
'androidIdentifier': FlutterProject.current().manifest.androidPackage ?? 'com.example.${FlutterProject.current().manifest.appName}',
|
||||
'componentName': name,
|
||||
};
|
||||
|
||||
template.render(outputDir, context);
|
||||
|
||||
final List<File> generatedFiles = <File>[];
|
||||
|
||||
final File tempBuildGradle = outputDir.childFile('build.gradle');
|
||||
if (!buildGradleFile.existsSync()) {
|
||||
generatedFiles.add(tempBuildGradle);
|
||||
} else {
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempBuildGradle);
|
||||
}
|
||||
final File tempAndroidManifest = outputDir
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
if (!androidManifestFile.existsSync()) {
|
||||
generatedFiles.add(tempAndroidManifest);
|
||||
} else {
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempAndroidManifest);
|
||||
}
|
||||
return generatedFiles;
|
||||
}
|
||||
}
|
@ -1,773 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/deferred_component.dart';
|
||||
import '../base/error_handling_io.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../template.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].
|
||||
class DeferredComponentsSetupValidator {
|
||||
/// Constructs a validator instance.
|
||||
///
|
||||
/// The [env] property is used to locate the project files that are checked.
|
||||
///
|
||||
/// The [templatesDir] parameter is optional. If null, the tool's default
|
||||
/// templates directory will be used.
|
||||
///
|
||||
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
|
||||
/// methods will exit the tool when this validator detects a recommended
|
||||
/// change. This defaults to true.
|
||||
DeferredComponentsSetupValidator(this.env, {
|
||||
this.exitOnFail = true,
|
||||
String title,
|
||||
Directory templatesDir,
|
||||
}) : _outputDir = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(kDeferredComponentsTempDirectory),
|
||||
_inputs = <File>[],
|
||||
_outputs = <File>[],
|
||||
_title = title ?? 'Deferred components setup verification',
|
||||
_templatesDir = templatesDir,
|
||||
_generatedFiles = <String>[],
|
||||
_modifiedFiles = <String>[],
|
||||
_invalidFiles = <String, String>{},
|
||||
_diffLines = <String>[];
|
||||
|
||||
/// The build environment that should be used to find the input files to run
|
||||
/// checks against.
|
||||
///
|
||||
/// The checks in this class are meant to be used as part of a build process,
|
||||
/// so an environment should be available.
|
||||
final Environment env;
|
||||
|
||||
/// 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.
|
||||
@visibleForTesting
|
||||
static const String kDeferredComponentsGoldenFileName = 'deferred_components_golden.yaml';
|
||||
/// The directory in the build folder to generate missing/modified files into.
|
||||
@visibleForTesting
|
||||
static const String kDeferredComponentsTempDirectory = 'android_deferred_components_setup_files';
|
||||
|
||||
final String _title;
|
||||
final Directory _templatesDir;
|
||||
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;
|
||||
// Output of the diff task.
|
||||
// TODO(garyq): implement the diff task.
|
||||
final List<String> _diffLines;
|
||||
// Tracks the new and missing loading units.
|
||||
Map<String, dynamic> _goldenComparisonResults;
|
||||
|
||||
/// All files read by the validator.
|
||||
List<File> get inputs => _inputs;
|
||||
final List<File> _inputs;
|
||||
|
||||
/// All files output by the validator.
|
||||
List<File> get outputs => _outputs;
|
||||
final List<File> _outputs;
|
||||
|
||||
/// Returns true if there were any recommended changes that should
|
||||
/// be applied.
|
||||
///
|
||||
/// Retuns 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
|
||||
|| (_goldenComparisonResults != null && !(_goldenComparisonResults['match'] as bool));
|
||||
|
||||
/// Checks if an android dynamic feature module exists for each deferred
|
||||
/// component.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// This method looks for the existence of `android/<componentname>/build.gradle`
|
||||
/// and `android/<componentname>/src/main/AndroidManifest.xml`. If either of
|
||||
/// these files does not exist, it will generate it in the validator output
|
||||
/// directory based off of a template.
|
||||
///
|
||||
/// This method does not check if the contents of either of the files are
|
||||
/// valid, as there are many ways that they can be validly configured.
|
||||
Future<bool> checkAndroidDynamicFeature(List<DeferredComponent> components) async {
|
||||
bool changesMade = false;
|
||||
for (final DeferredComponent component in components) {
|
||||
final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles(
|
||||
name: component.name,
|
||||
env: env,
|
||||
templatesDir: _templatesDir
|
||||
);
|
||||
if (!androidFiles.verifyFilesExist()) {
|
||||
// generate into temp directory
|
||||
final Map<String, List<File>> results =
|
||||
await androidFiles.generateFiles(
|
||||
alternateAndroidDir: _outputDir,
|
||||
clearAlternateOutputDir: true,
|
||||
);
|
||||
for (final File file in results['outputs']) {
|
||||
_generatedFiles.add(file.path);
|
||||
changesMade = true;
|
||||
}
|
||||
_outputs.addAll(results['outputs']);
|
||||
_inputs.addAll(results['inputs']);
|
||||
}
|
||||
}
|
||||
return !changesMade;
|
||||
}
|
||||
|
||||
|
||||
// The key used to identify the metadata element as the loading unit id to
|
||||
// deferred component mapping.
|
||||
static const String _mappingKey = 'io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping';
|
||||
|
||||
/// Checks if the base module `app`'s `AndroidManifest.xml` contains the
|
||||
/// required meta-data that maps loading units to deferred components.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// Flutter engine uses a manifest meta-data mapping to determine which
|
||||
/// deferred component includes a particular loading unit id. This method
|
||||
/// checks if `app`'s `AndroidManifest.xml` contains this metadata. If not, it
|
||||
/// will generate a modified AndroidManifest.xml with the correct metadata
|
||||
/// entry.
|
||||
///
|
||||
/// An example mapping:
|
||||
///
|
||||
/// 2:componentA,3:componentB,4:componentC
|
||||
///
|
||||
/// Where loading unit 2 is included in componentA, loading unit 3 is included
|
||||
/// in componentB, and loading unit 4 is included in componentC.
|
||||
bool checkAppAndroidManifestComponentLoadingUnitMapping(List<DeferredComponent> components, List<LoadingUnit> generatedLoadingUnits) {
|
||||
final Directory androidDir = env.projectDir.childDirectory('android');
|
||||
|
||||
// We do not use the Xml package to handle the writing, as we do not want to
|
||||
// erase any user applied formatting and comments. The changes can be
|
||||
// applied with dart io and custom parsing.
|
||||
final File appManifestFile = androidDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
_inputs.add(appManifestFile);
|
||||
if (!appManifestFile.existsSync()) {
|
||||
_invalidFiles[appManifestFile.path] = 'Error: $appManifestFile does not '
|
||||
'exist or could not be found. Please ensure an AndroidManifest.xml '
|
||||
'exists for the app\'s base module.';
|
||||
return false;
|
||||
}
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(appManifestFile.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
_invalidFiles[appManifestFile.path] = 'Error parsing $appManifestFile '
|
||||
'Please ensure that the android manifest is a valid XML document and '
|
||||
'try again.';
|
||||
return false;
|
||||
} on FileSystemException {
|
||||
_invalidFiles[appManifestFile.path] = 'Error reading $appManifestFile '
|
||||
'even though it exists. Please ensure that you have read permission for '
|
||||
'this file and try again.';
|
||||
return false;
|
||||
}
|
||||
// Create loading unit mapping.
|
||||
final Map<int, String> mapping = <int, String>{};
|
||||
for (final DeferredComponent component in components) {
|
||||
component.assignLoadingUnits(generatedLoadingUnits);
|
||||
for (final LoadingUnit unit in component.loadingUnits) {
|
||||
if (!mapping.containsKey(unit.id)) {
|
||||
mapping[unit.id] = component.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Encode the mapping as a string.
|
||||
final StringBuffer mappingBuffer = StringBuffer();
|
||||
for (final int key in mapping.keys) {
|
||||
mappingBuffer.write('$key:${mapping[key]},');
|
||||
}
|
||||
String encodedMapping = mappingBuffer.toString();
|
||||
// remove trailing comma.
|
||||
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
|
||||
// Check for existing metadata entry and see if needs changes.
|
||||
bool exists = false;
|
||||
bool modified = false;
|
||||
for (final XmlElement metaData in document.findAllElements('meta-data')) {
|
||||
final String name = metaData.getAttribute('android:name');
|
||||
if (name == _mappingKey) {
|
||||
exists = true;
|
||||
final String storedMappingString = metaData.getAttribute('android:value');
|
||||
if (storedMappingString != encodedMapping) {
|
||||
metaData.setAttribute('android:value', encodedMapping);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
// Create an meta-data XmlElement that contains the mapping.
|
||||
final XmlElement mappingMetadataElement = XmlElement(XmlName.fromString('meta-data'),
|
||||
<XmlAttribute>[
|
||||
XmlAttribute(XmlName.fromString('android:name'), _mappingKey),
|
||||
XmlAttribute(XmlName.fromString('android:value'), encodedMapping),
|
||||
],
|
||||
);
|
||||
for (final XmlElement application in document.findAllElements('application')) {
|
||||
application.children.add(mappingMetadataElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists || modified) {
|
||||
final File manifestOutput = _outputDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
ErrorHandlingFileSystem.deleteIfExists(manifestOutput);
|
||||
manifestOutput.createSync(recursive: true);
|
||||
manifestOutput.writeAsStringSync(document.toXmlString(pretty: true), flush: true);
|
||||
_modifiedFiles.add(manifestOutput.path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks if the base module `app`'s `strings.xml` contain string
|
||||
/// resources for each component's name.
|
||||
///
|
||||
/// Returns true if the check passed with no recommended changes, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// In each dynamic feature module's AndroidManifest.xml, the
|
||||
/// name of the module is a string resource. This checks if
|
||||
/// the needed string resources are in the base module `strings.xml`.
|
||||
/// If not, this method will generate a modified `strings.xml` (or a
|
||||
/// completely new one if the original file did not exist) in the
|
||||
/// validator's output directory.
|
||||
///
|
||||
/// For example, if there is a deferred component named `component1`,
|
||||
/// there should be the following string resource:
|
||||
///
|
||||
/// <string name="component1Name">component1</string>
|
||||
///
|
||||
/// The string element's name attribute should be the component name with
|
||||
/// `Name` as a suffix, and the text contents should be the component name.
|
||||
bool checkAndroidResourcesStrings(List<DeferredComponent> components) {
|
||||
final Directory androidDir = env.projectDir.childDirectory('android');
|
||||
|
||||
// Add component name mapping to strings.xml
|
||||
final File stringRes = androidDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
_inputs.add(stringRes);
|
||||
final File stringResOutput = _outputDir
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
ErrorHandlingFileSystem.deleteIfExists(stringResOutput);
|
||||
final Map<String, String> requiredEntriesMap = <String, String>{};
|
||||
for (final DeferredComponent component in components) {
|
||||
requiredEntriesMap['${component.name}Name'] = component.name;
|
||||
}
|
||||
if (stringRes.existsSync()) {
|
||||
bool modified = false;
|
||||
XmlDocument document;
|
||||
try {
|
||||
document = XmlDocument.parse(stringRes.readAsStringSync());
|
||||
} on XmlParserException {
|
||||
_invalidFiles[stringRes.path] = 'Error parsing $stringRes '
|
||||
'Please ensure that the strings.xml is a valid XML document and '
|
||||
'try again.';
|
||||
return false;
|
||||
}
|
||||
// Check if all required lines are present, and fix if name exists, but
|
||||
// wrong string stored.
|
||||
for (final XmlElement resources in document.findAllElements('resources')) {
|
||||
for (final XmlElement element in resources.findElements('string')) {
|
||||
final String name = element.getAttribute('name');
|
||||
if (requiredEntriesMap.containsKey(name)) {
|
||||
if (element.text != null && element.text != requiredEntriesMap[name]) {
|
||||
element.innerText = requiredEntriesMap[name];
|
||||
modified = true;
|
||||
}
|
||||
requiredEntriesMap.remove(name);
|
||||
}
|
||||
}
|
||||
for (final String key in requiredEntriesMap.keys) {
|
||||
modified = true;
|
||||
final XmlElement newStringElement = XmlElement(
|
||||
XmlName.fromString('string'),
|
||||
<XmlAttribute>[
|
||||
XmlAttribute(XmlName.fromString('name'), key),
|
||||
],
|
||||
<XmlNode>[
|
||||
XmlText(requiredEntriesMap[key]),
|
||||
],
|
||||
);
|
||||
resources.children.add(newStringElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (modified) {
|
||||
stringResOutput.createSync(recursive: true);
|
||||
stringResOutput.writeAsStringSync(document.toXmlString(pretty: true));
|
||||
_modifiedFiles.add(stringResOutput.path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// strings.xml does not exist, generate completely new file.
|
||||
stringResOutput.createSync(recursive: true);
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.writeln('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
''');
|
||||
for (final String key in requiredEntriesMap.keys) {
|
||||
buffer.write(' <string name="$key">${requiredEntriesMap[key]}</string>\n');
|
||||
}
|
||||
buffer.write(
|
||||
'''
|
||||
</resources>
|
||||
|
||||
''');
|
||||
stringResOutput.writeAsStringSync(buffer.toString(), flush: true, mode: FileMode.append);
|
||||
_generatedFiles.add(stringResOutput.path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Compares the provided loading units against the contents of the
|
||||
/// `deferred_components_golden.yaml` file.
|
||||
///
|
||||
/// Returns true if a golden exists and all loading units match, and false
|
||||
/// otherwise.
|
||||
///
|
||||
/// This method will parse the golden file if it exists and compare it to
|
||||
/// the provided generatedLoadingUnits. It will distinguish between newly
|
||||
/// added loading units and no longer existing loading units. If the golden
|
||||
/// file does not exist, then all generatedLoadingUnits will be considered
|
||||
/// new.
|
||||
bool checkAgainstLoadingUnitGolden(
|
||||
List<LoadingUnit> generatedLoadingUnits) {
|
||||
final List<LoadingUnit> goldenLoadingUnits = _parseGolden(env.projectDir.childFile(kDeferredComponentsGoldenFileName));
|
||||
_goldenComparisonResults = <String, dynamic>{};
|
||||
final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{};
|
||||
final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[];
|
||||
if (generatedLoadingUnits == null || goldenLoadingUnits == null) {
|
||||
_goldenComparisonResults['new'] = newLoadingUnits;
|
||||
_goldenComparisonResults['missing'] = unmatchedLoadingUnits;
|
||||
_goldenComparisonResults['match'] = false;
|
||||
return false;
|
||||
}
|
||||
_inputs.add(env.projectDir.childFile(kDeferredComponentsGoldenFileName));
|
||||
unmatchedLoadingUnits.addAll(goldenLoadingUnits);
|
||||
final Set<int> addedNewIds = <int>{};
|
||||
for (final LoadingUnit genUnit in generatedLoadingUnits) {
|
||||
bool matched = false;
|
||||
for (final LoadingUnit goldUnit in goldenLoadingUnits) {
|
||||
if (genUnit.equalsIgnoringPath(goldUnit)) {
|
||||
matched = true;
|
||||
unmatchedLoadingUnits.remove(goldUnit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched && !addedNewIds.contains(genUnit.id)) {
|
||||
newLoadingUnits.add(genUnit);
|
||||
addedNewIds.add(genUnit.id);
|
||||
}
|
||||
}
|
||||
_goldenComparisonResults['new'] = newLoadingUnits;
|
||||
_goldenComparisonResults['missing'] = unmatchedLoadingUnits;
|
||||
_goldenComparisonResults['match'] = newLoadingUnits.isEmpty && unmatchedLoadingUnits.isEmpty;
|
||||
return _goldenComparisonResults['match'] as bool;
|
||||
}
|
||||
|
||||
List<LoadingUnit> _parseGolden(File goldenFile) {
|
||||
final List<LoadingUnit> loadingUnits = <LoadingUnit>[];
|
||||
_inputs.add(goldenFile);
|
||||
if (!goldenFile.existsSync()) {
|
||||
return loadingUnits;
|
||||
}
|
||||
final YamlMap data = loadYaml(goldenFile.readAsStringSync()) as YamlMap;
|
||||
// validate yaml format.
|
||||
if (!data.containsKey('loading-units')) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, \'loading-units\' '
|
||||
'entry did not exist.';
|
||||
return loadingUnits;
|
||||
} else {
|
||||
if (data['loading-units'] is! YamlList && data['loading-units'] != null) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, \'loading-units\' '
|
||||
'is not a list.';
|
||||
return loadingUnits;
|
||||
}
|
||||
if (data['loading-units'] != null) {
|
||||
for (final dynamic loadingUnitData in data['loading-units']) {
|
||||
if (loadingUnitData is! YamlMap) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, \'loading-units\' '
|
||||
'is not a list of maps.';
|
||||
return loadingUnits;
|
||||
}
|
||||
final YamlMap loadingUnitDataMap = loadingUnitData as YamlMap;
|
||||
if (loadingUnitDataMap['id'] == null) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, all '
|
||||
'loading units must have an \'id\'';
|
||||
return loadingUnits;
|
||||
}
|
||||
if (loadingUnitDataMap['libraries'] != null) {
|
||||
if (loadingUnitDataMap['libraries'] is! YamlList) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, \'libraries\' '
|
||||
'is not a list.';
|
||||
return loadingUnits;
|
||||
}
|
||||
for (final dynamic node in loadingUnitDataMap['libraries'] as YamlList) {
|
||||
if (node is! String) {
|
||||
_invalidFiles[goldenFile.path] = 'Invalid golden yaml file, \'libraries\' '
|
||||
'is not a list of strings.';
|
||||
return loadingUnits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse out validated yaml.
|
||||
if (data.containsKey('loading-units')) {
|
||||
if (data['loading-units'] != null) {
|
||||
for (final dynamic loadingUnitData in data['loading-units']) {
|
||||
final YamlMap loadingUnitDataMap = loadingUnitData as YamlMap;
|
||||
final List<String> libraries = <String>[];
|
||||
if (loadingUnitDataMap['libraries'] != null) {
|
||||
for (final dynamic node in loadingUnitDataMap['libraries'] as YamlList) {
|
||||
libraries.add(node as String);
|
||||
}
|
||||
}
|
||||
loadingUnits.add(
|
||||
LoadingUnit(
|
||||
id: loadingUnitDataMap['id'] as int,
|
||||
path: null,
|
||||
libraries: libraries,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return loadingUnits;
|
||||
}
|
||||
|
||||
/// Writes the provided generatedLoadingUnits as `deferred_components_golden.yaml`
|
||||
///
|
||||
/// This golden file is used to detect any changes in the loading units
|
||||
/// produced by gen_snapshot. Running [checkAgainstLoadingUnitGolden] with a
|
||||
/// mismatching or missing golden will result in a failed validation. This
|
||||
/// prevents unexpected changes in loading units causing misconfigured
|
||||
/// deferred components.
|
||||
void writeGolden(List<LoadingUnit> generatedLoadingUnits) {
|
||||
generatedLoadingUnits ??= <LoadingUnit>[];
|
||||
final File goldenFile = env.projectDir.childFile(kDeferredComponentsGoldenFileName);
|
||||
_outputs.add(goldenFile);
|
||||
ErrorHandlingFileSystem.deleteIfExists(goldenFile);
|
||||
goldenFile.createSync(recursive: true);
|
||||
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.write('''
|
||||
# ===============================================================================
|
||||
# The contents of this file are automatically generated and it is not recommended
|
||||
# to modify this file manually.
|
||||
# ===============================================================================
|
||||
#
|
||||
# In order to prevent unexpected splitting of deferred apps, this golden
|
||||
# file records the last generated set of loading units. It only possible
|
||||
# to obtain the final configuration of loading units after compilation is
|
||||
# complete. This means improperly setup imports can only be detected after
|
||||
# compilation.
|
||||
#
|
||||
# This golden file allows the build tool to detect any changes in the generated
|
||||
# loading units. During the next build attempt, loading units in this file are
|
||||
# compared against the newly generated loading units to check for any new or
|
||||
# removed loading units. In the case where loading units do not match, the build
|
||||
# will fail and ask the developer to verify that the `deferred-components`
|
||||
# configuration in `pubspec.yaml` is correct. Developers should make any necessary
|
||||
# changes to integrate new and changed loading units or remove no longer existing
|
||||
# loading units from the configuration. The build command should then be
|
||||
# re-run to continue the build process.
|
||||
#
|
||||
# Sometimes, changes to the generated loading units may be unintentional. If
|
||||
# the list of loading units in this golden is not what is expected, the app's
|
||||
# deferred imports should be reviewed. Third party plugins and packages may
|
||||
# also introduce deferred imports that result in unexpected loading units.
|
||||
loading-units:
|
||||
''');
|
||||
final Set<int> usedIds = <int>{};
|
||||
for (final LoadingUnit unit in generatedLoadingUnits) {
|
||||
if (usedIds.contains(unit.id)) {
|
||||
continue;
|
||||
}
|
||||
buffer.write(' - id: ${unit.id}\n');
|
||||
if (unit.libraries != null && unit.libraries.isNotEmpty) {
|
||||
buffer.write(' libraries:\n');
|
||||
for (final String lib in unit.libraries) {
|
||||
buffer.write(' - $lib\n');
|
||||
}
|
||||
}
|
||||
usedIds.add(unit.id);
|
||||
}
|
||||
goldenFile.writeAsStringSync(buffer.toString(), flush: true);
|
||||
}
|
||||
|
||||
/// Deletes all files inside of the validator's output directory.
|
||||
void clearOutputDir() {
|
||||
final Directory dir = env.projectDir.childDirectory('build').childDirectory(kDeferredComponentsTempDirectory);
|
||||
ErrorHandlingFileSystem.deleteIfExists(dir, recursive: true);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
env.logger.printStatus(_thickDivider);
|
||||
env.logger.printStatus(_title, indent: (_thickDivider.length - _title.length) ~/ 2, emphasis: true);
|
||||
env.logger.printStatus(_thickDivider);
|
||||
// Log any file reading/existence errors.
|
||||
if (_invalidFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Errors checking the following files:\n', emphasis: true);
|
||||
for (final String key in _invalidFiles.keys) {
|
||||
env.logger.printStatus(' - $key: ${_invalidFiles[key]}\n');
|
||||
}
|
||||
}
|
||||
// Log diff file contents, with color highlighting
|
||||
if (_diffLines != null && _diffLines.isNotEmpty) {
|
||||
env.logger.printStatus('Diff between `android` and expected files:', emphasis: true);
|
||||
env.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;
|
||||
}
|
||||
env.logger.printStatus(line, color: color);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
// Log any newly generated and modified files.
|
||||
if (_generatedFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Newly generated android files:', emphasis: true);
|
||||
for (final String filePath in _generatedFiles) {
|
||||
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
|
||||
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (_modifiedFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Modified android files:', emphasis: true);
|
||||
for (final String filePath in _modifiedFiles) {
|
||||
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
|
||||
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (_generatedFiles.isNotEmpty || _modifiedFiles.isNotEmpty) {
|
||||
env.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 != null && _diffLines.isNotEmpty && !globals.platform.isWindows) {
|
||||
env.logger.printStatus(r'''
|
||||
|
||||
The recommended changes can be quickly applied by running:
|
||||
|
||||
$ patch -p0 < build/setup_deferred_components.diff
|
||||
''');
|
||||
}
|
||||
env.logger.printStatus('$_thinDivider\n');
|
||||
}
|
||||
// Log loading unit golden changes, if any.
|
||||
if (_goldenComparisonResults != null) {
|
||||
if ((_goldenComparisonResults['new'] as List<LoadingUnit>).isNotEmpty) {
|
||||
env.logger.printStatus('New loading units were found:', emphasis: true);
|
||||
for (final LoadingUnit unit in _goldenComparisonResults['new'] as List<LoadingUnit>) {
|
||||
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if ((_goldenComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) {
|
||||
env.logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
|
||||
for (final LoadingUnit unit in _goldenComparisonResults['missing'] as Set<LoadingUnit>) {
|
||||
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (_goldenComparisonResults['match'] as bool) {
|
||||
env.logger.printStatus('No change in generated loading units.\n');
|
||||
} else {
|
||||
env.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 $kDeferredComponentsGoldenFileName 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.
|
||||
env.logger.printStatus('''
|
||||
Setup verification can be skipped by passing the `--no-verify-deferred-components`
|
||||
flag, however, doing so may put your app at risk of not functioning even if the
|
||||
build is successful.
|
||||
$_thickDivider''');
|
||||
return;
|
||||
}
|
||||
env.logger.printStatus('$_title passed.');
|
||||
}
|
||||
|
||||
void attemptToolExit() {
|
||||
if (exitOnFail && changesNeeded) {
|
||||
throwToolExit('Setup for deferred components incomplete. See recommended actions.', exitCode: 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles a single deferred component's android dynamic feature module
|
||||
// directory.
|
||||
class _DeferredComponentAndroidFiles {
|
||||
_DeferredComponentAndroidFiles({
|
||||
@required this.name,
|
||||
@required this.env,
|
||||
Directory templatesDir,
|
||||
}) : _templatesDir = templatesDir;
|
||||
|
||||
// The name of the deferred component.
|
||||
final String name;
|
||||
final Environment env;
|
||||
final Directory _templatesDir;
|
||||
|
||||
Directory get androidDir => env.projectDir.childDirectory('android');
|
||||
Directory get componentDir => androidDir.childDirectory(name);
|
||||
|
||||
File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
File get buildGradleFile => componentDir.childFile('build.gradle');
|
||||
|
||||
// True when AndroidManifest.xml and build.gradle exist for the android dynamic feature.
|
||||
bool verifyFilesExist() {
|
||||
return androidManifestFile.existsSync() && buildGradleFile.existsSync();
|
||||
}
|
||||
|
||||
// Generates any missing basic files for the dynamic feature into a temporary directory.
|
||||
Future<Map<String, List<File>>> generateFiles({Directory alternateAndroidDir, bool clearAlternateOutputDir = false}) async {
|
||||
final Directory outputDir = alternateAndroidDir?.childDirectory(name) ?? componentDir;
|
||||
if (clearAlternateOutputDir && alternateAndroidDir != null) {
|
||||
ErrorHandlingFileSystem.deleteIfExists(outputDir);
|
||||
}
|
||||
final List<File> inputs = <File>[];
|
||||
inputs.add(androidManifestFile);
|
||||
inputs.add(buildGradleFile);
|
||||
final Map<String, List<File>> results = <String, List<File>>{'inputs': inputs};
|
||||
results['outputs'] = await _setupComponentFiles(outputDir);
|
||||
return results;
|
||||
}
|
||||
|
||||
// generates default build.gradle and AndroidManifest.xml for the deferred component.
|
||||
Future<List<File>> _setupComponentFiles(Directory outputDir) async {
|
||||
Template template;
|
||||
if (_templatesDir != null) {
|
||||
final Directory templateComponentDir = _templatesDir.childDirectory('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component');
|
||||
template = Template(templateComponentDir, templateComponentDir, _templatesDir,
|
||||
fileSystem: env.fileSystem,
|
||||
templateManifest: null,
|
||||
logger: env.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
} else {
|
||||
template = await Template.fromName('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component',
|
||||
fileSystem: env.fileSystem,
|
||||
templateManifest: null,
|
||||
logger: env.logger,
|
||||
templateRenderer: globals.templateRenderer,
|
||||
);
|
||||
}
|
||||
final Map<String, dynamic> context = <String, dynamic>{
|
||||
'androidIdentifier': FlutterProject.current().manifest.androidPackage ?? 'com.example.${FlutterProject.current().manifest.appName}',
|
||||
'componentName': name,
|
||||
};
|
||||
|
||||
template.render(outputDir, context);
|
||||
|
||||
final List<File> generatedFiles = <File>[];
|
||||
|
||||
final File tempBuildGradle = outputDir.childFile('build.gradle');
|
||||
if (!buildGradleFile.existsSync()) {
|
||||
generatedFiles.add(tempBuildGradle);
|
||||
} else {
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempBuildGradle);
|
||||
}
|
||||
final File tempAndroidManifest = outputDir
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
if (!androidManifestFile.existsSync()) {
|
||||
generatedFiles.add(tempAndroidManifest);
|
||||
} else {
|
||||
ErrorHandlingFileSystem.deleteIfExists(tempAndroidManifest);
|
||||
}
|
||||
return generatedFiles;
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/deferred_component.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
/// 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.env, {
|
||||
this.exitOnFail = true,
|
||||
String title,
|
||||
}) : outputDir = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(kDeferredComponentsTempDirectory),
|
||||
inputs = <File>[],
|
||||
outputs = <File>[],
|
||||
title = title ?? 'Deferred components setup verification',
|
||||
generatedFiles = <String>[],
|
||||
modifiedFiles = <String>[],
|
||||
invalidFiles = <String, String>{},
|
||||
diffLines = <String>[];
|
||||
/// The build environment that should be used to find the input files to run
|
||||
/// checks against.
|
||||
///
|
||||
/// The checks in this class are meant to be used as part of a build process,
|
||||
/// so an environment should be available.
|
||||
final Environment env;
|
||||
|
||||
/// 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 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.
|
||||
///
|
||||
/// Retuns 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) {
|
||||
env.logger.printStatus(_thickDivider);
|
||||
env.logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true);
|
||||
env.logger.printStatus(_thickDivider);
|
||||
// Log any file reading/existence errors.
|
||||
if (invalidFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Errors checking the following files:\n', emphasis: true);
|
||||
for (final String key in invalidFiles.keys) {
|
||||
env.logger.printStatus(' - $key: ${invalidFiles[key]}\n');
|
||||
}
|
||||
}
|
||||
// Log diff file contents, with color highlighting
|
||||
if (diffLines != null && diffLines.isNotEmpty) {
|
||||
env.logger.printStatus('Diff between `android` and expected files:', emphasis: true);
|
||||
env.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;
|
||||
}
|
||||
env.logger.printStatus(line, color: color);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
// Log any newly generated and modified files.
|
||||
if (generatedFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Newly generated android files:', emphasis: true);
|
||||
for (final String filePath in generatedFiles) {
|
||||
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
|
||||
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (modifiedFiles.isNotEmpty) {
|
||||
env.logger.printStatus('Modified android files:', emphasis: true);
|
||||
for (final String filePath in modifiedFiles) {
|
||||
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
|
||||
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) {
|
||||
env.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 != null && diffLines.isNotEmpty && !globals.platform.isWindows) {
|
||||
env.logger.printStatus(r'''
|
||||
|
||||
The recommended changes can be quickly applied by running:
|
||||
|
||||
$ patch -p0 < build/setup_deferred_components.diff
|
||||
''');
|
||||
}
|
||||
env.logger.printStatus('$_thinDivider\n');
|
||||
}
|
||||
// Log loading unit golden changes, if any.
|
||||
if (loadingUnitComparisonResults != null) {
|
||||
if ((loadingUnitComparisonResults['new'] as List<LoadingUnit>).isNotEmpty) {
|
||||
env.logger.printStatus('New loading units were found:', emphasis: true);
|
||||
for (final LoadingUnit unit in loadingUnitComparisonResults['new'] as List<LoadingUnit>) {
|
||||
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if ((loadingUnitComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) {
|
||||
env.logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
|
||||
for (final LoadingUnit unit in loadingUnitComparisonResults['missing'] as Set<LoadingUnit>) {
|
||||
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
|
||||
}
|
||||
env.logger.printStatus('');
|
||||
}
|
||||
if (loadingUnitComparisonResults['match'] as bool) {
|
||||
env.logger.printStatus('No change in generated loading units.\n');
|
||||
} else {
|
||||
env.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.
|
||||
env.logger.printStatus('''
|
||||
Setup verification can be skipped by passing the `--no-verify-deferred-components`
|
||||
flag, however, doing so may put your app at risk of not functioning even if the
|
||||
build is successful.
|
||||
$_thickDivider''');
|
||||
return;
|
||||
}
|
||||
env.logger.printStatus('$title passed.');
|
||||
}
|
||||
|
||||
void attemptToolExit() {
|
||||
if (exitOnFail && changesNeeded) {
|
||||
throwToolExit('Setup for deferred components incomplete. See recommended actions.', exitCode: 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -250,6 +250,29 @@ abstract class Target {
|
||||
}
|
||||
}
|
||||
|
||||
/// Target that contains multiple other targets.
|
||||
///
|
||||
/// This target does not do anything in its own [build]
|
||||
/// and acts as a wrapper around multiple other targets.
|
||||
class CompositeTarget extends Target {
|
||||
CompositeTarget(this.dependencies);
|
||||
|
||||
@override
|
||||
final List<Target> dependencies;
|
||||
|
||||
@override
|
||||
String get name => '_composite';
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async { }
|
||||
|
||||
@override
|
||||
List<Source> get inputs => <Source>[];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => <Source>[];
|
||||
}
|
||||
|
||||
/// The [Environment] defines several constants for use during the build.
|
||||
///
|
||||
/// The environment contains configuration and file paths that are safe to
|
||||
|
@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../android/deferred_components_gen_snapshot_validator.dart';
|
||||
import '../../base/deferred_component.dart';
|
||||
import '../../project.dart';
|
||||
import '../build_system.dart';
|
||||
import '../depfile.dart';
|
||||
|
||||
/// Creates a [DeferredComponentsGenSnapshotValidator], runs the checks, and displays the validator
|
||||
/// output to the developer if changes are recommended.
|
||||
class DeferredComponentsGenSnapshotValidatorTarget extends Target {
|
||||
/// Create an [AndroidAotDeferredComponentsBundle] implementation for a given [targetPlatform] and [buildMode].
|
||||
DeferredComponentsGenSnapshotValidatorTarget({
|
||||
@required this.dependency,
|
||||
@required this.abis,
|
||||
this.title,
|
||||
this.exitOnFail = true,
|
||||
String name = 'deferred_components_setup_validator',
|
||||
}) : _name = name;
|
||||
|
||||
/// The [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on packed
|
||||
/// as a [CompositeTarget].
|
||||
final CompositeTarget dependency;
|
||||
|
||||
/// The title of the [DeferredComponentsGenSnapshotValidator] that is
|
||||
/// displayed to the developer when logging results.
|
||||
final String title;
|
||||
|
||||
/// Whether to exit the tool if a recommended change is found by the
|
||||
/// [DeferredComponentsGenSnapshotValidator].
|
||||
final bool exitOnFail;
|
||||
|
||||
/// The abis to validate.
|
||||
final List<String> abis;
|
||||
|
||||
@override
|
||||
String get name => _name;
|
||||
final String _name;
|
||||
|
||||
@override
|
||||
List<Source> get inputs => const <Source>[];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[];
|
||||
|
||||
@override
|
||||
List<String> get depfiles => <String>[
|
||||
'flutter_$name.d',
|
||||
];
|
||||
|
||||
@override
|
||||
List<Target> get dependencies => dependency == null ? <Target>[] : <Target>[dependency];
|
||||
|
||||
@visibleForTesting
|
||||
DeferredComponentsGenSnapshotValidator validator;
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
final DepfileService depfileService = DepfileService(
|
||||
fileSystem: environment.fileSystem,
|
||||
logger: environment.logger,
|
||||
);
|
||||
validator = DeferredComponentsGenSnapshotValidator(
|
||||
environment,
|
||||
title: title,
|
||||
exitOnFail: exitOnFail,
|
||||
);
|
||||
|
||||
final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(
|
||||
environment.outputDir,
|
||||
environment.logger,
|
||||
abis: abis
|
||||
);
|
||||
|
||||
validator
|
||||
..checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
FlutterProject.current().manifest.deferredComponents,
|
||||
generatedLoadingUnits,
|
||||
)
|
||||
..checkAgainstLoadingUnitsCache(generatedLoadingUnits)
|
||||
..writeLoadingUnitsCache(generatedLoadingUnits);
|
||||
|
||||
validator.handleResults();
|
||||
|
||||
depfileService.writeToFile(
|
||||
Depfile(validator.inputs, validator.outputs),
|
||||
environment.buildDir.childFile('flutter_$name.d'),
|
||||
);
|
||||
}
|
||||
}
|
@ -227,7 +227,7 @@ class AssembleCommand extends FlutterCommand {
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final List<Target> targets = createTargets();
|
||||
final Target target = targets.length == 1 ? targets.single : _CompositeTarget(targets);
|
||||
final Target target = targets.length == 1 ? targets.single : CompositeTarget(targets);
|
||||
final BuildResult result = await globals.buildSystem.build(
|
||||
target,
|
||||
createEnvironment(),
|
||||
@ -308,22 +308,3 @@ void writePerformanceData(Iterable<PerformanceMeasurement> measurements, File ou
|
||||
}
|
||||
outFile.writeAsStringSync(json.encode(jsonData));
|
||||
}
|
||||
|
||||
class _CompositeTarget extends Target {
|
||||
_CompositeTarget(this.dependencies);
|
||||
|
||||
@override
|
||||
final List<Target> dependencies;
|
||||
|
||||
@override
|
||||
String get name => '_composite';
|
||||
|
||||
@override
|
||||
Future<void> build(Environment environment) async { }
|
||||
|
||||
@override
|
||||
List<Source> get inputs => <Source>[];
|
||||
|
||||
@override
|
||||
List<Source> get outputs => <Source>[];
|
||||
}
|
||||
|
@ -0,0 +1,628 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/deferred_components_gen_snapshot_validator.dart';
|
||||
import 'package:flutter_tools/src/android/deferred_components_validator.dart';
|
||||
import 'package:flutter_tools/src/base/deferred_component.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
|
||||
import '../../src/common.dart';
|
||||
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
BufferLogger logger;
|
||||
Environment env;
|
||||
|
||||
Environment createEnvironment() {
|
||||
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
|
||||
final Environment result = Environment(
|
||||
outputDir: fileSystem.directory('/output'),
|
||||
buildDir: fileSystem.directory('/build'),
|
||||
projectDir: fileSystem.directory('/project'),
|
||||
defines: defines,
|
||||
inputs: <String, String>{},
|
||||
cacheDir: fileSystem.directory('/cache'),
|
||||
flutterRootDir: fileSystem.directory('/flutter_root'),
|
||||
artifacts: globals.artifacts,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: globals.processManager,
|
||||
engineVersion: 'invalidEngineVersion',
|
||||
generateDartPluginRegistry: false,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
logger = BufferLogger.test();
|
||||
env = createEnvironment();
|
||||
});
|
||||
|
||||
testWithoutContext('No checks passes', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('writeCache passes', () async {
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.writeLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
|
||||
final File expectedFile = env.projectDir.childFile('deferred_components_loading_units.yaml');
|
||||
|
||||
expect(expectedFile.existsSync(), true);
|
||||
const String expectedContents =
|
||||
'''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''';
|
||||
expect(expectedFile.readAsStringSync().contains(expectedContents), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache identical passes', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
]
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache finds new loading units', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('New loading units were found:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache finds missing loading units', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('missing cache file counts as all new loading units', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('New loading units were found:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: missing main entry', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units-spelled-wrong:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, \'loading-units\' entry did not exist.'), true);
|
||||
|
||||
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), false);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: not a list', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units: hello
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, \'loading-units\' is not a list.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: not a list', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- 2
|
||||
- 3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, \'loading-units\' is not a list of maps.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: missing id', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, all loading units must have an \'id\''), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: libraries is list', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries: hello
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, \'libraries\' is not a list.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: libraries is list of strings', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- blah: hello
|
||||
blah2: hello2
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid loading units yaml file, \'libraries\' is not a list of strings.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitCache validator detects malformed file: empty libraries allowed', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
|
||||
if (cacheFile.existsSync()) {
|
||||
cacheFile.deleteSync();
|
||||
}
|
||||
cacheFile.createSync(recursive: true);
|
||||
cacheFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitsCache(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), false);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping modifies strings file', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
|
||||
android:value="invalidmapping"
|
||||
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('android:value="invalidmapping"'), false);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping adds mapping when no existing mapping', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
|
||||
// Tests if all of the regexp whitespace detection is working.
|
||||
testWithoutContext('androidStringMapping handles whitespace within entry', () async {
|
||||
final DeferredComponentsGenSnapshotValidator validator = DeferredComponentsGenSnapshotValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
|
||||
android:name = "io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
|
||||
android:value = "invalidmapping"
|
||||
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"')), false);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/deferred_components_prebuild_validator.dart';
|
||||
import 'package:flutter_tools/src/android/deferred_components_validator.dart';
|
||||
import 'package:flutter_tools/src/base/deferred_component.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
BufferLogger logger;
|
||||
Environment env;
|
||||
|
||||
Environment createEnvironment() {
|
||||
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
|
||||
final Environment result = Environment(
|
||||
outputDir: fileSystem.directory('/output'),
|
||||
buildDir: fileSystem.directory('/build'),
|
||||
projectDir: fileSystem.directory('/project'),
|
||||
defines: defines,
|
||||
inputs: <String, String>{},
|
||||
cacheDir: fileSystem.directory('/cache'),
|
||||
flutterRootDir: fileSystem.directory('/flutter_root'),
|
||||
artifacts: globals.artifacts,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: globals.processManager,
|
||||
engineVersion: 'invalidEngineVersion',
|
||||
generateDartPluginRegistry: false,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
logger = BufferLogger.test();
|
||||
env = createEnvironment();
|
||||
});
|
||||
|
||||
testWithoutContext('No checks passes', () async {
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('clearTempDir passes', () async {
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup build.gradle does not exist', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File file = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
file.deleteSync();
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/build.gradle\n'), true);
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup AndroidManifest.xml does not exist', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File file = componentDir.childFile('build.gradle');
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
file.deleteSync();
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/component1/src/main/AndroidManifest.xml\n'), true);
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup all files exist passes', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File buildGradle = componentDir.childFile('build.gradle');
|
||||
if (buildGradle.existsSync()) {
|
||||
buildGradle.deleteSync();
|
||||
}
|
||||
buildGradle.createSync(recursive: true);
|
||||
final File manifest = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
manifest.deleteSync();
|
||||
buildGradle.deleteSync();
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping creates new file', () async {
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
android:value="invalidmapping" />
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), false);
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping modifies strings file', () async {
|
||||
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
stringRes.createSync(recursive: true);
|
||||
stringRes.writeAsStringSync('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="component1Name">component1</string>
|
||||
</resources>
|
||||
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), false);
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
});
|
||||
}
|
@ -1,942 +0,0 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/deferred_components_setup_validator.dart';
|
||||
import 'package:flutter_tools/src/base/deferred_component.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
BufferLogger logger;
|
||||
Environment env;
|
||||
|
||||
Environment createEnvironment() {
|
||||
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
|
||||
final Environment result = Environment(
|
||||
outputDir: fileSystem.directory('/output'),
|
||||
buildDir: fileSystem.directory('/build'),
|
||||
projectDir: fileSystem.directory('/project'),
|
||||
defines: defines,
|
||||
inputs: <String, String>{},
|
||||
cacheDir: fileSystem.directory('/cache'),
|
||||
flutterRootDir: fileSystem.directory('/flutter_root'),
|
||||
artifacts: globals.artifacts,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
processManager: globals.processManager,
|
||||
engineVersion: 'invalidEngineVersion',
|
||||
generateDartPluginRegistry: false,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
logger = BufferLogger.test();
|
||||
env = createEnvironment();
|
||||
});
|
||||
|
||||
testWithoutContext('No checks passes', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('clearTempDir passes', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('writeGolden passes', () async {
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
validator.writeGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
|
||||
final File expectedFile = env.projectDir.childFile('deferred_components_golden.yaml');
|
||||
|
||||
expect(expectedFile.existsSync(), true);
|
||||
const String expectedContents =
|
||||
'''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''';
|
||||
expect(expectedFile.readAsStringSync().contains(expectedContents), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden identical passes', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
]
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden finds new loading units', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('New loading units were found:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden finds missing loading units', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('missing golden file counts as all new loading units', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('New loading units were found:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: missing main entry', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units-spelled-wrong:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, \'loading-units\' entry did not exist.'), true);
|
||||
|
||||
expect(logger.statusText.contains('Previously existing loading units no longer exist:\n\n LoadingUnit 2\n Libraries:\n - lib1\n'), false);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: not a list', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units: hello
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, \'loading-units\' is not a list.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: not a list', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- 2
|
||||
- 3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, \'loading-units\' is not a list of maps.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: missing id', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- libraries:
|
||||
- lib2
|
||||
- lib3
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, all loading units must have an \'id\''), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: libraries is list', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries: hello
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, \'libraries\' is not a list.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: libraries is list of strings', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
- blah: hello
|
||||
blah2: hello2
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), true);
|
||||
expect(logger.statusText.contains('Invalid golden yaml file, \'libraries\' is not a list of strings.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('loadingUnitGolden validator detects malformed file: empty libraries allowed', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final File goldenFile = env.projectDir.childFile(DeferredComponentsSetupValidator.kDeferredComponentsGoldenFileName);
|
||||
if (goldenFile.existsSync()) {
|
||||
goldenFile.deleteSync();
|
||||
}
|
||||
goldenFile.createSync(recursive: true);
|
||||
goldenFile.writeAsStringSync('''
|
||||
loading-units:
|
||||
- id: 2
|
||||
libraries:
|
||||
- lib1
|
||||
- id: 3
|
||||
libraries:
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAgainstLoadingUnitGolden(
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Errors checking the following files:'), false);
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup build.gradle does not exist', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File file = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
file.deleteSync();
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/component1/build.gradle\n'), true);
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup AndroidManifest.xml does not exist', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File file = componentDir.childFile('build.gradle');
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
file.deleteSync();
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/component1/src/main/AndroidManifest.xml\n'), true);
|
||||
});
|
||||
|
||||
testUsingContext('androidComponentSetup all files exist passes', () async {
|
||||
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
|
||||
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
|
||||
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
|
||||
if (templatesDir.existsSync()) {
|
||||
templatesDir.deleteSync(recursive: true);
|
||||
}
|
||||
buildGradleTemplate.createSync(recursive: true);
|
||||
androidManifestTemplate.createSync(recursive: true);
|
||||
buildGradleTemplate.writeAsStringSync('fake build.gradle template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
|
||||
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
templatesDir: templatesDir,
|
||||
);
|
||||
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
|
||||
final File buildGradle = componentDir.childFile('build.gradle');
|
||||
if (buildGradle.existsSync()) {
|
||||
buildGradle.deleteSync();
|
||||
}
|
||||
buildGradle.createSync(recursive: true);
|
||||
final File manifest = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
await validator.checkAndroidDynamicFeature(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1'),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
manifest.deleteSync();
|
||||
buildGradle.deleteSync();
|
||||
expect(logger.statusText, 'test check passed.\n');
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping creates new file', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
android:value="invalidmapping" />
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('Newly generated android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('android:value="invalidmapping"'), false);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping modifies strings file', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
stringRes.createSync(recursive: true);
|
||||
stringRes.writeAsStringSync('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="component1Name">component1</string>
|
||||
</resources>
|
||||
|
||||
''', flush: true, mode: FileMode.append);
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
|
||||
android:value="invalidmapping"
|
||||
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('android:value="invalidmapping"'), false);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
|
||||
testWithoutContext('androidStringMapping adds mapping when no existing mapping', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
stringRes.createSync(recursive: true);
|
||||
stringRes.writeAsStringSync('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="component1Name">component1</string>
|
||||
</resources>
|
||||
|
||||
''', flush: true, mode: FileMode.append);
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
|
||||
// Tests if all of the regexp whitespace detection is working.
|
||||
testWithoutContext('androidStringMapping handles whitespace within entry', () async {
|
||||
final DeferredComponentsSetupValidator validator = DeferredComponentsSetupValidator(
|
||||
env,
|
||||
exitOnFail: false,
|
||||
title: 'test check',
|
||||
);
|
||||
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
|
||||
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
|
||||
if (stringRes.existsSync()) {
|
||||
stringRes.deleteSync();
|
||||
}
|
||||
stringRes.createSync(recursive: true);
|
||||
stringRes.writeAsStringSync('''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="component1Name">component1</string>
|
||||
</resources>
|
||||
|
||||
''', flush: true, mode: FileMode.append);
|
||||
final File manifest = baseModuleDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
|
||||
if (manifest.existsSync()) {
|
||||
manifest.deleteSync();
|
||||
}
|
||||
manifest.createSync(recursive: true);
|
||||
manifest.writeAsStringSync('''
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.splitaot">
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
|
||||
android:label="splitaot"
|
||||
android:extractNativeLibs="false">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
<meta-data
|
||||
|
||||
android:name = "io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping"
|
||||
|
||||
android:value = "invalidmapping"
|
||||
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
''', flush: true, mode: FileMode.append);
|
||||
validator.checkAppAndroidManifestComponentLoadingUnitMapping(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
<LoadingUnit>[
|
||||
LoadingUnit(id: 2, libraries: <String>['lib1']),
|
||||
LoadingUnit(id: 3, libraries: <String>['lib2', 'lib3']),
|
||||
LoadingUnit(id: 4, libraries: <String>['lib4', 'lib5']),
|
||||
],
|
||||
);
|
||||
validator.checkAndroidResourcesStrings(
|
||||
<DeferredComponent>[
|
||||
DeferredComponent(name: 'component1', libraries: <String>['lib2']),
|
||||
DeferredComponent(name: 'component2', libraries: <String>['lib1', 'lib4']),
|
||||
],
|
||||
);
|
||||
validator.displayResults();
|
||||
validator.attemptToolExit();
|
||||
|
||||
expect(logger.statusText.contains('Modified android files:\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/AndroidManifest.xml\n'), true);
|
||||
expect(logger.statusText.contains('build/${DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
|
||||
|
||||
final File stringsOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childDirectory('res')
|
||||
.childDirectory('values')
|
||||
.childFile('strings.xml');
|
||||
expect(stringsOutput.existsSync(), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component1Name">component1</string>'), true);
|
||||
expect(stringsOutput.readAsStringSync().contains('<string name="component2Name">component2</string>'), true);
|
||||
|
||||
final File manifestOutput = env.projectDir
|
||||
.childDirectory('build')
|
||||
.childDirectory(DeferredComponentsSetupValidator.kDeferredComponentsTempDirectory)
|
||||
.childDirectory('app')
|
||||
.childDirectory('src')
|
||||
.childDirectory('main')
|
||||
.childFile('AndroidManifest.xml');
|
||||
expect(manifestOutput.existsSync(), true);
|
||||
expect(manifestOutput.readAsStringSync().contains('<meta-data android:name="io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager.loadingUnitMapping" android:value="3:component1,2:component2,4:component2"/>'), true);
|
||||
expect(manifestOutput.readAsStringSync().contains(RegExp(r'android:value[\s\n]*=[\s\n]*"invalidmapping"')), false);
|
||||
expect(manifestOutput.readAsStringSync().contains('<!-- Don\'t delete the meta-data below.'), true);
|
||||
});
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
// 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.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/deferred_components.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/android.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/common.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
import '../../../src/context.dart';
|
||||
|
||||
// These tests perform a simple check to verify if the check/task was executed at all.
|
||||
// Detailed per-check tests are in android/deferred_components_setup_validator_test.dart.
|
||||
void main() {
|
||||
FileSystem fileSystem;
|
||||
BufferLogger logger;
|
||||
|
||||
setUp(() {
|
||||
logger = BufferLogger.test();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
testUsingContext('checkAppAndroidManifestComponentLoadingUnitMapping checks runs', () async {
|
||||
final Environment environment = Environment.test(
|
||||
fileSystem.currentDirectory,
|
||||
outputDir: fileSystem.directory('out')..createSync(),
|
||||
buildDir: fileSystem.directory('build')..createSync(),
|
||||
projectDir: fileSystem.directory('project')..createSync(),
|
||||
defines: <String, String>{
|
||||
kSplitAot: 'true',
|
||||
},
|
||||
artifacts: null,
|
||||
processManager: null,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
|
||||
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
|
||||
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
|
||||
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
|
||||
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
|
||||
dependency: compositeTarget,
|
||||
abis: <String>['arm64-v8a'],
|
||||
title: 'test checks',
|
||||
exitOnFail: false,
|
||||
);
|
||||
|
||||
await validatorTarget.build(environment);
|
||||
|
||||
// We check the inputs to determine if the task was executed.
|
||||
expect(validatorTarget.validator.inputs.length, 3);
|
||||
expect(validatorTarget.validator.inputs[0].path, 'project/pubspec.yaml');
|
||||
expect(validatorTarget.validator.inputs[1].path, 'project/android/app/src/main/AndroidManifest.xml');
|
||||
});
|
||||
|
||||
testUsingContext('checkAgainstLoadingUnitsCache checks runs', () async {
|
||||
final Environment environment = Environment.test(
|
||||
fileSystem.currentDirectory,
|
||||
outputDir: fileSystem.directory('out')..createSync(),
|
||||
buildDir: fileSystem.directory('build')..createSync(),
|
||||
projectDir: fileSystem.directory('project')..createSync(),
|
||||
defines: <String, String>{
|
||||
kSplitAot: 'true',
|
||||
},
|
||||
artifacts: null,
|
||||
processManager: null,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
|
||||
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
|
||||
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
|
||||
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
|
||||
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
|
||||
dependency: compositeTarget,
|
||||
abis: <String>['arm64-v8a'],
|
||||
title: 'test checks',
|
||||
exitOnFail: false,
|
||||
);
|
||||
|
||||
await validatorTarget.build(environment);
|
||||
|
||||
// We check the inputs to determine if the task was executed.
|
||||
expect(validatorTarget.validator.inputs.length, 3);
|
||||
expect(validatorTarget.validator.inputs[2].path, 'project/deferred_components_loading_units.yaml');
|
||||
});
|
||||
|
||||
testUsingContext('writeLoadingUnitsCache task runs', () async {
|
||||
final Environment environment = Environment.test(
|
||||
fileSystem.currentDirectory,
|
||||
outputDir: fileSystem.directory('out')..createSync(),
|
||||
buildDir: fileSystem.directory('build')..createSync(),
|
||||
projectDir: fileSystem.directory('project')..createSync(),
|
||||
defines: <String, String>{
|
||||
kSplitAot: 'true',
|
||||
},
|
||||
artifacts: null,
|
||||
processManager: null,
|
||||
fileSystem: fileSystem,
|
||||
logger: logger,
|
||||
);
|
||||
environment.buildDir.createSync(recursive: true);
|
||||
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
|
||||
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
|
||||
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
|
||||
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
|
||||
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
|
||||
dependency: compositeTarget,
|
||||
abis: <String>['arm64-v8a'],
|
||||
title: 'test checks',
|
||||
exitOnFail: false,
|
||||
);
|
||||
|
||||
await validatorTarget.build(environment);
|
||||
|
||||
// We check the inputs to determine if the task was executed.
|
||||
expect(validatorTarget.validator.outputs.length, 1);
|
||||
expect(validatorTarget.validator.outputs[0].path, 'project/deferred_components_loading_units.yaml');
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user