Support ARM 32 and 64 bits in app bundles

This commit is contained in:
Emmanuel Garcia 2019-05-20 13:09:20 -07:00 committed by GitHub
parent c2a93bd545
commit 90f38907d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 283 additions and 106 deletions

View file

@ -63,6 +63,51 @@ Future<void> main() async {
await runProjectTest((FlutterProject project) async {
section('gradlew assembleRelease');
await project.runGradleTask('assembleRelease');
// When the platform-target isn't specified, we generate the snapshots
// for arm and arm64.
final List<String> targetPlatforms = <String>[
'android-arm',
'android-arm64'
];
for (final String targetPlatform in targetPlatforms) {
final String androidArmSnapshotPath = path.join(
project.rootPath,
'build',
'app',
'intermediates',
'flutter',
'release',
targetPlatform);
final String isolateSnapshotData =
path.join(androidArmSnapshotPath, 'isolate_snapshot_data');
if (!File(isolateSnapshotData).existsSync()) {
throw TaskResult.failure(
'Snapshot doesn\'t exist: $isolateSnapshotData');
}
final String isolateSnapshotInstr =
path.join(androidArmSnapshotPath, 'isolate_snapshot_instr');
if (!File(isolateSnapshotInstr).existsSync()) {
throw TaskResult.failure(
'Snapshot doesn\'t exist: $isolateSnapshotInstr');
}
final String vmSnapshotData =
path.join(androidArmSnapshotPath, 'vm_snapshot_data');
if (!File(isolateSnapshotData).existsSync()) {
throw TaskResult.failure(
'Snapshot doesn\'t exist: $vmSnapshotData');
}
final String vmSnapshotInstr =
path.join(androidArmSnapshotPath, 'vm_snapshot_instr');
if (!File(isolateSnapshotData).existsSync()) {
throw TaskResult.failure(
'Snapshot doesn\'t exist: $vmSnapshotInstr');
}
}
});
await runProjectTest((FlutterProject project) async {
@ -311,7 +356,7 @@ class _Dependencies {
String _validateSnapshotDependency(FlutterProject project, String expectedTarget) {
final _Dependencies deps = _Dependencies(
path.join(project.rootPath, 'build', 'app', 'intermediates',
'flutter', 'debug', 'snapshot_blob.bin.d'));
'flutter', 'debug', 'android-arm', 'snapshot_blob.bin.d'));
return deps.target == expectedTarget ? null :
'Dependency file should have $expectedTarget as target. Instead has ${deps.target}';
}

View file

@ -36,14 +36,13 @@ android {
apply plugin: FlutterPlugin
class FlutterPlugin implements Plugin<Project> {
private Path baseEnginePath
private File flutterRoot
private File flutterExecutable
private String localEngine
private String localEngineSrcPath
private Properties localProperties
private File flutterJar
private File flutterX86Jar
private File debugFlutterJar
private File profileFlutterJar
private File releaseFlutterJar
@ -57,6 +56,22 @@ class FlutterPlugin implements Plugin<Project> {
// to match.
static final String flutterBuildPrefix = "flutterBuild"
// The platforms (or CPU architectures) for which native code is generated.
static final Map allTargetPlatforms = [
'android-arm': 'armeabi-v7a',
'android-arm64': 'arm64-v8a',
'android-x64': 'x86_64',
'android-x86': 'x86',
]
// Supports ARM 32 and 64 bits.
// When splits are enabled, multiple APKs are generated per each CPU architecture,
// which helps decrease the size of each APK.
static final Set allArmPlatforms = [
'android-arm',
'android-arm64'
]
private Properties readPropertiesIfExist(File propertiesFile) {
Properties result = new Properties()
if (propertiesFile.exists()) {
@ -65,6 +80,20 @@ class FlutterPlugin implements Plugin<Project> {
return result
}
private String getTargetPlatform(Project project) {
if (project.hasProperty('target-platform')) {
return project.property('target-platform')
}
return 'android-arm-all';
}
private Boolean getBuildShareLibrary(Project project) {
if (project.hasProperty('build-shared-library')) {
return project.property('build-shared-library').toBoolean()
}
return false;
}
private String resolveProperty(Project project, String name, String defaultValue) {
if (localProperties == null) {
localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
@ -84,6 +113,23 @@ class FlutterPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create("flutter", FlutterExtension)
project.afterEvaluate this.&addFlutterTask
allTargetPlatforms.each { currentTargetPlatformValue, abi ->
project.android {
packagingOptions {
pickFirst "lib/${abi}/libflutter.so"
// Disable warning by *-android-strip: File format not recognized
doNotStrip "*/${abi}/lib_vm_snapshot_data.so"
doNotStrip "*/${abi}/lib_vm_snapshot_instr.so"
doNotStrip "*/${abi}/lib_isolate_snapshot_data.so"
doNotStrip "*/${abi}/lib_isolate_snapshot_instr.so"
}
}
}
// Add custom build types
project.android.buildTypes {
profile {
@ -124,64 +170,63 @@ class FlutterPlugin implements Plugin<Project> {
if (!engineOut.isDirectory()) {
throw new GradleException('localEngineOut must point to a local engine build')
}
flutterJar = Paths.get(engineOut.absolutePath, "flutter.jar").toFile()
baseEnginePath = Paths.get(engineOut.absolutePath)
flutterJar = baseEnginePath.resolve("flutter.jar").toFile()
if (!flutterJar.isFile()) {
throw new GradleException('File not found: ' + flutterJar)
}
localEngine = engineOut.name
localEngineSrcPath = engineOut.parentFile.parent
project.dependencies {
if (project.getConfigurations().findByName("api")) {
api project.files(flutterJar)
} else {
compile project.files(flutterJar)
}
}
// The local engine is built for one of the build type.
// However, we use the same engine for each of the build types.
debugFlutterJar = flutterJar
profileFlutterJar = flutterJar
releaseFlutterJar = flutterJar
dynamicProfileFlutterJar = flutterJar
dynamicReleaseFlutterJar = flutterJar
} else {
Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
String targetArch = 'arm'
if (project.hasProperty('target-platform') &&
project.property('target-platform') == 'android-arm64') {
targetArch = 'arm64'
}
String targetPlatform = getTargetPlatform(project)
String targetArch = targetPlatform == 'android-arm64' ? 'arm64' : 'arm'
baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
debugFlutterJar = baseEnginePath.resolve("android-${targetArch}").resolve("flutter.jar").toFile()
profileFlutterJar = baseEnginePath.resolve("android-${targetArch}-profile").resolve("flutter.jar").toFile()
releaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-release").resolve("flutter.jar").toFile()
dynamicProfileFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-profile").resolve("flutter.jar").toFile()
dynamicReleaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-release").resolve("flutter.jar").toFile()
if (!debugFlutterJar.isFile()) {
project.exec {
executable flutterExecutable.absolutePath
args "--suppress-analytics"
args "precache"
}
if (!debugFlutterJar.isFile()) {
throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}")
}
}
// Add x86/x86_64 native library. Debug mode only, for now.
flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) {
destinationDir flutterX86Jar.parentFile
archiveName flutterX86Jar.name
from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
into "lib/x86"
}
from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libflutter.so") {
into "lib/x86_64"
}
}
// Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones
// added after applying the Flutter plugin.
project.android.buildTypes.each { addFlutterJarApiDependency(project, it, flutterX86JarTask) }
project.android.buildTypes.whenObjectAdded { addFlutterJarApiDependency(project, it, flutterX86JarTask) }
}
project.extensions.create("flutter", FlutterExtension)
project.afterEvaluate this.&addFlutterTask
if (!debugFlutterJar.isFile()) {
project.exec {
executable flutterExecutable.absolutePath
args "--suppress-analytics"
args "precache"
}
if (!debugFlutterJar.isFile()) {
throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}")
}
}
// Add x86/x86_64 native library. Debug mode only, for now.
File flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
Task debugX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) {
destinationDir flutterX86Jar.parentFile
archiveName flutterX86Jar.name
from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
into 'lib/x86'
}
from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libflutter.so") {
into 'lib/x86_64'
}
}
// Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones
// added after applying the Flutter plugin.
project.android.buildTypes.each {
addFlutterJarApiDependency(project, it, debugX86JarTask)
}
project.android.buildTypes.whenObjectAdded {
addFlutterJarApiDependency(project, it, debugX86JarTask)
}
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
Properties plugins = readPropertiesIfExist(pluginsFile)
@ -196,13 +241,13 @@ class FlutterPlugin implements Plugin<Project> {
compile pluginProject
}
}
pluginProject.afterEvaluate {
pluginProject.afterEvaluate {
pluginProject.android.buildTypes {
profile {
initWith debug
}
}
}
}
pluginProject.afterEvaluate this.&addFlutterJarCompileOnlyDependency
} else {
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
@ -240,7 +285,7 @@ class FlutterPlugin implements Plugin<Project> {
*
* Note: The BuildType DSL type is not public, and is therefore omitted from the signature.
*/
private void addFlutterJarApiDependency(Project project, buildType, Task flutterX86JarTask) {
private void addFlutterJarApiDependency(Project project, buildType, Task debugX86JarTask) {
project.dependencies {
String configuration;
if (project.getConfigurations().findByName("api")) {
@ -250,16 +295,24 @@ class FlutterPlugin implements Plugin<Project> {
}
add(configuration, project.files {
String buildMode = buildModeFor(buildType)
if (buildMode == "debug") {
[flutterX86JarTask, debugFlutterJar]
} else if (buildMode == "profile") {
profileFlutterJar
} else if (buildMode == "dynamicProfile") {
dynamicProfileFlutterJar
} else if (buildMode == "dynamicRelease") {
dynamicReleaseFlutterJar
} else {
releaseFlutterJar
switch (buildMode) {
case "debug":
[debugX86JarTask, debugFlutterJar]
break
case "profile":
profileFlutterJar
break
case "dynamicProfile":
dynamicProfileFlutterJar
break
case "dynamicRelease":
dynamicReleaseFlutterJar
break
case "release":
releaseFlutterJar
break
default:
throw new GradleException("Invalid build mode: ${buildMode}")
}
})
}
@ -341,17 +394,13 @@ class FlutterPlugin implements Plugin<Project> {
if (project.hasProperty('extra-gen-snapshot-options')) {
extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
}
Boolean buildSharedLibraryValue = false
if (project.hasProperty('build-shared-library')) {
buildSharedLibraryValue = project.property('build-shared-library').toBoolean()
}
String targetPlatformValue = null
if (project.hasProperty('target-platform')) {
targetPlatformValue = project.property('target-platform')
}
Boolean buildSharedLibraryValue = this.getBuildShareLibrary(project)
String targetPlatformValue = this.getTargetPlatform(project)
def addFlutterDeps = { variant ->
String flutterBuildMode = buildModeFor(variant.buildType)
if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) {
Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
if (task) {
@ -363,35 +412,94 @@ class FlutterPlugin implements Plugin<Project> {
}
}
FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode flutterBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
targetPath target
verbose verboseValue
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
compilationTraceFilePath compilationTraceFilePathValue
createPatch createPatchValue
buildNumber buildNumberValue
baselineDir baselineDirValue
buildSharedLibrary buildSharedLibraryValue
targetPlatform targetPlatformValue
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
def flutterTasks = []
def targetPlatforms = []
if (targetPlatformValue == 'android-arm-all') {
if (flutterBuildMode == 'release') {
targetPlatforms.addAll(allArmPlatforms)
} else {
targetPlatforms.add('android-arm')
}
} else {
targetPlatforms.add(targetPlatformValue)
}
targetPlatforms.each { currentTargetPlatformValue ->
String abiValue = allTargetPlatforms[currentTargetPlatformValue]
FlutterTask compileTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}${currentTargetPlatformValue}:compile",
type: FlutterTask) {
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode flutterBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
abi abiValue
targetPath target
verbose verboseValue
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
compilationTraceFilePath compilationTraceFilePathValue
createPatch createPatchValue
buildNumber buildNumberValue
baselineDir baselineDirValue
buildSharedLibrary buildSharedLibraryValue
targetPlatform currentTargetPlatformValue
sourceDir project.file(project.flutter.source)
intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${currentTargetPlatformValue}")
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
}
flutterTasks.add(compileTask)
}
def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/libs.jar")
Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: ":flutter:package:packLibs${variant.name.capitalize()}", type: Jar) {
destinationDir libJar.parentFile
archiveName libJar.name
targetPlatforms.each { targetPlatform ->
// Include `libflutter.so` for each abi.
// TODO(blasten): The libs should be outside `flutter.jar` when the artifacts are downloaded.
from(project.zipTree("${flutterRoot}/bin/cache/artifacts/engine/${targetPlatform}-release/flutter.jar")) {
include 'lib/**'
}
}
dependsOn flutterTasks
// Add the snapshots and rename them as `lib/{abi}/*.so`.
flutterTasks.each { flutterTask ->
from(flutterTask.intermediateDir) {
include 'vm_snapshot_data'
include 'vm_snapshot_instr'
include 'isolate_snapshot_data'
include 'isolate_snapshot_instr'
rename { String filename ->
return "lib/${flutterTask.abi}/lib_${filename}.so"
}
}
}
}
// Include the snapshots and libflutter.so in `lib/`.
if (flutterBuildMode == 'release' && targetPlatformValue == 'android-arm-all') {
project.dependencies {
String configuration;
if (project.getConfigurations().findByName("api")) {
configuration = buildType.name + "Api";
} else {
configuration = buildType.name + "Compile";
}
add(configuration, project.files {
packFlutterSnapshotsAndLibsTask
})
}
}
// We know that the flutter app is a subproject in another Android app when these tasks exist.
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
dependsOn flutterTask
dependsOn flutterTasks
if (packageAssets && cleanPackageAssets) {
dependsOn packageAssets
dependsOn cleanPackageAssets
@ -401,9 +509,14 @@ class FlutterPlugin implements Plugin<Project> {
dependsOn "clean${variant.mergeAssets.name.capitalize()}"
into variant.mergeAssets.outputDir
}
with flutterTask.assets
flutterTasks.each { flutterTask ->
with flutterTask.assets
// Include the snapshots in the assets directory.
if (flutterBuildMode != 'release' || targetPlatformValue != 'android-arm-all') {
with flutterTask.snapshots
}
}
}
if (packageAssets) {
String mainModuleName = "app"
try {
@ -419,9 +532,11 @@ class FlutterPlugin implements Plugin<Project> {
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
} else {
variant.outputs[0].processResources.dependsOn(copyFlutterAssetsTask)
def processResources = variant.outputs.first().processResources
processResources.dependsOn(copyFlutterAssetsTask)
}
}
if (project.android.hasProperty("applicationVariants")) {
project.android.applicationVariants.all addFlutterDeps
} else {
@ -463,6 +578,8 @@ abstract class BaseFlutterTask extends DefaultTask {
Boolean buildSharedLibrary
@Optional @Input
String targetPlatform
@Input
String abi
File sourceDir
File intermediateDir
@Optional @Input
@ -505,7 +622,6 @@ abstract class BaseFlutterTask extends DefaultTask {
args "--suppress-analytics"
args "--quiet"
args "--target", targetPath
args "--target-platform", "android-arm"
args "--output-dir", "${intermediateDir}"
if (trackWidgetCreation) {
args "--track-widget-creation"
@ -519,7 +635,9 @@ abstract class BaseFlutterTask extends DefaultTask {
if (buildSharedLibrary) {
args "--build-shared-library"
}
if (targetPlatform != null) {
if (targetPlatform == null) {
args "--target-platform", "android-arm"
} else {
args "--target-platform", "${targetPlatform}"
}
args "--${buildMode}"
@ -603,8 +721,13 @@ class FlutterTask extends BaseFlutterTask {
CopySpec getAssets() {
return project.copySpec {
from "${intermediateDir}"
include "flutter_assets/**" // the working dir and its files
}
}
CopySpec getSnapshots() {
return project.copySpec {
from "${intermediateDir}"
if (buildMode == 'release' || buildMode == 'profile') {
if (buildSharedLibrary) {

View file

@ -27,7 +27,9 @@ class BuildApkCommand extends BuildSubCommand {
)
..addOption('target-platform',
defaultsTo: 'android-arm',
allowed: <String>['android-arm', 'android-arm64', 'android-x86', 'android-x64']);
allowed: <String>['android-arm', 'android-arm64', 'android-x86', 'android-x64'],
help: 'The target platform for which the app is compiled.',
);
}
@override

View file

@ -20,23 +20,30 @@ class BuildAppBundleCommand extends BuildSubCommand {
argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
..addFlag('build-shared-library',
..addFlag(
'build-shared-library',
negatable: false,
help: 'Whether to prefer compiling to a *.so file (android only).',
)
..addOption('target-platform',
defaultsTo: 'android-arm',
allowed: <String>['android-arm', 'android-arm64']);
..addOption(
'target-platform',
allowed: <String>['android-arm', 'android-arm64'],
help: 'The target platform for which the app is compiled.\n'
'By default, the bundle will include \'arm\' and \'arm64\', '
'which is the recommended configuration for app bundles.\n'
'For more, see https://developer.android.com/distribute/best-practices/develop/64-bit',
);
}
@override
final String name = 'appbundle';
@override
final String description = 'Build an Android App Bundle file from your app.\n\n'
'This command can build debug and release versions of an app bundle for your application. \'debug\' builds support '
'debugging and a quick development cycle. \'release\' builds don\'t support debugging and are '
'suitable for deploying to app stores. \n app bundle improves your app size';
final String description =
'Build an Android App Bundle file from your app.\n\n'
'This command can build debug and release versions of an app bundle for your application. \'debug\' builds support '
'debugging and a quick development cycle. \'release\' builds don\'t support debugging and are '
'suitable for deploying to app stores. \n app bundle improves your app size';
@override
Future<FlutterCommandResult> runCommand() async {