Unverified Commit df3505c1 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Improve performance of build APK (~50%) by running gen_snapshot concurrently (#44534)

parent 6e5769d4
...@@ -111,8 +111,8 @@ Future<void> main() async { ...@@ -111,8 +111,8 @@ Future<void> main() async {
// When the platform-target isn't specified, we generate the snapshots // When the platform-target isn't specified, we generate the snapshots
// for arm and arm64. // for arm and arm64.
final List<String> targetPlatforms = <String>[ final List<String> targetPlatforms = <String>[
'android-arm', 'arm64-v8a',
'android-arm64', 'armeabi-v7a',
]; ];
for (final String targetPlatform in targetPlatforms) { for (final String targetPlatform in targetPlatforms) {
final String androidArmSnapshotPath = path.join( final String androidArmSnapshotPath = path.join(
......
...@@ -216,12 +216,12 @@ Future<void> main() async { ...@@ -216,12 +216,12 @@ Future<void> main() async {
} }
final String analyticsOutput = analyticsOutputFile.readAsStringSync(); final String analyticsOutput = analyticsOutputFile.readAsStringSync();
if (!analyticsOutput.contains('cd24: android-arm64') if (!analyticsOutput.contains('cd24: android')
|| !analyticsOutput.contains('cd25: true') || !analyticsOutput.contains('cd25: true')
|| !analyticsOutput.contains('viewName: assemble')) { || !analyticsOutput.contains('viewName: assemble')) {
return TaskResult.failure( return TaskResult.failure(
'Building outer app produced the following analytics: "$analyticsOutput"' 'Building outer app produced the following analytics: "$analyticsOutput"'
'but not the expected strings: "cd24: android-arm64", "cd25: true" and ' 'but not the expected strings: "cd24: android", "cd25: true" and '
'"viewName: assemble"' '"viewName: assemble"'
); );
} }
......
...@@ -396,7 +396,7 @@ Future<ProcessResult> _resultOfGradleTask({String workingDirectory, String task, ...@@ -396,7 +396,7 @@ Future<ProcessResult> _resultOfGradleTask({String workingDirectory, String task,
String validateSnapshotDependency(FlutterProject project, String expectedTarget) { String validateSnapshotDependency(FlutterProject project, String expectedTarget) {
final File snapshotBlob = File( final File snapshotBlob = File(
path.join(project.rootPath, 'build', 'app', 'intermediates', path.join(project.rootPath, 'build', 'app', 'intermediates',
'flutter', 'debug', 'android-arm', 'flutter_build.d')); 'flutter', 'debug', 'flutter_build.d'));
assert(snapshotBlob.existsSync()); assert(snapshotBlob.existsSync());
final String contentSnapshot = snapshotBlob.readAsStringSync(); final String contentSnapshot = snapshotBlob.readAsStringSync();
......
...@@ -544,38 +544,36 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -544,38 +544,36 @@ class FlutterPlugin implements Plugin<Project> {
} }
} }
String variantBuildMode = buildModeFor(variant.buildType) String variantBuildMode = buildModeFor(variant.buildType)
List<FlutterTask> compileTasks = targetPlatforms.collect { targetArch -> String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')]) FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
project.tasks.create(name: taskName, type: FlutterTask) { flutterRoot this.flutterRoot
flutterRoot this.flutterRoot flutterExecutable this.flutterExecutable
flutterExecutable this.flutterExecutable buildMode variantBuildMode
buildMode variantBuildMode localEngine this.localEngine
localEngine this.localEngine localEngineSrcPath this.localEngineSrcPath
localEngineSrcPath this.localEngineSrcPath targetPath target
abi PLATFORM_ARCH_MAP[targetArch] verbose isVerbose()
targetPath target fileSystemRoots fileSystemRootsValue
verbose isVerbose() fileSystemScheme fileSystemSchemeValue
fileSystemRoots fileSystemRootsValue trackWidgetCreation trackWidgetCreationValue
fileSystemScheme fileSystemSchemeValue targetPlatformValues = targetPlatforms
trackWidgetCreation trackWidgetCreationValue sourceDir project.file(project.flutter.source)
targetPlatform targetArch intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")
sourceDir project.file(project.flutter.source) extraFrontEndOptions extraFrontEndOptionsValue
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}") extraGenSnapshotOptions extraGenSnapshotOptionsValue
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
}
} }
File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar") File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
destinationDir libJar.parentFile destinationDir libJar.parentFile
archiveName libJar.name archiveName libJar.name
dependsOn compileTasks dependsOn compileTask
compileTasks.each { compileTask -> targetPlatforms.each { targetPlatform ->
from(compileTask.intermediateDir) { String abi = PLATFORM_ARCH_MAP[targetPlatform]
include '*.so' from("${compileTask.intermediateDir}/${abi}") {
include "*.so"
// Move `app.so` to `lib/<abi>/libapp.so` // Move `app.so` to `lib/<abi>/libapp.so`
rename { String filename -> rename { String filename ->
return "lib/${compileTask.abi}/lib${filename}" return "lib/${abi}/lib${filename}"
} }
} }
} }
...@@ -594,11 +592,8 @@ class FlutterPlugin implements Plugin<Project> { ...@@ -594,11 +592,8 @@ class FlutterPlugin implements Plugin<Project> {
name: "copyFlutterAssets${variant.name.capitalize()}", name: "copyFlutterAssets${variant.name.capitalize()}",
type: Copy, type: Copy,
) { ) {
dependsOn compileTasks dependsOn compileTask
compileTasks.each { flutterTask -> with compileTask.assets
// Add flutter_assets.
with flutterTask.assets
}
if (isUsedAsSubproject) { if (isUsedAsSubproject) {
dependsOn packageAssets dependsOn packageAssets
dependsOn cleanPackageAssets dependsOn cleanPackageAssets
...@@ -687,9 +682,7 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -687,9 +682,7 @@ abstract class BaseFlutterTask extends DefaultTask {
@Input @Input
Boolean trackWidgetCreation Boolean trackWidgetCreation
@Optional @Input @Optional @Input
String targetPlatform List<String> targetPlatformValues
@Input
String abi
File sourceDir File sourceDir
File intermediateDir File intermediateDir
@Optional @Input @Optional @Input
...@@ -713,6 +706,16 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -713,6 +706,16 @@ abstract class BaseFlutterTask extends DefaultTask {
intermediateDir.mkdirs() intermediateDir.mkdirs()
// Compute the rule name for flutter assemble. To speed up builds that contain
// multiple ABIs, the target name is used to communicate which ones are required
// rather than the TargetPlatform. This allows multiple builds to share the same
// cache.
String[] ruleNames;
if (buildMode == "debug") {
ruleNames = ["debug_android_application"]
} else {
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
}
project.exec { project.exec {
executable flutterExecutable.absolutePath executable flutterExecutable.absolutePath
workingDir sourceDir workingDir sourceDir
...@@ -729,7 +732,7 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -729,7 +732,7 @@ abstract class BaseFlutterTask extends DefaultTask {
args "--depfile", "${intermediateDir}/flutter_build.d" args "--depfile", "${intermediateDir}/flutter_build.d"
args "--output", "${intermediateDir}" args "--output", "${intermediateDir}"
args "-dTargetFile=${targetPath}" args "-dTargetFile=${targetPath}"
args "-dTargetPlatform=${targetPlatform}" args "-dTargetPlatform=android"
args "-dBuildMode=${buildMode}" args "-dBuildMode=${buildMode}"
if (extraFrontEndOptions != null) { if (extraFrontEndOptions != null) {
args "-dExtraFrontEndOptions=${extraFrontEndOptions}" args "-dExtraFrontEndOptions=${extraFrontEndOptions}"
...@@ -737,15 +740,7 @@ abstract class BaseFlutterTask extends DefaultTask { ...@@ -737,15 +740,7 @@ abstract class BaseFlutterTask extends DefaultTask {
if (extraGenSnapshotOptions != null) { if (extraGenSnapshotOptions != null) {
args "-dExtraGenSnapshotOptions=${extraGenSnapshotOptions}" args "-dExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
} }
if (buildMode == "profile") { args ruleNames
args "profile_android_application"
}
if (buildMode == "release") {
args "release_android_application"
}
if (buildMode == "debug") {
args "debug_android_application"
}
} }
} }
} }
...@@ -768,7 +763,9 @@ class FlutterTask extends BaseFlutterTask { ...@@ -768,7 +763,9 @@ class FlutterTask extends BaseFlutterTask {
from "${intermediateDir}" from "${intermediateDir}"
if (buildMode == 'release' || buildMode == 'profile') { if (buildMode == 'release' || buildMode == 'profile') {
include "app.so" targetPlatformValues.each {
include "${PLATFORM_ARCH_MAP[targetArch]}/app.so"
}
} }
} }
} }
......
...@@ -36,6 +36,7 @@ class ApplicationPackageFactory { ...@@ -36,6 +36,7 @@ class ApplicationPackageFactory {
File applicationBinary, File applicationBinary,
}) async { }) async {
switch (platform) { switch (platform) {
case TargetPlatform.android:
case TargetPlatform.android_arm: case TargetPlatform.android_arm:
case TargetPlatform.android_arm64: case TargetPlatform.android_arm64:
case TargetPlatform.android_x64: case TargetPlatform.android_x64:
...@@ -415,6 +416,7 @@ class ApplicationPackageStore { ...@@ -415,6 +416,7 @@ class ApplicationPackageStore {
Future<ApplicationPackage> getPackageForPlatform(TargetPlatform platform) async { Future<ApplicationPackage> getPackageForPlatform(TargetPlatform platform) async {
switch (platform) { switch (platform) {
case TargetPlatform.android:
case TargetPlatform.android_arm: case TargetPlatform.android_arm:
case TargetPlatform.android_arm64: case TargetPlatform.android_arm64:
case TargetPlatform.android_x64: case TargetPlatform.android_x64:
......
...@@ -393,6 +393,9 @@ class CachedArtifacts extends Artifacts { ...@@ -393,6 +393,9 @@ class CachedArtifacts extends Artifacts {
assert(mode != null, 'Need to specify a build mode for platform $platform.'); assert(mode != null, 'Need to specify a build mode for platform $platform.');
final String suffix = mode != BuildMode.debug ? '-${snakeCase(getModeName(mode), '-')}' : ''; final String suffix = mode != BuildMode.debug ? '-${snakeCase(getModeName(mode), '-')}' : '';
return fs.path.join(engineDir, platformName + suffix); return fs.path.join(engineDir, platformName + suffix);
case TargetPlatform.android:
assert(false, 'cannot use TargetPlatform.android to look up artifacts');
return null;
} }
assert(false, 'Invalid platform $platform.'); assert(false, 'Invalid platform $platform.');
return null; return null;
......
...@@ -158,11 +158,11 @@ class AOTSnapshotter { ...@@ -158,11 +158,11 @@ class AOTSnapshotter {
genSnapshotArgs.add(mainPath); genSnapshotArgs.add(mainPath);
// Verify that all required inputs exist. // TODO(jonahwilliams): fully remove input checks once all callers are
// using assemble.
final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p)); final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
if (missingInputs.isNotEmpty) { if (missingInputs.isNotEmpty) {
printError('Missing input files: $missingInputs from $inputPaths'); printTrace('Missing input files: $missingInputs from $inputPaths');
return 1;
} }
final SnapshotType snapshotType = SnapshotType(platform, buildMode); final SnapshotType snapshotType = SnapshotType(platform, buildMode);
...@@ -197,6 +197,7 @@ class AOTSnapshotter { ...@@ -197,6 +197,7 @@ class AOTSnapshotter {
// Write path to gen_snapshot, since snapshots have to be re-generated when we roll // Write path to gen_snapshot, since snapshots have to be re-generated when we roll
// the Dart SDK. // the Dart SDK.
// TODO(jonahwilliams): remove when all callers are using assemble.
final String genSnapshotPath = GenSnapshot.getSnapshotterPath(snapshotType); final String genSnapshotPath = GenSnapshot.getSnapshotterPath(snapshotType);
outputDir.childFile('gen_snapshot.d').writeAsStringSync('gen_snapshot.d: $genSnapshotPath\n'); outputDir.childFile('gen_snapshot.d').writeAsStringSync('gen_snapshot.d: $genSnapshotPath\n');
......
...@@ -260,7 +260,8 @@ String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String build ...@@ -260,7 +260,8 @@ String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String build
} }
return tmpBuildName; return tmpBuildName;
} }
if (targetPlatform == TargetPlatform.android_arm || if (targetPlatform == TargetPlatform.android ||
targetPlatform == TargetPlatform.android_arm ||
targetPlatform == TargetPlatform.android_arm64 || targetPlatform == TargetPlatform.android_arm64 ||
targetPlatform == TargetPlatform.android_x64 || targetPlatform == TargetPlatform.android_x64 ||
targetPlatform == TargetPlatform.android_x86) { targetPlatform == TargetPlatform.android_x86) {
...@@ -306,10 +307,7 @@ String getNameForHostPlatform(HostPlatform platform) { ...@@ -306,10 +307,7 @@ String getNameForHostPlatform(HostPlatform platform) {
} }
enum TargetPlatform { enum TargetPlatform {
android_arm, android,
android_arm64,
android_x64,
android_x86,
ios, ios,
darwin_x64, darwin_x64,
linux_x64, linux_x64,
...@@ -318,6 +316,14 @@ enum TargetPlatform { ...@@ -318,6 +316,14 @@ enum TargetPlatform {
fuchsia_x64, fuchsia_x64,
tester, tester,
web_javascript, web_javascript,
// The arch specific android target platforms are soft-depreacted.
// Instead of using TargetPlatform as a combination arch + platform
// the code will be updated to carry arch information in [DarwinArch]
// and [AndroidArch].
android_arm,
android_arm64,
android_x64,
android_x86,
} }
/// iOS and macOS target device architecture. /// iOS and macOS target device architecture.
...@@ -329,6 +335,7 @@ enum DarwinArch { ...@@ -329,6 +335,7 @@ enum DarwinArch {
x86_64, x86_64,
} }
// TODO(jonahwilliams): replace all android TargetPlatform usage with AndroidArch.
enum AndroidArch { enum AndroidArch {
armeabi_v7a, armeabi_v7a,
arm64_v8a, arm64_v8a,
...@@ -391,6 +398,8 @@ String getNameForTargetPlatform(TargetPlatform platform) { ...@@ -391,6 +398,8 @@ String getNameForTargetPlatform(TargetPlatform platform) {
return 'flutter-tester'; return 'flutter-tester';
case TargetPlatform.web_javascript: case TargetPlatform.web_javascript:
return 'web-javascript'; return 'web-javascript';
case TargetPlatform.android:
return 'android';
} }
assert(false); assert(false);
return null; return null;
...@@ -398,6 +407,8 @@ String getNameForTargetPlatform(TargetPlatform platform) { ...@@ -398,6 +407,8 @@ String getNameForTargetPlatform(TargetPlatform platform) {
TargetPlatform getTargetPlatformForName(String platform) { TargetPlatform getTargetPlatformForName(String platform) {
switch (platform) { switch (platform) {
case 'android':
return TargetPlatform.android;
case 'android-arm': case 'android-arm':
return TargetPlatform.android_arm; return TargetPlatform.android_arm;
case 'android-arm64': case 'android-arm64':
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../../artifacts.dart'; import '../../artifacts.dart';
import '../../base/build.dart';
import '../../base/file_system.dart'; import '../../base/file_system.dart';
import '../../build_info.dart'; import '../../build_info.dart';
import '../../globals.dart'; import '../../globals.dart';
...@@ -124,3 +125,150 @@ class ReleaseAndroidApplication extends CopyFlutterAotBundle { ...@@ -124,3 +125,150 @@ class ReleaseAndroidApplication extends CopyFlutterAotBundle {
AotAndroidAssetBundle(), AotAndroidAssetBundle(),
]; ];
} }
/// Generate an ELF binary from a dart kernel file in release mode.
///
/// This rule implementation outputs the generated so to a unique location
/// based on the Android ABI. This allows concurrent invocations of gen_snapshot
/// to run simultaneously.
///
/// The name of an instance of this rule would be 'android_aot_profile_android-x64'
/// and is relied upon by flutter.gradle to match the correct rule.
///
/// It will produce an 'app.so` in the build directory under a folder named with
/// the matching Android ABI.
class AndroidAot extends AotElfBase {
/// Create an [AndroidAot] implementation for a given [targetPlatform] and [buildMode].
const AndroidAot(this.targetPlatform, this.buildMode);
/// The name of the produced Android ABI.
String get _androidAbiName {
return getNameForAndroidArch(
getAndroidArchForName(getNameForTargetPlatform(targetPlatform)));
}
@override
String get name => 'android_aot_${getNameForBuildMode(buildMode)}_'
'${getNameForTargetPlatform(targetPlatform)}';
/// The specific Android ABI we are building for.
final TargetPlatform targetPlatform;
/// The selected build mode.
///
/// This is restricted to [BuildMode.profile] or [BuildMode.relese].
final BuildMode buildMode;
@override
List<Source> get inputs => <Source>[
const Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/dart.dart'),
const Source.pattern('{BUILD_DIR}/app.dill'),
const Source.pattern('{PROJECT_DIR}/.packages'),
const Source.artifact(Artifact.engineDartBinary),
const Source.artifact(Artifact.skyEnginePath),
Source.artifact(Artifact.genSnapshot,
mode: buildMode,
platform: targetPlatform,
),
];
@override
List<Source> get outputs => <Source>[
Source.pattern('{BUILD_DIR}/$_androidAbiName/app.so'),
];
@override
List<Target> get dependencies => const <Target>[
KernelSnapshot(),
];
@override
Future<void> build(Environment environment) async {
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
final Directory output = environment.buildDir.childDirectory(_androidAbiName);
if (environment.defines[kBuildMode] == null) {
throw MissingDefineException(kBuildMode, 'aot_elf');
}
if (!output.existsSync()) {
output.createSync(recursive: true);
}
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
final int snapshotExitCode = await snapshotter.build(
platform: targetPlatform,
buildMode: buildMode,
mainPath: environment.buildDir.childFile('app.dill').path,
packagesPath: environment.projectDir.childFile('.packages').path,
outputPath: output.path,
bitcode: false,
);
if (snapshotExitCode != 0) {
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
}
}
}
// AndroidAot instances used by the bundle rules below.
const Target androidArmProfile = AndroidAot(TargetPlatform.android_arm, BuildMode.profile);
const Target androidArm64Profile = AndroidAot(TargetPlatform.android_arm64, BuildMode.profile);
const Target androidx64Profile = AndroidAot(TargetPlatform.android_x64, BuildMode.profile);
const Target androidArmRelease = AndroidAot(TargetPlatform.android_arm, BuildMode.release);
const Target androidArm64Release = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const Target androidx64Release = AndroidAot(TargetPlatform.android_x64, BuildMode.release);
/// A rule paired with [AndroidAot] that copies the produced so files into the output directory.
class AndroidAotBundle extends Target {
/// Create an [AndroidAotBundle] implementation for a given [targetPlatform] and [buildMode].
const AndroidAotBundle(this.dependency);
/// The [AndroidAot] instance this bundle rule depends on.
final AndroidAot dependency;
/// The name of the produced Android ABI.
String get _androidAbiName {
return getNameForAndroidArch(
getAndroidArchForName(getNameForTargetPlatform(dependency.targetPlatform)));
}
@override
String get name => 'android_aot_bundle_${getNameForBuildMode(dependency.buildMode)}_'
'${getNameForTargetPlatform(dependency.targetPlatform)}';
@override
List<Source> get inputs => <Source>[
Source.pattern('{BUILD_DIR}/$_androidAbiName/app.so'),
];
// flutter.gradle has been updated to correctly consume it.
@override
List<Source> get outputs => <Source>[
Source.pattern('{OUTPUT_DIR}/$_androidAbiName/app.so'),
];
@override
List<Target> get dependencies => <Target>[
dependency,
const AotAndroidAssetBundle(),
];
@override
Future<void> build(Environment environment) async {
final File outputFile = environment.buildDir
.childDirectory(_androidAbiName)
.childFile('app.so');
final Directory outputDirectory = environment.outputDir
.childDirectory(_androidAbiName);
if (!outputDirectory.existsSync()) {
outputDirectory.createSync(recursive: true);
}
outputFile.copySync(outputDirectory.childFile('app.so').path);
}
}
// AndroidBundleAot instances.
const Target androidArmProfileBundle = AndroidAotBundle(androidArmProfile);
const Target androidArm64ProfileBundle = AndroidAotBundle(androidArm64Profile);
const Target androidx64ProfileBundle = AndroidAotBundle(androidx64Profile);
const Target androidArmReleaseBundle = AndroidAotBundle(androidArmRelease);
const Target androidArm64ReleaseBundle = AndroidAotBundle(androidArm64Release);
const Target androidx64ReleaseBundle = AndroidAotBundle(androidx64Release);
...@@ -43,6 +43,13 @@ const List<Target> _kDefaultTargets = <Target>[ ...@@ -43,6 +43,13 @@ const List<Target> _kDefaultTargets = <Target>[
ReleaseCopyFlutterAotBundle(), ReleaseCopyFlutterAotBundle(),
ProfileCopyFlutterAotBundle(), ProfileCopyFlutterAotBundle(),
CopyFlutterBundle(), CopyFlutterBundle(),
// Android ABI specific AOT rules.
androidArmProfileBundle,
androidArm64ProfileBundle,
androidx64ProfileBundle,
androidArmReleaseBundle,
androidArm64ReleaseBundle,
androidx64ReleaseBundle,
]; ];
/// Assemble provides a low level API to interact with the flutter tool build /// Assemble provides a low level API to interact with the flutter tool build
...@@ -99,18 +106,25 @@ class AssembleCommand extends FlutterCommand { ...@@ -99,18 +106,25 @@ class AssembleCommand extends FlutterCommand {
return const <CustomDimensions, String>{}; return const <CustomDimensions, String>{};
} }
/// The target we are building. /// The target(s) we are building.
Target get target { List<Target> get targets {
if (argResults.rest.isEmpty) { if (argResults.rest.isEmpty) {
throwToolExit('missing target name for flutter assemble.'); throwToolExit('missing target name for flutter assemble.');
} }
final String name = argResults.rest.first; final String name = argResults.rest.first;
final Target result = _kDefaultTargets final Map<String, Target> targetMap = <String, Target>{
.firstWhere((Target target) => target.name == name, orElse: () => null); for (Target target in _kDefaultTargets)
if (result == null) { target.name: target
};
final List<Target> results = <Target>[
for (String targetName in argResults.rest)
if (targetMap.containsKey(targetName))
targetMap[targetName]
];
if (results.isEmpty) {
throwToolExit('No target named "$name" defined.'); throwToolExit('No target named "$name" defined.');
} }
return result; return results;
} }
/// The environmental configuration for a build invocation. /// The environmental configuration for a build invocation.
...@@ -151,8 +165,12 @@ class AssembleCommand extends FlutterCommand { ...@@ -151,8 +165,12 @@ class AssembleCommand extends FlutterCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final List<Target> targets = this.targets;
final Target target = targets.length == 1 ? targets.single : _CompositeTarget(targets);
final BuildResult result = await buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig( final BuildResult result = await buildSystem.build(target, environment, buildSystemConfig: BuildSystemConfig(
resourcePoolSize: argResults.wasParsed('resource-pool-size') ? int.parse(stringArg('resource-pool-size')) : null, resourcePoolSize: argResults.wasParsed('resource-pool-size')
? int.tryParse(argResults['resource-pool-size'])
: null,
)); ));
if (!result.success) { if (!result.success) {
for (ExceptionMeasurement measurement in result.exceptions.values) { for (ExceptionMeasurement measurement in result.exceptions.values) {
...@@ -197,3 +215,22 @@ void writeListIfChanged(List<File> files, String path) { ...@@ -197,3 +215,22 @@ void writeListIfChanged(List<File> files, String path) {
file.writeAsStringSync(newContents); file.writeAsStringSync(newContents);
} }
} }
class _CompositeTarget extends Target {
_CompositeTarget(this.dependencies);
@override
final List<Target> dependencies;
@override
String get name => '_composite';
@override
Future<void> build(Environment environment) async { }
@override
List<Source> get inputs => <Source>[];
@override
List<Source> get outputs => <Source>[];
}
...@@ -766,6 +766,7 @@ mixin TargetPlatformBasedDevelopmentArtifacts on FlutterCommand { ...@@ -766,6 +766,7 @@ mixin TargetPlatformBasedDevelopmentArtifacts on FlutterCommand {
// if none is supported // if none is supported
DevelopmentArtifact _artifactFromTargetPlatform(TargetPlatform targetPlatform) { DevelopmentArtifact _artifactFromTargetPlatform(TargetPlatform targetPlatform) {
switch (targetPlatform) { switch (targetPlatform) {
case TargetPlatform.android:
case TargetPlatform.android_arm: case TargetPlatform.android_arm:
case TargetPlatform.android_arm64: case TargetPlatform.android_arm64:
case TargetPlatform.android_x64: case TargetPlatform.android_x64:
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/build.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/build_system/targets/android.dart'; import 'package:flutter_tools/src/build_system/targets/android.dart';
import 'package:flutter_tools/src/build_system/targets/dart.dart'; import 'package:flutter_tools/src/build_system/targets/dart.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:mockito/mockito.dart';
import '../../../src/common.dart'; import '../../../src/common.dart';
import '../../../src/testbed.dart'; import '../../../src/testbed.dart';
...@@ -84,4 +86,68 @@ void main() { ...@@ -84,4 +86,68 @@ void main() {
expect(fs.file(fs.path.join('out', 'app.so')).existsSync(), true); expect(fs.file(fs.path.join('out', 'app.so')).existsSync(), true);
}); });
testbed.test('AndroidAot can build provided target platform', () async {
final Environment environment = Environment(
outputDir: fs.directory('out')..createSync(),
projectDir: fs.currentDirectory,
buildDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: 'release',
}
);
when(genSnapshot.run(
snapshotType: anyNamed('snapshotType'),
darwinArch: anyNamed('darwinArch'),
additionalArgs: anyNamed('additionalArgs'),
)).thenAnswer((Invocation invocation) async {
return 0;
});
environment.buildDir.createSync(recursive: true);
environment.buildDir.childFile('app.dill').createSync();
environment.projectDir.childFile('.packages')
.writeAsStringSync('sky_engine:file:///\n');
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
await androidAot.build(environment);
final SnapshotType snapshotType = verify(genSnapshot.run(
snapshotType: captureAnyNamed('snapshotType'),
darwinArch: anyNamed('darwinArch'),
additionalArgs: anyNamed('additionalArgs')
)).captured.single;
expect(snapshotType.platform, TargetPlatform.android_arm64);
expect(snapshotType.mode, BuildMode.release);
}, overrides: <Type, Generator>{
GenSnapshot: () => MockGenSnapshot(),
});
testbed.test('android aot bundle copies so from abi directory', () async {
final Environment environment = Environment(
outputDir: fs.directory('out')..createSync(),
projectDir: fs.currentDirectory,
buildDir: fs.currentDirectory,
defines: <String, String>{
kBuildMode: 'release',
}
);
environment.buildDir.createSync(recursive: true);
const AndroidAot androidAot = AndroidAot(TargetPlatform.android_arm64, BuildMode.release);
const AndroidAotBundle androidAotBundle = AndroidAotBundle(androidAot);
// Create required files.
environment.buildDir
.childDirectory('arm64-v8a')
.childFile('app.so')
.createSync(recursive: true);
await androidAotBundle.build(environment);
expect(environment.outputDir
.childDirectory('arm64-v8a')
.childFile('app.so').existsSync(), true);
});
} }
class MockGenSnapshot extends Mock implements GenSnapshot {}
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