diff --git a/dev/bots/test.dart b/dev/bots/test.dart index d306d7e03b..2eb3596e23 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -519,7 +519,7 @@ Future _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); } } diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 8564e8655b..5bb73d14fc 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -134,6 +134,14 @@ class FlutterPlugin implements Plugin { } } } + + 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 { 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.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 { 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 { 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 { 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 { // | ----------------- | ----------------------------- | // | 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" } } diff --git a/packages/flutter_tools/lib/src/android/android_builder.dart b/packages/flutter_tools/lib/src/android/android_builder.dart index 3b7c70640e..dfac543e1f 100644 --- a/packages/flutter_tools/lib/src/android/android_builder.dart +++ b/packages/flutter_tools/lib/src/android/android_builder.dart @@ -38,5 +38,7 @@ abstract class AndroidBuilder { @required FlutterProject project, @required AndroidBuildInfo androidBuildInfo, @required String target, + bool validateDeferredComponents = true, + bool deferredComponentsEnabled = false, }); } diff --git a/packages/flutter_tools/lib/src/android/deferred_components_gen_snapshot_validator.dart b/packages/flutter_tools/lib/src/android/deferred_components_gen_snapshot_validator.dart index 16df5637f3..0c30ed8b45 100644 --- a/packages/flutter_tools/lib/src/android/deferred_components_gen_snapshot_validator.dart +++ b/packages/flutter_tools/lib/src/android/deferred_components_gen_snapshot_validator.dart @@ -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 components, List 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 generatedLoadingUnits) { - final List cachedLoadingUnits = _parseLodingUnitsCache(env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName)); + final List cachedLoadingUnits = _parseLodingUnitsCache(projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName)); loadingUnitComparisonResults = {}; final Set unmatchedLoadingUnits = {}; final List newLoadingUnits = []; @@ -276,7 +285,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator /// deferred components. void writeLoadingUnitsCache(List generatedLoadingUnits) { generatedLoadingUnits ??= []; - 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); diff --git a/packages/flutter_tools/lib/src/android/deferred_components_prebuild_validator.dart b/packages/flutter_tools/lib/src/android/deferred_components_prebuild_validator.dart index 9bac5c5830..b14e9fc4a6 100644 --- a/packages/flutter_tools/lib/src/android/deferred_components_prebuild_validator.dart +++ b/packages/flutter_tools/lib/src/android/deferred_components_prebuild_validator.dart @@ -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 checkAndroidDynamicFeature(List 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 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> _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, ); } diff --git a/packages/flutter_tools/lib/src/android/deferred_components_validator.dart b/packages/flutter_tools/lib/src/android/deferred_components_validator.dart index f0c4386693..1f80a27a93 100644 --- a/packages/flutter_tools/lib/src/android/deferred_components_validator.dart +++ b/packages/flutter_tools/lib/src/android/deferred_components_validator.dart @@ -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 = [], @@ -34,12 +34,9 @@ abstract class DeferredComponentsValidator { modifiedFiles = [], invalidFiles = {}, diffLines = []; - /// 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).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) { - 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).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) { - 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() { diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 7edc387093..404d6ee966 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -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 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 componentNames = []; + 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('|')}'); } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 2dd08fda97..5ddb9f8452 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -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 get entries; + /// The files that were specified under the deferred components assets sections + /// in pubspec. + Map> get deferredComponentsEntries; + /// Additional files that this bundle depends on that are not included in the /// output result. List 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 entries = {}; + @override + final Map> deferredComponentsEntries = >{}; + // 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 excludeDirs = [ + 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: [ - 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>> 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> 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] = {}; + 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>> _parseDeferredComponentsAssets( + FlutterManifest flutterManifest, + PackageConfig packageConfig, + String assetBasePath, + List wildcardDirectories, + Directory projectDirectory, { + List excludeDirs = const [], + }) { + final List components = flutterManifest.deferredComponents; + final Map>> deferredComponentsAssetVariants = >>{}; + if (components == null) { + return deferredComponentsAssetVariants; + } + for (final DeferredComponent component in components) { + deferredComponentsAssetVariants[component.name] = <_Asset, List<_Asset>>{}; + final _AssetDirectoryCache cache = _AssetDirectoryCache([], _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> jsonObject = >{}; final List<_Asset> assets = assetVariants.keys.toList() diff --git a/packages/flutter_tools/lib/src/base/terminal.dart b/packages/flutter_tools/lib/src/base/terminal.dart index 19c3a01cf5..cad92fa9eb 100644 --- a/packages/flutter_tools/lib/src/base/terminal.dart +++ b/packages/flutter_tools/lib/src/base/terminal.dart @@ -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 _colorMap = { TerminalColor.red: red, diff --git a/packages/flutter_tools/lib/src/build_system/depfile.dart b/packages/flutter_tools/lib/src/build_system/depfile.dart index 2ac1be952c..7f206061f7 100644 --- a/packages/flutter_tools/lib/src/build_system/depfile.dart +++ b/packages/flutter_tools/lib/src/build_system/depfile.dart @@ -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; } diff --git a/packages/flutter_tools/lib/src/build_system/source.dart b/packages/flutter_tools/lib/src/build_system/source.dart index 7a173d781f..c6a2464a13 100644 --- a/packages/flutter_tools/lib/src/build_system/source.dart +++ b/packages/flutter_tools/lib/src/build_system/source.dart @@ -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); diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart index 5747c4b5d7..9f80f23a8b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/android.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart @@ -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 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 get depfiles => [ + 'flutter_$name.d', + ]; + @override List get dependencies => const [ KernelSnapshot(), @@ -217,6 +223,12 @@ class AndroidAot extends AotElfBase { output.createSync(recursive: true); } final List extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions); + final List outputs = []; // 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 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([], 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 get inputs => [ - 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 get depfiles => [ + 'flutter_$name.d', + ]; + @override List get dependencies => [ dependency, @@ -293,25 +333,134 @@ class AndroidAotBundle extends Target { @override Future 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 inputs = []; + final List outputs = []; + 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 components}) : _components = components; + + /// The [AndroidAotBundle] instance this bundle rule depends on. + final AndroidAotBundle dependency; + + List _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 get inputs => [ + // 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 get outputs => const []; + + @override + List get depfiles => [ + 'flutter_$name.d', + ]; + + @override + List get dependencies => [ + dependency, + ]; + + @override + Future build(Environment environment) async { + _components ??= FlutterProject.current().manifest.deferredComponents ?? []; + final List abis = [_androidAbiName]; + final List 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 deferredComponentsTargets = { + 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. diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index a5a9a419ab..0e6b0591e1 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -34,6 +34,7 @@ const String kBundleSkSLPath = 'BundleSkSLPath'; Future copyAssets(Environment environment, Directory outputDirectory, { Map additionalContent, @required TargetPlatform targetPlatform, + BuildMode buildMode, }) async { // Check for an SkSL bundle. final String shaderBundlePath = environment.inputs[kBundleSkSLPath]; @@ -51,11 +52,13 @@ Future 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 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( + assetBundle.deferredComponentsEntries.entries.map>((MapEntry> componentEntries) async { + final Directory componentOutputDir = + environment.projectDir + .childDirectory('build') + .childDirectory(componentEntries.key) + .childDirectory('intermediates') + .childDirectory('flutter'); + await Future.wait( + componentEntries.value.entries.map>((MapEntry 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 diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index 7cc6f9637f..44c7614aeb 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -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, diff --git a/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart b/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart index 5eff1b2b0c..a01ab26bab 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/deferred_components.dart @@ -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 deferredComponentsDependencies; + final List 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 abis; + List get _abis { + final List abis = []; + 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 get inputs => const []; @@ -55,7 +65,11 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { ]; @override - List get dependencies => dependency == null ? [] : [dependency]; + List get dependencies { + final List deps = [CompositeTarget(deferredComponentsDependencies)]; + deps.addAll(nonDeferredComponentsDependencies); + return deps; + } @visibleForTesting DeferredComponentsGenSnapshotValidator validator; @@ -75,7 +89,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { final List generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits( environment.outputDir, environment.logger, - abis: abis + abis: _abis ); validator diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 792a1f4774..3c9c50439c 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -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 _kDefaultTargets = [ +List _kDefaultTargets = [ // 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 _kDefaultTargets = [ 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).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).join(','); } @@ -229,11 +259,37 @@ class AssembleCommand extends FlutterCommand { @override Future runCommand() async { + final Environment env = createEnvironment(); final List targets = createTargets(); - final Target target = targets.length == 1 ? targets.single : CompositeTarget(targets); + final List nonDeferredTargets = []; + final List deferredTargets = []; + for (final Target target in targets) { + if (deferredComponentsTargets.contains(target.name)) { + deferredTargets.add(target); + } else { + nonDeferredTargets.add(target); + } + } + Target target; + final List 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(), + 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')); } diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index aa8c45f339..81e15bff25 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -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: ['android-arm', 'android-arm64', 'android-x64'], - allowed: ['android-arm', 'android-arm64', 'android-x64'], - help: 'The target platform for which the app is compiled.', - ); + splitCommas: true, + defaultsTo: ['android-arm', 'android-arm64', 'android-x64'], + allowed: ['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(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(); } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index b4ed9b7e8f..28a1e27139 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -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 { diff --git a/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl new file mode 100644 index 0000000000..4eb7e49664 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl @@ -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") +} diff --git a/packages/flutter_tools/templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl new file mode 100644 index 0000000000..71fe20f019 --- /dev/null +++ b/packages/flutter_tools/templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index 80aa2dc115..45d65abeb0 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -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", diff --git a/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart b/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart index 08605de5df..6289d73fd4 100644 --- a/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/deferred_components_gen_snapshot_validator_test.dart @@ -22,7 +22,7 @@ void main() { Environment env; Environment createEnvironment() { - final Map defines = { kSplitAot: 'true' }; + final Map defines = { kDeferredComponents: 'true' }; final Environment result = Environment( outputDir: fileSystem.directory('/output'), buildDir: fileSystem.directory('/build'), diff --git a/packages/flutter_tools/test/general.shard/android/deferred_components_prebuild_validator_test.dart b/packages/flutter_tools/test/general.shard/android/deferred_components_prebuild_validator_test.dart index 3d205ed5e6..b9a7edcf49 100644 --- a/packages/flutter_tools/test/general.shard/android/deferred_components_prebuild_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/deferred_components_prebuild_validator_test.dart @@ -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 defines = { kSplitAot: 'true' }; - final Environment result = Environment( - outputDir: fileSystem.directory('/output'), - buildDir: fileSystem.directory('/build'), - projectDir: fileSystem.directory('/project'), - defines: defines, - inputs: {}, - 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') diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart index 299dcdce56..73fe33568b 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -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: { + 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: { + 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: { + FileSystem: () => testFileSystem, + ProcessManager: () => FakeProcessManager.any(), + }); }); testUsingContext('Failed directory delete shows message', () async { diff --git a/packages/flutter_tools/test/general.shard/base/command_help_test.dart b/packages/flutter_tools/test/general.shard/base/command_help_test.dart index 973fa21c6a..f0c0a67a51 100644 --- a/packages/flutter_tools/test/general.shard/base/command_help_test.dart +++ b/packages/flutter_tools/test/general.shard/base/command_help_test.dart @@ -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.')); }); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart index 16c0a85018..2b88c6958c 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/deferred_components_test.dart @@ -34,7 +34,7 @@ void main() { buildDir: fileSystem.directory('build')..createSync(), projectDir: fileSystem.directory('project')..createSync(), defines: { - 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([androidAotBundle]); - final CompositeTarget compositeTarget = CompositeTarget([androidDefBundle]); + final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( - dependency: compositeTarget, - abis: ['arm64-v8a'], + deferredComponentsDependencies: [androidDefBundle], + nonDeferredComponentsDependencies: [], title: 'test checks', exitOnFail: false, ); @@ -68,7 +67,7 @@ void main() { buildDir: fileSystem.directory('build')..createSync(), projectDir: fileSystem.directory('project')..createSync(), defines: { - 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([androidAotBundle]); - final CompositeTarget compositeTarget = CompositeTarget([androidDefBundle]); + final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( - dependency: compositeTarget, - abis: ['arm64-v8a'], + deferredComponentsDependencies: [androidDefBundle], + nonDeferredComponentsDependencies: [], title: 'test checks', exitOnFail: false, ); @@ -101,7 +99,7 @@ void main() { buildDir: fileSystem.directory('build')..createSync(), projectDir: fileSystem.directory('project')..createSync(), defines: { - 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([androidAotBundle]); - final CompositeTarget compositeTarget = CompositeTarget([androidDefBundle]); + final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle); final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( - dependency: compositeTarget, - abis: ['arm64-v8a'], + deferredComponentsDependencies: [androidDefBundle], + nonDeferredComponentsDependencies: [], title: 'test checks', exitOnFail: false, ); diff --git a/packages/flutter_tools/test/integration.shard/deferred_components_test.dart b/packages/flutter_tools/test/integration.shard/deferred_components_test.dart new file mode 100644 index 0000000000..35fc0660cb --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/deferred_components_test.dart @@ -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([ + 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([ + 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([ + 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([ + 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([ + 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([ + 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([ + 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); + }); +} diff --git a/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart new file mode 100644 index 0000000000..119da499fc --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_config.dart @@ -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 get androidKey; + String get appBuild; + String get appManifest; + String get appStrings; + String get appStyles; + String get appLaunchBackground; + String get asset1; + String get asset2; + List 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'), ''' + + + + + + + + + + '''); + } + } +} \ No newline at end of file diff --git a/packages/flutter_tools/test/integration.shard/test_data/deferred_components_project.dart b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_project.dart new file mode 100644 index 0000000000..3d282233b6 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/test_data/deferred_components_project.dart @@ -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? libFuture; + String deferredText = 'incomplete'; + + Future 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 androidKey = [ + 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''' + + + + + + + + + + + + + + + + + + + '''; + + @override + String get appStrings => r''' + + + component1 + + '''; + + @override + String get appStyles => r''' + + + + + + + + '''; + + @override + String get appLaunchBackground => r''' + + + + + + + + + '''; + + @override + String get asset1 => r''' +asset 1 contents + '''; + + @override + String get asset2 => r''' +asset 2 contents + '''; + + @override + List get deferredComponents => [DeferredComponentModule('component1')]; +} + +/// Missing android dynamic feature module. +class NoAndroidDynamicFeatureModuleDeferredComponentsConfig extends BasicDeferredComponentsConfig { + @override + List get deferredComponents => []; +} + +/// 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 + '''; +} diff --git a/packages/flutter_tools/test/integration.shard/test_data/project.dart b/packages/flutter_tools/test/integration.shard/test_data/project.dart index df281c0bf8..47d299bc13 100644 --- a/packages/flutter_tools/test/integration.shard/test_data/project.dart +++ b/packages/flutter_tools/test/integration.shard/test_data/project.dart @@ -7,6 +7,7 @@ import 'package:file/file.dart'; import '../test_utils.dart'; +import 'deferred_components_config.dart'; const String _kDefaultHtml = ''' @@ -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); diff --git a/packages/flutter_tools/test/integration.shard/test_utils.dart b/packages/flutter_tools/test/integration.shard/test_utils.dart index 45596f12cc..aa40ac332a 100644 --- a/packages/flutter_tools/test/integration.shard/test_utils.dart +++ b/packages/flutter_tools/test/integration.shard/test_utils.dart @@ -40,6 +40,13 @@ void writeFile(String path, String content) { ..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10))); } +void writeBytesFile(String path, List 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')}/ diff --git a/packages/flutter_tools/test/src/android_common.dart b/packages/flutter_tools/test/src/android_common.dart index 7fef28dad3..18ce01f8c8 100644 --- a/packages/flutter_tools/test/src/android_common.dart +++ b/packages/flutter_tools/test/src/android_common.dart @@ -35,6 +35,8 @@ class FakeAndroidBuilder implements AndroidBuilder { @required FlutterProject project, @required AndroidBuildInfo androidBuildInfo, @required String target, + bool validateDeferredComponents = true, + bool deferredComponentsEnabled = false, }) async {} } diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index 738c7435de..faf64622a4 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart @@ -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 createTestCommandRunner([ FlutterCommand command ]) { final FlutterCommandRunner runner = TestFlutterCommandRunner(); if (command != null) {