[flutter_tools] Deferred components build system (#76192)

This commit is contained in:
Gary Qian 2021-03-04 21:09:02 -05:00 committed by GitHub
parent faabc9ab46
commit ee41782732
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 2008 additions and 245 deletions

View File

@ -519,7 +519,7 @@ Future<void> _flutterBuild(
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset');
print('The target performance data was: ${file.readAsStringSync()}');
print('The target performance data was: ${file.readAsStringSync().replaceAll('},', '},\n')}');
exit(1);
}
}

View File

@ -134,6 +134,14 @@ class FlutterPlugin implements Plugin<Project> {
}
}
}
if (project.hasProperty('deferred-component-names')) {
String[] componentNames = project.property('deferred-component-names').split(',').collect {":${it}"}
project.android {
dynamicFeatures = componentNames
}
}
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
@ -173,17 +181,23 @@ class FlutterPlugin implements Plugin<Project> {
matchingFallbacks = ["debug", "release"]
}
}
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
// NOTE: The resource shrinker can't be used for libraries.
shrinkResources isBuiltAsApp(project)
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
// TODO(garyq): Shrinking is only false for multi apk split aot builds, where shrinking is not allowed yet.
// This limitation has been removed experimentally in gradle plugin version 4.2, so we can remove
// this check when we upgrade to 4.2+ gradle. Currently, deferred components apps may see
// increased app size due to this.
if (shouldShrinkResources(project)) {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
// NOTE: The resource shrinker can't be used for libraries.
shrinkResources isBuiltAsApp(project)
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
}
}
}
@ -200,6 +214,13 @@ class FlutterPlugin implements Plugin<Project> {
project.android.buildTypes.all this.&addFlutterDependencies
}
private static Boolean shouldShrinkResources(Project project) {
if (project.hasProperty("shrink")) {
return project.property("shrink").toBoolean()
}
return true
}
/**
* Adds the dependencies required by the Flutter project.
* This includes:
@ -706,6 +727,14 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('code-size-directory')) {
codeSizeDirectoryValue = project.property('code-size-directory')
}
Boolean deferredComponentsValue = false
if (project.hasProperty('deferred-components')) {
deferredComponentsValue = project.property('deferred-components').toBoolean()
}
Boolean validateDeferredComponentsValue = true
if (project.hasProperty('validate-deferred-components')) {
validateDeferredComponentsValue = project.property('validate-deferred-components').toBoolean()
}
def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
@ -747,6 +776,8 @@ class FlutterPlugin implements Plugin<Project> {
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
deferredComponents deferredComponentsValue
validateDeferredComponents validateDeferredComponentsValue
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
@ -810,7 +841,8 @@ class FlutterPlugin implements Plugin<Project> {
processResources.dependsOn(copyFlutterAssetsTask)
}
return copyFlutterAssetsTask
}
} // end def addFlutterDeps
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
Task assembleTask = getAssembleTask(variant)
@ -883,7 +915,7 @@ class FlutterPlugin implements Plugin<Project> {
// | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- |
// | freeRelease | relese |
// | freeRelease | release |
// | freeDebug | debug |
// | freeDevelop | debug |
// | profile | profile |
@ -961,6 +993,10 @@ abstract class BaseFlutterTask extends DefaultTask {
@Optional @Input
String codeSizeDirectory;
String performanceMeasurementFile;
@Optional @Input
Boolean deferredComponents
@Optional @Input
Boolean validateDeferredComponents
@OutputFiles
FileCollection getDependenciesFiles() {
@ -985,6 +1021,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String[] ruleNames;
if (buildMode == "debug") {
ruleNames = ["debug_android_application"]
} else if (deferredComponents) {
ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" }
} else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
}

View File

@ -38,5 +38,7 @@ abstract class AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
});
}

View File

@ -30,10 +30,17 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// 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, {
DeferredComponentsGenSnapshotValidator(this.env, {
bool exitOnFail = true,
String title,
}) : super(env, exitOnFail: exitOnFail, title: title);
}) : super(env.projectDir, env.logger, exitOnFail: exitOnFail, title: title);
/// 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;
// The key used to identify the metadata element as the loading unit id to
// deferred component mapping.
@ -58,8 +65,8 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// 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'));
final Directory androidDir = projectDir.childDirectory('android');
inputs.add(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
@ -106,8 +113,10 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
mappingBuffer.write('$key:${mapping[key]},');
}
String encodedMapping = mappingBuffer.toString();
// remove trailing comma.
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
// remove trailing comma if any
if (encodedMapping.endsWith(',')) {
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
}
// Check for existing metadata entry and see if needs changes.
bool exists = false;
bool modified = false;
@ -163,7 +172,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// considered new.
bool checkAgainstLoadingUnitsCache(
List<LoadingUnit> generatedLoadingUnits) {
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
loadingUnitComparisonResults = <String, dynamic>{};
final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{};
final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[];
@ -276,7 +285,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// deferred components.
void writeLoadingUnitsCache(List<LoadingUnit> generatedLoadingUnits) {
generatedLoadingUnits ??= <LoadingUnit>[];
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
final File cacheFile = projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
outputs.add(cacheFile);
ErrorHandlingFileSystem.deleteIfExists(cacheFile);
cacheFile.createSync(recursive: true);

View File

@ -10,7 +10,7 @@ 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 '../base/logger.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../template.dart';
@ -25,20 +25,18 @@ import 'deferred_components_validator.dart';
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, {
DeferredComponentsPrebuildValidator(Directory projectDir, Logger logger, {
bool exitOnFail = true,
String title,
Directory templatesDir,
}) : _templatesDir = templatesDir,
super(env, exitOnFail: exitOnFail, title: title);
super(projectDir, logger, exitOnFail: exitOnFail, title: title);
final Directory _templatesDir;
@ -56,7 +54,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// 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'));
inputs.add(projectDir.childFile('pubspec.yaml'));
if (components == null || components.isEmpty) {
return false;
}
@ -64,7 +62,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
for (final DeferredComponent component in components) {
final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles(
name: component.name,
env: env,
projectDir: projectDir,
logger: logger,
templatesDir: _templatesDir
);
if (!androidFiles.verifyFilesExist()) {
@ -106,8 +105,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// 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'));
final Directory androidDir = projectDir.childDirectory('android');
inputs.add(projectDir.childFile('pubspec.yaml'));
// Add component name mapping to strings.xml
final File stringRes = androidDir
@ -202,7 +201,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// Deletes all files inside of the validator's output directory.
void clearOutputDir() {
final Directory dir = env.projectDir.childDirectory('build').childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory);
final Directory dir = projectDir.childDirectory('build').childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory);
ErrorHandlingFileSystem.deleteIfExists(dir, recursive: true);
}
}
@ -212,16 +211,18 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
class _DeferredComponentAndroidFiles {
_DeferredComponentAndroidFiles({
@required this.name,
@required this.env,
@required this.projectDir,
this.logger,
Directory templatesDir,
}) : _templatesDir = templatesDir;
// The name of the deferred component.
final String name;
final Environment env;
final Directory projectDir;
final Logger logger;
final Directory _templatesDir;
Directory get androidDir => env.projectDir.childDirectory('android');
Directory get androidDir => projectDir.childDirectory('android');
Directory get componentDir => androidDir.childDirectory(name);
File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
@ -250,18 +251,18 @@ class _DeferredComponentAndroidFiles {
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');
final Directory templateComponentDir = _templatesDir.childDirectory('module${globals.fs.path.separator}android${globals.fs.path.separator}deferred_component');
template = Template(templateComponentDir, templateComponentDir, _templatesDir,
fileSystem: env.fileSystem,
fileSystem: globals.fs,
templateManifest: null,
logger: env.logger,
logger: logger,
templateRenderer: globals.templateRenderer,
);
} else {
template = await Template.fromName('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component',
fileSystem: env.fileSystem,
template = await Template.fromName('module${globals.fs.path.separator}android${globals.fs.path.separator}deferred_component',
fileSystem: globals.fs,
templateManifest: null,
logger: env.logger,
logger: logger,
templateRenderer: globals.templateRenderer,
);
}

View File

@ -7,8 +7,8 @@
import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../base/logger.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
@ -21,10 +21,10 @@ import '../globals.dart' as globals;
/// The results of each check are handled internally as they are not meant to
/// be run isolated.
abstract class DeferredComponentsValidator {
DeferredComponentsValidator(this.env, {
DeferredComponentsValidator(this.projectDir, this.logger, {
this.exitOnFail = true,
String title,
}) : outputDir = env.projectDir
}) : outputDir = projectDir
.childDirectory('build')
.childDirectory(kDeferredComponentsTempDirectory),
inputs = <File>[],
@ -34,12 +34,9 @@ abstract class DeferredComponentsValidator {
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;
/// Logger to use for [displayResults] output.
final Logger logger;
/// When true, failed checks and tasks will result in [attemptToolExit]
/// triggering [throwToolExit].
@ -54,6 +51,9 @@ abstract class DeferredComponentsValidator {
/// The title printed at the top of the results of [displayResults]
final String title;
/// The root directory of the flutter project.
final Directory projectDir;
/// The temporary directory that the validator writes recommended files into.
final Directory outputDir;
@ -111,20 +111,20 @@ abstract class DeferredComponentsValidator {
/// 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);
logger.printStatus(_thickDivider);
logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true);
logger.printStatus(_thickDivider);
// Log any file reading/existence errors.
if (invalidFiles.isNotEmpty) {
env.logger.printStatus('Errors checking the following files:\n', emphasis: true);
logger.printStatus('Errors checking the following files:\n', emphasis: true);
for (final String key in invalidFiles.keys) {
env.logger.printStatus(' - $key: ${invalidFiles[key]}\n');
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('');
logger.printStatus('Diff between `android` and expected files:', emphasis: true);
logger.printStatus('');
for (final String line in diffLines) {
// We only care about diffs in files that have
// counterparts.
@ -137,62 +137,62 @@ abstract class DeferredComponentsValidator {
} else if (line.startsWith('-')) {
color = TerminalColor.red;
}
env.logger.printStatus(line, color: color);
logger.printStatus(line, color: color);
}
env.logger.printStatus('');
logger.printStatus('');
}
// Log any newly generated and modified files.
if (generatedFiles.isNotEmpty) {
env.logger.printStatus('Newly generated android files:', emphasis: true);
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);
final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (modifiedFiles.isNotEmpty) {
env.logger.printStatus('Modified android files:', emphasis: true);
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);
final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) {
env.logger.printStatus('''
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'''
logger.printStatus(r'''
The recommended changes can be quickly applied by running:
$ patch -p0 < build/setup_deferred_components.diff
''');
}
env.logger.printStatus('$_thinDivider\n');
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);
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);
logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
logger.printStatus('');
}
if ((loadingUnitComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
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);
logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (loadingUnitComparisonResults['match'] as bool) {
env.logger.printStatus('No change in generated loading units.\n');
logger.printStatus('No change in generated loading units.\n');
} else {
env.logger.printStatus('''
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
@ -205,14 +205,14 @@ $_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`
logger.printStatus('''
Setup verification can be skipped by passing the `--no-validate-deferred-components`
flag, however, doing so may put your app at risk of not functioning even if the
build is successful.
$_thickDivider''');
return;
}
env.logger.printStatus('$title passed.');
logger.printStatus('$title passed.');
}
void attemptToolExit() {

View File

@ -12,6 +12,7 @@ import 'package:xml/xml.dart';
import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
@ -242,6 +243,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async {
await buildGradleApp(
project: project,
@ -249,6 +252,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
target: target,
isBuildingBundle: true,
localGradleErrors: gradleErrors,
validateDeferredComponents: validateDeferredComponents,
deferredComponentsEnabled: deferredComponentsEnabled,
);
}
@ -270,6 +275,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required bool isBuildingBundle,
@required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
int retries = 1,
}) async {
assert(project != null);
@ -355,8 +362,29 @@ class AndroidGradleBuilder implements AndroidBuilder {
if (target != null) {
command.add('-Ptarget=$target');
}
if (project.manifest.deferredComponents != null) {
if (deferredComponentsEnabled) {
command.add('-Pdeferred-components=true');
androidBuildInfo.buildInfo.dartDefines.add('validate-deferred-components=$validateDeferredComponents');
}
// Pass in deferred components regardless of building split aot to satisfy
// android dynamic features registry in build.gradle.
final List<String> componentNames = <String>[];
for (final DeferredComponent component in project.manifest.deferredComponents) {
componentNames.add(component.name);
}
if (componentNames.isNotEmpty) {
command.add('-Pdeferred-component-names=${componentNames.join(',')}');
// Multi-apk applications cannot use shrinking. This is only relevant when using
// android dynamic feature modules.
_logger.printStatus(
'Shrinking has been disabled for this build due to deferred components. Shrinking is '
'not available for multi-apk applications. This limitation is expected to be removed '
'when Gradle plugin 4.2+ is available in Flutter.', color: TerminalColor.yellow);
command.add('-Pshrink=false');
}
}
command.addAll(androidBuildInfo.buildInfo.toGradleConfig());
if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) {
command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}');
}

View File

@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'base/context.dart';
import 'base/deferred_component.dart';
import 'base/file_system.dart';
import 'base/logger.dart';
import 'base/platform.dart';
@ -53,7 +54,8 @@ abstract class AssetBundleFactory {
@required Logger logger,
@required FileSystem fileSystem,
@required Platform platform,
}) => _ManifestAssetBundleFactory(logger: logger, fileSystem: fileSystem, platform: platform);
bool splitDeferredAssets = false,
}) => _ManifestAssetBundleFactory(logger: logger, fileSystem: fileSystem, platform: platform, splitDeferredAssets: splitDeferredAssets);
/// Creates a new [AssetBundle].
AssetBundle createBundle();
@ -62,6 +64,10 @@ abstract class AssetBundleFactory {
abstract class AssetBundle {
Map<String, DevFSContent> get entries;
/// The files that were specified under the deferred components assets sections
/// in pubspec.
Map<String, Map<String, DevFSContent>> get deferredComponentsEntries;
/// Additional files that this bundle depends on that are not included in the
/// output result.
List<File> get additionalDependencies;
@ -75,6 +81,7 @@ abstract class AssetBundle {
String manifestPath = defaultManifestPath,
String assetDirPath,
@required String packagesPath,
bool deferredComponentsEnabled = false,
});
}
@ -83,16 +90,19 @@ class _ManifestAssetBundleFactory implements AssetBundleFactory {
@required Logger logger,
@required FileSystem fileSystem,
@required Platform platform,
bool splitDeferredAssets = false,
}) : _logger = logger,
_fileSystem = fileSystem,
_platform = platform;
_platform = platform,
_splitDeferredAssets = splitDeferredAssets;
final Logger _logger;
final FileSystem _fileSystem;
final Platform _platform;
final bool _splitDeferredAssets;
@override
AssetBundle createBundle() => ManifestAssetBundle(logger: _logger, fileSystem: _fileSystem, platform: _platform);
AssetBundle createBundle() => ManifestAssetBundle(logger: _logger, fileSystem: _fileSystem, platform: _platform, splitDeferredAssets: _splitDeferredAssets);
}
/// An asset bundle based on a pubspec.yaml file.
@ -103,19 +113,25 @@ class ManifestAssetBundle implements AssetBundle {
@required Logger logger,
@required FileSystem fileSystem,
@required Platform platform,
bool splitDeferredAssets = false,
}) : _logger = logger,
_fileSystem = fileSystem,
_platform = platform,
_splitDeferredAssets = splitDeferredAssets,
_licenseCollector = LicenseCollector(fileSystem: fileSystem);
final Logger _logger;
final FileSystem _fileSystem;
final LicenseCollector _licenseCollector;
final Platform _platform;
final bool _splitDeferredAssets;
@override
final Map<String, DevFSContent> entries = <String, DevFSContent>{};
@override
final Map<String, Map<String, DevFSContent>> deferredComponentsEntries = <String, Map<String, DevFSContent>>{};
// If an asset corresponds to a wildcard directory, then it may have been
// updated without changes to the manifest. These are only tracked for
// the current project.
@ -163,6 +179,7 @@ class ManifestAssetBundle implements AssetBundle {
String manifestPath = defaultManifestPath,
String assetDirPath,
@required String packagesPath,
bool deferredComponentsEnabled = false,
}) async {
assetDirPath ??= getAssetBuildDirectory();
FlutterProject flutterProject;
@ -197,29 +214,49 @@ class ManifestAssetBundle implements AssetBundle {
// in the pubspec.yaml file's assets and font and sections. The
// value of each image asset is a list of resolution-specific "variants",
// see _AssetDirectoryCache.
final List<String> excludeDirs = <String>[
assetDirPath,
getBuildDirectory(),
if (flutterProject.ios.existsSync())
flutterProject.ios.hostAppRoot.path,
if (flutterProject.macos.existsSync())
flutterProject.macos.managedDirectory.path,
if (flutterProject.windows.existsSync())
flutterProject.windows.managedDirectory.path,
if (flutterProject.linux.existsSync())
flutterProject.linux.managedDirectory.path,
];
final Map<_Asset, List<_Asset>> assetVariants = _parseAssets(
packageConfig,
flutterManifest,
wildcardDirectories,
assetBasePath,
excludeDirs: <String>[
assetDirPath,
getBuildDirectory(),
if (flutterProject.ios.existsSync())
flutterProject.ios.hostAppRoot.path,
if (flutterProject.macos.existsSync())
flutterProject.macos.managedDirectory.path,
if (flutterProject.windows.existsSync())
flutterProject.windows.managedDirectory.path,
if (flutterProject.linux.existsSync())
flutterProject.linux.managedDirectory.path,
],
excludeDirs: excludeDirs,
);
if (assetVariants == null) {
return 1;
}
// Parse assets for deferred components.
final Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants = _parseDeferredComponentsAssets(
flutterManifest,
packageConfig,
assetBasePath,
wildcardDirectories,
flutterProject.directory,
excludeDirs: excludeDirs,
);
if (!_splitDeferredAssets || !deferredComponentsEnabled) {
// Include the assets in the regular set of assets if not using deferred
// components.
for (final String componentName in deferredComponentsAssetVariants.keys) {
assetVariants.addAll(deferredComponentsAssetVariants[componentName]);
}
deferredComponentsAssetVariants.clear();
deferredComponentsEntries.clear();
}
final bool includesMaterialFonts = flutterManifest.usesMaterialDesign;
final List<Map<String, dynamic>> fonts = _parseFonts(
flutterManifest,
@ -314,6 +351,39 @@ class ManifestAssetBundle implements AssetBundle {
entries[variant.entryUri.path] ??= DevFSFileContent(variantFile);
}
}
// Save the contents of each deferred component image, image variant, and font
// asset in deferredComponentsEntries.
if (deferredComponentsAssetVariants != null) {
for (final String componentName in deferredComponentsAssetVariants.keys) {
deferredComponentsEntries[componentName] = <String, DevFSContent>{};
for (final _Asset asset in deferredComponentsAssetVariants[componentName].keys) {
final File assetFile = asset.lookupAssetFile(_fileSystem);
if (!assetFile.existsSync() && deferredComponentsAssetVariants[componentName][asset].isEmpty) {
_logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
_logger.printError('No file or variants found for $asset.\n');
if (asset.package != null) {
_logger.printError('This asset was included from package ${asset.package.name}.');
}
return 1;
}
// The file name for an asset's "main" entry is whatever appears in
// the pubspec.yaml file. The main entry's file must always exist for
// font assets. It need not exist for an image if resolution-specific
// variant files exist. An image's main entry is treated the same as a
// "1x" resolution variant and if both exist then the explicit 1x
// variant is preferred.
if (assetFile.existsSync()) {
assert(!deferredComponentsAssetVariants[componentName][asset].contains(asset));
deferredComponentsAssetVariants[componentName][asset].insert(0, asset);
}
for (final _Asset variant in deferredComponentsAssetVariants[componentName][asset]) {
final File variantFile = variant.lookupAssetFile(_fileSystem);
assert(variantFile.existsSync());
deferredComponentsEntries[componentName][variant.entryUri.path] ??= DevFSFileContent(variantFile);
}
}
}
}
final List<_Asset> materialAssets = <_Asset>[
if (flutterManifest.usesMaterialDesign)
..._getMaterialAssets(),
@ -410,6 +480,50 @@ class ManifestAssetBundle implements AssetBundle {
];
}
Map<String, Map<_Asset, List<_Asset>>> _parseDeferredComponentsAssets(
FlutterManifest flutterManifest,
PackageConfig packageConfig,
String assetBasePath,
List<Uri> wildcardDirectories,
Directory projectDirectory, {
List<String> excludeDirs = const <String>[],
}) {
final List<DeferredComponent> components = flutterManifest.deferredComponents;
final Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants = <String, Map<_Asset, List<_Asset>>>{};
if (components == null) {
return deferredComponentsAssetVariants;
}
for (final DeferredComponent component in components) {
deferredComponentsAssetVariants[component.name] = <_Asset, List<_Asset>>{};
final _AssetDirectoryCache cache = _AssetDirectoryCache(<String>[], _fileSystem);
for (final Uri assetUri in component.assets) {
if (assetUri.path.endsWith('/')) {
wildcardDirectories.add(assetUri);
_parseAssetsFromFolder(
packageConfig,
flutterManifest,
assetBasePath,
cache,
deferredComponentsAssetVariants[component.name],
assetUri,
excludeDirs: excludeDirs,
);
} else {
_parseAssetFromFile(
packageConfig,
flutterManifest,
assetBasePath,
cache,
deferredComponentsAssetVariants[component.name],
assetUri,
excludeDirs: excludeDirs,
);
}
}
}
return deferredComponentsAssetVariants;
}
DevFSStringContent _createAssetManifest(Map<_Asset, List<_Asset>> assetVariants) {
final Map<String, List<String>> jsonObject = <String, List<String>>{};
final List<_Asset> assets = assetVariants.keys.toList()

View File

@ -167,7 +167,7 @@ class AnsiTerminal implements Terminal {
static const String cyan = '\u001b[36m';
static const String magenta = '\u001b[35m';
static const String yellow = '\u001b[33m';
static const String grey = '\u001b[1;30m';
static const String grey = '\u001b[90m';
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
TerminalColor.red: red,

View File

@ -25,10 +25,12 @@ class DepfileService {
/// Given an [depfile] File, write the depfile contents.
///
/// If either [inputs] or [outputs] is empty, ensures the file does not
/// exist.
void writeToFile(Depfile depfile, File output) {
if (depfile.inputs.isEmpty || depfile.outputs.isEmpty) {
/// If both [inputs] and [outputs] are empty, ensures the file does not
/// exist. This can be overriden with the [writeEmpty] parameter when
/// both static and runtime dependencies exist and it is not desired
/// to force a rerun due to no depfile.
void writeToFile(Depfile depfile, File output, {bool writeEmpty = false}) {
if (depfile.inputs.isEmpty && depfile.outputs.isEmpty && !writeEmpty) {
ErrorHandlingFileSystem.deleteIfExists(output);
return;
}

View File

@ -146,7 +146,7 @@ class SourceVisitor implements ResolvedFiles {
throw InvalidPatternException(pattern);
}
if (!environment.fileSystem.directory(filePath).existsSync()) {
throw Exception('$filePath does not exist!');
environment.fileSystem.directory(filePath).createSync(recursive: true);
}
for (final FileSystemEntity entity in environment.fileSystem.directory(filePath).listSync()) {
final String filename = environment.fileSystem.path.basename(entity.path);

View File

@ -10,6 +10,7 @@ import '../../base/deferred_component.dart';
import '../../base/file_system.dart';
import '../../build_info.dart';
import '../../globals.dart' as globals hide fs, artifacts, logger, processManager;
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
import '../exceptions.dart';
@ -40,7 +41,6 @@ abstract class AndroidAssetBundle extends Target {
'flutter_assets.d',
];
@override
Future<void> build(Environment environment) async {
if (environment.defines[kBuildMode] == null) {
@ -66,6 +66,7 @@ abstract class AndroidAssetBundle extends Target {
environment,
outputDirectory,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
);
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
@ -173,7 +174,7 @@ class AndroidAot extends AotElfBase {
/// The selected build mode.
///
/// This is restricted to [BuildMode.profile] or [BuildMode.release].
/// Build mode is restricted to [BuildMode.profile] or [BuildMode.release] for AOT builds.
final BuildMode buildMode;
@override
@ -193,6 +194,11 @@ class AndroidAot extends AotElfBase {
Source.pattern('{BUILD_DIR}/$_androidAbiName/app.so'),
];
@override
List<String> get depfiles => <String>[
'flutter_$name.d',
];
@override
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
@ -217,6 +223,12 @@ class AndroidAot extends AotElfBase {
output.createSync(recursive: true);
}
final List<String> extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions);
final List<File> outputs = <File>[]; // outputs for the depfile
final String manifestPath = '${output.path}${globals.platform.pathSeparator}manifest.json';
if (environment.defines[kDeferredComponents] == 'true') {
extraGenSnapshotOptions.add('--loading_unit_manifest=$manifestPath');
outputs.add(environment.fileSystem.file(manifestPath));
}
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
final String codeSizeDirectory = environment.defines[kCodeSizeDirectory];
@ -245,6 +257,22 @@ class AndroidAot extends AotElfBase {
if (snapshotExitCode != 0) {
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
}
if (environment.defines[kDeferredComponents] == 'true') {
// Parse the manifest for .so paths
final List<LoadingUnit> loadingUnits = LoadingUnit.parseLoadingUnitManifest(environment.fileSystem.file(manifestPath), environment.logger);
for (final LoadingUnit unit in loadingUnits) {
outputs.add(environment.fileSystem.file(unit.path));
}
}
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
logger: environment.logger,
);
depfileService.writeToFile(
Depfile(<File>[], outputs),
environment.buildDir.childFile('flutter_$name.d'),
writeEmpty: true,
);
}
}
@ -256,7 +284,7 @@ const AndroidAot androidArmRelease = AndroidAot(TargetPlatform.android_arm, Bui
const AndroidAot androidArm64Release = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAot androidx64Release = AndroidAot(TargetPlatform.android_x64, BuildMode.release);
/// A rule paired with [AndroidAot] that copies the produced so files into the output directory.
/// A rule paired with [AndroidAot] that copies the produced so file and manifest.json (if present) into the output directory.
class AndroidAotBundle extends Target {
/// Create an [AndroidAotBundle] implementation for a given [targetPlatform] and [buildMode].
const AndroidAotBundle(this.dependency);
@ -274,9 +302,16 @@ class AndroidAotBundle extends Target {
String get name => 'android_aot_bundle_${getNameForBuildMode(dependency.buildMode)}_'
'${getNameForTargetPlatform(dependency.targetPlatform)}';
TargetPlatform get targetPlatform => dependency.targetPlatform;
/// The selected build mode.
///
/// This is restricted to [BuildMode.profile] or [BuildMode.release].
BuildMode get buildMode => dependency.buildMode;
@override
List<Source> get inputs => <Source>[
Source.pattern('{BUILD_DIR}/$_androidAbiName/app.so'),
Source.pattern('{BUILD_DIR}/$_androidAbiName/app.so'),
];
// flutter.gradle has been updated to correctly consume it.
@ -285,6 +320,11 @@ class AndroidAotBundle extends Target {
Source.pattern('{OUTPUT_DIR}/$_androidAbiName/app.so'),
];
@override
List<String> get depfiles => <String>[
'flutter_$name.d',
];
@override
List<Target> get dependencies => <Target>[
dependency,
@ -293,25 +333,134 @@ class AndroidAotBundle extends Target {
@override
Future<void> build(Environment environment) async {
final File outputFile = environment.buildDir
.childDirectory(_androidAbiName)
.childFile('app.so');
final Directory buildDir = environment.buildDir.childDirectory(_androidAbiName);
final Directory outputDirectory = environment.outputDir
.childDirectory(_androidAbiName);
if (!outputDirectory.existsSync()) {
outputDirectory.createSync(recursive: true);
}
outputFile.copySync(outputDirectory.childFile('app.so').path);
final File outputLibFile = buildDir.childFile('app.so');
outputLibFile.copySync(outputDirectory.childFile('app.so').path);
final List<File> inputs = <File>[];
final List<File> outputs = <File>[];
final File manifestFile = buildDir.childFile('manifest.json');
if (manifestFile.existsSync()) {
final File destinationFile = outputDirectory.childFile('manifest.json');
manifestFile.copySync(destinationFile.path);
inputs.add(manifestFile);
outputs.add(destinationFile);
}
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
logger: environment.logger,
);
depfileService.writeToFile(
Depfile(inputs, outputs),
environment.buildDir.childFile('flutter_$name.d'),
writeEmpty: true,
);
}
}
// AndroidBundleAot instances.
const Target androidArmProfileBundle = AndroidAotBundle(androidArmProfile);
const Target androidArm64ProfileBundle = AndroidAotBundle(androidArm64Profile);
const Target androidx64ProfileBundle = AndroidAotBundle(androidx64Profile);
const Target androidArmReleaseBundle = AndroidAotBundle(androidArmRelease);
const Target androidArm64ReleaseBundle = AndroidAotBundle(androidArm64Release);
const Target androidx64ReleaseBundle = AndroidAotBundle(androidx64Release);
const AndroidAotBundle androidArmProfileBundle = AndroidAotBundle(androidArmProfile);
const AndroidAotBundle androidArm64ProfileBundle = AndroidAotBundle(androidArm64Profile);
const AndroidAotBundle androidx64ProfileBundle = AndroidAotBundle(androidx64Profile);
const AndroidAotBundle androidArmReleaseBundle = AndroidAotBundle(androidArmRelease);
const AndroidAotBundle androidArm64ReleaseBundle = AndroidAotBundle(androidArm64Release);
const AndroidAotBundle androidx64ReleaseBundle = AndroidAotBundle(androidx64Release);
// Rule that copies split aot library files to the intermediate dirs of each deferred component.
class AndroidAotDeferredComponentsBundle extends Target {
/// Create an [AndroidAotDeferredComponentsBundle] implementation for a given [targetPlatform] and [buildMode].
///
/// If [components] is not provided, it will be read from the pubspec.yaml manifest.
AndroidAotDeferredComponentsBundle(this.dependency, {List<DeferredComponent> components}) : _components = components;
/// The [AndroidAotBundle] instance this bundle rule depends on.
final AndroidAotBundle dependency;
List<DeferredComponent> _components;
/// The name of the produced Android ABI.
String get _androidAbiName {
return getNameForAndroidArch(
getAndroidArchForName(getNameForTargetPlatform(dependency.targetPlatform)));
}
@override
String get name => 'android_aot_deferred_components_bundle_${getNameForBuildMode(dependency.buildMode)}_'
'${getNameForTargetPlatform(dependency.targetPlatform)}';
TargetPlatform get targetPlatform => dependency.targetPlatform;
@override
List<Source> get inputs => <Source>[
// Tracking app.so is enough to invalidate the dynamically named
// loading unit libs as changes to loading units guarantee
// changes to app.so as well. This task does not actually
// copy app.so.
Source.pattern('{OUTPUT_DIR}/$_androidAbiName/app.so'),
const Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
];
@override
List<Source> get outputs => const <Source>[];
@override
List<String> get depfiles => <String>[
'flutter_$name.d',
];
@override
List<Target> get dependencies => <Target>[
dependency,
];
@override
Future<void> build(Environment environment) async {
_components ??= FlutterProject.current().manifest.deferredComponents ?? <DeferredComponent>[];
final List<String> abis = <String>[_androidAbiName];
final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(environment.outputDir, environment.logger, abis: abis);
for (final DeferredComponent component in _components) {
component.assignLoadingUnits(generatedLoadingUnits);
}
final Depfile libDepfile = copyDeferredComponentSoFiles(environment, _components, generatedLoadingUnits, environment.projectDir.childDirectory('build'), abis, dependency.buildMode);
final File manifestFile = environment.outputDir.childDirectory(_androidAbiName).childFile('manifest.json');
if (manifestFile.existsSync()) {
libDepfile.inputs.add(manifestFile);
}
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
logger: environment.logger,
);
depfileService.writeToFile(
libDepfile,
environment.buildDir.childFile('flutter_$name.d'),
writeEmpty: true,
);
}
}
Target androidArmProfileDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidArmProfileBundle);
Target androidArm64ProfileDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidArm64ProfileBundle);
Target androidx64ProfileDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidx64ProfileBundle);
Target androidArmReleaseDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidArmReleaseBundle);
Target androidArm64ReleaseDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidArm64ReleaseBundle);
Target androidx64ReleaseDeferredComponentsBundle = AndroidAotDeferredComponentsBundle(androidx64ReleaseBundle);
/// A set of all target names that build deferred component apps.
Set<String> deferredComponentsTargets = <String>{
androidArmProfileDeferredComponentsBundle.name,
androidArm64ProfileDeferredComponentsBundle.name,
androidx64ProfileDeferredComponentsBundle.name,
androidArmReleaseDeferredComponentsBundle.name,
androidArm64ReleaseDeferredComponentsBundle.name,
androidx64ReleaseDeferredComponentsBundle.name,
};
/// Utility method to copy and rename the required .so shared libs from the build output
/// to the correct component intermediate directory.

View File

@ -34,6 +34,7 @@ const String kBundleSkSLPath = 'BundleSkSLPath';
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
Map<String, DevFSContent> additionalContent,
@required TargetPlatform targetPlatform,
BuildMode buildMode,
}) async {
// Check for an SkSL bundle.
final String shaderBundlePath = environment.inputs[kBundleSkSLPath];
@ -51,11 +52,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
logger: environment.logger,
fileSystem: environment.fileSystem,
platform: globals.platform,
splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease,
).createBundle();
final int resultCode = await assetBundle.build(
manifestPath: pubspecFile.path,
packagesPath: environment.projectDir.childFile('.packages').path,
assetDirPath: null,
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
);
if (resultCode != 0) {
throw Exception('Failed to bundle asset files.');
@ -114,6 +117,56 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
resource.release();
}
}));
// Copy deferred components assets only for release or profile builds.
// The assets are included in assetBundle.entries as a normal asset when
// building as debug.
if (environment.defines[kDeferredComponents] == 'true') {
await Future.wait<void>(
assetBundle.deferredComponentsEntries.entries.map<Future<void>>((MapEntry<String, Map<String, DevFSContent>> componentEntries) async {
final Directory componentOutputDir =
environment.projectDir
.childDirectory('build')
.childDirectory(componentEntries.key)
.childDirectory('intermediates')
.childDirectory('flutter');
await Future.wait<void>(
componentEntries.value.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
// This will result in strange looking files, for example files with `/`
// on Windows or files that end up getting URI encoded such as `#.ext`
// to `%23.ext`. However, we have to keep it this way since the
// platform channels in the framework will URI encode these values,
// and the native APIs will look for files this way.
// If deferred components are disabled, then copy assets to regular location.
final File file = environment.defines[kDeferredComponents] == 'true'
? environment.fileSystem.file(
environment.fileSystem.path.join(componentOutputDir.path, buildMode.name, 'deferred_assets', 'flutter_assets', entry.key))
: environment.fileSystem.file(
environment.fileSystem.path.join(outputDirectory.path, entry.key));
outputs.add(file);
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
inputs.add(content.file as File);
if (!await iconTreeShaker.subsetFont(
input: content.file as File,
outputPath: file.path,
relativePath: entry.key,
)) {
await (content.file as File).copy(file.path);
}
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
}));
}
final Depfile depfile = Depfile(inputs + assetBundle.additionalDependencies, outputs);
if (shaderBundlePath != null) {
final File skSLBundleFile = environment.fileSystem

View File

@ -45,9 +45,9 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions';
/// This is expected to be a comma separated list of strings.
const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions';
/// Whether the app should run gen_snapshot as a split aot build for deferred
/// Whether the build should run gen_snapshot as a split aot build for deferred
/// components.
const String kSplitAot = 'SplitAot';
const String kDeferredComponents = 'DeferredComponents';
/// Whether to strip source code information out of release builds and where to save it.
const String kSplitDebugInfo = 'SplitDebugInfo';
@ -131,6 +131,7 @@ class CopyFlutterBundle extends Target {
environment,
environment.outputDir,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
);
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,

View File

@ -8,25 +8,26 @@ import 'package:meta/meta.dart';
import '../../android/deferred_components_gen_snapshot_validator.dart';
import '../../base/deferred_component.dart';
import '../../build_info.dart';
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
import 'android.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,
@required this.deferredComponentsDependencies,
@required this.nonDeferredComponentsDependencies,
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 [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on.
final List<AndroidAotDeferredComponentsBundle> deferredComponentsDependencies;
final List<Target> nonDeferredComponentsDependencies;
/// The title of the [DeferredComponentsGenSnapshotValidator] that is
/// displayed to the developer when logging results.
@ -37,11 +38,20 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final bool exitOnFail;
/// The abis to validate.
final List<String> abis;
List<String> get _abis {
final List<String> abis = <String>[];
for (final AndroidAotDeferredComponentsBundle target in deferredComponentsDependencies) {
if (deferredComponentsTargets.contains(target.name)) {
abis.add(
getNameForAndroidArch(getAndroidArchForName(getNameForTargetPlatform(target.dependency.targetPlatform)))
);
}
}
return abis;
}
@override
String get name => _name;
final String _name;
String get name => 'deferred_components_gen_snapshot_validator';
@override
List<Source> get inputs => const <Source>[];
@ -55,7 +65,11 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
];
@override
List<Target> get dependencies => dependency == null ? <Target>[] : <Target>[dependency];
List<Target> get dependencies {
final List<Target> deps = <Target>[CompositeTarget(deferredComponentsDependencies)];
deps.addAll(nonDeferredComponentsDependencies);
return deps;
}
@visibleForTesting
DeferredComponentsGenSnapshotValidator validator;
@ -75,7 +89,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(
environment.outputDir,
environment.logger,
abis: abis
abis: _abis
);
validator

View File

@ -14,6 +14,7 @@ import '../build_system/depfile.dart';
import '../build_system/targets/android.dart';
import '../build_system/targets/assets.dart';
import '../build_system/targets/common.dart';
import '../build_system/targets/deferred_components.dart';
import '../build_system/targets/ios.dart';
import '../build_system/targets/linux.dart';
import '../build_system/targets/macos.dart';
@ -27,34 +28,34 @@ import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
/// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[
List<Target> _kDefaultTargets = <Target>[
// Shared targets
CopyAssets(),
KernelSnapshot(),
AotElfProfile(TargetPlatform.android_arm),
AotElfRelease(TargetPlatform.android_arm),
AotAssemblyProfile(),
AotAssemblyRelease(),
const CopyAssets(),
const KernelSnapshot(),
const AotElfProfile(TargetPlatform.android_arm),
const AotElfRelease(TargetPlatform.android_arm),
const AotAssemblyProfile(),
const AotAssemblyRelease(),
// macOS targets
DebugMacOSFramework(),
DebugMacOSBundleFlutterAssets(),
ProfileMacOSBundleFlutterAssets(),
ReleaseMacOSBundleFlutterAssets(),
const DebugMacOSFramework(),
const DebugMacOSBundleFlutterAssets(),
const ProfileMacOSBundleFlutterAssets(),
const ReleaseMacOSBundleFlutterAssets(),
// Linux targets
DebugBundleLinuxAssets(TargetPlatform.linux_x64),
DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
ProfileBundleLinuxAssets(TargetPlatform.linux_x64),
ProfileBundleLinuxAssets(TargetPlatform.linux_arm64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
const DebugBundleLinuxAssets(TargetPlatform.linux_x64),
const DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
const ProfileBundleLinuxAssets(TargetPlatform.linux_x64),
const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64),
const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
// Web targets
WebServiceWorker(),
ReleaseAndroidApplication(),
const WebServiceWorker(),
const ReleaseAndroidApplication(),
// This is a one-off rule for bundle and aot compat.
CopyFlutterBundle(),
const CopyFlutterBundle(),
// Android targets,
DebugAndroidApplication(),
ProfileAndroidApplication(),
const DebugAndroidApplication(),
const ProfileAndroidApplication(),
// Android ABI specific AOT rules.
androidArmProfileBundle,
androidArm64ProfileBundle,
@ -62,15 +63,22 @@ const List<Target> _kDefaultTargets = <Target>[
androidArmReleaseBundle,
androidArm64ReleaseBundle,
androidx64ReleaseBundle,
// Deferred component enabled AOT rules
androidArmProfileDeferredComponentsBundle,
androidArm64ProfileDeferredComponentsBundle,
androidx64ProfileDeferredComponentsBundle,
androidArmReleaseDeferredComponentsBundle,
androidArm64ReleaseDeferredComponentsBundle,
androidx64ReleaseDeferredComponentsBundle,
// iOS targets
DebugIosApplicationBundle(),
ProfileIosApplicationBundle(),
ReleaseIosApplicationBundle(),
const DebugIosApplicationBundle(),
const ProfileIosApplicationBundle(),
const ReleaseIosApplicationBundle(),
// Windows targets
UnpackWindows(),
DebugBundleWindowsAssets(),
ProfileBundleWindowsAssets(),
ReleaseBundleWindowsAssets(),
const UnpackWindows(),
const DebugBundleWindowsAssets(),
const ProfileBundleWindowsAssets(),
const ReleaseBundleWindowsAssets(),
];
// TODO(ianh): https://github.com/dart-lang/args/issues/181 will allow us to remove useLegacyNames
@ -171,6 +179,24 @@ class AssembleCommand extends FlutterCommand {
return results;
}
bool isDeferredComponentsTargets() {
for (final String targetName in argResults.rest) {
if (deferredComponentsTargets.contains(targetName)) {
return true;
}
}
return false;
}
bool isDebug() {
for (final String targetName in argResults.rest) {
if (targetName.contains('debug')) {
return true;
}
}
return false;
}
/// The environmental configuration for a build invocation.
Environment createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current();
@ -221,6 +247,10 @@ class AssembleCommand extends FlutterCommand {
if (argResults.wasParsed(useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption)) {
results[kDartDefines] = (argResults[useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption] as List<String>).join(',');
}
results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true';
}
if (argResults.wasParsed(useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions)) {
results[kExtraFrontEndOptions] = (argResults[useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions] as List<String>).join(',');
}
@ -229,11 +259,37 @@ class AssembleCommand extends FlutterCommand {
@override
Future<FlutterCommandResult> runCommand() async {
final Environment env = createEnvironment();
final List<Target> targets = createTargets();
final Target target = targets.length == 1 ? targets.single : CompositeTarget(targets);
final List<Target> nonDeferredTargets = <Target>[];
final List<Target> deferredTargets = <AndroidAotDeferredComponentsBundle>[];
for (final Target target in targets) {
if (deferredComponentsTargets.contains(target.name)) {
deferredTargets.add(target);
} else {
nonDeferredTargets.add(target);
}
}
Target target;
final List<String> decodedDefines = decodeDartDefines(env.defines, kDartDefines);
if (FlutterProject.current().manifest.deferredComponents != null
&& decodedDefines.contains('validate-deferred-components=true')
&& deferredTargets.isNotEmpty
&& !isDebug()) {
// Add deferred components validation target that require loading units.
target = DeferredComponentsGenSnapshotValidatorTarget(
deferredComponentsDependencies: deferredTargets.cast<AndroidAotDeferredComponentsBundle>(),
nonDeferredComponentsDependencies: nonDeferredTargets,
title: 'Deferred components gen_snapshot validation',
);
} else if (targets.length > 1) {
target = CompositeTarget(targets);
} else if (targets.isNotEmpty) {
target = targets.single;
}
final BuildResult result = await _buildSystem.build(
target,
createEnvironment(),
env,
buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size'))
@ -251,6 +307,7 @@ class AssembleCommand extends FlutterCommand {
throwToolExit('');
}
globals.printTrace('build succeeded.');
if (argResults.wasParsed('build-inputs')) {
writeListIfChanged(result.inputFiles, stringArg('build-inputs'));
}

View File

@ -6,7 +6,10 @@
import '../android/android_builder.dart';
import '../android/build_validation.dart';
import '../android/deferred_components_prebuild_validator.dart';
import '../android/gradle_utils.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart' as globals;
@ -37,11 +40,30 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesAnalyzeSizeFlag();
addAndroidSpecificBuildOptions(hide: !verboseHelp);
argParser.addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the app is compiled.',
);
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the app is compiled.',
);
argParser.addFlag('deferred-components',
negatable: true,
defaultsTo: true,
help: 'Setting to false disables building with deferred components. All deferred code '
'will be compiled into the base app, and assets act as if they were defined under'
' the regular assets section in pubspec.yaml. This flag has no effect on '
'non-deferred components apps.',
);
argParser.addFlag('validate-deferred-components',
negatable: true,
defaultsTo: true,
help: 'When enabled, deferred component apps will fail to build if setup problems are '
'detected that would prevent deferred components from functioning properly. The '
'tooling also provides guidance on how to set up the project files to pass this '
'verification. Disabling setup verification will always attempt to fully build '
'the app regardless of any problems detected. Builds that are part of CI testing '
'and advanced users with custom deferred components implementations should disable '
'setup verification. This flag has no effect on non-deferred components apps.',
);
}
@override
@ -84,15 +106,49 @@ class BuildAppBundleCommand extends BuildSubCommand {
if (globals.androidSdk == null) {
exitWithNoSdkMessage();
}
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(),
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
);
// Do all setup verification that doesn't involve loading units. Checks that
// require generated loading units are done after gen_snapshot in assemble.
if (FlutterProject.current().manifest.deferredComponents != null && boolArg('deferred-components') && boolArg('validate-deferred-components') && !boolArg('debug')) {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
FlutterProject.current().directory,
globals.logger,
title: 'Deferred components prebuild validation',
exitOnFail: true,
);
validator.clearOutputDir();
await validator.checkAndroidDynamicFeature(FlutterProject.current().manifest.deferredComponents);
validator.checkAndroidResourcesStrings(FlutterProject.current().manifest.deferredComponents);
validator.handleResults();
// Delete intermediates libs dir for components to resolve mismatching
// abis supported by base and dynamic feature modules.
for (final DeferredComponent component in FlutterProject.current().manifest.deferredComponents) {
final Directory deferredLibsIntermediate = FlutterProject.current().directory
.childDirectory('build')
.childDirectory(component.name)
.childDirectory('intermediates')
.childDirectory('flutter')
.childDirectory(androidBuildInfo.buildInfo.mode.name)
.childDirectory('deferred_libs');
if (deferredLibsIntermediate.existsSync()) {
deferredLibsIntermediate.deleteSync(recursive: true);
}
}
}
validateBuild(androidBuildInfo);
displayNullSafetyMode(androidBuildInfo.buildInfo);
await androidBuilder.buildAab(
project: FlutterProject.current(),
target: targetFile,
androidBuildInfo: androidBuildInfo,
validateDeferredComponents: boolArg('validate-deferred-components'),
deferredComponentsEnabled: boolArg('deferred-components') && !boolArg('debug'),
);
return FlutterCommandResult.success();
}

View File

@ -120,6 +120,7 @@ class FlutterOptions {
static const String kAnalyzeSize = 'analyze-size';
static const String kNullAssertions = 'null-assertions';
static const String kAndroidGradleDaemon = 'android-gradle-daemon';
static const String kDeferredComponents = 'deferred-components';
}
abstract class FlutterCommand extends Command<void> {

View File

@ -0,0 +1,46 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: "com.android.dynamic-feature"
android {
compileSdkVersion 30
sourceSets {
applicationVariants.all { variant ->
main.assets.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_assets"
main.jniLibs.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_libs"
}
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation project(":app")
}

View File

@ -0,0 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="{{androidIdentifier}}.{{componentName}}">
<dist:module
dist:instant="false"
dist:title="@string/{{componentName}}Name">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>

View File

@ -140,6 +140,8 @@
"templates/cocoapods/Podfile-ios-objc",
"templates/cocoapods/Podfile-ios-swift",
"templates/cocoapods/Podfile-macos",
"templates/module/android/deferred_component/build.gradle.tmpl",
"templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl",
"templates/module/android/gradle/build.gradle.copy.tmpl",
"templates/module/android/gradle/gradle.properties.tmpl",
"templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl",

View File

@ -22,7 +22,7 @@ void main() {
Environment env;
Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' };
final Environment result = Environment(
outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'),

View File

@ -10,9 +10,6 @@ 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';
@ -20,37 +17,20 @@ 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;
}
Directory projectDir;
Directory flutterRootDir;
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
env = createEnvironment();
projectDir = fileSystem.directory('/project');
flutterRootDir = fileSystem.directory('/flutter_root');
});
testWithoutContext('No checks passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
@ -61,7 +41,8 @@ void main() {
testWithoutContext('clearTempDir passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
@ -72,7 +53,7 @@ void main() {
});
testUsingContext('androidComponentSetup build.gradle does not exist', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = 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()) {
@ -84,12 +65,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File file = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
if (file.existsSync()) {
file.deleteSync();
@ -109,7 +91,7 @@ void main() {
});
testUsingContext('androidComponentSetup AndroidManifest.xml does not exist', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = 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()) {
@ -121,12 +103,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File file = componentDir.childFile('build.gradle');
if (file.existsSync()) {
file.deleteSync();
@ -146,7 +129,7 @@ void main() {
});
testUsingContext('androidComponentSetup all files exist passes', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = 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()) {
@ -158,12 +141,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File buildGradle = componentDir.childFile('build.gradle');
if (buildGradle.existsSync()) {
buildGradle.deleteSync();
@ -189,11 +173,12 @@ void main() {
testWithoutContext('androidStringMapping creates new file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
final Directory baseModuleDir = 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();
@ -239,7 +224,7 @@ void main() {
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
final File stringsOutput = projectDir
.childDirectory('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app')
@ -255,11 +240,12 @@ void main() {
testWithoutContext('androidStringMapping modifies strings file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
final Directory baseModuleDir = 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();
@ -285,7 +271,7 @@ void main() {
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
final File stringsOutput = projectDir
.childDirectory('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app')

View File

@ -176,6 +176,129 @@ flutter:
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets are parsed', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets are parsed regularly when splitDeferredAssets Disabled', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: false);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 6);
expect(bundle.deferredComponentsEntries.isEmpty, true);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets wildcard parsed', () async {
final File packageFile = globals.fs.file('.packages')..createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
// Simulate modifying the files by updating the filestat time manually.
globals.fs.file(globals.fs.path.join('assets', 'wild', 'fizz.txt'))
..createSync(recursive: true)
..setLastModifiedSync(packageFile.lastModifiedSync().add(const Duration(hours: 1)));
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), true);
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 3);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
testUsingContext('Failed directory delete shows message', () async {

View File

@ -130,16 +130,16 @@ void main() {
wrapColumn: maxLineWidth,
);
expect(commandHelp.L.toString(), endsWith('\x1B[1;30m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), endsWith('\x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), endsWith('\x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), endsWith('\x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), endsWith('\x1B[1;30m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), endsWith('\x1B[1;30m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), endsWith('\x1B[1;30m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), endsWith('\x1B[1;30m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.L.toString(), endsWith('\x1B[90m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), endsWith('\x1B[90m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), endsWith('\x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), endsWith('\x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), endsWith('\x1B[90m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), endsWith('\x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), endsWith('\x1B[90m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), endsWith('\x1B[90m(debugDumpApp)\x1B[39m\x1b[22m'));
});
testWithoutContext('should not create a help text longer than maxLineWidth without ansi support', () {
@ -180,24 +180,24 @@ void main() {
wrapColumn: maxLineWidth,
);
expect(commandHelp.L.toString(), equals('\x1B[1mL\x1B[22m Dump layer tree to the console. \x1B[1;30m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), equals('\x1B[1mP\x1B[22m Toggle performance overlay. \x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.L.toString(), equals('\x1B[1mL\x1B[22m Dump layer tree to the console. \x1B[90m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), equals('\x1B[1mP\x1B[22m Toggle performance overlay. \x1B[90m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.R.toString(), equals('\x1B[1mR\x1B[22m Hot restart.'));
expect(commandHelp.S.toString(), equals('\x1B[1mS\x1B[22m Dump accessibility tree in traversal order. \x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), equals('\x1B[1mS\x1B[22m Dump accessibility tree in traversal order. \x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
expect(commandHelp.g.toString(), equals('\x1B[1mg\x1B[22m Run source code generators.'));
expect(commandHelp.h.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[1;30m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), equals('\x1B[1mp\x1B[22m Toggle the display of construction lines. \x1B[1;30m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[90m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), equals('\x1B[1mp\x1B[22m Toggle the display of construction lines. \x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.q.toString(), equals('\x1B[1mq\x1B[22m Quit (terminate the application on the device).'));
expect(commandHelp.r.toString(), equals('\x1B[1mr\x1B[22m Hot reload. $fire$fire$fire'));
expect(commandHelp.s.toString(), equals('\x1B[1ms\x1B[22m Save a screenshot to flutter.png.'));
expect(commandHelp.t.toString(), equals('\x1B[1mt\x1B[22m Dump rendering tree to the console. \x1B[1;30m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), equals('\x1B[1mt\x1B[22m Dump rendering tree to the console. \x1B[90m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.v.toString(), equals('\x1B[1mv\x1B[22m Launch DevTools.'));
expect(commandHelp.w.toString(), equals('\x1B[1mw\x1B[22m Dump widget hierarchy to the console. \x1B[1;30m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), equals('\x1B[1mw\x1B[22m Dump widget hierarchy to the console. \x1B[90m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.z.toString(), equals('\x1B[1mz\x1B[22m Toggle elevation checker.'));
});

View File

@ -34,7 +34,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
@ -44,11 +44,10 @@ void main() {
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 AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);
@ -68,7 +67,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
@ -78,11 +77,10 @@ void main() {
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 AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);
@ -101,7 +99,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
@ -111,11 +109,10 @@ void main() {
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 AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);

View File

@ -0,0 +1,274 @@
// 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:archive/archive.dart';
import 'package:file/file.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart';
import '../src/common.dart';
import 'test_data/deferred_components_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
Directory tempDir;
FlutterRunTestDriver _flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('run_test.');
_flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await _flutter.stop();
tryToDelete(tempDir);
});
testWithoutContext('simple build appbundle android-arm64 target succeeds', () async {
final DeferredComponentsProject project = DeferredComponentsProject(BasicDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
'--target-platform=android-arm64'
], workingDirectory: tempDir.path);
expect(result.stdout.toString(), contains('app-release.aab'));
expect(result.stdout.toString(), contains('Deferred components prebuild validation passed.'));
expect(result.stdout.toString(), contains('Deferred components gen_snapshot validation passed.'));
final String line = result.stdout.toString()
.split('\n')
.firstWhere((String line) => line.contains('app-release.aab'));
final String outputFilePath = line.split(' ')[2].trim();
final File outputFile = fileSystem.file(fileSystem.path.join(tempDir.path, outputFilePath));
expect(outputFile, exists);
final Archive archive = ZipDecoder().decodeBytes(outputFile.readAsBytesSync());
expect(archive.findFile('base/lib/arm64-v8a/libapp.so') != null, true);
expect(archive.findFile('base/lib/arm64-v8a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/arm64-v8a/libapp.so-2.part.so') != null, true);
expect(archive.findFile('component1/assets/flutter_assets/test_assets/asset2.txt') != null, true);
expect(archive.findFile('base/assets/flutter_assets/test_assets/asset1.txt') != null, true);
expect(result.exitCode, 0);
}, timeout: const Timeout(Duration(minutes: 2)));
testWithoutContext('simple build appbundle all targets succeeds', () async {
final DeferredComponentsProject project = DeferredComponentsProject(BasicDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
], workingDirectory: tempDir.path);
expect(result.stdout.toString(), contains('app-release.aab'));
expect(result.stdout.toString(), contains('Deferred components prebuild validation passed.'));
expect(result.stdout.toString(), contains('Deferred components gen_snapshot validation passed.'));
final String line = result.stdout.toString()
.split('\n')
.firstWhere((String line) => line.contains('app-release.aab'));
final String outputFilePath = line.split(' ')[2].trim();
final File outputFile = fileSystem.file(fileSystem.path.join(tempDir.path, outputFilePath));
expect(outputFile, exists);
final Archive archive = ZipDecoder().decodeBytes(outputFile.readAsBytesSync());
expect(archive.findFile('base/lib/arm64-v8a/libapp.so') != null, true);
expect(archive.findFile('base/lib/arm64-v8a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/arm64-v8a/libapp.so-2.part.so') != null, true);
expect(archive.findFile('base/lib/armeabi-v7a/libapp.so') != null, true);
expect(archive.findFile('base/lib/armeabi-v7a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/armeabi-v7a/libapp.so-2.part.so') != null, true);
expect(archive.findFile('base/lib/x86_64/libapp.so') != null, true);
expect(archive.findFile('base/lib/x86_64/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/x86_64/libapp.so-2.part.so') != null, true);
expect(archive.findFile('component1/assets/flutter_assets/test_assets/asset2.txt') != null, true);
expect(archive.findFile('base/assets/flutter_assets/test_assets/asset1.txt') != null, true);
expect(result.exitCode, 0);
}, timeout: const Timeout(Duration(minutes: 3)));
testWithoutContext('simple build appbundle no-deferred-components succeeds', () async {
final DeferredComponentsProject project = DeferredComponentsProject(BasicDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
'--no-deferred-components'
], workingDirectory: tempDir.path);
expect(result.stdout.toString().contains('app-release.aab'), true);
expect(result.stdout.toString().contains('Deferred components prebuild validation passed.'), false);
expect(result.stdout.toString().contains('Deferred components gen_snapshot validation passed.'), false);
final String line = result.stdout.toString()
.split('\n')
.firstWhere((String line) => line.contains('app-release.aab'));
final String outputFilePath = line.split(' ')[2].trim();
final File outputFile = fileSystem.file(fileSystem.path.join(tempDir.path, outputFilePath));
expect(outputFile, exists);
final Archive archive = ZipDecoder().decodeBytes(outputFile.readAsBytesSync());
expect(archive.findFile('base/lib/arm64-v8a/libapp.so') != null, true);
expect(archive.findFile('base/lib/arm64-v8a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/arm64-v8a/libapp.so-2.part.so') != null, false);
expect(archive.findFile('base/lib/armeabi-v7a/libapp.so') != null, true);
expect(archive.findFile('base/lib/armeabi-v7a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/armeabi-v7a/libapp.so-2.part.so') != null, false);
expect(archive.findFile('base/lib/x86_64/libapp.so') != null, true);
expect(archive.findFile('base/lib/x86_64/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/x86_64/libapp.so-2.part.so') != null, false);
// Asset 2 is merged into the base module assets.
expect(archive.findFile('component1/assets/flutter_assets/test_assets/asset2.txt') != null, false);
expect(archive.findFile('base/assets/flutter_assets/test_assets/asset2.txt') != null, true);
expect(archive.findFile('base/assets/flutter_assets/test_assets/asset1.txt') != null, true);
expect(result.exitCode, 0);
}, timeout: const Timeout(Duration(minutes: 3)));
testWithoutContext('simple build appbundle mismatched golden no-validate-deferred-components succeeds', () async {
final DeferredComponentsProject project = DeferredComponentsProject(MismatchedGoldenDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
'--no-validate-deferred-components',
], workingDirectory: tempDir.path);
expect(result.stdout.toString().contains('app-release.aab'), true);
expect(result.stdout.toString().contains('Deferred components prebuild validation passed.'), false);
expect(result.stdout.toString().contains('Deferred components gen_snapshot validation passed.'), false);
expect(result.stdout.toString().contains('New loading units were found:'), false);
expect(result.stdout.toString().contains('Previously existing loading units no longer exist:'), false);
final String line = result.stdout.toString()
.split('\n')
.firstWhere((String line) => line.contains('app-release.aab'));
final String outputFilePath = line.split(' ')[2].trim();
final File outputFile = fileSystem.file(fileSystem.path.join(tempDir.path, outputFilePath));
expect(outputFile, exists);
final Archive archive = ZipDecoder().decodeBytes(outputFile.readAsBytesSync());
expect(archive.findFile('base/lib/arm64-v8a/libapp.so') != null, true);
expect(archive.findFile('base/lib/arm64-v8a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/arm64-v8a/libapp.so-2.part.so') != null, true);
expect(archive.findFile('base/lib/armeabi-v7a/libapp.so') != null, true);
expect(archive.findFile('base/lib/armeabi-v7a/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/armeabi-v7a/libapp.so-2.part.so') != null, true);
expect(archive.findFile('base/lib/x86_64/libapp.so') != null, true);
expect(archive.findFile('base/lib/x86_64/libflutter.so') != null, true);
expect(archive.findFile('component1/lib/x86_64/libapp.so-2.part.so') != null, true);
expect(archive.findFile('component1/assets/flutter_assets/test_assets/asset2.txt') != null, true);
expect(archive.findFile('base/assets/flutter_assets/test_assets/asset1.txt') != null, true);
expect(result.exitCode, 0);
}, timeout: const Timeout(Duration(minutes: 3)));
testWithoutContext('simple build appbundle missing android dynamic feature module fails', () async {
final DeferredComponentsProject project = DeferredComponentsProject(NoAndroidDynamicFeatureModuleDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
], workingDirectory: tempDir.path);
expect(result.stdout.toString().contains('app-release.aab'), false);
expect(result.stdout.toString().contains('Deferred components prebuild validation passed.'), false);
expect(result.stdout.toString().contains('Deferred components gen_snapshot validation passed.'), false);
expect(result.stdout.toString(), contains('Newly generated android files:'));
final String pathSeparator = fileSystem.path.separator;
expect(result.stdout.toString(), contains('build${pathSeparator}android_deferred_components_setup_files${pathSeparator}component1${pathSeparator}build.gradle'));
expect(result.stdout.toString(), contains('build${pathSeparator}android_deferred_components_setup_files${pathSeparator}component1${pathSeparator}src${pathSeparator}main${pathSeparator}AndroidManifest.xml'));
expect(result.exitCode, 1);
});
testWithoutContext('simple build appbundle missing golden fails', () async {
final DeferredComponentsProject project = DeferredComponentsProject(NoGoldenDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
], workingDirectory: tempDir.path);
expect(result.stdout.toString().contains('app-release.aab'), false);
expect(result.stdout.toString().contains('Deferred components prebuild validation passed.'), true);
expect(result.stdout.toString().contains('Deferred components gen_snapshot validation passed.'), false);
expect(result.stdout.toString(), contains('New loading units were found:'));
expect(result.stdout.toString(), contains('- package:test/deferred_library.dart'));
expect(result.stdout.toString().contains('Previously existing loading units no longer exist:'), false);
expect(result.exitCode, 1);
});
testWithoutContext('simple build appbundle mismatched golden fails', () async {
final DeferredComponentsProject project = DeferredComponentsProject(MismatchedGoldenDeferredComponentsConfig());
await project.setUpIn(tempDir);
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
flutterBin,
...getLocalEngineArguments(),
'build',
'appbundle',
], workingDirectory: tempDir.path);
expect(result.stdout.toString().contains('app-release.aab'), false);
expect(result.stdout.toString().contains('Deferred components prebuild validation passed.'), true);
expect(result.stdout.toString().contains('Deferred components gen_snapshot validation passed.'), false);
expect(result.stdout.toString(), contains('New loading units were found:'));
expect(result.stdout.toString(), contains('- package:test/deferred_library.dart'));
expect(result.stdout.toString(), contains('Previously existing loading units no longer exist:'));
expect(result.stdout.toString(), contains('- package:test/invalid_lib_name.dart'));
expect(result.stdout.toString(), contains('This loading unit check will not fail again on the next build attempt'));
expect(result.exitCode, 1);
});
}

View File

@ -0,0 +1,155 @@
// 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/file.dart';
import '../test_utils.dart';
abstract class DeferredComponentsConfig {
String get deferredLibrary;
String get deferredComponentsGolden;
String get androidSettings;
String get androidBuild;
String get androidLocalProperties;
String get androidGradleProperties;
String get androidKeyProperties;
List<int> get androidKey;
String get appBuild;
String get appManifest;
String get appStrings;
String get appStyles;
String get appLaunchBackground;
String get asset1;
String get asset2;
List<DeferredComponentModule> get deferredComponents;
void setUpIn(Directory dir) {
if (deferredLibrary != null) {
writeFile(fileSystem.path.join(dir.path, 'lib', 'deferred_library.dart'), deferredLibrary);
}
if (deferredComponentsGolden != null) {
writeFile(fileSystem.path.join(dir.path, 'deferred_components_loading_units.yaml'), deferredComponentsGolden);
}
if (androidSettings != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'settings.gradle'), androidSettings);
}
if (androidBuild != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'build.gradle'), androidBuild);
}
if (androidLocalProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'local.properties'), androidLocalProperties);
}
if (androidGradleProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'gradle.properties'), androidGradleProperties);
}
if (androidKeyProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'key.properties'), androidKeyProperties);
}
if (androidKey != null) {
writeBytesFile(fileSystem.path.join(dir.path, 'android', 'app', 'key.jks'), androidKey);
}
if (appBuild != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'build.gradle'), appBuild);
}
if (appManifest != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'AndroidManifest.xml'), appManifest);
}
if (appStrings != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml'), appStrings);
}
if (appStyles != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'styles.xml'), appStyles);
}
if (appLaunchBackground != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'drawable', 'launch_background.xml'), appLaunchBackground);
}
if (asset1 != null) {
writeFile(fileSystem.path.join(dir.path, 'test_assets/asset1.txt'), asset1);
}
if (asset2 != null) {
writeFile(fileSystem.path.join(dir.path, 'test_assets/asset2.txt'), asset2);
}
if (deferredComponents != null) {
for (final DeferredComponentModule component in deferredComponents) {
component.setUpIn(dir);
}
}
}
}
class DeferredComponentModule {
DeferredComponentModule(this.name);
String name;
void setUpIn(Directory dir) {
if (name != null) {
writeFile(fileSystem.path.join(dir.path, 'android', name, 'build.gradle'), r'''
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: "com.android.dynamic-feature"
android {
compileSdkVersion 30
sourceSets {
applicationVariants.all { variant ->
main.assets.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_assets"
main.jniLibs.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_libs"
}
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation project(":app")
}
''');
writeFile(fileSystem.path.join(dir.path, 'android', name, 'src', 'main', 'AndroidManifest.xml'), '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.$name">
<dist:module
dist:instant="false"
dist:title="@string/component1Name">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>
''');
}
}
}

View File

@ -0,0 +1,619 @@
// 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 '../../src/common.dart';
import 'deferred_components_config.dart';
import 'project.dart';
class DeferredComponentsProject extends Project {
DeferredComponentsProject(this.deferredComponents);
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.12.0-0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter:
assets:
- test_assets/asset1.txt
deferred-components:
- name: component1
libraries:
- package:test/deferred_library.dart
assets:
- test_assets/asset2.txt
''';
@override
final String main = r'''
import 'dart:async';
import 'package:flutter/material.dart';
import 'deferred_library.dart' deferred as DeferredLibrary;
Future<void>? libFuture;
String deferredText = 'incomplete';
Future<void> main() async {
while (true) {
if (libFuture == null) {
libFuture = DeferredLibrary.loadLibrary();
libFuture?.whenComplete(() => deferredText = 'complete ${DeferredLibrary.add(10, 42)}');
}
runApp(new MyApp());
await Future.delayed(const Duration(milliseconds: 50));
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
topLevelFunction();
return new MaterialApp( // BUILD BREAKPOINT
title: 'Flutter Demo',
home: new Container(),
);
}
}
topLevelFunction() {
print(deferredText); // TOP LEVEL BREAKPOINT
}
''';
@override
final DeferredComponentsConfig deferredComponents;
}
/// Contains the necessary files for a bare-bones deferred component release app.
class BasicDeferredComponentsConfig extends DeferredComponentsConfig {
@override
String get deferredLibrary => r'''
library DeferredLibrary;
int add(int i, int j) {
return i + j;
}
''';
@override
String get deferredComponentsGolden => r'''
loading-units:
- id: 2
libraries:
- package:test/deferred_library.dart
''';
@override
String get androidSettings => r'''
include ':app', ':component1'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
''';
@override
String get androidBuild => r'''
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
''';
@override
String get appBuild => r'''
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "ninja.qian.splitaottest1"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.google.android.play:core:1.8.0"
}
''';
@override
String get androidLocalProperties => '''
flutter.sdk=${getFlutterRoot()}
flutter.buildMode=release
flutter.versionName=1.0.0
flutter.versionCode=22
''';
@override
String get androidGradleProperties => '''
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
android.experimental.enableNewResourceShrinker=true
''';
@override
String get androidKeyProperties => '''
storePassword=123456
keyPassword=123456
keyAlias=test_release_key
storeFile=key.jks
''';
// This is a test jks keystore, generated for testing use only. Do not use this key in an actual
// application.
@override
final List<int> androidKey = <int>[
0xfe, 0xed, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
0x00, 0x10, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x6b,
0x65, 0x79, 0x00, 0x00, 0x01, 0x77, 0x87, 0xc9, 0x8c, 0x4f, 0x00, 0x00, 0x05, 0x00, 0x30, 0x82,
0x04, 0xfc, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01,
0x05, 0x00, 0x04, 0x82, 0x04, 0xe8, 0xe2, 0xca, 0x1f, 0xd5, 0x47, 0xf1, 0x6a, 0x4a, 0xc5, 0xf8,
0x0b, 0xc1, 0x11, 0x36, 0xfc, 0xb3, 0x25, 0xb3, 0x54, 0x34, 0x53, 0x2c, 0x71, 0x81, 0xd6, 0x64,
0x54, 0xef, 0x5f, 0x85, 0x27, 0xbe, 0xe5, 0x0a, 0x08, 0xc0, 0x76, 0x2d, 0xec, 0xbf, 0x82, 0x9f,
0xf9, 0xf0, 0xb3, 0x20, 0x86, 0x9b, 0x25, 0x01, 0x02, 0x15, 0xa8, 0x78, 0x53, 0xd9, 0x97, 0xb8,
0x15, 0x84, 0xad, 0x21, 0xe7, 0x04, 0x01, 0x53, 0xc0, 0x8f, 0x14, 0x0c, 0x45, 0xe0, 0x7a, 0x4e,
0x95, 0x4e, 0xa9, 0xcd, 0x23, 0xbf, 0x78, 0xcd, 0x10, 0xd3, 0x09, 0xec, 0xfd, 0x64, 0x8d, 0xec,
0xe8, 0x02, 0x3e, 0x5a, 0x04, 0x0b, 0xd3, 0x57, 0x66, 0xb9, 0xd0, 0x28, 0xcf, 0x28, 0x04, 0x6e,
0x45, 0x67, 0x81, 0xb9, 0x1a, 0x64, 0xd6, 0x05, 0xf5, 0x3d, 0x90, 0x8e, 0x2d, 0x52, 0x95, 0x51,
0x5c, 0x26, 0xcc, 0x43, 0x86, 0x7f, 0x07, 0x8c, 0xf8, 0x06, 0x25, 0xff, 0x53, 0xb4, 0x2b, 0x87,
0xef, 0x93, 0xac, 0x99, 0xc3, 0x35, 0x6e, 0x1c, 0xf0, 0x9f, 0xb1, 0xda, 0x30, 0x88, 0x1a, 0x50,
0xa5, 0x53, 0x39, 0xef, 0x4b, 0x5c, 0xc7, 0x72, 0xc6, 0xe2, 0xf6, 0x2e, 0xf3, 0xcc, 0xc8, 0xd8,
0x80, 0xe5, 0x64, 0x45, 0xcb, 0xcc, 0x8c, 0x93, 0x7e, 0x00, 0x49, 0x3f, 0x27, 0xd8, 0xa1, 0xb2,
0xa4, 0x7c, 0xc7, 0x39, 0xc9, 0x27, 0x1a, 0x2e, 0xca, 0x88, 0x7f, 0xf1, 0xf1, 0xad, 0x68, 0xb9,
0x6b, 0xd8, 0xfe, 0xf1, 0xda, 0xad, 0x2d, 0xb2, 0x08, 0x33, 0x42, 0x12, 0x4b, 0x1f, 0x93, 0x17,
0x7d, 0x50, 0xba, 0x68, 0x86, 0xdd, 0x61, 0x74, 0x0c, 0xed, 0x55, 0x60, 0x90, 0x01, 0x93, 0x11,
0x13, 0xc2, 0x39, 0x78, 0xf6, 0xa4, 0x28, 0x8f, 0xa6, 0x63, 0x35, 0x28, 0xbd, 0xa1, 0x95, 0xc5,
0x31, 0xa2, 0xae, 0x59, 0x67, 0x58, 0x08, 0x57, 0x45, 0xf3, 0x27, 0xfe, 0x83, 0x6a, 0xb3, 0x56,
0x2c, 0x9b, 0xae, 0xf7, 0x78, 0x88, 0x88, 0xd4, 0xbe, 0x67, 0x11, 0x6a, 0x64, 0x39, 0x99, 0xaf,
0xad, 0xc5, 0xd6, 0x3e, 0x30, 0x91, 0xee, 0x94, 0xdc, 0x50, 0x33, 0x57, 0x80, 0x1c, 0x4d, 0x80,
0xda, 0x07, 0xea, 0x8c, 0x37, 0x26, 0x0e, 0xfe, 0xbc, 0x8c, 0x7a, 0xf7, 0x33, 0x3b, 0x85, 0x7c,
0xc9, 0xf6, 0x44, 0x15, 0xc1, 0xa6, 0x95, 0x1e, 0x3e, 0x4a, 0x70, 0xb1, 0x31, 0xae, 0x1f, 0x61,
0xad, 0x59, 0xc3, 0xc9, 0xa2, 0x0e, 0x11, 0xb8, 0x25, 0x84, 0x90, 0x66, 0x0c, 0x4b, 0x4a, 0xf4,
0xec, 0xc0, 0x6a, 0x95, 0x81, 0x22, 0xfc, 0xd1, 0xda, 0xd0, 0x4f, 0x8a, 0x39, 0x6f, 0x24, 0x49,
0xd7, 0xa6, 0x82, 0xbd, 0x8d, 0x5e, 0xbd, 0xe2, 0x69, 0x9b, 0xb4, 0xde, 0x37, 0x6a, 0x02, 0x7e,
0x40, 0x8c, 0x3c, 0x34, 0x97, 0xfd, 0xc9, 0xc5, 0x75, 0x74, 0xa5, 0x04, 0x93, 0xa4, 0x04, 0x64,
0x9f, 0xfd, 0xe2, 0x57, 0x63, 0x11, 0x5e, 0x51, 0x25, 0x36, 0xcc, 0x25, 0xe0, 0xda, 0x6d, 0x82,
0xfe, 0xd2, 0x7b, 0x40, 0x82, 0x33, 0x69, 0xb0, 0xe8, 0x91, 0xb7, 0x23, 0xac, 0x22, 0x85, 0x98,
0x3e, 0x02, 0x81, 0xa5, 0x4e, 0xaf, 0x99, 0xf1, 0x1b, 0x73, 0x38, 0xcd, 0x4c, 0xaa, 0x33, 0xdc,
0x49, 0xd6, 0xf7, 0xdc, 0xa6, 0x59, 0x38, 0x67, 0x89, 0x78, 0x26, 0x8e, 0x1c, 0xee, 0xbe, 0x8b,
0x6c, 0xae, 0xcf, 0x26, 0x67, 0x2a, 0xe6, 0xbe, 0x24, 0xc3, 0x6f, 0x2b, 0x1a, 0xcf, 0xb6, 0xd0,
0x82, 0x58, 0x05, 0x44, 0x19, 0x91, 0xfb, 0x9f, 0x53, 0x14, 0x4a, 0xf5, 0x84, 0x54, 0x57, 0x8e,
0xcc, 0xec, 0x6f, 0x29, 0xdd, 0xa6, 0x38, 0xa4, 0x17, 0x0e, 0x86, 0x63, 0x62, 0xd6, 0x43, 0x1c,
0xd5, 0xee, 0x93, 0x35, 0x2f, 0x66, 0x35, 0xc8, 0x33, 0x5c, 0x2b, 0xbe, 0xc7, 0xba, 0xf2, 0xd5,
0xe6, 0x51, 0xe1, 0xac, 0xac, 0x80, 0x71, 0x05, 0xed, 0x8a, 0x2f, 0x47, 0x30, 0xb5, 0x6b, 0x3d,
0x1d, 0x90, 0xe0, 0x82, 0x76, 0xba, 0x85, 0x7b, 0xb1, 0x78, 0xc1, 0x6f, 0x9c, 0xc1, 0x45, 0xfc,
0x31, 0x51, 0x04, 0xf3, 0x80, 0x98, 0x9d, 0xd7, 0xd3, 0xef, 0x4b, 0x1a, 0xeb, 0x59, 0x62, 0x97,
0x64, 0xcd, 0x4b, 0x7f, 0xaf, 0x07, 0x2a, 0xc1, 0x77, 0x4c, 0xa0, 0x29, 0x41, 0x80, 0x01, 0xca,
0xc5, 0xb2, 0xe0, 0x6e, 0x30, 0x70, 0xc4, 0xcf, 0xf7, 0xd6, 0x7f, 0x1d, 0x84, 0x9e, 0x31, 0x4b,
0xa8, 0xa8, 0x26, 0x60, 0x7f, 0x76, 0x4c, 0x75, 0x46, 0xcf, 0x86, 0x39, 0xbd, 0x5b, 0x99, 0x1b,
0x0f, 0xa2, 0x1a, 0x94, 0x62, 0xe7, 0x95, 0x9c, 0xcb, 0x4d, 0x13, 0xa4, 0x84, 0x79, 0xec, 0xd3,
0x7c, 0xbd, 0x3f, 0xb7, 0x22, 0xa9, 0x14, 0xf0, 0xd3, 0x66, 0xb0, 0xa9, 0xe7, 0xdf, 0x01, 0x47,
0x5c, 0xb0, 0x8e, 0x49, 0xfa, 0xfd, 0xa9, 0x9f, 0xf4, 0x29, 0x7e, 0x0c, 0x6a, 0x9f, 0x67, 0x7f,
0x38, 0xe0, 0xe6, 0x48, 0x0d, 0x51, 0x7d, 0x79, 0x0d, 0xb8, 0x27, 0xec, 0x6e, 0x99, 0x3e, 0x00,
0xb2, 0x18, 0x8e, 0x8d, 0xbf, 0x89, 0xd2, 0x4b, 0xce, 0xcc, 0x64, 0xb7, 0xae, 0x4a, 0x34, 0x8d,
0xe1, 0x73, 0x4e, 0x2c, 0x50, 0x7e, 0xc5, 0xc7, 0x14, 0xa6, 0x8c, 0x51, 0x5b, 0x4a, 0x94, 0xf2,
0x16, 0x58, 0x11, 0x1c, 0xc4, 0x81, 0x9d, 0xfc, 0x5d, 0x57, 0xe2, 0x8e, 0xdb, 0x51, 0x99, 0x5f,
0xd5, 0x71, 0x72, 0x3a, 0xa1, 0xe0, 0xc1, 0x6f, 0xdb, 0x0c, 0xf0, 0x91, 0x7c, 0xb7, 0xc0, 0xf5,
0x6f, 0x6c, 0x3c, 0xea, 0x0e, 0xd3, 0xab, 0x13, 0xed, 0x1e, 0x55, 0x9e, 0xdf, 0xf7, 0x37, 0x75,
0x38, 0xb5, 0x07, 0x97, 0x1d, 0x82, 0x5b, 0x28, 0x1f, 0xbd, 0xe3, 0x70, 0xdb, 0x18, 0x3f, 0x69,
0x95, 0x84, 0x8f, 0x99, 0xc4, 0xa6, 0xed, 0x60, 0xfc, 0x1e, 0x3b, 0x4d, 0x63, 0x34, 0xeb, 0xb8,
0xd6, 0x18, 0x24, 0xf5, 0xd9, 0x3b, 0xc3, 0xdd, 0x3e, 0x49, 0x55, 0x7a, 0xa3, 0x49, 0x2c, 0x58,
0x2a, 0x9c, 0x11, 0x21, 0xdc, 0x82, 0xa5, 0xc2, 0xf7, 0x87, 0x3a, 0xd8, 0xe8, 0x03, 0x3a, 0x5c,
0xdf, 0xac, 0xa7, 0xbe, 0x34, 0x7f, 0x1a, 0xaa, 0xe9, 0x27, 0x91, 0xd0, 0x7c, 0x23, 0x8d, 0x6e,
0x00, 0x86, 0x4f, 0x23, 0x87, 0xdc, 0x53, 0x99, 0x99, 0x9b, 0xc9, 0xae, 0x50, 0xf1, 0x55, 0xd0,
0x04, 0x89, 0xe2, 0x2d, 0x5a, 0x2a, 0x54, 0x89, 0xc7, 0x70, 0x48, 0x3e, 0xf1, 0xc8, 0x01, 0x87,
0x67, 0x35, 0xdb, 0x0f, 0x08, 0x82, 0x62, 0x53, 0xb5, 0x7b, 0xcb, 0xc5, 0x60, 0x85, 0x56, 0xc0,
0xda, 0xed, 0x1b, 0x06, 0x02, 0xfc, 0xa9, 0x55, 0x99, 0x71, 0x54, 0x88, 0xd7, 0x33, 0xb4, 0xce,
0x85, 0x2f, 0x24, 0x82, 0x80, 0xb3, 0xca, 0x56, 0x33, 0x3b, 0x5a, 0x2a, 0xd5, 0xed, 0xbb, 0x6b,
0xc9, 0xc1, 0x26, 0x93, 0x51, 0xe2, 0x01, 0x88, 0x39, 0xe4, 0xe7, 0x56, 0xd3, 0x0f, 0x5d, 0xe9,
0xfd, 0xcd, 0xeb, 0x13, 0xd6, 0xa0, 0xe3, 0x6c, 0x57, 0x50, 0x09, 0xe8, 0xb2, 0xd4, 0x47, 0xd9,
0x0c, 0x3e, 0xac, 0xee, 0x65, 0x53, 0x8d, 0x00, 0x95, 0x90, 0x58, 0x94, 0x32, 0x1a, 0x32, 0xe2,
0xe2, 0xc9, 0x66, 0x6d, 0xc8, 0xf2, 0x1e, 0x70, 0xe5, 0xaa, 0xa6, 0x48, 0xad, 0x4a, 0xaf, 0x2a,
0x97, 0x59, 0x5e, 0x79, 0xec, 0xdf, 0x1f, 0xe1, 0x37, 0xb5, 0x48, 0x6f, 0x0e, 0xab, 0xce, 0x29,
0x32, 0x99, 0x8d, 0xe0, 0xc9, 0x73, 0xba, 0x76, 0xa1, 0x25, 0xd8, 0x98, 0x19, 0x67, 0x87, 0x24,
0x00, 0xbb, 0x52, 0x15, 0x41, 0x28, 0x56, 0x1a, 0x6c, 0xb5, 0xbc, 0x4c, 0xcc, 0x17, 0x8a, 0xc7,
0x1b, 0xc9, 0xea, 0x31, 0xbb, 0x68, 0x16, 0x5a, 0x72, 0x04, 0xbf, 0x9d, 0x3a, 0xb1, 0xc6, 0xe0,
0x45, 0x51, 0x39, 0x03, 0x48, 0x82, 0xa6, 0x0c, 0x8a, 0xd2, 0x22, 0x30, 0xf3, 0x74, 0x4d, 0xd3,
0x5c, 0x6c, 0x3e, 0x36, 0x90, 0xc5, 0xe6, 0xcf, 0xd3, 0xde, 0x68, 0x6a, 0x43, 0x5b, 0x2b, 0x90,
0xbe, 0xa6, 0x23, 0xa0, 0x3b, 0x40, 0x6b, 0x99, 0x60, 0xdf, 0xff, 0x9f, 0x7c, 0x84, 0xc7, 0xf0,
0xc7, 0x99, 0x1b, 0x99, 0x50, 0x06, 0x9b, 0xe7, 0x78, 0x2a, 0x4e, 0x76, 0x81, 0xea, 0x06, 0x29,
0xab, 0xd8, 0xa6, 0x1e, 0x7f, 0x5a, 0x28, 0x0b, 0x90, 0x0e, 0x5d, 0x04, 0xf7, 0x57, 0x0a, 0x31,
0x14, 0x2c, 0x84, 0x56, 0x57, 0x17, 0x8a, 0xa0, 0xd7, 0x7c, 0x3a, 0xf7, 0xda, 0x8d, 0x8a, 0xcb,
0x47, 0xe8, 0xb2, 0xee, 0x32, 0x2a, 0xf8, 0x42, 0x42, 0xd8, 0x75, 0x8c, 0x47, 0x73, 0x2a, 0x87,
0xe0, 0x0a, 0xe8, 0x6f, 0xed, 0xf2, 0x1a, 0x14, 0x3a, 0x59, 0xc7, 0x8e, 0x21, 0x72, 0xf7, 0x05,
0x5b, 0xd1, 0x2b, 0x7d, 0x53, 0xfb, 0x22, 0xfb, 0x7e, 0xe7, 0x25, 0x0b, 0x1c, 0x15, 0xb2, 0xde,
0xdf, 0x66, 0xcc, 0xe9, 0x30, 0xfc, 0x75, 0x8c, 0xa3, 0x09, 0xab, 0x42, 0x27, 0x6b, 0x33, 0xa0,
0xdf, 0x15, 0xa4, 0x00, 0x10, 0x18, 0xe5, 0x10, 0x97, 0x47, 0xe9, 0x2e, 0x65, 0xd3, 0x9e, 0x44,
0x1b, 0xaf, 0x36, 0x60, 0xcf, 0xe2, 0x1a, 0x6b, 0xbe, 0x7b, 0x1f, 0x80, 0xdd, 0x5c, 0x10, 0x5c,
0x89, 0x23, 0xba, 0xa1, 0x98, 0xcc, 0x88, 0x74, 0xbf, 0x26, 0x4c, 0x0c, 0xa5, 0xc7, 0x00, 0x00,
0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35, 0x30, 0x39, 0x00, 0x00, 0x03, 0x7f, 0x30, 0x82, 0x03,
0x7b, 0x30, 0x82, 0x02, 0x63, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x22, 0x12, 0x53, 0x46,
0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30,
0x6d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f, 0x72,
0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x4d, 0x6f,
0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x10, 0x30, 0x0e, 0x06,
0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72, 0x31, 0x10, 0x30,
0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72, 0x31,
0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x44, 0x61, 0x73, 0x68, 0x30, 0x20,
0x17, 0x0d, 0x32, 0x31, 0x30, 0x32, 0x30, 0x39, 0x31, 0x37, 0x31, 0x34, 0x32, 0x37, 0x5a, 0x18,
0x0f, 0x32, 0x32, 0x39, 0x34, 0x31, 0x31, 0x32, 0x35, 0x31, 0x37, 0x31, 0x34, 0x32, 0x37, 0x5a,
0x30, 0x6d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66, 0x6f,
0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d, 0x4d,
0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x10, 0x30, 0x0e,
0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72, 0x31, 0x10,
0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72,
0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x44, 0x61, 0x73, 0x68, 0x30,
0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00,
0x94, 0x7d, 0x0d, 0x32, 0x57, 0xf8, 0x90, 0x9b, 0x5a, 0xc5, 0x61, 0x48, 0xa8, 0xbb, 0x5e, 0x9d,
0x4c, 0x4f, 0x53, 0xb9, 0x3b, 0x1f, 0x46, 0xc4, 0xd1, 0xba, 0x69, 0x7b, 0x71, 0x37, 0x20, 0xa1,
0x44, 0x4f, 0xd1, 0x87, 0x71, 0x88, 0xc9, 0xe4, 0x49, 0xe1, 0xf0, 0x3d, 0x18, 0xca, 0xf7, 0x56,
0xc4, 0x61, 0x4e, 0xa7, 0x9a, 0x93, 0x26, 0x8b, 0x03, 0x01, 0xa8, 0xef, 0x44, 0x89, 0xc6, 0x4d,
0xab, 0x63, 0x92, 0xb2, 0xb5, 0xcd, 0x51, 0xb4, 0x12, 0x98, 0x2b, 0x89, 0x73, 0x28, 0xfa, 0x73,
0xb2, 0xf4, 0xb7, 0xf2, 0x85, 0x3f, 0xe8, 0xf0, 0x38, 0x4f, 0x1d, 0xd0, 0x3b, 0xe7, 0xd3, 0xf0,
0x22, 0xfd, 0x50, 0x11, 0x6d, 0x20, 0xe4, 0x8d, 0x87, 0xd7, 0x99, 0xd4, 0x70, 0x4e, 0xb9, 0xcb,
0x5b, 0xbb, 0x4f, 0xd9, 0xa6, 0xf6, 0x51, 0x8b, 0x44, 0x5b, 0x9d, 0x68, 0x0b, 0x40, 0x2d, 0x11,
0x18, 0xdc, 0xb6, 0x29, 0x73, 0xdb, 0x6a, 0x00, 0x12, 0xa0, 0x9f, 0xf4, 0xed, 0xeb, 0xa3, 0x4b,
0x60, 0xdc, 0x51, 0xed, 0xbe, 0x6c, 0x70, 0x4b, 0x32, 0xda, 0xa0, 0x53, 0x15, 0xac, 0x5e, 0xfd,
0x4d, 0x14, 0xd7, 0x75, 0xc8, 0x6f, 0x02, 0x85, 0x33, 0x95, 0xbe, 0x86, 0xee, 0x6d, 0x4d, 0x75,
0x0f, 0x64, 0xfe, 0x9d, 0xa1, 0x3f, 0x53, 0x1b, 0xa1, 0xeb, 0x7b, 0xe6, 0xdd, 0xa1, 0x0a, 0x38,
0xad, 0xce, 0xa3, 0x66, 0xda, 0x51, 0x38, 0xf3, 0x33, 0x3d, 0x96, 0x06, 0xa5, 0x0e, 0xa7, 0xfd,
0xb2, 0x7b, 0xd5, 0x21, 0x1a, 0x35, 0x76, 0x25, 0x95, 0x97, 0xd6, 0xd2, 0xfe, 0xbd, 0x86, 0x22,
0x05, 0xa3, 0x7d, 0x67, 0x60, 0x86, 0x04, 0xc3, 0xa5, 0xd3, 0xb7, 0x40, 0x0c, 0x31, 0x30, 0xc8,
0x93, 0xb7, 0x61, 0xb3, 0x68, 0x90, 0x9d, 0xa0, 0x49, 0x79, 0x9b, 0xc1, 0x9c, 0x47, 0xa8, 0x81,
0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x21, 0x30, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
0x04, 0x16, 0x04, 0x14, 0x71, 0x50, 0x13, 0x2c, 0x3e, 0xaa, 0xfc, 0x7e, 0x6d, 0x16, 0x18, 0xc0,
0x6f, 0x32, 0x2e, 0xf0, 0xe1, 0x03, 0x46, 0x94, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x24, 0x98, 0xc6, 0xd6,
0xab, 0xb3, 0x94, 0xa1, 0x5d, 0x50, 0x0a, 0x82, 0xf7, 0x1d, 0xf5, 0xdf, 0x93, 0xf2, 0xf2, 0xf2,
0xe6, 0x2a, 0x28, 0x67, 0x06, 0x36, 0xf6, 0x1f, 0x8c, 0xa3, 0x41, 0x43, 0x98, 0xc4, 0x6a, 0xd0,
0x14, 0x0b, 0x1b, 0x89, 0x75, 0xb1, 0xe5, 0x79, 0x2c, 0xe8, 0xfd, 0x6d, 0x77, 0x72, 0xaa, 0x06,
0x15, 0xd3, 0xca, 0x95, 0xca, 0x7d, 0xd5, 0xce, 0xca, 0x5f, 0x88, 0xd2, 0x3c, 0x08, 0xd2, 0x83,
0xed, 0x7a, 0x0d, 0x2f, 0x34, 0x37, 0x97, 0x75, 0xd1, 0x2c, 0xa6, 0x30, 0x68, 0x8e, 0x19, 0x23,
0x3d, 0x22, 0x62, 0x73, 0x4e, 0xd5, 0x42, 0x09, 0x82, 0xb6, 0x06, 0x17, 0xb8, 0xb6, 0x08, 0x64,
0x73, 0x93, 0x02, 0x87, 0xd4, 0xf6, 0xa6, 0xce, 0xad, 0xfd, 0xcc, 0x9f, 0x69, 0x8b, 0xa8, 0xb3,
0x4a, 0x45, 0x9e, 0xad, 0xa7, 0xf2, 0xb5, 0x91, 0xc8, 0x61, 0x48, 0x95, 0x8b, 0x36, 0x3e, 0x2f,
0x40, 0x80, 0x69, 0xab, 0x3d, 0x45, 0xe1, 0x60, 0x3a, 0xe8, 0x33, 0x06, 0x12, 0x1a, 0x7e, 0x6e,
0x11, 0x01, 0xb9, 0x66, 0x1b, 0x61, 0xbf, 0x01, 0x6d, 0x1d, 0x33, 0x58, 0x9a, 0xdd, 0x12, 0xf8,
0xc1, 0xa3, 0x71, 0x89, 0x72, 0xed, 0xf4, 0xb2, 0xf3, 0x39, 0xc3, 0xf1, 0xf1, 0xe3, 0xe1, 0x9b,
0xce, 0xc7, 0x83, 0x80, 0x32, 0x76, 0x16, 0x8c, 0x95, 0x35, 0xc0, 0xe8, 0xae, 0x02, 0x1b, 0x05,
0x21, 0x36, 0xed, 0x4a, 0x71, 0xe0, 0x18, 0x76, 0xaa, 0xb9, 0x98, 0x07, 0x35, 0x27, 0x9b, 0xf2,
0x5d, 0xdc, 0x79, 0xe6, 0xaa, 0x4a, 0x01, 0x23, 0x7d, 0x6d, 0x21, 0xbd, 0x97, 0x1b, 0x41, 0x60,
0x7c, 0xb7, 0xfa, 0x21, 0x48, 0x52, 0x22, 0x94, 0x2d, 0xb0, 0xef, 0x2d, 0xc3, 0xe2, 0xe6, 0x37,
0x55, 0x9d, 0xd9, 0x4d, 0xdd, 0xdd, 0x25, 0x25, 0x6b, 0x13, 0xb6, 0xb0, 0x00, 0x00, 0x00, 0x01,
0x00, 0x08, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x00, 0x00, 0x01, 0x77, 0x87, 0xc7,
0x7b, 0x73, 0x00, 0x00, 0x02, 0xba, 0x30, 0x82, 0x02, 0xb6, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06,
0x01, 0x04, 0x01, 0x2a, 0x02, 0x11, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x02, 0xa2, 0x23, 0xce,
0x04, 0x35, 0x63, 0x08, 0xa7, 0x0b, 0x42, 0x93, 0x53, 0x16, 0xa6, 0xbe, 0x54, 0xf2, 0xe4, 0x27,
0x8d, 0xbb, 0x64, 0xe4, 0x23, 0xeb, 0x70, 0x6c, 0xd5, 0x28, 0xe7, 0x8b, 0x73, 0x68, 0xd0, 0x03,
0xc5, 0x32, 0xfe, 0x4d, 0x80, 0xa5, 0xff, 0xcd, 0xf0, 0x5c, 0x31, 0xd1, 0xf0, 0xf1, 0xc7, 0x53,
0xeb, 0xea, 0xa3, 0xb1, 0x07, 0xdc, 0x6b, 0x9a, 0xff, 0xcd, 0x36, 0x88, 0x83, 0x6c, 0x0a, 0xbf,
0x85, 0x3a, 0x5d, 0xc4, 0x54, 0x0a, 0x63, 0xd6, 0xf1, 0x30, 0xa8, 0x18, 0x89, 0x6d, 0xba, 0x27,
0x7f, 0xdc, 0xe1, 0x38, 0x6b, 0xa7, 0xc4, 0x2e, 0xdc, 0x21, 0x01, 0xf6, 0xdc, 0xc9, 0x36, 0x2a,
0x6e, 0xb7, 0xbc, 0xdd, 0xe2, 0xd8, 0x44, 0x16, 0x9e, 0x28, 0x34, 0x9d, 0x59, 0x43, 0xc3, 0xe9,
0x21, 0xb1, 0x46, 0x6b, 0x08, 0x81, 0x51, 0xa8, 0xa6, 0x84, 0xe1, 0xe3, 0x7d, 0x60, 0x6c, 0x3d,
0xbc, 0x28, 0xea, 0xe7, 0x10, 0xd4, 0x07, 0xcc, 0x2e, 0xa4, 0xed, 0x66, 0xe6, 0xaa, 0xbf, 0xf5,
0x95, 0xd9, 0xc9, 0xcd, 0x69, 0x44, 0xc6, 0x46, 0x66, 0xab, 0x99, 0xdb, 0x74, 0x9f, 0x7f, 0x4e,
0x02, 0x37, 0x1e, 0x23, 0x52, 0x58, 0x10, 0x8c, 0x41, 0x6f, 0xe4, 0xc1, 0xca, 0x1e, 0xd0, 0xd3,
0x8e, 0xd0, 0x36, 0xc6, 0xea, 0x61, 0x3d, 0x97, 0x35, 0x54, 0x81, 0xc4, 0x0e, 0x1a, 0x05, 0xa3,
0x2b, 0x0b, 0x9e, 0xb2, 0x0d, 0xe9, 0xfc, 0x3a, 0xd9, 0x02, 0xce, 0x1b, 0x56, 0x92, 0x1a, 0x97,
0x78, 0xda, 0xba, 0x40, 0x2c, 0x7c, 0x96, 0xe1, 0x94, 0x88, 0x1a, 0x7b, 0x47, 0x77, 0x59, 0xcf,
0x98, 0x52, 0xb0, 0xfb, 0xcb, 0xb5, 0xf5, 0xe4, 0x16, 0x38, 0xb6, 0x05, 0x20, 0x5c, 0x36, 0x19,
0xc5, 0xa9, 0x70, 0xe6, 0x89, 0x9f, 0x76, 0x3d, 0x88, 0x78, 0x56, 0xf2, 0x85, 0xff, 0x89, 0xc3,
0xc5, 0x32, 0xd3, 0xb0, 0x8f, 0x1c, 0xc3, 0x23, 0xb4, 0x70, 0xd4, 0x8b, 0xd3, 0x6c, 0x27, 0xd1,
0xc4, 0xe9, 0x0a, 0xaa, 0x06, 0x3a, 0xd3, 0xd6, 0x20, 0xe1, 0x08, 0x65, 0x10, 0x50, 0x45, 0x59,
0xa5, 0xd4, 0xb2, 0xd5, 0xcb, 0x74, 0x52, 0x05, 0x95, 0x08, 0xe4, 0xb9, 0xe9, 0xc9, 0x7f, 0xbc,
0x4b, 0x3f, 0xfa, 0x00, 0x5c, 0x20, 0xda, 0x8e, 0xb2, 0xe0, 0x4e, 0x51, 0x0e, 0xfe, 0x98, 0xd8,
0xe9, 0xd5, 0x44, 0x0b, 0x1c, 0x1f, 0xe3, 0xa6, 0xc5, 0x03, 0xb1, 0x5e, 0x23, 0x7a, 0x0a, 0x1e,
0xa1, 0x49, 0xaf, 0xea, 0xb8, 0xea, 0x74, 0x64, 0x05, 0xda, 0xad, 0xb3, 0x5f, 0x17, 0xa9, 0x9a,
0x89, 0x16, 0x6d, 0xd2, 0xef, 0xa1, 0x35, 0x40, 0x43, 0x71, 0xee, 0x6c, 0x0c, 0x99, 0xbf, 0xcf,
0xd5, 0xae, 0xad, 0x83, 0xeb, 0x60, 0x8d, 0x4e, 0xae, 0xe2, 0x01, 0xcb, 0xbe, 0x18, 0x94, 0x39,
0xdf, 0xcb, 0xae, 0x47, 0xf7, 0x89, 0x9c, 0x37, 0x35, 0x43, 0x9b, 0xb4, 0x6c, 0xb9, 0x86, 0xc5,
0xd6, 0x39, 0xd3, 0x47, 0x1f, 0x91, 0x8d, 0xe8, 0xd3, 0x13, 0xe7, 0xe2, 0x2c, 0x27, 0x36, 0xd9,
0xc8, 0xf4, 0xc6, 0x25, 0x78, 0x02, 0x78, 0x31, 0x7f, 0x8a, 0xa1, 0x32, 0x50, 0x4d, 0xc1, 0x49,
0x23, 0x8c, 0x72, 0x09, 0xc8, 0xe3, 0x7e, 0xa0, 0xdd, 0x1b, 0x96, 0x47, 0x24, 0xab, 0xb4, 0x14,
0x6d, 0x07, 0x8e, 0x90, 0x29, 0x6c, 0xb2, 0x13, 0xe4, 0xe1, 0x69, 0x17, 0xfd, 0xb8, 0x9e, 0x52,
0x90, 0x19, 0xbe, 0xf6, 0xd7, 0x60, 0x0e, 0x22, 0xb5, 0x01, 0x45, 0xab, 0xf5, 0xe4, 0x2e, 0x53,
0x3f, 0xf0, 0x58, 0xbb, 0x2c, 0xa5, 0x31, 0x59, 0x4e, 0x4b, 0xf0, 0x3e, 0x36, 0x77, 0xe0, 0x05,
0x38, 0x81, 0x07, 0xf6, 0x53, 0xf3, 0xff, 0x0c, 0x8d, 0x6d, 0xbc, 0xa9, 0x6f, 0x6a, 0x75, 0xef,
0x99, 0x01, 0xc9, 0xd6, 0x4d, 0xa4, 0x9b, 0x35, 0x95, 0xe3, 0x20, 0xfd, 0x13, 0x51, 0x71, 0xbb,
0xbd, 0x93, 0xc4, 0x8b, 0x98, 0x6c, 0x8c, 0x6a, 0x30, 0xea, 0x3e, 0xe7, 0x9b, 0x98, 0x10, 0xab,
0x03, 0x3a, 0x90, 0xf4, 0x98, 0xf1, 0x30, 0xa5, 0xd6, 0x3a, 0x06, 0x62, 0xc0, 0x39, 0xf7, 0x36,
0xa5, 0x66, 0xfd, 0x3d, 0x30, 0x51, 0x7a, 0x4d, 0x06, 0xf9, 0xfe, 0xa5, 0xda, 0xa8, 0x80, 0xaa,
0x50, 0x5d, 0xff, 0xf2, 0x23, 0x6f, 0x1a, 0x7d, 0x63, 0xa6, 0x6a, 0x64, 0x5c, 0x2a, 0xeb, 0x83,
0x42, 0x29, 0x5e, 0x26, 0x9d, 0x25, 0x0f, 0x58, 0xb6, 0x3b, 0x48, 0x66, 0xb0, 0x1f, 0x40, 0x07,
0x85, 0xd4, 0xc3, 0x61, 0x7a, 0xa5, 0x6b, 0xa1, 0x61, 0x09, 0x78, 0x03, 0x58, 0xfa, 0x52, 0xde,
0xb4, 0xf2, 0xc7, 0x40, 0x94, 0x7b, 0x6c, 0x4f, 0xec, 0x5f, 0x17, 0xdf, 0x97, 0xd2, 0x95, 0xa6,
0x94, 0x8b, 0xb7, 0xcf, 0x05, 0x3d, 0x9d, 0xcc, 0x55, 0x1e, 0x83, 0x79, 0xf9, 0xe6, 0x22, 0x8e,
0xdd, 0x20, 0x22, 0x87, 0x3b, 0xb6, 0x79, 0xc9, 0xcf, 0x4c, 0x8f, 0xbb, 0x4e, 0x1f, 0xd6, 0xa5,
0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x58, 0x2e, 0x35, 0x30, 0x39, 0x00, 0x00, 0x02, 0x7a, 0x30,
0x82, 0x02, 0x76, 0x30, 0x82, 0x01, 0xdf, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x44, 0xbb,
0xd2, 0x52, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05,
0x00, 0x30, 0x6d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53,
0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69, 0x66,
0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0d,
0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x10, 0x30,
0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72, 0x31,
0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65,
0x72, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x44, 0x61, 0x73, 0x68,
0x30, 0x20, 0x17, 0x0d, 0x32, 0x31, 0x30, 0x32, 0x30, 0x39, 0x31, 0x37, 0x31, 0x32, 0x31, 0x30,
0x5a, 0x18, 0x0f, 0x34, 0x37, 0x35, 0x39, 0x30, 0x31, 0x30, 0x37, 0x31, 0x37, 0x31, 0x32, 0x31,
0x30, 0x5a, 0x30, 0x6d, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x43, 0x61, 0x6c, 0x69,
0x66, 0x6f, 0x72, 0x6e, 0x69, 0x61, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13,
0x0d, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x56, 0x69, 0x65, 0x77, 0x31, 0x10,
0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74, 0x65, 0x72,
0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x07, 0x46, 0x6c, 0x75, 0x74, 0x74,
0x65, 0x72, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x44, 0x61, 0x73,
0x68, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xce, 0x0c,
0xe2, 0x0c, 0xc5, 0xb8, 0x20, 0x93, 0xf7, 0x7a, 0x38, 0x3c, 0xb5, 0x52, 0x1c, 0x48, 0x1a, 0x44,
0xba, 0xca, 0x88, 0xa0, 0xf0, 0xd6, 0xad, 0x91, 0x9e, 0x0c, 0x09, 0x64, 0xdd, 0x2d, 0x84, 0x0f,
0x9e, 0xcb, 0xbb, 0xd9, 0x24, 0xd6, 0xd6, 0xaa, 0x63, 0x1c, 0xfa, 0xb6, 0x45, 0x88, 0xf4, 0x8e,
0xb9, 0x2e, 0xe9, 0xa9, 0xed, 0x6b, 0xda, 0xc6, 0x6b, 0x91, 0x06, 0xf9, 0x0a, 0x71, 0x42, 0x2e,
0x18, 0x97, 0x80, 0x0c, 0x84, 0xea, 0x69, 0x8f, 0xc0, 0xb0, 0xa8, 0x76, 0xfc, 0x31, 0x86, 0xf9,
0x09, 0x7e, 0xa4, 0xff, 0x24, 0x94, 0x79, 0x29, 0xca, 0xd0, 0x9a, 0x07, 0xf3, 0x25, 0x21, 0xa7,
0x61, 0x6e, 0x81, 0x1c, 0x13, 0x02, 0xc9, 0xec, 0xa4, 0x42, 0xcc, 0x6c, 0x95, 0x01, 0xe5, 0x51,
0x8e, 0x4a, 0xb3, 0x0b, 0x29, 0xf1, 0x8a, 0x1c, 0xb5, 0xe1, 0x41, 0xb6, 0xe3, 0x3d, 0x02, 0x03,
0x01, 0x00, 0x01, 0xa3, 0x21, 0x30, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
0x04, 0x14, 0x5f, 0xf5, 0x0a, 0x1f, 0xe7, 0xf5, 0xde, 0xbd, 0x7c, 0x59, 0xdd, 0x94, 0x26, 0x39,
0xf5, 0xc9, 0x21, 0x88, 0xbf, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x17, 0x3d, 0x26, 0x25, 0xfb, 0xd1, 0x5b,
0x9b, 0xd5, 0xed, 0x84, 0x4f, 0x06, 0xa3, 0x4c, 0x17, 0x6d, 0x91, 0xa9, 0x19, 0xc8, 0xa2, 0x1a,
0x65, 0x4f, 0xf3, 0x23, 0x28, 0x18, 0x46, 0xe3, 0x77, 0x78, 0xae, 0x2c, 0x5a, 0xfa, 0x1a, 0x01,
0x3b, 0x92, 0xa6, 0x7c, 0xee, 0x3c, 0xa0, 0x4c, 0x9e, 0xb1, 0x26, 0xed, 0x9e, 0x1b, 0x81, 0x40,
0xa5, 0xce, 0xa8, 0xad, 0x09, 0xdd, 0x7c, 0x83, 0xc8, 0x1d, 0x1e, 0x6a, 0x3e, 0x60, 0x2e, 0x95,
0xc6, 0x17, 0x5b, 0x88, 0x0b, 0x54, 0x48, 0x80, 0x95, 0x77, 0x78, 0xcc, 0x5e, 0x09, 0x9e, 0x66,
0xe5, 0x87, 0x64, 0x4d, 0x36, 0x12, 0x40, 0xc4, 0x67, 0x78, 0xce, 0x38, 0x60, 0x24, 0xdf, 0x3c,
0xc0, 0xbb, 0xf7, 0x7d, 0x2f, 0x66, 0x56, 0xfb, 0xfa, 0x75, 0x2a, 0xe5, 0x23, 0x7a, 0xad, 0x5c,
0xef, 0x2d, 0xa1, 0xb6, 0x7c, 0xbd, 0xfa, 0xb3, 0xdc, 0x68, 0x55, 0xd1, 0xa0, 0xac, 0x8c, 0x06,
0x62, 0x21, 0xe9, 0x7d, 0x64, 0xd0, 0x60, 0xb3, 0x12, 0x2e, 0x6a, 0x50, 0xf4
];
@override
String get appManifest => r'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.splitaot">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterPlayStoreSplitApplication"
android:label="splitaot"
android:extractNativeLibs="false">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</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="2:component1" />
</application>
</manifest>
''';
@override
String get appStrings => r'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="component1Name">component1</string>
</resources>
''';
@override
String get appStyles => r'''
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@android:color/white</item>
</style>
</resources>
''';
@override
String get appLaunchBackground => r'''
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
''';
@override
String get asset1 => r'''
asset 1 contents
''';
@override
String get asset2 => r'''
asset 2 contents
''';
@override
List<DeferredComponentModule> get deferredComponents => <DeferredComponentModule>[DeferredComponentModule('component1')];
}
/// Missing android dynamic feature module.
class NoAndroidDynamicFeatureModuleDeferredComponentsConfig extends BasicDeferredComponentsConfig {
@override
List<DeferredComponentModule> get deferredComponents => <DeferredComponentModule>[];
}
/// Missing golden
class NoGoldenDeferredComponentsConfig extends BasicDeferredComponentsConfig {
@override
String get deferredComponentsGolden => null;
}
/// Missing golden
class MismatchedGoldenDeferredComponentsConfig extends BasicDeferredComponentsConfig {
@override
String get deferredComponentsGolden => r'''
loading-units:
- id: 2
libraries:
- package:test/invalid_lib_name.dart
''';
}

View File

@ -7,6 +7,7 @@
import 'package:file/file.dart';
import '../test_utils.dart';
import 'deferred_components_config.dart';
const String _kDefaultHtml = '''
<html>
@ -26,6 +27,7 @@ abstract class Project {
String get main;
String get test => null;
String get generatedFile => null;
DeferredComponentsConfig get deferredComponents => null;
Uri get mainDart => Uri.parse('package:test/main.dart');
@ -41,6 +43,9 @@ abstract class Project {
if (generatedFile != null) {
writeFile(fileSystem.path.join(dir.path, '.dart_tool', 'flutter_gen', 'flutter_gen.dart'), generatedFile);
}
if (deferredComponents != null) {
deferredComponents.setUpIn(dir);
}
writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), _kDefaultHtml);
writePackages(dir.path);
await getPackages(dir.path);

View File

@ -40,6 +40,13 @@ void writeFile(String path, String content) {
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
}
void writeBytesFile(String path, List<int> content) {
fileSystem.file(path)
..createSync(recursive: true)
..writeAsBytesSync(content)
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
}
void writePackages(String folder) {
writeFile(fileSystem.path.join(folder, '.packages'), '''
test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/

View File

@ -35,6 +35,8 @@ class FakeAndroidBuilder implements AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async {}
}

View File

@ -90,6 +90,15 @@ String getFlutterRoot() {
return path.normalize(path.join(toolsPath, '..', '..'));
}
/// Gets the path to the root of the Android SDK from the environment variable.
String getAndroidSdkRoot() {
const Platform platform = LocalPlatform();
if (platform.environment.containsKey('ANDROID_SDK_ROOT')) {
return platform.environment['ANDROID_SDK_ROOT'];
}
throw StateError('ANDROID_SDK_ROOT environment varible not set');
}
CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) {
final FlutterCommandRunner runner = TestFlutterCommandRunner();
if (command != null) {