Unverified Commit ee417827 authored by Gary Qian's avatar Gary Qian Committed by GitHub

[flutter_tools] Deferred components build system (#76192)

parent faabc9ab
...@@ -519,7 +519,7 @@ Future<void> _flutterBuild( ...@@ -519,7 +519,7 @@ Future<void> _flutterBuild(
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json')); final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) { if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset'); 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); exit(1);
} }
} }
......
...@@ -134,6 +134,14 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -134,6 +134,14 @@ class FlutterPlugin implements Plugin<Project> {
} }
} }
} }
if (project.hasProperty('deferred-component-names')) {
String[] componentNames = project.property('deferred-component-names').split(',').collect {":${it}"}
project.android {
dynamicFeatures = componentNames
}
}
getTargetPlatforms().each { targetArch -> getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch] String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android { project.android {
...@@ -173,6 +181,11 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -173,6 +181,11 @@ class FlutterPlugin implements Plugin<Project> {
matchingFallbacks = ["debug", "release"] matchingFallbacks = ["debug", "release"]
} }
} }
// 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 { release {
// Enables code shrinking, obfuscation, and optimization for only // Enables code shrinking, obfuscation, and optimization for only
// your project's release build type. // your project's release build type.
...@@ -186,6 +199,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -186,6 +199,7 @@ class FlutterPlugin implements Plugin<Project> {
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro" proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
} }
} }
}
if (useLocalEngine()) { if (useLocalEngine()) {
// This is required to pass the local engine to flutter build aot. // This is required to pass the local engine to flutter build aot.
...@@ -200,6 +214,13 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -200,6 +214,13 @@ class FlutterPlugin implements Plugin<Project> {
project.android.buildTypes.all this.&addFlutterDependencies 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. * Adds the dependencies required by the Flutter project.
* This includes: * This includes:
...@@ -706,6 +727,14 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -706,6 +727,14 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('code-size-directory')) { if (project.hasProperty('code-size-directory')) {
codeSizeDirectoryValue = project.property('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 targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant -> def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) { if (shouldSplitPerAbi()) {
...@@ -747,6 +776,8 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -747,6 +776,8 @@ class FlutterPlugin implements Plugin<Project> {
bundleSkSLPath bundleSkSLPathValue bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue codeSizeDirectory codeSizeDirectoryValue
deferredComponents deferredComponentsValue
validateDeferredComponents validateDeferredComponentsValue
doLast { doLast {
project.exec { project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
...@@ -810,7 +841,8 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -810,7 +841,8 @@ class FlutterPlugin implements Plugin<Project> {
processResources.dependsOn(copyFlutterAssetsTask) processResources.dependsOn(copyFlutterAssetsTask)
} }
return copyFlutterAssetsTask return copyFlutterAssetsTask
} } // end def addFlutterDeps
if (isFlutterAppProject()) { if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant -> project.android.applicationVariants.all { variant ->
Task assembleTask = getAssembleTask(variant) Task assembleTask = getAssembleTask(variant)
...@@ -883,7 +915,7 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -883,7 +915,7 @@ class FlutterPlugin implements Plugin<Project> {
// | ----------------- | ----------------------------- | // | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant | // | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- | // | ----------------- | ----------------------------- |
// | freeRelease | relese | // | freeRelease | release |
// | freeDebug | debug | // | freeDebug | debug |
// | freeDevelop | debug | // | freeDevelop | debug |
// | profile | profile | // | profile | profile |
...@@ -961,6 +993,10 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -961,6 +993,10 @@ abstract class BaseFlutterTask extends DefaultTask {
@Optional @Input @Optional @Input
String codeSizeDirectory; String codeSizeDirectory;
String performanceMeasurementFile; String performanceMeasurementFile;
@Optional @Input
Boolean deferredComponents
@Optional @Input
Boolean validateDeferredComponents
@OutputFiles @OutputFiles
FileCollection getDependenciesFiles() { FileCollection getDependenciesFiles() {
...@@ -985,6 +1021,8 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -985,6 +1021,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String[] ruleNames; String[] ruleNames;
if (buildMode == "debug") { if (buildMode == "debug") {
ruleNames = ["debug_android_application"] ruleNames = ["debug_android_application"]
} else if (deferredComponents) {
ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" }
} else { } else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
} }
......
...@@ -38,5 +38,7 @@ abstract class AndroidBuilder { ...@@ -38,5 +38,7 @@ abstract class AndroidBuilder {
@required FlutterProject project, @required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo, @required AndroidBuildInfo androidBuildInfo,
@required String target, @required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}); });
} }
...@@ -30,10 +30,17 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator ...@@ -30,10 +30,17 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit] /// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
/// methods will exit the tool when this validator detects a recommended /// methods will exit the tool when this validator detects a recommended
/// change. This defaults to true. /// change. This defaults to true.
DeferredComponentsGenSnapshotValidator(Environment env, { DeferredComponentsGenSnapshotValidator(this.env, {
bool exitOnFail = true, bool exitOnFail = true,
String title, 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 // The key used to identify the metadata element as the loading unit id to
// deferred component mapping. // deferred component mapping.
...@@ -58,8 +65,8 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator ...@@ -58,8 +65,8 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// Where loading unit 2 is included in componentA, loading unit 3 is included /// Where loading unit 2 is included in componentA, loading unit 3 is included
/// in componentB, and loading unit 4 is included in componentC. /// in componentB, and loading unit 4 is included in componentC.
bool checkAppAndroidManifestComponentLoadingUnitMapping(List<DeferredComponent> components, List<LoadingUnit> generatedLoadingUnits) { bool checkAppAndroidManifestComponentLoadingUnitMapping(List<DeferredComponent> components, List<LoadingUnit> generatedLoadingUnits) {
final Directory androidDir = env.projectDir.childDirectory('android'); final Directory androidDir = projectDir.childDirectory('android');
inputs.add(env.projectDir.childFile('pubspec.yaml')); inputs.add(projectDir.childFile('pubspec.yaml'));
// We do not use the Xml package to handle the writing, as we do not want to // 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 // erase any user applied formatting and comments. The changes can be
...@@ -106,8 +113,10 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator ...@@ -106,8 +113,10 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
mappingBuffer.write('$key:${mapping[key]},'); mappingBuffer.write('$key:${mapping[key]},');
} }
String encodedMapping = mappingBuffer.toString(); String encodedMapping = mappingBuffer.toString();
// remove trailing comma. // remove trailing comma if any
if (encodedMapping.endsWith(',')) {
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1); encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
}
// Check for existing metadata entry and see if needs changes. // Check for existing metadata entry and see if needs changes.
bool exists = false; bool exists = false;
bool modified = false; bool modified = false;
...@@ -163,7 +172,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator ...@@ -163,7 +172,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// considered new. /// considered new.
bool checkAgainstLoadingUnitsCache( bool checkAgainstLoadingUnitsCache(
List<LoadingUnit> generatedLoadingUnits) { List<LoadingUnit> generatedLoadingUnits) {
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName)); final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
loadingUnitComparisonResults = <String, dynamic>{}; loadingUnitComparisonResults = <String, dynamic>{};
final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{}; final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{};
final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[]; final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[];
...@@ -276,7 +285,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator ...@@ -276,7 +285,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// deferred components. /// deferred components.
void writeLoadingUnitsCache(List<LoadingUnit> generatedLoadingUnits) { void writeLoadingUnitsCache(List<LoadingUnit> generatedLoadingUnits) {
generatedLoadingUnits ??= <LoadingUnit>[]; generatedLoadingUnits ??= <LoadingUnit>[];
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName); final File cacheFile = projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
outputs.add(cacheFile); outputs.add(cacheFile);
ErrorHandlingFileSystem.deleteIfExists(cacheFile); ErrorHandlingFileSystem.deleteIfExists(cacheFile);
cacheFile.createSync(recursive: true); cacheFile.createSync(recursive: true);
......
...@@ -10,7 +10,7 @@ import 'package:xml/xml.dart'; ...@@ -10,7 +10,7 @@ import 'package:xml/xml.dart';
import '../base/deferred_component.dart'; import '../base/deferred_component.dart';
import '../base/error_handling_io.dart'; import '../base/error_handling_io.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../build_system/build_system.dart'; import '../base/logger.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../template.dart'; import '../template.dart';
...@@ -25,20 +25,18 @@ import 'deferred_components_validator.dart'; ...@@ -25,20 +25,18 @@ import 'deferred_components_validator.dart';
class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// Constructs a validator instance. /// 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 /// The [templatesDir] parameter is optional. If null, the tool's default
/// templates directory will be used. /// templates directory will be used.
/// ///
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit] /// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
/// methods will exit the tool when this validator detects a recommended /// methods will exit the tool when this validator detects a recommended
/// change. This defaults to true. /// change. This defaults to true.
DeferredComponentsPrebuildValidator(Environment env, { DeferredComponentsPrebuildValidator(Directory projectDir, Logger logger, {
bool exitOnFail = true, bool exitOnFail = true,
String title, String title,
Directory templatesDir, Directory templatesDir,
}) : _templatesDir = templatesDir, }) : _templatesDir = templatesDir,
super(env, exitOnFail: exitOnFail, title: title); super(projectDir, logger, exitOnFail: exitOnFail, title: title);
final Directory _templatesDir; final Directory _templatesDir;
...@@ -56,7 +54,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { ...@@ -56,7 +54,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// This method does not check if the contents of either of the files are /// 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. /// valid, as there are many ways that they can be validly configured.
Future<bool> checkAndroidDynamicFeature(List<DeferredComponent> components) async { Future<bool> checkAndroidDynamicFeature(List<DeferredComponent> components) async {
inputs.add(env.projectDir.childFile('pubspec.yaml')); inputs.add(projectDir.childFile('pubspec.yaml'));
if (components == null || components.isEmpty) { if (components == null || components.isEmpty) {
return false; return false;
} }
...@@ -64,7 +62,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { ...@@ -64,7 +62,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
for (final DeferredComponent component in components) { for (final DeferredComponent component in components) {
final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles( final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles(
name: component.name, name: component.name,
env: env, projectDir: projectDir,
logger: logger,
templatesDir: _templatesDir templatesDir: _templatesDir
); );
if (!androidFiles.verifyFilesExist()) { if (!androidFiles.verifyFilesExist()) {
...@@ -106,8 +105,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { ...@@ -106,8 +105,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// The string element's name attribute should be the component name with /// 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. /// `Name` as a suffix, and the text contents should be the component name.
bool checkAndroidResourcesStrings(List<DeferredComponent> components) { bool checkAndroidResourcesStrings(List<DeferredComponent> components) {
final Directory androidDir = env.projectDir.childDirectory('android'); final Directory androidDir = projectDir.childDirectory('android');
inputs.add(env.projectDir.childFile('pubspec.yaml')); inputs.add(projectDir.childFile('pubspec.yaml'));
// Add component name mapping to strings.xml // Add component name mapping to strings.xml
final File stringRes = androidDir final File stringRes = androidDir
...@@ -202,7 +201,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { ...@@ -202,7 +201,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// Deletes all files inside of the validator's output directory. /// Deletes all files inside of the validator's output directory.
void clearOutputDir() { 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); ErrorHandlingFileSystem.deleteIfExists(dir, recursive: true);
} }
} }
...@@ -212,16 +211,18 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator { ...@@ -212,16 +211,18 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
class _DeferredComponentAndroidFiles { class _DeferredComponentAndroidFiles {
_DeferredComponentAndroidFiles({ _DeferredComponentAndroidFiles({
@required this.name, @required this.name,
@required this.env, @required this.projectDir,
this.logger,
Directory templatesDir, Directory templatesDir,
}) : _templatesDir = templatesDir; }) : _templatesDir = templatesDir;
// The name of the deferred component. // The name of the deferred component.
final String name; final String name;
final Environment env; final Directory projectDir;
final Logger logger;
final Directory _templatesDir; final Directory _templatesDir;
Directory get androidDir => env.projectDir.childDirectory('android'); Directory get androidDir => projectDir.childDirectory('android');
Directory get componentDir => androidDir.childDirectory(name); Directory get componentDir => androidDir.childDirectory(name);
File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml'); File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
...@@ -250,18 +251,18 @@ class _DeferredComponentAndroidFiles { ...@@ -250,18 +251,18 @@ class _DeferredComponentAndroidFiles {
Future<List<File>> _setupComponentFiles(Directory outputDir) async { Future<List<File>> _setupComponentFiles(Directory outputDir) async {
Template template; Template template;
if (_templatesDir != null) { 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, template = Template(templateComponentDir, templateComponentDir, _templatesDir,
fileSystem: env.fileSystem, fileSystem: globals.fs,
templateManifest: null, templateManifest: null,
logger: env.logger, logger: logger,
templateRenderer: globals.templateRenderer, templateRenderer: globals.templateRenderer,
); );
} else { } else {
template = await Template.fromName('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component', template = await Template.fromName('module${globals.fs.path.separator}android${globals.fs.path.separator}deferred_component',
fileSystem: env.fileSystem, fileSystem: globals.fs,
templateManifest: null, templateManifest: null,
logger: env.logger, logger: logger,
templateRenderer: globals.templateRenderer, templateRenderer: globals.templateRenderer,
); );
} }
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
import '../base/common.dart'; import '../base/common.dart';
import '../base/deferred_component.dart'; import '../base/deferred_component.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart'; import '../base/terminal.dart';
import '../build_system/build_system.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
/// A class to configure and run deferred component setup verification checks /// A class to configure and run deferred component setup verification checks
...@@ -21,10 +21,10 @@ import '../globals.dart' as globals; ...@@ -21,10 +21,10 @@ import '../globals.dart' as globals;
/// The results of each check are handled internally as they are not meant to /// The results of each check are handled internally as they are not meant to
/// be run isolated. /// be run isolated.
abstract class DeferredComponentsValidator { abstract class DeferredComponentsValidator {
DeferredComponentsValidator(this.env, { DeferredComponentsValidator(this.projectDir, this.logger, {
this.exitOnFail = true, this.exitOnFail = true,
String title, String title,
}) : outputDir = env.projectDir }) : outputDir = projectDir
.childDirectory('build') .childDirectory('build')
.childDirectory(kDeferredComponentsTempDirectory), .childDirectory(kDeferredComponentsTempDirectory),
inputs = <File>[], inputs = <File>[],
...@@ -34,12 +34,9 @@ abstract class DeferredComponentsValidator { ...@@ -34,12 +34,9 @@ abstract class DeferredComponentsValidator {
modifiedFiles = <String>[], modifiedFiles = <String>[],
invalidFiles = <String, String>{}, invalidFiles = <String, String>{},
diffLines = <String>[]; diffLines = <String>[];
/// The build environment that should be used to find the input files to run
/// checks against. /// Logger to use for [displayResults] output.
/// final Logger logger;
/// The checks in this class are meant to be used as part of a build process,
/// so an environment should be available.
final Environment env;
/// When true, failed checks and tasks will result in [attemptToolExit] /// When true, failed checks and tasks will result in [attemptToolExit]
/// triggering [throwToolExit]. /// triggering [throwToolExit].
...@@ -54,6 +51,9 @@ abstract class DeferredComponentsValidator { ...@@ -54,6 +51,9 @@ abstract class DeferredComponentsValidator {
/// The title printed at the top of the results of [displayResults] /// The title printed at the top of the results of [displayResults]
final String title; final String title;
/// The root directory of the flutter project.
final Directory projectDir;
/// The temporary directory that the validator writes recommended files into. /// The temporary directory that the validator writes recommended files into.
final Directory outputDir; final Directory outputDir;
...@@ -111,20 +111,20 @@ abstract class DeferredComponentsValidator { ...@@ -111,20 +111,20 @@ abstract class DeferredComponentsValidator {
/// All checks that are desired should be run before calling this method. /// All checks that are desired should be run before calling this method.
void displayResults() { void displayResults() {
if (changesNeeded) { if (changesNeeded) {
env.logger.printStatus(_thickDivider); logger.printStatus(_thickDivider);
env.logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true); logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true);
env.logger.printStatus(_thickDivider); logger.printStatus(_thickDivider);
// Log any file reading/existence errors. // Log any file reading/existence errors.
if (invalidFiles.isNotEmpty) { 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) { 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 // Log diff file contents, with color highlighting
if (diffLines != null && diffLines.isNotEmpty) { if (diffLines != null && diffLines.isNotEmpty) {
env.logger.printStatus('Diff between `android` and expected files:', emphasis: true); logger.printStatus('Diff between `android` and expected files:', emphasis: true);
env.logger.printStatus(''); logger.printStatus('');
for (final String line in diffLines) { for (final String line in diffLines) {
// We only care about diffs in files that have // We only care about diffs in files that have
// counterparts. // counterparts.
...@@ -137,62 +137,62 @@ abstract class DeferredComponentsValidator { ...@@ -137,62 +137,62 @@ abstract class DeferredComponentsValidator {
} else if (line.startsWith('-')) { } else if (line.startsWith('-')) {
color = TerminalColor.red; 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. // Log any newly generated and modified files.
if (generatedFiles.isNotEmpty) { 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) { for (final String filePath in generatedFiles) {
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1); final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey); logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
} }
env.logger.printStatus(''); logger.printStatus('');
} }
if (modifiedFiles.isNotEmpty) { if (modifiedFiles.isNotEmpty) {
env.logger.printStatus('Modified android files:', emphasis: true); logger.printStatus('Modified android files:', emphasis: true);
for (final String filePath in modifiedFiles) { for (final String filePath in modifiedFiles) {
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1); final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey); logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
} }
env.logger.printStatus(''); logger.printStatus('');
} }
if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) { if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) {
env.logger.printStatus(''' logger.printStatus('''
The above files have been placed into `build/$kDeferredComponentsTempDirectory`, The above files have been placed into `build/$kDeferredComponentsTempDirectory`,
a temporary directory. The files should be reviewed and moved into the project's a temporary directory. The files should be reviewed and moved into the project's
`android` directory.'''); `android` directory.''');
if (diffLines != null && diffLines.isNotEmpty && !globals.platform.isWindows) { if (diffLines != null && diffLines.isNotEmpty && !globals.platform.isWindows) {
env.logger.printStatus(r''' logger.printStatus(r'''
The recommended changes can be quickly applied by running: The recommended changes can be quickly applied by running:
$ patch -p0 < build/setup_deferred_components.diff $ patch -p0 < build/setup_deferred_components.diff
'''); ''');
} }
env.logger.printStatus('$_thinDivider\n'); logger.printStatus('$_thinDivider\n');
} }
// Log loading unit golden changes, if any. // Log loading unit golden changes, if any.
if (loadingUnitComparisonResults != null) { if (loadingUnitComparisonResults != null) {
if ((loadingUnitComparisonResults['new'] as List<LoadingUnit>).isNotEmpty) { if ((loadingUnitComparisonResults['new'] as List<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('New loading units were found:', emphasis: true); logger.printStatus('New loading units were found:', emphasis: true);
for (final LoadingUnit unit in loadingUnitComparisonResults['new'] as List<LoadingUnit>) { for (final LoadingUnit unit in loadingUnitComparisonResults['new'] as List<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2); logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
} }
env.logger.printStatus(''); logger.printStatus('');
} }
if ((loadingUnitComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) { if ((loadingUnitComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('Previously existing loading units no longer exist:', emphasis: true); logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
for (final LoadingUnit unit in loadingUnitComparisonResults['missing'] as Set<LoadingUnit>) { for (final LoadingUnit unit in loadingUnitComparisonResults['missing'] as Set<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2); logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
} }
env.logger.printStatus(''); logger.printStatus('');
} }
if (loadingUnitComparisonResults['match'] as bool) { 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 { } else {
env.logger.printStatus(''' logger.printStatus('''
It is recommended to verify that the changed loading units are expected It is recommended to verify that the changed loading units are expected
and to update the `deferred-components` section in `pubspec.yaml` to and to update the `deferred-components` section in `pubspec.yaml` to
incorporate any changes. The full list of generated loading units can be incorporate any changes. The full list of generated loading units can be
...@@ -205,14 +205,14 @@ $_thinDivider\n'''); ...@@ -205,14 +205,14 @@ $_thinDivider\n''');
} }
} }
// TODO(garyq): Add link to web tutorial/guide once it is written. // TODO(garyq): Add link to web tutorial/guide once it is written.
env.logger.printStatus(''' logger.printStatus('''
Setup verification can be skipped by passing the `--no-verify-deferred-components` 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 flag, however, doing so may put your app at risk of not functioning even if the
build is successful. build is successful.
$_thickDivider'''); $_thickDivider''');
return; return;
} }
env.logger.printStatus('$title passed.'); logger.printStatus('$title passed.');
} }
void attemptToolExit() { void attemptToolExit() {
......
...@@ -12,6 +12,7 @@ import 'package:xml/xml.dart'; ...@@ -12,6 +12,7 @@ import 'package:xml/xml.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/analyze_size.dart'; import '../base/analyze_size.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -242,6 +243,8 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -242,6 +243,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required FlutterProject project, @required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo, @required AndroidBuildInfo androidBuildInfo,
@required String target, @required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async { }) async {
await buildGradleApp( await buildGradleApp(
project: project, project: project,
...@@ -249,6 +252,8 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -249,6 +252,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
target: target, target: target,
isBuildingBundle: true, isBuildingBundle: true,
localGradleErrors: gradleErrors, localGradleErrors: gradleErrors,
validateDeferredComponents: validateDeferredComponents,
deferredComponentsEnabled: deferredComponentsEnabled,
); );
} }
...@@ -270,6 +275,8 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -270,6 +275,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required bool isBuildingBundle, @required bool isBuildingBundle,
@required List<GradleHandledError> localGradleErrors, @required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false, bool shouldBuildPluginAsAar = false,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
int retries = 1, int retries = 1,
}) async { }) async {
assert(project != null); assert(project != null);
...@@ -355,8 +362,29 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -355,8 +362,29 @@ class AndroidGradleBuilder implements AndroidBuilder {
if (target != null) { if (target != null) {
command.add('-Ptarget=$target'); command.add('-Ptarget=$target');
} }
if (project.manifest.deferredComponents != null) {
if (deferredComponentsEnabled) {
command.add('-Pdeferred-components=true');
androidBuildInfo.buildInfo.dartDefines.add('validate-deferred-components=$validateDeferredComponents');
}
// Pass in deferred components regardless of building split aot to satisfy
// android dynamic features registry in build.gradle.
final List<String> componentNames = <String>[];
for (final DeferredComponent component in project.manifest.deferredComponents) {
componentNames.add(component.name);
}
if (componentNames.isNotEmpty) {
command.add('-Pdeferred-component-names=${componentNames.join(',')}');
// Multi-apk applications cannot use shrinking. This is only relevant when using
// android dynamic feature modules.
_logger.printStatus(
'Shrinking has been disabled for this build due to deferred components. Shrinking is '
'not available for multi-apk applications. This limitation is expected to be removed '
'when Gradle plugin 4.2+ is available in Flutter.', color: TerminalColor.yellow);
command.add('-Pshrink=false');
}
}
command.addAll(androidBuildInfo.buildInfo.toGradleConfig()); command.addAll(androidBuildInfo.buildInfo.toGradleConfig());
if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) { if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) {
command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}'); command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}');
} }
......
...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart'; ...@@ -8,6 +8,7 @@ import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import 'base/context.dart'; import 'base/context.dart';
import 'base/deferred_component.dart';
import 'base/file_system.dart'; import 'base/file_system.dart';
import 'base/logger.dart'; import 'base/logger.dart';
import 'base/platform.dart'; import 'base/platform.dart';
...@@ -53,7 +54,8 @@ abstract class AssetBundleFactory { ...@@ -53,7 +54,8 @@ abstract class AssetBundleFactory {
@required Logger logger, @required Logger logger,
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required Platform platform, @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]. /// Creates a new [AssetBundle].
AssetBundle createBundle(); AssetBundle createBundle();
...@@ -62,6 +64,10 @@ abstract class AssetBundleFactory { ...@@ -62,6 +64,10 @@ abstract class AssetBundleFactory {
abstract class AssetBundle { abstract class AssetBundle {
Map<String, DevFSContent> get entries; Map<String, DevFSContent> get entries;
/// The files that were specified under the deferred components assets sections
/// in pubspec.
Map<String, Map<String, DevFSContent>> get deferredComponentsEntries;
/// Additional files that this bundle depends on that are not included in the /// Additional files that this bundle depends on that are not included in the
/// output result. /// output result.
List<File> get additionalDependencies; List<File> get additionalDependencies;
...@@ -75,6 +81,7 @@ abstract class AssetBundle { ...@@ -75,6 +81,7 @@ abstract class AssetBundle {
String manifestPath = defaultManifestPath, String manifestPath = defaultManifestPath,
String assetDirPath, String assetDirPath,
@required String packagesPath, @required String packagesPath,
bool deferredComponentsEnabled = false,
}); });
} }
...@@ -83,16 +90,19 @@ class _ManifestAssetBundleFactory implements AssetBundleFactory { ...@@ -83,16 +90,19 @@ class _ManifestAssetBundleFactory implements AssetBundleFactory {
@required Logger logger, @required Logger logger,
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required Platform platform, @required Platform platform,
bool splitDeferredAssets = false,
}) : _logger = logger, }) : _logger = logger,
_fileSystem = fileSystem, _fileSystem = fileSystem,
_platform = platform; _platform = platform,
_splitDeferredAssets = splitDeferredAssets;
final Logger _logger; final Logger _logger;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final Platform _platform; final Platform _platform;
final bool _splitDeferredAssets;
@override @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. /// An asset bundle based on a pubspec.yaml file.
...@@ -103,19 +113,25 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -103,19 +113,25 @@ class ManifestAssetBundle implements AssetBundle {
@required Logger logger, @required Logger logger,
@required FileSystem fileSystem, @required FileSystem fileSystem,
@required Platform platform, @required Platform platform,
bool splitDeferredAssets = false,
}) : _logger = logger, }) : _logger = logger,
_fileSystem = fileSystem, _fileSystem = fileSystem,
_platform = platform, _platform = platform,
_splitDeferredAssets = splitDeferredAssets,
_licenseCollector = LicenseCollector(fileSystem: fileSystem); _licenseCollector = LicenseCollector(fileSystem: fileSystem);
final Logger _logger; final Logger _logger;
final FileSystem _fileSystem; final FileSystem _fileSystem;
final LicenseCollector _licenseCollector; final LicenseCollector _licenseCollector;
final Platform _platform; final Platform _platform;
final bool _splitDeferredAssets;
@override @override
final Map<String, DevFSContent> entries = <String, DevFSContent>{}; final Map<String, DevFSContent> entries = <String, DevFSContent>{};
@override
final Map<String, Map<String, DevFSContent>> deferredComponentsEntries = <String, Map<String, DevFSContent>>{};
// If an asset corresponds to a wildcard directory, then it may have been // If an asset corresponds to a wildcard directory, then it may have been
// updated without changes to the manifest. These are only tracked for // updated without changes to the manifest. These are only tracked for
// the current project. // the current project.
...@@ -163,6 +179,7 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -163,6 +179,7 @@ class ManifestAssetBundle implements AssetBundle {
String manifestPath = defaultManifestPath, String manifestPath = defaultManifestPath,
String assetDirPath, String assetDirPath,
@required String packagesPath, @required String packagesPath,
bool deferredComponentsEnabled = false,
}) async { }) async {
assetDirPath ??= getAssetBuildDirectory(); assetDirPath ??= getAssetBuildDirectory();
FlutterProject flutterProject; FlutterProject flutterProject;
...@@ -197,12 +214,7 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -197,12 +214,7 @@ class ManifestAssetBundle implements AssetBundle {
// in the pubspec.yaml file's assets and font and sections. The // in the pubspec.yaml file's assets and font and sections. The
// value of each image asset is a list of resolution-specific "variants", // value of each image asset is a list of resolution-specific "variants",
// see _AssetDirectoryCache. // see _AssetDirectoryCache.
final Map<_Asset, List<_Asset>> assetVariants = _parseAssets( final List<String> excludeDirs = <String>[
packageConfig,
flutterManifest,
wildcardDirectories,
assetBasePath,
excludeDirs: <String>[
assetDirPath, assetDirPath,
getBuildDirectory(), getBuildDirectory(),
if (flutterProject.ios.existsSync()) if (flutterProject.ios.existsSync())
...@@ -213,13 +225,38 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -213,13 +225,38 @@ class ManifestAssetBundle implements AssetBundle {
flutterProject.windows.managedDirectory.path, flutterProject.windows.managedDirectory.path,
if (flutterProject.linux.existsSync()) if (flutterProject.linux.existsSync())
flutterProject.linux.managedDirectory.path, flutterProject.linux.managedDirectory.path,
], ];
final Map<_Asset, List<_Asset>> assetVariants = _parseAssets(
packageConfig,
flutterManifest,
wildcardDirectories,
assetBasePath,
excludeDirs: excludeDirs,
); );
if (assetVariants == null) { if (assetVariants == null) {
return 1; return 1;
} }
// Parse assets for deferred components.
final Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants = _parseDeferredComponentsAssets(
flutterManifest,
packageConfig,
assetBasePath,
wildcardDirectories,
flutterProject.directory,
excludeDirs: excludeDirs,
);
if (!_splitDeferredAssets || !deferredComponentsEnabled) {
// Include the assets in the regular set of assets if not using deferred
// components.
for (final String componentName in deferredComponentsAssetVariants.keys) {
assetVariants.addAll(deferredComponentsAssetVariants[componentName]);
}
deferredComponentsAssetVariants.clear();
deferredComponentsEntries.clear();
}
final bool includesMaterialFonts = flutterManifest.usesMaterialDesign; final bool includesMaterialFonts = flutterManifest.usesMaterialDesign;
final List<Map<String, dynamic>> fonts = _parseFonts( final List<Map<String, dynamic>> fonts = _parseFonts(
flutterManifest, flutterManifest,
...@@ -314,6 +351,39 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -314,6 +351,39 @@ class ManifestAssetBundle implements AssetBundle {
entries[variant.entryUri.path] ??= DevFSFileContent(variantFile); entries[variant.entryUri.path] ??= DevFSFileContent(variantFile);
} }
} }
// Save the contents of each deferred component image, image variant, and font
// asset in deferredComponentsEntries.
if (deferredComponentsAssetVariants != null) {
for (final String componentName in deferredComponentsAssetVariants.keys) {
deferredComponentsEntries[componentName] = <String, DevFSContent>{};
for (final _Asset asset in deferredComponentsAssetVariants[componentName].keys) {
final File assetFile = asset.lookupAssetFile(_fileSystem);
if (!assetFile.existsSync() && deferredComponentsAssetVariants[componentName][asset].isEmpty) {
_logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
_logger.printError('No file or variants found for $asset.\n');
if (asset.package != null) {
_logger.printError('This asset was included from package ${asset.package.name}.');
}
return 1;
}
// The file name for an asset's "main" entry is whatever appears in
// the pubspec.yaml file. The main entry's file must always exist for
// font assets. It need not exist for an image if resolution-specific
// variant files exist. An image's main entry is treated the same as a
// "1x" resolution variant and if both exist then the explicit 1x
// variant is preferred.
if (assetFile.existsSync()) {
assert(!deferredComponentsAssetVariants[componentName][asset].contains(asset));
deferredComponentsAssetVariants[componentName][asset].insert(0, asset);
}
for (final _Asset variant in deferredComponentsAssetVariants[componentName][asset]) {
final File variantFile = variant.lookupAssetFile(_fileSystem);
assert(variantFile.existsSync());
deferredComponentsEntries[componentName][variant.entryUri.path] ??= DevFSFileContent(variantFile);
}
}
}
}
final List<_Asset> materialAssets = <_Asset>[ final List<_Asset> materialAssets = <_Asset>[
if (flutterManifest.usesMaterialDesign) if (flutterManifest.usesMaterialDesign)
..._getMaterialAssets(), ..._getMaterialAssets(),
...@@ -410,6 +480,50 @@ class ManifestAssetBundle implements AssetBundle { ...@@ -410,6 +480,50 @@ class ManifestAssetBundle implements AssetBundle {
]; ];
} }
Map<String, Map<_Asset, List<_Asset>>> _parseDeferredComponentsAssets(
FlutterManifest flutterManifest,
PackageConfig packageConfig,
String assetBasePath,
List<Uri> wildcardDirectories,
Directory projectDirectory, {
List<String> excludeDirs = const <String>[],
}) {
final List<DeferredComponent> components = flutterManifest.deferredComponents;
final Map<String, Map<_Asset, List<_Asset>>> deferredComponentsAssetVariants = <String, Map<_Asset, List<_Asset>>>{};
if (components == null) {
return deferredComponentsAssetVariants;
}
for (final DeferredComponent component in components) {
deferredComponentsAssetVariants[component.name] = <_Asset, List<_Asset>>{};
final _AssetDirectoryCache cache = _AssetDirectoryCache(<String>[], _fileSystem);
for (final Uri assetUri in component.assets) {
if (assetUri.path.endsWith('/')) {
wildcardDirectories.add(assetUri);
_parseAssetsFromFolder(
packageConfig,
flutterManifest,
assetBasePath,
cache,
deferredComponentsAssetVariants[component.name],
assetUri,
excludeDirs: excludeDirs,
);
} else {
_parseAssetFromFile(
packageConfig,
flutterManifest,
assetBasePath,
cache,
deferredComponentsAssetVariants[component.name],
assetUri,
excludeDirs: excludeDirs,
);
}
}
}
return deferredComponentsAssetVariants;
}
DevFSStringContent _createAssetManifest(Map<_Asset, List<_Asset>> assetVariants) { DevFSStringContent _createAssetManifest(Map<_Asset, List<_Asset>> assetVariants) {
final Map<String, List<String>> jsonObject = <String, List<String>>{}; final Map<String, List<String>> jsonObject = <String, List<String>>{};
final List<_Asset> assets = assetVariants.keys.toList() final List<_Asset> assets = assetVariants.keys.toList()
......
...@@ -167,7 +167,7 @@ class AnsiTerminal implements Terminal { ...@@ -167,7 +167,7 @@ class AnsiTerminal implements Terminal {
static const String cyan = '\u001b[36m'; static const String cyan = '\u001b[36m';
static const String magenta = '\u001b[35m'; static const String magenta = '\u001b[35m';
static const String yellow = '\u001b[33m'; static const String yellow = '\u001b[33m';
static const String grey = '\u001b[1;30m'; static const String grey = '\u001b[90m';
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{ static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
TerminalColor.red: red, TerminalColor.red: red,
......
...@@ -25,10 +25,12 @@ class DepfileService { ...@@ -25,10 +25,12 @@ class DepfileService {
/// Given an [depfile] File, write the depfile contents. /// Given an [depfile] File, write the depfile contents.
/// ///
/// If either [inputs] or [outputs] is empty, ensures the file does not /// If both [inputs] and [outputs] are empty, ensures the file does not
/// exist. /// exist. This can be overriden with the [writeEmpty] parameter when
void writeToFile(Depfile depfile, File output) { /// both static and runtime dependencies exist and it is not desired
if (depfile.inputs.isEmpty || depfile.outputs.isEmpty) { /// 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); ErrorHandlingFileSystem.deleteIfExists(output);
return; return;
} }
......
...@@ -146,7 +146,7 @@ class SourceVisitor implements ResolvedFiles { ...@@ -146,7 +146,7 @@ class SourceVisitor implements ResolvedFiles {
throw InvalidPatternException(pattern); throw InvalidPatternException(pattern);
} }
if (!environment.fileSystem.directory(filePath).existsSync()) { 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()) { for (final FileSystemEntity entity in environment.fileSystem.directory(filePath).listSync()) {
final String filename = environment.fileSystem.path.basename(entity.path); final String filename = environment.fileSystem.path.basename(entity.path);
......
...@@ -34,6 +34,7 @@ const String kBundleSkSLPath = 'BundleSkSLPath'; ...@@ -34,6 +34,7 @@ const String kBundleSkSLPath = 'BundleSkSLPath';
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, { Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
Map<String, DevFSContent> additionalContent, Map<String, DevFSContent> additionalContent,
@required TargetPlatform targetPlatform, @required TargetPlatform targetPlatform,
BuildMode buildMode,
}) async { }) async {
// Check for an SkSL bundle. // Check for an SkSL bundle.
final String shaderBundlePath = environment.inputs[kBundleSkSLPath]; final String shaderBundlePath = environment.inputs[kBundleSkSLPath];
...@@ -51,11 +52,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, { ...@@ -51,11 +52,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
logger: environment.logger, logger: environment.logger,
fileSystem: environment.fileSystem, fileSystem: environment.fileSystem,
platform: globals.platform, platform: globals.platform,
splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease,
).createBundle(); ).createBundle();
final int resultCode = await assetBundle.build( final int resultCode = await assetBundle.build(
manifestPath: pubspecFile.path, manifestPath: pubspecFile.path,
packagesPath: environment.projectDir.childFile('.packages').path, packagesPath: environment.projectDir.childFile('.packages').path,
assetDirPath: null, assetDirPath: null,
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
); );
if (resultCode != 0) { if (resultCode != 0) {
throw Exception('Failed to bundle asset files.'); throw Exception('Failed to bundle asset files.');
...@@ -114,6 +117,56 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, { ...@@ -114,6 +117,56 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
resource.release(); resource.release();
} }
})); }));
// Copy deferred components assets only for release or profile builds.
// The assets are included in assetBundle.entries as a normal asset when
// building as debug.
if (environment.defines[kDeferredComponents] == 'true') {
await Future.wait<void>(
assetBundle.deferredComponentsEntries.entries.map<Future<void>>((MapEntry<String, Map<String, DevFSContent>> componentEntries) async {
final Directory componentOutputDir =
environment.projectDir
.childDirectory('build')
.childDirectory(componentEntries.key)
.childDirectory('intermediates')
.childDirectory('flutter');
await Future.wait<void>(
componentEntries.value.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
// This will result in strange looking files, for example files with `/`
// on Windows or files that end up getting URI encoded such as `#.ext`
// to `%23.ext`. However, we have to keep it this way since the
// platform channels in the framework will URI encode these values,
// and the native APIs will look for files this way.
// If deferred components are disabled, then copy assets to regular location.
final File file = environment.defines[kDeferredComponents] == 'true'
? environment.fileSystem.file(
environment.fileSystem.path.join(componentOutputDir.path, buildMode.name, 'deferred_assets', 'flutter_assets', entry.key))
: environment.fileSystem.file(
environment.fileSystem.path.join(outputDirectory.path, entry.key));
outputs.add(file);
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
inputs.add(content.file as File);
if (!await iconTreeShaker.subsetFont(
input: content.file as File,
outputPath: file.path,
relativePath: entry.key,
)) {
await (content.file as File).copy(file.path);
}
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
}));
}
final Depfile depfile = Depfile(inputs + assetBundle.additionalDependencies, outputs); final Depfile depfile = Depfile(inputs + assetBundle.additionalDependencies, outputs);
if (shaderBundlePath != null) { if (shaderBundlePath != null) {
final File skSLBundleFile = environment.fileSystem final File skSLBundleFile = environment.fileSystem
......
...@@ -45,9 +45,9 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions'; ...@@ -45,9 +45,9 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions';
/// This is expected to be a comma separated list of strings. /// This is expected to be a comma separated list of strings.
const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions'; 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. /// components.
const String kSplitAot = 'SplitAot'; const String kDeferredComponents = 'DeferredComponents';
/// Whether to strip source code information out of release builds and where to save it. /// Whether to strip source code information out of release builds and where to save it.
const String kSplitDebugInfo = 'SplitDebugInfo'; const String kSplitDebugInfo = 'SplitDebugInfo';
...@@ -131,6 +131,7 @@ class CopyFlutterBundle extends Target { ...@@ -131,6 +131,7 @@ class CopyFlutterBundle extends Target {
environment, environment,
environment.outputDir, environment.outputDir,
targetPlatform: TargetPlatform.android, targetPlatform: TargetPlatform.android,
buildMode: buildMode,
); );
final DepfileService depfileService = DepfileService( final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem, fileSystem: environment.fileSystem,
......
...@@ -8,25 +8,26 @@ import 'package:meta/meta.dart'; ...@@ -8,25 +8,26 @@ import 'package:meta/meta.dart';
import '../../android/deferred_components_gen_snapshot_validator.dart'; import '../../android/deferred_components_gen_snapshot_validator.dart';
import '../../base/deferred_component.dart'; import '../../base/deferred_component.dart';
import '../../build_info.dart';
import '../../project.dart'; import '../../project.dart';
import '../build_system.dart'; import '../build_system.dart';
import '../depfile.dart'; import '../depfile.dart';
import 'android.dart';
/// Creates a [DeferredComponentsGenSnapshotValidator], runs the checks, and displays the validator /// Creates a [DeferredComponentsGenSnapshotValidator], runs the checks, and displays the validator
/// output to the developer if changes are recommended. /// output to the developer if changes are recommended.
class DeferredComponentsGenSnapshotValidatorTarget extends Target { class DeferredComponentsGenSnapshotValidatorTarget extends Target {
/// Create an [AndroidAotDeferredComponentsBundle] implementation for a given [targetPlatform] and [buildMode]. /// Create an [AndroidAotDeferredComponentsBundle] implementation for a given [targetPlatform] and [buildMode].
DeferredComponentsGenSnapshotValidatorTarget({ DeferredComponentsGenSnapshotValidatorTarget({
@required this.dependency, @required this.deferredComponentsDependencies,
@required this.abis, @required this.nonDeferredComponentsDependencies,
this.title, this.title,
this.exitOnFail = true, this.exitOnFail = true,
String name = 'deferred_components_setup_validator', });
}) : _name = name;
/// The [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on packed /// The [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on.
/// as a [CompositeTarget]. final List<AndroidAotDeferredComponentsBundle> deferredComponentsDependencies;
final CompositeTarget dependency; final List<Target> nonDeferredComponentsDependencies;
/// The title of the [DeferredComponentsGenSnapshotValidator] that is /// The title of the [DeferredComponentsGenSnapshotValidator] that is
/// displayed to the developer when logging results. /// displayed to the developer when logging results.
...@@ -37,11 +38,20 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { ...@@ -37,11 +38,20 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final bool exitOnFail; final bool exitOnFail;
/// The abis to validate. /// The abis to validate.
final List<String> abis; List<String> get _abis {
final List<String> abis = <String>[];
for (final AndroidAotDeferredComponentsBundle target in deferredComponentsDependencies) {
if (deferredComponentsTargets.contains(target.name)) {
abis.add(
getNameForAndroidArch(getAndroidArchForName(getNameForTargetPlatform(target.dependency.targetPlatform)))
);
}
}
return abis;
}
@override @override
String get name => _name; String get name => 'deferred_components_gen_snapshot_validator';
final String _name;
@override @override
List<Source> get inputs => const <Source>[]; List<Source> get inputs => const <Source>[];
...@@ -55,7 +65,11 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { ...@@ -55,7 +65,11 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
]; ];
@override @override
List<Target> get dependencies => dependency == null ? <Target>[] : <Target>[dependency]; List<Target> get dependencies {
final List<Target> deps = <Target>[CompositeTarget(deferredComponentsDependencies)];
deps.addAll(nonDeferredComponentsDependencies);
return deps;
}
@visibleForTesting @visibleForTesting
DeferredComponentsGenSnapshotValidator validator; DeferredComponentsGenSnapshotValidator validator;
...@@ -75,7 +89,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target { ...@@ -75,7 +89,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits( final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(
environment.outputDir, environment.outputDir,
environment.logger, environment.logger,
abis: abis abis: _abis
); );
validator validator
......
...@@ -14,6 +14,7 @@ import '../build_system/depfile.dart'; ...@@ -14,6 +14,7 @@ import '../build_system/depfile.dart';
import '../build_system/targets/android.dart'; import '../build_system/targets/android.dart';
import '../build_system/targets/assets.dart'; import '../build_system/targets/assets.dart';
import '../build_system/targets/common.dart'; import '../build_system/targets/common.dart';
import '../build_system/targets/deferred_components.dart';
import '../build_system/targets/ios.dart'; import '../build_system/targets/ios.dart';
import '../build_system/targets/linux.dart'; import '../build_system/targets/linux.dart';
import '../build_system/targets/macos.dart'; import '../build_system/targets/macos.dart';
...@@ -27,34 +28,34 @@ import '../reporting/reporting.dart'; ...@@ -27,34 +28,34 @@ import '../reporting/reporting.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
/// All currently implemented targets. /// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[ List<Target> _kDefaultTargets = <Target>[
// Shared targets // Shared targets
CopyAssets(), const CopyAssets(),
KernelSnapshot(), const KernelSnapshot(),
AotElfProfile(TargetPlatform.android_arm), const AotElfProfile(TargetPlatform.android_arm),
AotElfRelease(TargetPlatform.android_arm), const AotElfRelease(TargetPlatform.android_arm),
AotAssemblyProfile(), const AotAssemblyProfile(),
AotAssemblyRelease(), const AotAssemblyRelease(),
// macOS targets // macOS targets
DebugMacOSFramework(), const DebugMacOSFramework(),
DebugMacOSBundleFlutterAssets(), const DebugMacOSBundleFlutterAssets(),
ProfileMacOSBundleFlutterAssets(), const ProfileMacOSBundleFlutterAssets(),
ReleaseMacOSBundleFlutterAssets(), const ReleaseMacOSBundleFlutterAssets(),
// Linux targets // Linux targets
DebugBundleLinuxAssets(TargetPlatform.linux_x64), const DebugBundleLinuxAssets(TargetPlatform.linux_x64),
DebugBundleLinuxAssets(TargetPlatform.linux_arm64), const DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
ProfileBundleLinuxAssets(TargetPlatform.linux_x64), const ProfileBundleLinuxAssets(TargetPlatform.linux_x64),
ProfileBundleLinuxAssets(TargetPlatform.linux_arm64), const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_x64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
// Web targets // Web targets
WebServiceWorker(), const WebServiceWorker(),
ReleaseAndroidApplication(), const ReleaseAndroidApplication(),
// This is a one-off rule for bundle and aot compat. // This is a one-off rule for bundle and aot compat.
CopyFlutterBundle(), const CopyFlutterBundle(),
// Android targets, // Android targets,
DebugAndroidApplication(), const DebugAndroidApplication(),
ProfileAndroidApplication(), const ProfileAndroidApplication(),
// Android ABI specific AOT rules. // Android ABI specific AOT rules.
androidArmProfileBundle, androidArmProfileBundle,
androidArm64ProfileBundle, androidArm64ProfileBundle,
...@@ -62,15 +63,22 @@ const List<Target> _kDefaultTargets = <Target>[ ...@@ -62,15 +63,22 @@ const List<Target> _kDefaultTargets = <Target>[
androidArmReleaseBundle, androidArmReleaseBundle,
androidArm64ReleaseBundle, androidArm64ReleaseBundle,
androidx64ReleaseBundle, androidx64ReleaseBundle,
// Deferred component enabled AOT rules
androidArmProfileDeferredComponentsBundle,
androidArm64ProfileDeferredComponentsBundle,
androidx64ProfileDeferredComponentsBundle,
androidArmReleaseDeferredComponentsBundle,
androidArm64ReleaseDeferredComponentsBundle,
androidx64ReleaseDeferredComponentsBundle,
// iOS targets // iOS targets
DebugIosApplicationBundle(), const DebugIosApplicationBundle(),
ProfileIosApplicationBundle(), const ProfileIosApplicationBundle(),
ReleaseIosApplicationBundle(), const ReleaseIosApplicationBundle(),
// Windows targets // Windows targets
UnpackWindows(), const UnpackWindows(),
DebugBundleWindowsAssets(), const DebugBundleWindowsAssets(),
ProfileBundleWindowsAssets(), const ProfileBundleWindowsAssets(),
ReleaseBundleWindowsAssets(), const ReleaseBundleWindowsAssets(),
]; ];
// TODO(ianh): https://github.com/dart-lang/args/issues/181 will allow us to remove useLegacyNames // TODO(ianh): https://github.com/dart-lang/args/issues/181 will allow us to remove useLegacyNames
...@@ -171,6 +179,24 @@ class AssembleCommand extends FlutterCommand { ...@@ -171,6 +179,24 @@ class AssembleCommand extends FlutterCommand {
return results; 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. /// The environmental configuration for a build invocation.
Environment createEnvironment() { Environment createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
...@@ -221,6 +247,10 @@ class AssembleCommand extends FlutterCommand { ...@@ -221,6 +247,10 @@ class AssembleCommand extends FlutterCommand {
if (argResults.wasParsed(useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption)) { if (argResults.wasParsed(useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption)) {
results[kDartDefines] = (argResults[useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption] as List<String>).join(','); results[kDartDefines] = (argResults[useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption] as List<String>).join(',');
} }
results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true';
}
if (argResults.wasParsed(useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions)) { if (argResults.wasParsed(useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions)) {
results[kExtraFrontEndOptions] = (argResults[useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions] as List<String>).join(','); results[kExtraFrontEndOptions] = (argResults[useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions] as List<String>).join(',');
} }
...@@ -229,11 +259,37 @@ class AssembleCommand extends FlutterCommand { ...@@ -229,11 +259,37 @@ class AssembleCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final Environment env = createEnvironment();
final List<Target> targets = createTargets(); final List<Target> targets = createTargets();
final Target target = targets.length == 1 ? targets.single : CompositeTarget(targets); final List<Target> nonDeferredTargets = <Target>[];
final List<Target> deferredTargets = <AndroidAotDeferredComponentsBundle>[];
for (final Target target in targets) {
if (deferredComponentsTargets.contains(target.name)) {
deferredTargets.add(target);
} else {
nonDeferredTargets.add(target);
}
}
Target target;
final List<String> decodedDefines = decodeDartDefines(env.defines, kDartDefines);
if (FlutterProject.current().manifest.deferredComponents != null
&& decodedDefines.contains('validate-deferred-components=true')
&& deferredTargets.isNotEmpty
&& !isDebug()) {
// Add deferred components validation target that require loading units.
target = DeferredComponentsGenSnapshotValidatorTarget(
deferredComponentsDependencies: deferredTargets.cast<AndroidAotDeferredComponentsBundle>(),
nonDeferredComponentsDependencies: nonDeferredTargets,
title: 'Deferred components gen_snapshot validation',
);
} else if (targets.length > 1) {
target = CompositeTarget(targets);
} else if (targets.isNotEmpty) {
target = targets.single;
}
final BuildResult result = await _buildSystem.build( final BuildResult result = await _buildSystem.build(
target, target,
createEnvironment(), env,
buildSystemConfig: BuildSystemConfig( buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size') resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size')) ? int.tryParse(stringArg('resource-pool-size'))
...@@ -251,6 +307,7 @@ class AssembleCommand extends FlutterCommand { ...@@ -251,6 +307,7 @@ class AssembleCommand extends FlutterCommand {
throwToolExit(''); throwToolExit('');
} }
globals.printTrace('build succeeded.'); globals.printTrace('build succeeded.');
if (argResults.wasParsed('build-inputs')) { if (argResults.wasParsed('build-inputs')) {
writeListIfChanged(result.inputFiles, stringArg('build-inputs')); writeListIfChanged(result.inputFiles, stringArg('build-inputs'));
} }
......
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
import '../android/android_builder.dart'; import '../android/android_builder.dart';
import '../android/build_validation.dart'; import '../android/build_validation.dart';
import '../android/deferred_components_prebuild_validator.dart';
import '../android/gradle_utils.dart'; import '../android/gradle_utils.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
...@@ -42,6 +45,25 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -42,6 +45,25 @@ class BuildAppBundleCommand extends BuildSubCommand {
allowed: <String>['android-arm', 'android-arm64', 'android-x64'], allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the app is compiled.', 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 @override
...@@ -84,15 +106,49 @@ class BuildAppBundleCommand extends BuildSubCommand { ...@@ -84,15 +106,49 @@ class BuildAppBundleCommand extends BuildSubCommand {
if (globals.androidSdk == null) { if (globals.androidSdk == null) {
exitWithNoSdkMessage(); exitWithNoSdkMessage();
} }
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(), final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(),
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName), targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
); );
// Do all setup verification that doesn't involve loading units. Checks that
// require generated loading units are done after gen_snapshot in assemble.
if (FlutterProject.current().manifest.deferredComponents != null && boolArg('deferred-components') && boolArg('validate-deferred-components') && !boolArg('debug')) {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
FlutterProject.current().directory,
globals.logger,
title: 'Deferred components prebuild validation',
exitOnFail: true,
);
validator.clearOutputDir();
await validator.checkAndroidDynamicFeature(FlutterProject.current().manifest.deferredComponents);
validator.checkAndroidResourcesStrings(FlutterProject.current().manifest.deferredComponents);
validator.handleResults();
// Delete intermediates libs dir for components to resolve mismatching
// abis supported by base and dynamic feature modules.
for (final DeferredComponent component in FlutterProject.current().manifest.deferredComponents) {
final Directory deferredLibsIntermediate = FlutterProject.current().directory
.childDirectory('build')
.childDirectory(component.name)
.childDirectory('intermediates')
.childDirectory('flutter')
.childDirectory(androidBuildInfo.buildInfo.mode.name)
.childDirectory('deferred_libs');
if (deferredLibsIntermediate.existsSync()) {
deferredLibsIntermediate.deleteSync(recursive: true);
}
}
}
validateBuild(androidBuildInfo); validateBuild(androidBuildInfo);
displayNullSafetyMode(androidBuildInfo.buildInfo); displayNullSafetyMode(androidBuildInfo.buildInfo);
await androidBuilder.buildAab( await androidBuilder.buildAab(
project: FlutterProject.current(), project: FlutterProject.current(),
target: targetFile, target: targetFile,
androidBuildInfo: androidBuildInfo, androidBuildInfo: androidBuildInfo,
validateDeferredComponents: boolArg('validate-deferred-components'),
deferredComponentsEnabled: boolArg('deferred-components') && !boolArg('debug'),
); );
return FlutterCommandResult.success(); return FlutterCommandResult.success();
} }
......
...@@ -120,6 +120,7 @@ class FlutterOptions { ...@@ -120,6 +120,7 @@ class FlutterOptions {
static const String kAnalyzeSize = 'analyze-size'; static const String kAnalyzeSize = 'analyze-size';
static const String kNullAssertions = 'null-assertions'; static const String kNullAssertions = 'null-assertions';
static const String kAndroidGradleDaemon = 'android-gradle-daemon'; static const String kAndroidGradleDaemon = 'android-gradle-daemon';
static const String kDeferredComponents = 'deferred-components';
} }
abstract class FlutterCommand extends Command<void> { abstract class FlutterCommand extends Command<void> {
......
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")
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="{{androidIdentifier}}.{{componentName}}">
<dist:module
dist:instant="false"
dist:title="@string/{{componentName}}Name">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>
...@@ -140,6 +140,8 @@ ...@@ -140,6 +140,8 @@
"templates/cocoapods/Podfile-ios-objc", "templates/cocoapods/Podfile-ios-objc",
"templates/cocoapods/Podfile-ios-swift", "templates/cocoapods/Podfile-ios-swift",
"templates/cocoapods/Podfile-macos", "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/build.gradle.copy.tmpl",
"templates/module/android/gradle/gradle.properties.tmpl", "templates/module/android/gradle/gradle.properties.tmpl",
"templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl", "templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl",
......
...@@ -22,7 +22,7 @@ void main() { ...@@ -22,7 +22,7 @@ void main() {
Environment env; Environment env;
Environment createEnvironment() { Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' }; final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' };
final Environment result = Environment( final Environment result = Environment(
outputDir: fileSystem.directory('/output'), outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'), buildDir: fileSystem.directory('/build'),
......
...@@ -10,9 +10,6 @@ import 'package:flutter_tools/src/android/deferred_components_validator.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/deferred_component.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.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/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -20,37 +17,20 @@ import '../../src/context.dart'; ...@@ -20,37 +17,20 @@ import '../../src/context.dart';
void main() { void main() {
FileSystem fileSystem; FileSystem fileSystem;
BufferLogger logger; BufferLogger logger;
Environment env; Directory projectDir;
Directory flutterRootDir;
Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
final Environment result = Environment(
outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'),
projectDir: fileSystem.directory('/project'),
defines: defines,
inputs: <String, String>{},
cacheDir: fileSystem.directory('/cache'),
flutterRootDir: fileSystem.directory('/flutter_root'),
artifacts: globals.artifacts,
fileSystem: fileSystem,
logger: logger,
processManager: globals.processManager,
engineVersion: 'invalidEngineVersion',
generateDartPluginRegistry: false,
);
return result;
}
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(); fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test(); logger = BufferLogger.test();
env = createEnvironment(); projectDir = fileSystem.directory('/project');
flutterRootDir = fileSystem.directory('/flutter_root');
}); });
testWithoutContext('No checks passes', () async { testWithoutContext('No checks passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', title: 'test check',
); );
...@@ -61,7 +41,8 @@ void main() { ...@@ -61,7 +41,8 @@ void main() {
testWithoutContext('clearTempDir passes', () async { testWithoutContext('clearTempDir passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', title: 'test check',
); );
...@@ -72,7 +53,7 @@ void main() { ...@@ -72,7 +53,7 @@ void main() {
}); });
testUsingContext('androidComponentSetup build.gradle does not exist', () async { 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 buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl'); final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) { if (templatesDir.existsSync()) {
...@@ -84,12 +65,13 @@ void main() { ...@@ -84,12 +65,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append); androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', title: 'test check',
templatesDir: templatesDir, 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'); final File file = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
if (file.existsSync()) { if (file.existsSync()) {
file.deleteSync(); file.deleteSync();
...@@ -109,7 +91,7 @@ void main() { ...@@ -109,7 +91,7 @@ void main() {
}); });
testUsingContext('androidComponentSetup AndroidManifest.xml does not exist', () async { 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 buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl'); final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) { if (templatesDir.existsSync()) {
...@@ -121,12 +103,13 @@ void main() { ...@@ -121,12 +103,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append); androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', title: 'test check',
templatesDir: templatesDir, 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'); final File file = componentDir.childFile('build.gradle');
if (file.existsSync()) { if (file.existsSync()) {
file.deleteSync(); file.deleteSync();
...@@ -146,7 +129,7 @@ void main() { ...@@ -146,7 +129,7 @@ void main() {
}); });
testUsingContext('androidComponentSetup all files exist passes', () async { 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 buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl'); final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) { if (templatesDir.existsSync()) {
...@@ -158,12 +141,13 @@ void main() { ...@@ -158,12 +141,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append); androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', title: 'test check',
templatesDir: templatesDir, 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'); final File buildGradle = componentDir.childFile('build.gradle');
if (buildGradle.existsSync()) { if (buildGradle.existsSync()) {
buildGradle.deleteSync(); buildGradle.deleteSync();
...@@ -189,11 +173,12 @@ void main() { ...@@ -189,11 +173,12 @@ void main() {
testWithoutContext('androidStringMapping creates new file', () async { testWithoutContext('androidStringMapping creates new file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', 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'); final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
if (stringRes.existsSync()) { if (stringRes.existsSync()) {
stringRes.deleteSync(); stringRes.deleteSync();
...@@ -239,7 +224,7 @@ void main() { ...@@ -239,7 +224,7 @@ void main() {
expect(logger.statusText.contains('Newly generated android files:\n'), true); 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); 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('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory) .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app') .childDirectory('app')
...@@ -255,11 +240,12 @@ void main() { ...@@ -255,11 +240,12 @@ void main() {
testWithoutContext('androidStringMapping modifies strings file', () async { testWithoutContext('androidStringMapping modifies strings file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator( final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env, projectDir,
logger,
exitOnFail: false, exitOnFail: false,
title: 'test check', 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'); final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
if (stringRes.existsSync()) { if (stringRes.existsSync()) {
stringRes.deleteSync(); stringRes.deleteSync();
...@@ -285,7 +271,7 @@ void main() { ...@@ -285,7 +271,7 @@ void main() {
expect(logger.statusText.contains('Modified android files:\n'), true); 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); 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('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory) .childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app') .childDirectory('app')
......
...@@ -176,6 +176,129 @@ flutter: ...@@ -176,6 +176,129 @@ flutter:
FileSystem: () => testFileSystem, FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('deferred assets are parsed', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets are parsed regularly when splitDeferredAssets Disabled', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: false);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 6);
expect(bundle.deferredComponentsEntries.isEmpty, true);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets wildcard parsed', () async {
final File packageFile = globals.fs.file('.packages')..createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
// Simulate modifying the files by updating the filestat time manually.
globals.fs.file(globals.fs.path.join('assets', 'wild', 'fizz.txt'))
..createSync(recursive: true)
..setLastModifiedSync(packageFile.lastModifiedSync().add(const Duration(hours: 1)));
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), true);
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 3);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
}); });
testUsingContext('Failed directory delete shows message', () async { testUsingContext('Failed directory delete shows message', () async {
......
...@@ -130,16 +130,16 @@ void main() { ...@@ -130,16 +130,16 @@ void main() {
wrapColumn: maxLineWidth, wrapColumn: maxLineWidth,
); );
expect(commandHelp.L.toString(), endsWith('\x1B[1;30m(debugDumpLayerTree)\x1B[39m\x1b[22m')); expect(commandHelp.L.toString(), endsWith('\x1B[90m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), endsWith('\x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m')); expect(commandHelp.P.toString(), endsWith('\x1B[90m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m')); expect(commandHelp.S.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m')); expect(commandHelp.U.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), endsWith('\x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m')); expect(commandHelp.a.toString(), endsWith('\x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), endsWith('\x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m')); expect(commandHelp.i.toString(), endsWith('\x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), endsWith('\x1B[1;30m(defaultTargetPlatform)\x1B[39m\x1b[22m')); expect(commandHelp.o.toString(), endsWith('\x1B[90m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), endsWith('\x1B[1;30m(debugPaintSizeEnabled)\x1B[39m\x1b[22m')); expect(commandHelp.p.toString(), endsWith('\x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), endsWith('\x1B[1;30m(debugDumpRenderTree)\x1B[39m\x1b[22m')); expect(commandHelp.t.toString(), endsWith('\x1B[90m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), endsWith('\x1B[1;30m(debugDumpApp)\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', () { testWithoutContext('should not create a help text longer than maxLineWidth without ansi support', () {
...@@ -180,24 +180,24 @@ void main() { ...@@ -180,24 +180,24 @@ void main() {
wrapColumn: maxLineWidth, 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.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[1;30m(WidgetsApp.showPerformanceOverlay)\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.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.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[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[90m(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.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.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.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.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.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[1;30m(defaultTargetPlatform)\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[1;30m(debugPaintSizeEnabled)\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.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.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.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.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.')); expect(commandHelp.z.toString(), equals('\x1B[1mz\x1B[22m Toggle elevation checker.'));
}); });
......
...@@ -34,7 +34,7 @@ void main() { ...@@ -34,7 +34,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(), buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(), projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{ defines: <String, String>{
kSplitAot: 'true', kDeferredComponents: 'true',
}, },
artifacts: null, artifacts: null,
processManager: null, processManager: null,
...@@ -44,11 +44,10 @@ void main() { ...@@ -44,11 +44,10 @@ void main() {
environment.buildDir.createSync(recursive: true); environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot); const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]); final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget, deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
abis: <String>['arm64-v8a'], nonDeferredComponentsDependencies: <Target>[],
title: 'test checks', title: 'test checks',
exitOnFail: false, exitOnFail: false,
); );
...@@ -68,7 +67,7 @@ void main() { ...@@ -68,7 +67,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(), buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(), projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{ defines: <String, String>{
kSplitAot: 'true', kDeferredComponents: 'true',
}, },
artifacts: null, artifacts: null,
processManager: null, processManager: null,
...@@ -78,11 +77,10 @@ void main() { ...@@ -78,11 +77,10 @@ void main() {
environment.buildDir.createSync(recursive: true); environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot); const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]); final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget, deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
abis: <String>['arm64-v8a'], nonDeferredComponentsDependencies: <Target>[],
title: 'test checks', title: 'test checks',
exitOnFail: false, exitOnFail: false,
); );
...@@ -101,7 +99,7 @@ void main() { ...@@ -101,7 +99,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(), buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(), projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{ defines: <String, String>{
kSplitAot: 'true', kDeferredComponents: 'true',
}, },
artifacts: null, artifacts: null,
processManager: null, processManager: null,
...@@ -111,11 +109,10 @@ void main() { ...@@ -111,11 +109,10 @@ void main() {
environment.buildDir.createSync(recursive: true); environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release); const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot); const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]); final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget( final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget, deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
abis: <String>['arm64-v8a'], nonDeferredComponentsDependencies: <Target>[],
title: 'test checks', title: 'test checks',
exitOnFail: false, exitOnFail: false,
); );
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:file/file.dart';
import '../test_utils.dart';
abstract class DeferredComponentsConfig {
String get deferredLibrary;
String get deferredComponentsGolden;
String get androidSettings;
String get androidBuild;
String get androidLocalProperties;
String get androidGradleProperties;
String get androidKeyProperties;
List<int> get androidKey;
String get appBuild;
String get appManifest;
String get appStrings;
String get appStyles;
String get appLaunchBackground;
String get asset1;
String get asset2;
List<DeferredComponentModule> get deferredComponents;
void setUpIn(Directory dir) {
if (deferredLibrary != null) {
writeFile(fileSystem.path.join(dir.path, 'lib', 'deferred_library.dart'), deferredLibrary);
}
if (deferredComponentsGolden != null) {
writeFile(fileSystem.path.join(dir.path, 'deferred_components_loading_units.yaml'), deferredComponentsGolden);
}
if (androidSettings != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'settings.gradle'), androidSettings);
}
if (androidBuild != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'build.gradle'), androidBuild);
}
if (androidLocalProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'local.properties'), androidLocalProperties);
}
if (androidGradleProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'gradle.properties'), androidGradleProperties);
}
if (androidKeyProperties != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'key.properties'), androidKeyProperties);
}
if (androidKey != null) {
writeBytesFile(fileSystem.path.join(dir.path, 'android', 'app', 'key.jks'), androidKey);
}
if (appBuild != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'build.gradle'), appBuild);
}
if (appManifest != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'AndroidManifest.xml'), appManifest);
}
if (appStrings != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml'), appStrings);
}
if (appStyles != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'styles.xml'), appStyles);
}
if (appLaunchBackground != null) {
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'drawable', 'launch_background.xml'), appLaunchBackground);
}
if (asset1 != null) {
writeFile(fileSystem.path.join(dir.path, 'test_assets/asset1.txt'), asset1);
}
if (asset2 != null) {
writeFile(fileSystem.path.join(dir.path, 'test_assets/asset2.txt'), asset2);
}
if (deferredComponents != null) {
for (final DeferredComponentModule component in deferredComponents) {
component.setUpIn(dir);
}
}
}
}
class DeferredComponentModule {
DeferredComponentModule(this.name);
String name;
void setUpIn(Directory dir) {
if (name != null) {
writeFile(fileSystem.path.join(dir.path, 'android', name, 'build.gradle'), r'''
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: "com.android.dynamic-feature"
android {
compileSdkVersion 30
sourceSets {
applicationVariants.all { variant ->
main.assets.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_assets"
main.jniLibs.srcDirs += "${project.buildDir}/intermediates/flutter/${variant.name}/deferred_libs"
}
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
implementation project(":app")
}
''');
writeFile(fileSystem.path.join(dir.path, 'android', name, 'src', 'main', 'AndroidManifest.xml'), '''
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.$name">
<dist:module
dist:instant="false"
dist:title="@string/component1Name">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="true" />
</dist:module>
</manifest>
''');
}
}
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import 'package:file/file.dart'; import 'package:file/file.dart';
import '../test_utils.dart'; import '../test_utils.dart';
import 'deferred_components_config.dart';
const String _kDefaultHtml = ''' const String _kDefaultHtml = '''
<html> <html>
...@@ -26,6 +27,7 @@ abstract class Project { ...@@ -26,6 +27,7 @@ abstract class Project {
String get main; String get main;
String get test => null; String get test => null;
String get generatedFile => null; String get generatedFile => null;
DeferredComponentsConfig get deferredComponents => null;
Uri get mainDart => Uri.parse('package:test/main.dart'); Uri get mainDart => Uri.parse('package:test/main.dart');
...@@ -41,6 +43,9 @@ abstract class Project { ...@@ -41,6 +43,9 @@ abstract class Project {
if (generatedFile != null) { if (generatedFile != null) {
writeFile(fileSystem.path.join(dir.path, '.dart_tool', 'flutter_gen', 'flutter_gen.dart'), generatedFile); 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); writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), _kDefaultHtml);
writePackages(dir.path); writePackages(dir.path);
await getPackages(dir.path); await getPackages(dir.path);
......
...@@ -40,6 +40,13 @@ void writeFile(String path, String content) { ...@@ -40,6 +40,13 @@ void writeFile(String path, String content) {
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10))); ..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
} }
void writeBytesFile(String path, List<int> content) {
fileSystem.file(path)
..createSync(recursive: true)
..writeAsBytesSync(content)
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
}
void writePackages(String folder) { void writePackages(String folder) {
writeFile(fileSystem.path.join(folder, '.packages'), ''' writeFile(fileSystem.path.join(folder, '.packages'), '''
test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/ test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/
......
...@@ -35,6 +35,8 @@ class FakeAndroidBuilder implements AndroidBuilder { ...@@ -35,6 +35,8 @@ class FakeAndroidBuilder implements AndroidBuilder {
@required FlutterProject project, @required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo, @required AndroidBuildInfo androidBuildInfo,
@required String target, @required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async {} }) async {}
} }
......
...@@ -90,6 +90,15 @@ String getFlutterRoot() { ...@@ -90,6 +90,15 @@ String getFlutterRoot() {
return path.normalize(path.join(toolsPath, '..', '..')); return path.normalize(path.join(toolsPath, '..', '..'));
} }
/// Gets the path to the root of the Android SDK from the environment variable.
String getAndroidSdkRoot() {
const Platform platform = LocalPlatform();
if (platform.environment.containsKey('ANDROID_SDK_ROOT')) {
return platform.environment['ANDROID_SDK_ROOT'];
}
throw StateError('ANDROID_SDK_ROOT environment varible not set');
}
CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) { CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) {
final FlutterCommandRunner runner = TestFlutterCommandRunner(); final FlutterCommandRunner runner = TestFlutterCommandRunner();
if (command != null) { if (command != null) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment