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(
final File file = File(path.join(flutterRoot, relativePathToApplication, 'perf.json'));
if (!_allTargetsCached(file)) {
print('${red}Not all build targets cached after second run.$reset');
print('The target performance data was: ${file.readAsStringSync()}');
print('The target performance data was: ${file.readAsStringSync().replaceAll('},', '},\n')}');
exit(1);
}
}
......
......@@ -134,6 +134,14 @@ class FlutterPlugin implements Plugin<Project> {
}
}
}
if (project.hasProperty('deferred-component-names')) {
String[] componentNames = project.property('deferred-component-names').split(',').collect {":${it}"}
project.android {
dynamicFeatures = componentNames
}
}
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
......@@ -173,17 +181,23 @@ class FlutterPlugin implements Plugin<Project> {
matchingFallbacks = ["debug", "release"]
}
}
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
// NOTE: The resource shrinker can't be used for libraries.
shrinkResources isBuiltAsApp(project)
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
// TODO(garyq): Shrinking is only false for multi apk split aot builds, where shrinking is not allowed yet.
// This limitation has been removed experimentally in gradle plugin version 4.2, so we can remove
// this check when we upgrade to 4.2+ gradle. Currently, deferred components apps may see
// increased app size due to this.
if (shouldShrinkResources(project)) {
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
// NOTE: The resource shrinker can't be used for libraries.
shrinkResources isBuiltAsApp(project)
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
}
}
}
......@@ -200,6 +214,13 @@ class FlutterPlugin implements Plugin<Project> {
project.android.buildTypes.all this.&addFlutterDependencies
}
private static Boolean shouldShrinkResources(Project project) {
if (project.hasProperty("shrink")) {
return project.property("shrink").toBoolean()
}
return true
}
/**
* Adds the dependencies required by the Flutter project.
* This includes:
......@@ -706,6 +727,14 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('code-size-directory')) {
codeSizeDirectoryValue = project.property('code-size-directory')
}
Boolean deferredComponentsValue = false
if (project.hasProperty('deferred-components')) {
deferredComponentsValue = project.property('deferred-components').toBoolean()
}
Boolean validateDeferredComponentsValue = true
if (project.hasProperty('validate-deferred-components')) {
validateDeferredComponentsValue = project.property('validate-deferred-components').toBoolean()
}
def targetPlatforms = getTargetPlatforms()
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
......@@ -747,6 +776,8 @@ class FlutterPlugin implements Plugin<Project> {
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
deferredComponents deferredComponentsValue
validateDeferredComponents validateDeferredComponentsValue
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
......@@ -810,7 +841,8 @@ class FlutterPlugin implements Plugin<Project> {
processResources.dependsOn(copyFlutterAssetsTask)
}
return copyFlutterAssetsTask
}
} // end def addFlutterDeps
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
Task assembleTask = getAssembleTask(variant)
......@@ -883,7 +915,7 @@ class FlutterPlugin implements Plugin<Project> {
// | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- |
// | freeRelease | relese |
// | freeRelease | release |
// | freeDebug | debug |
// | freeDevelop | debug |
// | profile | profile |
......@@ -961,6 +993,10 @@ abstract class BaseFlutterTask extends DefaultTask {
@Optional @Input
String codeSizeDirectory;
String performanceMeasurementFile;
@Optional @Input
Boolean deferredComponents
@Optional @Input
Boolean validateDeferredComponents
@OutputFiles
FileCollection getDependenciesFiles() {
......@@ -985,6 +1021,8 @@ abstract class BaseFlutterTask extends DefaultTask {
String[] ruleNames;
if (buildMode == "debug") {
ruleNames = ["debug_android_application"]
} else if (deferredComponents) {
ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" }
} else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
}
......
......@@ -38,5 +38,7 @@ abstract class AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
});
}
......@@ -30,10 +30,17 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
/// methods will exit the tool when this validator detects a recommended
/// change. This defaults to true.
DeferredComponentsGenSnapshotValidator(Environment env, {
DeferredComponentsGenSnapshotValidator(this.env, {
bool exitOnFail = true,
String title,
}) : super(env, exitOnFail: exitOnFail, title: title);
}) : super(env.projectDir, env.logger, exitOnFail: exitOnFail, title: title);
/// The build environment that should be used to find the input files to run
/// checks against.
///
/// The checks in this class are meant to be used as part of a build process,
/// so an environment should be available.
final Environment env;
// The key used to identify the metadata element as the loading unit id to
// deferred component mapping.
......@@ -58,8 +65,8 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// Where loading unit 2 is included in componentA, loading unit 3 is included
/// in componentB, and loading unit 4 is included in componentC.
bool checkAppAndroidManifestComponentLoadingUnitMapping(List<DeferredComponent> components, List<LoadingUnit> generatedLoadingUnits) {
final Directory androidDir = env.projectDir.childDirectory('android');
inputs.add(env.projectDir.childFile('pubspec.yaml'));
final Directory androidDir = projectDir.childDirectory('android');
inputs.add(projectDir.childFile('pubspec.yaml'));
// We do not use the Xml package to handle the writing, as we do not want to
// erase any user applied formatting and comments. The changes can be
......@@ -106,8 +113,10 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
mappingBuffer.write('$key:${mapping[key]},');
}
String encodedMapping = mappingBuffer.toString();
// remove trailing comma.
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
// remove trailing comma if any
if (encodedMapping.endsWith(',')) {
encodedMapping = encodedMapping.substring(0, encodedMapping.length - 1);
}
// Check for existing metadata entry and see if needs changes.
bool exists = false;
bool modified = false;
......@@ -163,7 +172,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// considered new.
bool checkAgainstLoadingUnitsCache(
List<LoadingUnit> generatedLoadingUnits) {
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
final List<LoadingUnit> cachedLoadingUnits = _parseLodingUnitsCache(projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName));
loadingUnitComparisonResults = <String, dynamic>{};
final Set<LoadingUnit> unmatchedLoadingUnits = <LoadingUnit>{};
final List<LoadingUnit> newLoadingUnits = <LoadingUnit>[];
......@@ -276,7 +285,7 @@ class DeferredComponentsGenSnapshotValidator extends DeferredComponentsValidator
/// deferred components.
void writeLoadingUnitsCache(List<LoadingUnit> generatedLoadingUnits) {
generatedLoadingUnits ??= <LoadingUnit>[];
final File cacheFile = env.projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
final File cacheFile = projectDir.childFile(DeferredComponentsValidator.kLoadingUnitsCacheFileName);
outputs.add(cacheFile);
ErrorHandlingFileSystem.deleteIfExists(cacheFile);
cacheFile.createSync(recursive: true);
......
......@@ -10,7 +10,7 @@ import 'package:xml/xml.dart';
import '../base/deferred_component.dart';
import '../base/error_handling_io.dart';
import '../base/file_system.dart';
import '../build_system/build_system.dart';
import '../base/logger.dart';
import '../globals.dart' as globals;
import '../project.dart';
import '../template.dart';
......@@ -25,20 +25,18 @@ import 'deferred_components_validator.dart';
class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// Constructs a validator instance.
///
/// The [env] property is used to locate the project files that are checked.
///
/// The [templatesDir] parameter is optional. If null, the tool's default
/// templates directory will be used.
///
/// When [exitOnFail] is set to true, the [handleResults] and [attemptToolExit]
/// methods will exit the tool when this validator detects a recommended
/// change. This defaults to true.
DeferredComponentsPrebuildValidator(Environment env, {
DeferredComponentsPrebuildValidator(Directory projectDir, Logger logger, {
bool exitOnFail = true,
String title,
Directory templatesDir,
}) : _templatesDir = templatesDir,
super(env, exitOnFail: exitOnFail, title: title);
super(projectDir, logger, exitOnFail: exitOnFail, title: title);
final Directory _templatesDir;
......@@ -56,7 +54,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// This method does not check if the contents of either of the files are
/// valid, as there are many ways that they can be validly configured.
Future<bool> checkAndroidDynamicFeature(List<DeferredComponent> components) async {
inputs.add(env.projectDir.childFile('pubspec.yaml'));
inputs.add(projectDir.childFile('pubspec.yaml'));
if (components == null || components.isEmpty) {
return false;
}
......@@ -64,7 +62,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
for (final DeferredComponent component in components) {
final _DeferredComponentAndroidFiles androidFiles = _DeferredComponentAndroidFiles(
name: component.name,
env: env,
projectDir: projectDir,
logger: logger,
templatesDir: _templatesDir
);
if (!androidFiles.verifyFilesExist()) {
......@@ -106,8 +105,8 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// The string element's name attribute should be the component name with
/// `Name` as a suffix, and the text contents should be the component name.
bool checkAndroidResourcesStrings(List<DeferredComponent> components) {
final Directory androidDir = env.projectDir.childDirectory('android');
inputs.add(env.projectDir.childFile('pubspec.yaml'));
final Directory androidDir = projectDir.childDirectory('android');
inputs.add(projectDir.childFile('pubspec.yaml'));
// Add component name mapping to strings.xml
final File stringRes = androidDir
......@@ -202,7 +201,7 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
/// Deletes all files inside of the validator's output directory.
void clearOutputDir() {
final Directory dir = env.projectDir.childDirectory('build').childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory);
final Directory dir = projectDir.childDirectory('build').childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory);
ErrorHandlingFileSystem.deleteIfExists(dir, recursive: true);
}
}
......@@ -212,16 +211,18 @@ class DeferredComponentsPrebuildValidator extends DeferredComponentsValidator {
class _DeferredComponentAndroidFiles {
_DeferredComponentAndroidFiles({
@required this.name,
@required this.env,
@required this.projectDir,
this.logger,
Directory templatesDir,
}) : _templatesDir = templatesDir;
// The name of the deferred component.
final String name;
final Environment env;
final Directory projectDir;
final Logger logger;
final Directory _templatesDir;
Directory get androidDir => env.projectDir.childDirectory('android');
Directory get androidDir => projectDir.childDirectory('android');
Directory get componentDir => androidDir.childDirectory(name);
File get androidManifestFile => componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
......@@ -250,18 +251,18 @@ class _DeferredComponentAndroidFiles {
Future<List<File>> _setupComponentFiles(Directory outputDir) async {
Template template;
if (_templatesDir != null) {
final Directory templateComponentDir = _templatesDir.childDirectory('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component');
final Directory templateComponentDir = _templatesDir.childDirectory('module${globals.fs.path.separator}android${globals.fs.path.separator}deferred_component');
template = Template(templateComponentDir, templateComponentDir, _templatesDir,
fileSystem: env.fileSystem,
fileSystem: globals.fs,
templateManifest: null,
logger: env.logger,
logger: logger,
templateRenderer: globals.templateRenderer,
);
} else {
template = await Template.fromName('module${env.fileSystem.path.separator}android${env.fileSystem.path.separator}deferred_component',
fileSystem: env.fileSystem,
template = await Template.fromName('module${globals.fs.path.separator}android${globals.fs.path.separator}deferred_component',
fileSystem: globals.fs,
templateManifest: null,
logger: env.logger,
logger: logger,
templateRenderer: globals.templateRenderer,
);
}
......
......@@ -7,8 +7,8 @@
import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../base/terminal.dart';
import '../build_system/build_system.dart';
import '../globals.dart' as globals;
/// A class to configure and run deferred component setup verification checks
......@@ -21,10 +21,10 @@ import '../globals.dart' as globals;
/// The results of each check are handled internally as they are not meant to
/// be run isolated.
abstract class DeferredComponentsValidator {
DeferredComponentsValidator(this.env, {
DeferredComponentsValidator(this.projectDir, this.logger, {
this.exitOnFail = true,
String title,
}) : outputDir = env.projectDir
}) : outputDir = projectDir
.childDirectory('build')
.childDirectory(kDeferredComponentsTempDirectory),
inputs = <File>[],
......@@ -34,12 +34,9 @@ abstract class DeferredComponentsValidator {
modifiedFiles = <String>[],
invalidFiles = <String, String>{},
diffLines = <String>[];
/// The build environment that should be used to find the input files to run
/// checks against.
///
/// The checks in this class are meant to be used as part of a build process,
/// so an environment should be available.
final Environment env;
/// Logger to use for [displayResults] output.
final Logger logger;
/// When true, failed checks and tasks will result in [attemptToolExit]
/// triggering [throwToolExit].
......@@ -54,6 +51,9 @@ abstract class DeferredComponentsValidator {
/// The title printed at the top of the results of [displayResults]
final String title;
/// The root directory of the flutter project.
final Directory projectDir;
/// The temporary directory that the validator writes recommended files into.
final Directory outputDir;
......@@ -111,20 +111,20 @@ abstract class DeferredComponentsValidator {
/// All checks that are desired should be run before calling this method.
void displayResults() {
if (changesNeeded) {
env.logger.printStatus(_thickDivider);
env.logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true);
env.logger.printStatus(_thickDivider);
logger.printStatus(_thickDivider);
logger.printStatus(title, indent: (_thickDivider.length - title.length) ~/ 2, emphasis: true);
logger.printStatus(_thickDivider);
// Log any file reading/existence errors.
if (invalidFiles.isNotEmpty) {
env.logger.printStatus('Errors checking the following files:\n', emphasis: true);
logger.printStatus('Errors checking the following files:\n', emphasis: true);
for (final String key in invalidFiles.keys) {
env.logger.printStatus(' - $key: ${invalidFiles[key]}\n');
logger.printStatus(' - $key: ${invalidFiles[key]}\n');
}
}
// Log diff file contents, with color highlighting
if (diffLines != null && diffLines.isNotEmpty) {
env.logger.printStatus('Diff between `android` and expected files:', emphasis: true);
env.logger.printStatus('');
logger.printStatus('Diff between `android` and expected files:', emphasis: true);
logger.printStatus('');
for (final String line in diffLines) {
// We only care about diffs in files that have
// counterparts.
......@@ -137,62 +137,62 @@ abstract class DeferredComponentsValidator {
} else if (line.startsWith('-')) {
color = TerminalColor.red;
}
env.logger.printStatus(line, color: color);
logger.printStatus(line, color: color);
}
env.logger.printStatus('');
logger.printStatus('');
}
// Log any newly generated and modified files.
if (generatedFiles.isNotEmpty) {
env.logger.printStatus('Newly generated android files:', emphasis: true);
logger.printStatus('Newly generated android files:', emphasis: true);
for (final String filePath in generatedFiles) {
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (modifiedFiles.isNotEmpty) {
env.logger.printStatus('Modified android files:', emphasis: true);
logger.printStatus('Modified android files:', emphasis: true);
for (final String filePath in modifiedFiles) {
final String shortenedPath = filePath.substring(env.projectDir.parent.path.length + 1);
env.logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
final String shortenedPath = filePath.substring(projectDir.parent.path.length + 1);
logger.printStatus(' - $shortenedPath', color: TerminalColor.grey);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (generatedFiles.isNotEmpty || modifiedFiles.isNotEmpty) {
env.logger.printStatus('''
logger.printStatus('''
The above files have been placed into `build/$kDeferredComponentsTempDirectory`,
a temporary directory. The files should be reviewed and moved into the project's
`android` directory.''');
if (diffLines != null && diffLines.isNotEmpty && !globals.platform.isWindows) {
env.logger.printStatus(r'''
logger.printStatus(r'''
The recommended changes can be quickly applied by running:
$ patch -p0 < build/setup_deferred_components.diff
''');
}
env.logger.printStatus('$_thinDivider\n');
logger.printStatus('$_thinDivider\n');
}
// Log loading unit golden changes, if any.
if (loadingUnitComparisonResults != null) {
if ((loadingUnitComparisonResults['new'] as List<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('New loading units were found:', emphasis: true);
logger.printStatus('New loading units were found:', emphasis: true);
for (final LoadingUnit unit in loadingUnitComparisonResults['new'] as List<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
logger.printStatus('');
}
if ((loadingUnitComparisonResults['missing'] as Set<LoadingUnit>).isNotEmpty) {
env.logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
logger.printStatus('Previously existing loading units no longer exist:', emphasis: true);
for (final LoadingUnit unit in loadingUnitComparisonResults['missing'] as Set<LoadingUnit>) {
env.logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
logger.printStatus(unit.toString(), color: TerminalColor.grey, indent: 2);
}
env.logger.printStatus('');
logger.printStatus('');
}
if (loadingUnitComparisonResults['match'] as bool) {
env.logger.printStatus('No change in generated loading units.\n');
logger.printStatus('No change in generated loading units.\n');
} else {
env.logger.printStatus('''
logger.printStatus('''
It is recommended to verify that the changed loading units are expected
and to update the `deferred-components` section in `pubspec.yaml` to
incorporate any changes. The full list of generated loading units can be
......@@ -205,14 +205,14 @@ $_thinDivider\n''');
}
}
// TODO(garyq): Add link to web tutorial/guide once it is written.
env.logger.printStatus('''
Setup verification can be skipped by passing the `--no-verify-deferred-components`
logger.printStatus('''
Setup verification can be skipped by passing the `--no-validate-deferred-components`
flag, however, doing so may put your app at risk of not functioning even if the
build is successful.
$_thickDivider''');
return;
}
env.logger.printStatus('$title passed.');
logger.printStatus('$title passed.');
}
void attemptToolExit() {
......
......@@ -12,6 +12,7 @@ import 'package:xml/xml.dart';
import '../artifacts.dart';
import '../base/analyze_size.dart';
import '../base/common.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
......@@ -242,6 +243,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async {
await buildGradleApp(
project: project,
......@@ -249,6 +252,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
target: target,
isBuildingBundle: true,
localGradleErrors: gradleErrors,
validateDeferredComponents: validateDeferredComponents,
deferredComponentsEnabled: deferredComponentsEnabled,
);
}
......@@ -270,6 +275,8 @@ class AndroidGradleBuilder implements AndroidBuilder {
@required bool isBuildingBundle,
@required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
int retries = 1,
}) async {
assert(project != null);
......@@ -355,8 +362,29 @@ class AndroidGradleBuilder implements AndroidBuilder {
if (target != null) {
command.add('-Ptarget=$target');
}
if (project.manifest.deferredComponents != null) {
if (deferredComponentsEnabled) {
command.add('-Pdeferred-components=true');
androidBuildInfo.buildInfo.dartDefines.add('validate-deferred-components=$validateDeferredComponents');
}
// Pass in deferred components regardless of building split aot to satisfy
// android dynamic features registry in build.gradle.
final List<String> componentNames = <String>[];
for (final DeferredComponent component in project.manifest.deferredComponents) {
componentNames.add(component.name);
}
if (componentNames.isNotEmpty) {
command.add('-Pdeferred-component-names=${componentNames.join(',')}');
// Multi-apk applications cannot use shrinking. This is only relevant when using
// android dynamic feature modules.
_logger.printStatus(
'Shrinking has been disabled for this build due to deferred components. Shrinking is '
'not available for multi-apk applications. This limitation is expected to be removed '
'when Gradle plugin 4.2+ is available in Flutter.', color: TerminalColor.yellow);
command.add('-Pshrink=false');
}
}
command.addAll(androidBuildInfo.buildInfo.toGradleConfig());
if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) {
command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}');
}
......
This diff is collapsed.
......@@ -167,7 +167,7 @@ class AnsiTerminal implements Terminal {
static const String cyan = '\u001b[36m';
static const String magenta = '\u001b[35m';
static const String yellow = '\u001b[33m';
static const String grey = '\u001b[1;30m';
static const String grey = '\u001b[90m';
static const Map<TerminalColor, String> _colorMap = <TerminalColor, String>{
TerminalColor.red: red,
......
......@@ -25,10 +25,12 @@ class DepfileService {
/// Given an [depfile] File, write the depfile contents.
///
/// If either [inputs] or [outputs] is empty, ensures the file does not
/// exist.
void writeToFile(Depfile depfile, File output) {
if (depfile.inputs.isEmpty || depfile.outputs.isEmpty) {
/// If both [inputs] and [outputs] are empty, ensures the file does not
/// exist. This can be overriden with the [writeEmpty] parameter when
/// both static and runtime dependencies exist and it is not desired
/// to force a rerun due to no depfile.
void writeToFile(Depfile depfile, File output, {bool writeEmpty = false}) {
if (depfile.inputs.isEmpty && depfile.outputs.isEmpty && !writeEmpty) {
ErrorHandlingFileSystem.deleteIfExists(output);
return;
}
......
......@@ -146,7 +146,7 @@ class SourceVisitor implements ResolvedFiles {
throw InvalidPatternException(pattern);
}
if (!environment.fileSystem.directory(filePath).existsSync()) {
throw Exception('$filePath does not exist!');
environment.fileSystem.directory(filePath).createSync(recursive: true);
}
for (final FileSystemEntity entity in environment.fileSystem.directory(filePath).listSync()) {
final String filename = environment.fileSystem.path.basename(entity.path);
......
......@@ -34,6 +34,7 @@ const String kBundleSkSLPath = 'BundleSkSLPath';
Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
Map<String, DevFSContent> additionalContent,
@required TargetPlatform targetPlatform,
BuildMode buildMode,
}) async {
// Check for an SkSL bundle.
final String shaderBundlePath = environment.inputs[kBundleSkSLPath];
......@@ -51,11 +52,13 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
logger: environment.logger,
fileSystem: environment.fileSystem,
platform: globals.platform,
splitDeferredAssets: buildMode != BuildMode.debug && buildMode != BuildMode.jitRelease,
).createBundle();
final int resultCode = await assetBundle.build(
manifestPath: pubspecFile.path,
packagesPath: environment.projectDir.childFile('.packages').path,
assetDirPath: null,
deferredComponentsEnabled: environment.defines[kDeferredComponents] == 'true',
);
if (resultCode != 0) {
throw Exception('Failed to bundle asset files.');
......@@ -114,6 +117,56 @@ Future<Depfile> copyAssets(Environment environment, Directory outputDirectory, {
resource.release();
}
}));
// Copy deferred components assets only for release or profile builds.
// The assets are included in assetBundle.entries as a normal asset when
// building as debug.
if (environment.defines[kDeferredComponents] == 'true') {
await Future.wait<void>(
assetBundle.deferredComponentsEntries.entries.map<Future<void>>((MapEntry<String, Map<String, DevFSContent>> componentEntries) async {
final Directory componentOutputDir =
environment.projectDir
.childDirectory('build')
.childDirectory(componentEntries.key)
.childDirectory('intermediates')
.childDirectory('flutter');
await Future.wait<void>(
componentEntries.value.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
final PoolResource resource = await pool.request();
try {
// This will result in strange looking files, for example files with `/`
// on Windows or files that end up getting URI encoded such as `#.ext`
// to `%23.ext`. However, we have to keep it this way since the
// platform channels in the framework will URI encode these values,
// and the native APIs will look for files this way.
// If deferred components are disabled, then copy assets to regular location.
final File file = environment.defines[kDeferredComponents] == 'true'
? environment.fileSystem.file(
environment.fileSystem.path.join(componentOutputDir.path, buildMode.name, 'deferred_assets', 'flutter_assets', entry.key))
: environment.fileSystem.file(
environment.fileSystem.path.join(outputDirectory.path, entry.key));
outputs.add(file);
file.parent.createSync(recursive: true);
final DevFSContent content = entry.value;
if (content is DevFSFileContent && content.file is File) {
inputs.add(content.file as File);
if (!await iconTreeShaker.subsetFont(
input: content.file as File,
outputPath: file.path,
relativePath: entry.key,
)) {
await (content.file as File).copy(file.path);
}
} else {
await file.writeAsBytes(await entry.value.contentsAsBytes());
}
} finally {
resource.release();
}
}));
}));
}
final Depfile depfile = Depfile(inputs + assetBundle.additionalDependencies, outputs);
if (shaderBundlePath != null) {
final File skSLBundleFile = environment.fileSystem
......
......@@ -45,9 +45,9 @@ const String kExtraFrontEndOptions = 'ExtraFrontEndOptions';
/// This is expected to be a comma separated list of strings.
const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions';
/// Whether the app should run gen_snapshot as a split aot build for deferred
/// Whether the build should run gen_snapshot as a split aot build for deferred
/// components.
const String kSplitAot = 'SplitAot';
const String kDeferredComponents = 'DeferredComponents';
/// Whether to strip source code information out of release builds and where to save it.
const String kSplitDebugInfo = 'SplitDebugInfo';
......@@ -131,6 +131,7 @@ class CopyFlutterBundle extends Target {
environment,
environment.outputDir,
targetPlatform: TargetPlatform.android,
buildMode: buildMode,
);
final DepfileService depfileService = DepfileService(
fileSystem: environment.fileSystem,
......
......@@ -8,25 +8,26 @@ import 'package:meta/meta.dart';
import '../../android/deferred_components_gen_snapshot_validator.dart';
import '../../base/deferred_component.dart';
import '../../build_info.dart';
import '../../project.dart';
import '../build_system.dart';
import '../depfile.dart';
import 'android.dart';
/// Creates a [DeferredComponentsGenSnapshotValidator], runs the checks, and displays the validator
/// output to the developer if changes are recommended.
class DeferredComponentsGenSnapshotValidatorTarget extends Target {
/// Create an [AndroidAotDeferredComponentsBundle] implementation for a given [targetPlatform] and [buildMode].
DeferredComponentsGenSnapshotValidatorTarget({
@required this.dependency,
@required this.abis,
@required this.deferredComponentsDependencies,
@required this.nonDeferredComponentsDependencies,
this.title,
this.exitOnFail = true,
String name = 'deferred_components_setup_validator',
}) : _name = name;
});
/// The [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on packed
/// as a [CompositeTarget].
final CompositeTarget dependency;
/// The [AndroidAotDeferredComponentsBundle] derived target instances this rule depends on.
final List<AndroidAotDeferredComponentsBundle> deferredComponentsDependencies;
final List<Target> nonDeferredComponentsDependencies;
/// The title of the [DeferredComponentsGenSnapshotValidator] that is
/// displayed to the developer when logging results.
......@@ -37,11 +38,20 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final bool exitOnFail;
/// The abis to validate.
final List<String> abis;
List<String> get _abis {
final List<String> abis = <String>[];
for (final AndroidAotDeferredComponentsBundle target in deferredComponentsDependencies) {
if (deferredComponentsTargets.contains(target.name)) {
abis.add(
getNameForAndroidArch(getAndroidArchForName(getNameForTargetPlatform(target.dependency.targetPlatform)))
);
}
}
return abis;
}
@override
String get name => _name;
final String _name;
String get name => 'deferred_components_gen_snapshot_validator';
@override
List<Source> get inputs => const <Source>[];
......@@ -55,7 +65,11 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
];
@override
List<Target> get dependencies => dependency == null ? <Target>[] : <Target>[dependency];
List<Target> get dependencies {
final List<Target> deps = <Target>[CompositeTarget(deferredComponentsDependencies)];
deps.addAll(nonDeferredComponentsDependencies);
return deps;
}
@visibleForTesting
DeferredComponentsGenSnapshotValidator validator;
......@@ -75,7 +89,7 @@ class DeferredComponentsGenSnapshotValidatorTarget extends Target {
final List<LoadingUnit> generatedLoadingUnits = LoadingUnit.parseGeneratedLoadingUnits(
environment.outputDir,
environment.logger,
abis: abis
abis: _abis
);
validator
......
......@@ -14,6 +14,7 @@ import '../build_system/depfile.dart';
import '../build_system/targets/android.dart';
import '../build_system/targets/assets.dart';
import '../build_system/targets/common.dart';
import '../build_system/targets/deferred_components.dart';
import '../build_system/targets/ios.dart';
import '../build_system/targets/linux.dart';
import '../build_system/targets/macos.dart';
......@@ -27,34 +28,34 @@ import '../reporting/reporting.dart';
import '../runner/flutter_command.dart';
/// All currently implemented targets.
const List<Target> _kDefaultTargets = <Target>[
List<Target> _kDefaultTargets = <Target>[
// Shared targets
CopyAssets(),
KernelSnapshot(),
AotElfProfile(TargetPlatform.android_arm),
AotElfRelease(TargetPlatform.android_arm),
AotAssemblyProfile(),
AotAssemblyRelease(),
const CopyAssets(),
const KernelSnapshot(),
const AotElfProfile(TargetPlatform.android_arm),
const AotElfRelease(TargetPlatform.android_arm),
const AotAssemblyProfile(),
const AotAssemblyRelease(),
// macOS targets
DebugMacOSFramework(),
DebugMacOSBundleFlutterAssets(),
ProfileMacOSBundleFlutterAssets(),
ReleaseMacOSBundleFlutterAssets(),
const DebugMacOSFramework(),
const DebugMacOSBundleFlutterAssets(),
const ProfileMacOSBundleFlutterAssets(),
const ReleaseMacOSBundleFlutterAssets(),
// Linux targets
DebugBundleLinuxAssets(TargetPlatform.linux_x64),
DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
ProfileBundleLinuxAssets(TargetPlatform.linux_x64),
ProfileBundleLinuxAssets(TargetPlatform.linux_arm64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
const DebugBundleLinuxAssets(TargetPlatform.linux_x64),
const DebugBundleLinuxAssets(TargetPlatform.linux_arm64),
const ProfileBundleLinuxAssets(TargetPlatform.linux_x64),
const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64),
const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64),
const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64),
// Web targets
WebServiceWorker(),
ReleaseAndroidApplication(),
const WebServiceWorker(),
const ReleaseAndroidApplication(),
// This is a one-off rule for bundle and aot compat.
CopyFlutterBundle(),
const CopyFlutterBundle(),
// Android targets,
DebugAndroidApplication(),
ProfileAndroidApplication(),
const DebugAndroidApplication(),
const ProfileAndroidApplication(),
// Android ABI specific AOT rules.
androidArmProfileBundle,
androidArm64ProfileBundle,
......@@ -62,15 +63,22 @@ const List<Target> _kDefaultTargets = <Target>[
androidArmReleaseBundle,
androidArm64ReleaseBundle,
androidx64ReleaseBundle,
// Deferred component enabled AOT rules
androidArmProfileDeferredComponentsBundle,
androidArm64ProfileDeferredComponentsBundle,
androidx64ProfileDeferredComponentsBundle,
androidArmReleaseDeferredComponentsBundle,
androidArm64ReleaseDeferredComponentsBundle,
androidx64ReleaseDeferredComponentsBundle,
// iOS targets
DebugIosApplicationBundle(),
ProfileIosApplicationBundle(),
ReleaseIosApplicationBundle(),
const DebugIosApplicationBundle(),
const ProfileIosApplicationBundle(),
const ReleaseIosApplicationBundle(),
// Windows targets
UnpackWindows(),
DebugBundleWindowsAssets(),
ProfileBundleWindowsAssets(),
ReleaseBundleWindowsAssets(),
const UnpackWindows(),
const DebugBundleWindowsAssets(),
const ProfileBundleWindowsAssets(),
const ReleaseBundleWindowsAssets(),
];
// TODO(ianh): https://github.com/dart-lang/args/issues/181 will allow us to remove useLegacyNames
......@@ -171,6 +179,24 @@ class AssembleCommand extends FlutterCommand {
return results;
}
bool isDeferredComponentsTargets() {
for (final String targetName in argResults.rest) {
if (deferredComponentsTargets.contains(targetName)) {
return true;
}
}
return false;
}
bool isDebug() {
for (final String targetName in argResults.rest) {
if (targetName.contains('debug')) {
return true;
}
}
return false;
}
/// The environmental configuration for a build invocation.
Environment createEnvironment() {
final FlutterProject flutterProject = FlutterProject.current();
......@@ -221,6 +247,10 @@ class AssembleCommand extends FlutterCommand {
if (argResults.wasParsed(useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption)) {
results[kDartDefines] = (argResults[useLegacyNames ? kDartDefines : FlutterOptions.kDartDefinesOption] as List<String>).join(',');
}
results[kDeferredComponents] = 'false';
if (FlutterProject.current().manifest.deferredComponents != null && isDeferredComponentsTargets() && !isDebug()) {
results[kDeferredComponents] = 'true';
}
if (argResults.wasParsed(useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions)) {
results[kExtraFrontEndOptions] = (argResults[useLegacyNames ? kExtraFrontEndOptions : FlutterOptions.kExtraFrontEndOptions] as List<String>).join(',');
}
......@@ -229,11 +259,37 @@ class AssembleCommand extends FlutterCommand {
@override
Future<FlutterCommandResult> runCommand() async {
final Environment env = createEnvironment();
final List<Target> targets = createTargets();
final Target target = targets.length == 1 ? targets.single : CompositeTarget(targets);
final List<Target> nonDeferredTargets = <Target>[];
final List<Target> deferredTargets = <AndroidAotDeferredComponentsBundle>[];
for (final Target target in targets) {
if (deferredComponentsTargets.contains(target.name)) {
deferredTargets.add(target);
} else {
nonDeferredTargets.add(target);
}
}
Target target;
final List<String> decodedDefines = decodeDartDefines(env.defines, kDartDefines);
if (FlutterProject.current().manifest.deferredComponents != null
&& decodedDefines.contains('validate-deferred-components=true')
&& deferredTargets.isNotEmpty
&& !isDebug()) {
// Add deferred components validation target that require loading units.
target = DeferredComponentsGenSnapshotValidatorTarget(
deferredComponentsDependencies: deferredTargets.cast<AndroidAotDeferredComponentsBundle>(),
nonDeferredComponentsDependencies: nonDeferredTargets,
title: 'Deferred components gen_snapshot validation',
);
} else if (targets.length > 1) {
target = CompositeTarget(targets);
} else if (targets.isNotEmpty) {
target = targets.single;
}
final BuildResult result = await _buildSystem.build(
target,
createEnvironment(),
env,
buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(stringArg('resource-pool-size'))
......@@ -251,6 +307,7 @@ class AssembleCommand extends FlutterCommand {
throwToolExit('');
}
globals.printTrace('build succeeded.');
if (argResults.wasParsed('build-inputs')) {
writeListIfChanged(result.inputFiles, stringArg('build-inputs'));
}
......
......@@ -6,7 +6,10 @@
import '../android/android_builder.dart';
import '../android/build_validation.dart';
import '../android/deferred_components_prebuild_validator.dart';
import '../android/gradle_utils.dart';
import '../base/deferred_component.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../cache.dart';
import '../globals.dart' as globals;
......@@ -37,11 +40,30 @@ class BuildAppBundleCommand extends BuildSubCommand {
usesAnalyzeSizeFlag();
addAndroidSpecificBuildOptions(hide: !verboseHelp);
argParser.addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the app is compiled.',
);
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x64'],
help: 'The target platform for which the app is compiled.',
);
argParser.addFlag('deferred-components',
negatable: true,
defaultsTo: true,
help: 'Setting to false disables building with deferred components. All deferred code '
'will be compiled into the base app, and assets act as if they were defined under'
' the regular assets section in pubspec.yaml. This flag has no effect on '
'non-deferred components apps.',
);
argParser.addFlag('validate-deferred-components',
negatable: true,
defaultsTo: true,
help: 'When enabled, deferred component apps will fail to build if setup problems are '
'detected that would prevent deferred components from functioning properly. The '
'tooling also provides guidance on how to set up the project files to pass this '
'verification. Disabling setup verification will always attempt to fully build '
'the app regardless of any problems detected. Builds that are part of CI testing '
'and advanced users with custom deferred components implementations should disable '
'setup verification. This flag has no effect on non-deferred components apps.',
);
}
@override
......@@ -84,15 +106,49 @@ class BuildAppBundleCommand extends BuildSubCommand {
if (globals.androidSdk == null) {
exitWithNoSdkMessage();
}
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(),
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
);
// Do all setup verification that doesn't involve loading units. Checks that
// require generated loading units are done after gen_snapshot in assemble.
if (FlutterProject.current().manifest.deferredComponents != null && boolArg('deferred-components') && boolArg('validate-deferred-components') && !boolArg('debug')) {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
FlutterProject.current().directory,
globals.logger,
title: 'Deferred components prebuild validation',
exitOnFail: true,
);
validator.clearOutputDir();
await validator.checkAndroidDynamicFeature(FlutterProject.current().manifest.deferredComponents);
validator.checkAndroidResourcesStrings(FlutterProject.current().manifest.deferredComponents);
validator.handleResults();
// Delete intermediates libs dir for components to resolve mismatching
// abis supported by base and dynamic feature modules.
for (final DeferredComponent component in FlutterProject.current().manifest.deferredComponents) {
final Directory deferredLibsIntermediate = FlutterProject.current().directory
.childDirectory('build')
.childDirectory(component.name)
.childDirectory('intermediates')
.childDirectory('flutter')
.childDirectory(androidBuildInfo.buildInfo.mode.name)
.childDirectory('deferred_libs');
if (deferredLibsIntermediate.existsSync()) {
deferredLibsIntermediate.deleteSync(recursive: true);
}
}
}
validateBuild(androidBuildInfo);
displayNullSafetyMode(androidBuildInfo.buildInfo);
await androidBuilder.buildAab(
project: FlutterProject.current(),
target: targetFile,
androidBuildInfo: androidBuildInfo,
validateDeferredComponents: boolArg('validate-deferred-components'),
deferredComponentsEnabled: boolArg('deferred-components') && !boolArg('debug'),
);
return FlutterCommandResult.success();
}
......
......@@ -120,6 +120,7 @@ class FlutterOptions {
static const String kAnalyzeSize = 'analyze-size';
static const String kNullAssertions = 'null-assertions';
static const String kAndroidGradleDaemon = 'android-gradle-daemon';
static const String kDeferredComponents = 'deferred-components';
}
abstract class FlutterCommand extends Command<void> {
......
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 @@
"templates/cocoapods/Podfile-ios-objc",
"templates/cocoapods/Podfile-ios-swift",
"templates/cocoapods/Podfile-macos",
"templates/module/android/deferred_component/build.gradle.tmpl",
"templates/module/android/deferred_component/src/main/AndroidManifest.xml.tmpl",
"templates/module/android/gradle/build.gradle.copy.tmpl",
"templates/module/android/gradle/gradle.properties.tmpl",
"templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl",
......
......@@ -22,7 +22,7 @@ void main() {
Environment env;
Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
final Map<String, String> defines = <String, String>{ kDeferredComponents: 'true' };
final Environment result = Environment(
outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'),
......
......@@ -10,9 +10,6 @@ import 'package:flutter_tools/src/android/deferred_components_validator.dart';
import 'package:flutter_tools/src/base/deferred_component.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/common.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -20,37 +17,20 @@ import '../../src/context.dart';
void main() {
FileSystem fileSystem;
BufferLogger logger;
Environment env;
Environment createEnvironment() {
final Map<String, String> defines = <String, String>{ kSplitAot: 'true' };
final Environment result = Environment(
outputDir: fileSystem.directory('/output'),
buildDir: fileSystem.directory('/build'),
projectDir: fileSystem.directory('/project'),
defines: defines,
inputs: <String, String>{},
cacheDir: fileSystem.directory('/cache'),
flutterRootDir: fileSystem.directory('/flutter_root'),
artifacts: globals.artifacts,
fileSystem: fileSystem,
logger: logger,
processManager: globals.processManager,
engineVersion: 'invalidEngineVersion',
generateDartPluginRegistry: false,
);
return result;
}
Directory projectDir;
Directory flutterRootDir;
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
env = createEnvironment();
projectDir = fileSystem.directory('/project');
flutterRootDir = fileSystem.directory('/flutter_root');
});
testWithoutContext('No checks passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
......@@ -61,7 +41,8 @@ void main() {
testWithoutContext('clearTempDir passes', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
......@@ -72,7 +53,7 @@ void main() {
});
testUsingContext('androidComponentSetup build.gradle does not exist', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) {
......@@ -84,12 +65,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File file = componentDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml');
if (file.existsSync()) {
file.deleteSync();
......@@ -109,7 +91,7 @@ void main() {
});
testUsingContext('androidComponentSetup AndroidManifest.xml does not exist', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) {
......@@ -121,12 +103,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File file = componentDir.childFile('build.gradle');
if (file.existsSync()) {
file.deleteSync();
......@@ -146,7 +129,7 @@ void main() {
});
testUsingContext('androidComponentSetup all files exist passes', () async {
final Directory templatesDir = env.flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final Directory templatesDir = flutterRootDir.childDirectory('templates').childDirectory('deferred_component');
final File buildGradleTemplate = templatesDir.childFile('build.gradle.tmpl');
final File androidManifestTemplate = templatesDir.childDirectory('src').childDirectory('main').childFile('AndroidManifest.xml.tmpl');
if (templatesDir.existsSync()) {
......@@ -158,12 +141,13 @@ void main() {
androidManifestTemplate.writeAsStringSync('fake AndroidManigest.xml template {{componentName}}', flush: true, mode: FileMode.append);
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
templatesDir: templatesDir,
);
final Directory componentDir = env.projectDir.childDirectory('android').childDirectory('component1');
final Directory componentDir = projectDir.childDirectory('android').childDirectory('component1');
final File buildGradle = componentDir.childFile('build.gradle');
if (buildGradle.existsSync()) {
buildGradle.deleteSync();
......@@ -189,11 +173,12 @@ void main() {
testWithoutContext('androidStringMapping creates new file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app');
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
if (stringRes.existsSync()) {
stringRes.deleteSync();
......@@ -239,7 +224,7 @@ void main() {
expect(logger.statusText.contains('Newly generated android files:\n'), true);
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
final File stringsOutput = env.projectDir
final File stringsOutput = projectDir
.childDirectory('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app')
......@@ -255,11 +240,12 @@ void main() {
testWithoutContext('androidStringMapping modifies strings file', () async {
final DeferredComponentsPrebuildValidator validator = DeferredComponentsPrebuildValidator(
env,
projectDir,
logger,
exitOnFail: false,
title: 'test check',
);
final Directory baseModuleDir = env.projectDir.childDirectory('android').childDirectory('app');
final Directory baseModuleDir = projectDir.childDirectory('android').childDirectory('app');
final File stringRes = baseModuleDir.childDirectory('src').childDirectory('main').childDirectory('res').childDirectory('values').childFile('strings.xml');
if (stringRes.existsSync()) {
stringRes.deleteSync();
......@@ -285,7 +271,7 @@ void main() {
expect(logger.statusText.contains('Modified android files:\n'), true);
expect(logger.statusText.contains('build/${DeferredComponentsValidator.kDeferredComponentsTempDirectory}/app/src/main/res/values/strings.xml\n'), true);
final File stringsOutput = env.projectDir
final File stringsOutput = projectDir
.childDirectory('build')
.childDirectory(DeferredComponentsValidator.kDeferredComponentsTempDirectory)
.childDirectory('app')
......
......@@ -176,6 +176,129 @@ flutter:
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets are parsed', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets are parsed regularly when splitDeferredAssets Disabled', () async {
globals.fs.file('.packages').createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.instance.createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: false);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 6);
expect(bundle.deferredComponentsEntries.isEmpty, true);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('deferred assets wildcard parsed', () async {
final File packageFile = globals.fs.file('.packages')..createSync();
globals.fs.file(globals.fs.path.join('assets', 'foo', 'bar.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'bar', 'barbie.txt')).createSync(recursive: true);
globals.fs.file(globals.fs.path.join('assets', 'wild', 'dash.txt')).createSync(recursive: true);
globals.fs.file('pubspec.yaml')
..createSync()
..writeAsStringSync(r'''
name: example
flutter:
assets:
- assets/foo/
deferred-components:
- name: component1
assets:
- assets/bar/barbie.txt
- assets/wild/
''');
final AssetBundle bundle = AssetBundleFactory.defaultInstance(
logger: globals.logger,
fileSystem: globals.fs,
platform: globals.platform,
splitDeferredAssets: true,
).createBundle();
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
// Expected assets:
// - asset manifest
// - font manifest
// - license file
// - assets/foo/bar.txt
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 2);
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), false);
// Simulate modifying the files by updating the filestat time manually.
globals.fs.file(globals.fs.path.join('assets', 'wild', 'fizz.txt'))
..createSync(recursive: true)
..setLastModifiedSync(packageFile.lastModifiedSync().add(const Duration(hours: 1)));
expect(bundle.needsBuild(manifestPath: 'pubspec.yaml'), true);
await bundle.build(manifestPath: 'pubspec.yaml', packagesPath: '.packages', deferredComponentsEnabled: true);
expect(bundle.entries.length, 4);
expect(bundle.deferredComponentsEntries.length, 1);
expect(bundle.deferredComponentsEntries['component1'].length, 3);
}, overrides: <Type, Generator>{
FileSystem: () => testFileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
testUsingContext('Failed directory delete shows message', () async {
......
......@@ -130,16 +130,16 @@ void main() {
wrapColumn: maxLineWidth,
);
expect(commandHelp.L.toString(), endsWith('\x1B[1;30m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), endsWith('\x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), endsWith('\x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), endsWith('\x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), endsWith('\x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), endsWith('\x1B[1;30m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), endsWith('\x1B[1;30m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), endsWith('\x1B[1;30m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), endsWith('\x1B[1;30m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.L.toString(), endsWith('\x1B[90m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), endsWith('\x1B[90m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), endsWith('\x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), endsWith('\x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), endsWith('\x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), endsWith('\x1B[90m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), endsWith('\x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), endsWith('\x1B[90m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), endsWith('\x1B[90m(debugDumpApp)\x1B[39m\x1b[22m'));
});
testWithoutContext('should not create a help text longer than maxLineWidth without ansi support', () {
......@@ -180,24 +180,24 @@ void main() {
wrapColumn: maxLineWidth,
);
expect(commandHelp.L.toString(), equals('\x1B[1mL\x1B[22m Dump layer tree to the console. \x1B[1;30m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), equals('\x1B[1mP\x1B[22m Toggle performance overlay. \x1B[1;30m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.L.toString(), equals('\x1B[1mL\x1B[22m Dump layer tree to the console. \x1B[90m(debugDumpLayerTree)\x1B[39m\x1b[22m'));
expect(commandHelp.P.toString(), equals('\x1B[1mP\x1B[22m Toggle performance overlay. \x1B[90m(WidgetsApp.showPerformanceOverlay)\x1B[39m\x1b[22m'));
expect(commandHelp.R.toString(), equals('\x1B[1mR\x1B[22m Hot restart.'));
expect(commandHelp.S.toString(), equals('\x1B[1mS\x1B[22m Dump accessibility tree in traversal order. \x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[1;30m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[1;30m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.S.toString(), equals('\x1B[1mS\x1B[22m Dump accessibility tree in traversal order. \x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.U.toString(), equals('\x1B[1mU\x1B[22m Dump accessibility tree in inverse hit test order. \x1B[90m(debugDumpSemantics)\x1B[39m\x1b[22m'));
expect(commandHelp.a.toString(), equals('\x1B[1ma\x1B[22m Toggle timeline events for all widget build methods. \x1B[90m(debugProfileWidgetBuilds)\x1B[39m\x1b[22m'));
expect(commandHelp.d.toString(), equals('\x1B[1md\x1B[22m Detach (terminate "flutter run" but leave application running).'));
expect(commandHelp.g.toString(), equals('\x1B[1mg\x1B[22m Run source code generators.'));
expect(commandHelp.h.toString(), equals('\x1B[1mh\x1B[22m Repeat this help message.'));
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[1;30m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[1;30m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), equals('\x1B[1mp\x1B[22m Toggle the display of construction lines. \x1B[1;30m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.i.toString(), equals('\x1B[1mi\x1B[22m Toggle widget inspector. \x1B[90m(WidgetsApp.showWidgetInspectorOverride)\x1B[39m\x1b[22m'));
expect(commandHelp.o.toString(), equals('\x1B[1mo\x1B[22m Simulate different operating systems. \x1B[90m(defaultTargetPlatform)\x1B[39m\x1b[22m'));
expect(commandHelp.p.toString(), equals('\x1B[1mp\x1B[22m Toggle the display of construction lines. \x1B[90m(debugPaintSizeEnabled)\x1B[39m\x1b[22m'));
expect(commandHelp.q.toString(), equals('\x1B[1mq\x1B[22m Quit (terminate the application on the device).'));
expect(commandHelp.r.toString(), equals('\x1B[1mr\x1B[22m Hot reload. $fire$fire$fire'));
expect(commandHelp.s.toString(), equals('\x1B[1ms\x1B[22m Save a screenshot to flutter.png.'));
expect(commandHelp.t.toString(), equals('\x1B[1mt\x1B[22m Dump rendering tree to the console. \x1B[1;30m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.t.toString(), equals('\x1B[1mt\x1B[22m Dump rendering tree to the console. \x1B[90m(debugDumpRenderTree)\x1B[39m\x1b[22m'));
expect(commandHelp.v.toString(), equals('\x1B[1mv\x1B[22m Launch DevTools.'));
expect(commandHelp.w.toString(), equals('\x1B[1mw\x1B[22m Dump widget hierarchy to the console. \x1B[1;30m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.w.toString(), equals('\x1B[1mw\x1B[22m Dump widget hierarchy to the console. \x1B[90m(debugDumpApp)\x1B[39m\x1b[22m'));
expect(commandHelp.z.toString(), equals('\x1B[1mz\x1B[22m Toggle elevation checker.'));
});
......
......@@ -34,7 +34,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
......@@ -44,11 +44,10 @@ void main() {
environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);
......@@ -68,7 +67,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
......@@ -78,11 +77,10 @@ void main() {
environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);
......@@ -101,7 +99,7 @@ void main() {
buildDir: fileSystem.directory('build')..createSync(),
projectDir: fileSystem.directory('project')..createSync(),
defines: <String, String>{
kSplitAot: 'true',
kDeferredComponents: 'true',
},
artifacts: null,
processManager: null,
......@@ -111,11 +109,10 @@ void main() {
environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
final CompositeTarget androidDefBundle = CompositeTarget(<Target>[androidAotBundle]);
final CompositeTarget compositeTarget = CompositeTarget(<Target>[androidDefBundle]);
final AndroidAotDeferredComponentsBundle androidDefBundle = AndroidAotDeferredComponentsBundle(androidAotBundle);
final DeferredComponentsGenSnapshotValidatorTarget validatorTarget = DeferredComponentsGenSnapshotValidatorTarget(
dependency: compositeTarget,
abis: <String>['arm64-v8a'],
deferredComponentsDependencies: <AndroidAotDeferredComponentsBundle>[androidDefBundle],
nonDeferredComponentsDependencies: <Target>[],
title: 'test checks',
exitOnFail: false,
);
......
// 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 @@
import 'package:file/file.dart';
import '../test_utils.dart';
import 'deferred_components_config.dart';
const String _kDefaultHtml = '''
<html>
......@@ -26,6 +27,7 @@ abstract class Project {
String get main;
String get test => null;
String get generatedFile => null;
DeferredComponentsConfig get deferredComponents => null;
Uri get mainDart => Uri.parse('package:test/main.dart');
......@@ -41,6 +43,9 @@ abstract class Project {
if (generatedFile != null) {
writeFile(fileSystem.path.join(dir.path, '.dart_tool', 'flutter_gen', 'flutter_gen.dart'), generatedFile);
}
if (deferredComponents != null) {
deferredComponents.setUpIn(dir);
}
writeFile(fileSystem.path.join(dir.path, 'web', 'index.html'), _kDefaultHtml);
writePackages(dir.path);
await getPackages(dir.path);
......
......@@ -40,6 +40,13 @@ void writeFile(String path, String content) {
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
}
void writeBytesFile(String path, List<int> content) {
fileSystem.file(path)
..createSync(recursive: true)
..writeAsBytesSync(content)
..setLastModifiedSync(DateTime.now().add(const Duration(seconds: 10)));
}
void writePackages(String folder) {
writeFile(fileSystem.path.join(folder, '.packages'), '''
test:${fileSystem.path.join(fileSystem.currentDirectory.path, 'lib')}/
......
......@@ -35,6 +35,8 @@ class FakeAndroidBuilder implements AndroidBuilder {
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
}) async {}
}
......
......@@ -90,6 +90,15 @@ String getFlutterRoot() {
return path.normalize(path.join(toolsPath, '..', '..'));
}
/// Gets the path to the root of the Android SDK from the environment variable.
String getAndroidSdkRoot() {
const Platform platform = LocalPlatform();
if (platform.environment.containsKey('ANDROID_SDK_ROOT')) {
return platform.environment['ANDROID_SDK_ROOT'];
}
throw StateError('ANDROID_SDK_ROOT environment varible not set');
}
CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) {
final FlutterCommandRunner runner = TestFlutterCommandRunner();
if (command != null) {
......
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