[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] defines several constants for use during the build.
|
||||||
///
|
///
|
||||||
/// The environment contains configuration and file paths that are safe to
|
/// 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
|
@override
|
||||||
Future<FlutterCommandResult> runCommand() async {
|
Future<FlutterCommandResult> runCommand() async {
|
||||||
final List<Target> targets = createTargets();
|
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(
|
final BuildResult result = await globals.buildSystem.build(
|
||||||
target,
|
target,
|
||||||
createEnvironment(),
|
createEnvironment(),
|
||||||
@ -308,22 +308,3 @@ void writePerformanceData(Iterable<PerformanceMeasurement> measurements, File ou
|
|||||||
}
|
}
|
||||||
outFile.writeAsStringSync(json.encode(jsonData));
|
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