diff --git a/dev/devicelab/bin/tasks/gradle_plugin_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_test.dart index c71a01d659..15939ab36a 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_test.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; @@ -47,263 +46,6 @@ Future main() async { print('\nUsing JAVA_HOME=$javaHome'); try { - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleDebug without explicit target platform'); - await pluginProject.runGradleTask('assembleDebug'); - - if (!pluginProject.hasDebugApk) - throw TaskResult.failure( - 'Gradle did not produce a debug apk file at: ${pluginProject.debugApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - 'lib/arm64-v8a/libflutter.so', - 'lib/armeabi-v7a/libflutter.so', - // Debug mode intentionally includes `x86` and `x86_64`. - 'lib/x86/libflutter.so', - 'lib/x86_64/libflutter.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'lib/arm64-v8a/libapp.so', - 'lib/armeabi-v7a/libapp.so', - 'lib/x86/libapp.so', - 'lib/x86_64/libapp.so', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleDebug with target platform = android-arm'); - await pluginProject.runGradleTask('assembleDebug', - options: ['-Ptarget-platform=android-arm']); - - if (!pluginProject.hasDebugApk) - throw TaskResult.failure( - 'Gradle did not produce a debug apk file at: ${pluginProject.debugApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - 'lib/armeabi-v7a/libflutter.so', - // Debug mode intentionally includes `x86` and `x86_64`. - 'lib/x86/libflutter.so', - 'lib/x86_64/libflutter.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'lib/armeabi-v7a/libapp.so', - 'lib/x86/libapp.so', - 'lib/x86_64/libapp.so', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleRelease without explicit target platform'); - await pluginProject.runGradleTask('assembleRelease'); - - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', - 'lib/armeabi-v7a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleRelease with target platform = android-arm'); - await pluginProject.runGradleTask('assembleRelease', - options: ['-Ptarget-platform=android-arm']); - - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/armeabi-v7a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleRelease with target platform = android-arm64'); - await pluginProject.runGradleTask('assembleRelease', - options: ['-Ptarget-platform=android-arm64']); - - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'lib/armeabi-v7a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleRelease with target platform = android-arm, android-arm64'); - await pluginProject.runGradleTask('assembleRelease', - options: ['-Ptarget-platform=android-arm,android-arm64']); - - if (!pluginProject.hasReleaseApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseApkPath}'); - - final Iterable apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/armeabi-v7a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', - ], apkFiles); - - _checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('APK content for task assembleRelease with ' - 'target platform = android-arm, android-arm64 and split per ABI'); - await pluginProject.runGradleTask('assembleRelease', - options: ['-Ptarget-platform=android-arm,android-arm64', '-Psplit-per-abi=true']); - - if (!pluginProject.hasReleaseArmApk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseArmApkPath}'); - - final Iterable armApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArmApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/armeabi-v7a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', - ], armApkFiles); - - _checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], armApkFiles); - - if (!pluginProject.hasReleaseArm64Apk) - throw TaskResult.failure( - 'Gradle did not produce a release apk at: ${pluginProject.releaseArm64ApkPath}'); - - final Iterable arm64ApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArm64ApkPath); - - _checkItContains([ - 'AndroidManifest.xml', - 'classes.dex', - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', - ], arm64ApkFiles); - - _checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], arm64ApkFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('App bundle content for task bundleRelease without explicit target platform'); - await pluginProject.runGradleTask('bundleRelease'); - - if (!pluginProject.hasReleaseBundle) - throw TaskResult.failure( - 'Gradle did not produce a release aab file at: ${pluginProject.releaseBundlePath}'); - - final Iterable bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath); - - _checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', - 'base/lib/arm64-v8a/libapp.so', - 'base/lib/arm64-v8a/libflutter.so', - 'base/lib/armeabi-v7a/libapp.so', - 'base/lib/armeabi-v7a/libflutter.so', - ], bundleFiles); - }); - - await runPluginProjectTest((FlutterPluginProject pluginProject) async { - section('App bundle content for task bundleRelease with target platform = android-arm'); - await pluginProject.runGradleTask('bundleRelease', - options: ['-Ptarget-platform=android-arm']); - - if (!pluginProject.hasReleaseBundle) - throw TaskResult.failure( - 'Gradle did not produce a release aab file at: ${pluginProject.releaseBundlePath}'); - - final Iterable bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath); - - _checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', - 'base/lib/armeabi-v7a/libapp.so', - 'base/lib/armeabi-v7a/libflutter.so', - ], bundleFiles); - - _checkItDoesNotContain([ - 'base/lib/arm64-v8a/libapp.so', - 'base/lib/arm64-v8a/libflutter.so', - ], bundleFiles); - }); - await runProjectTest((FlutterProject project) async { section('gradlew assembleDebug'); await project.runGradleTask('assembleDebug'); @@ -338,9 +80,32 @@ Future main() async { 'release', targetPlatform); - final String sharedLibrary = path.join(androidArmSnapshotPath, 'app.so'); - if (!File(sharedLibrary).existsSync()) { - throw TaskResult.failure('Shared library doesn\'t exist'); + 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'); } } }); @@ -418,23 +183,6 @@ Future main() async { }); } - -void _checkItContains(Iterable values, Iterable collection) { - for (T value in values) { - if (!collection.contains(value)) { - throw TaskResult.failure('Expected to find `$value` in `$collection`.'); - } - } -} - -void _checkItDoesNotContain(Iterable values, Iterable collection) { - for (T value in values) { - if (collection.contains(value)) { - throw TaskResult.failure('Did not expect to find `$value` in `$collection`.'); - } - } -} - TaskResult _failure(String message, ProcessResult result) { print('Unexpected process result:'); print('Exit code: ${result.exitCode}'); @@ -540,37 +288,12 @@ class FlutterPluginProject { String get examplePath => path.join(rootPath, 'example'); String get exampleAndroidPath => path.join(examplePath, 'android'); String get debugApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'apk', 'debug', 'app-debug.apk'); - String get releaseApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'apk', 'release', 'app-release.apk'); - String get releaseArmApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'apk', 'release', 'app-armeabi-v7a-release.apk'); - String get releaseArm64ApkPath => path.join(examplePath, 'build', 'app', 'outputs', 'apk', 'release', 'app-arm64-v8a-release.apk'); - String get releaseBundlePath => path.join(examplePath, 'build', 'app', 'outputs', 'bundle', 'release', 'app.aab'); - - bool get hasDebugApk => File(debugApkPath).existsSync(); - bool get hasReleaseApk => File(releaseApkPath).existsSync(); - bool get hasReleaseArmApk => File(releaseArmApkPath).existsSync(); - bool get hasReleaseArm64Apk => File(releaseArm64ApkPath).existsSync(); - bool get hasReleaseBundle => File(releaseBundlePath).existsSync(); Future runGradleTask(String task, {List options}) async { return _runGradleTask(workingDirectory: exampleAndroidPath, task: task, options: options); } - Future> getFilesInApk(String apk) async { - final Process unzip = await startProcess( - 'unzip', - ['-v', apk], - isBot: false, // we just want to test the output, not have any debugging info - ); - return unzip.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .map((String line) => line.split(' ').last) - .toList(); - } - - Future> getFilesInAppBundle(String bundle) { - return getFilesInApk(bundle); - } + bool get hasDebugApk => File(debugApkPath).existsSync(); } Future _runGradleTask({String workingDirectory, String task, List options}) async { diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index e8f090d0ec..2f56149c83 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/flutter environment: # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite. - sdk: ">=2.3.0 <3.0.0" + sdk: ">=2.2.2 <3.0.0" dependencies: args: 1.5.2 diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 66530cb5d8..c06edba6fc 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -2,7 +2,6 @@ import java.nio.file.Path import java.nio.file.Paths import com.android.builder.model.AndroidProject -import com.android.build.OutputFile import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -37,49 +36,6 @@ android { apply plugin: FlutterPlugin class FlutterPlugin implements Plugin { - // The platforms that can be passed to the `--Ptarget-platform` flag. - private static final String PLATFORM_ARM32 = "android-arm"; - private static final String PLATFORM_ARM64 = "android-arm64"; - private static final String PLATFORM_X86 = "android-x86"; - private static final String PLATFORM_X86_64 = "android-x64"; - - // The ABI architectures. - private static final String ARCH_ARM32 = "armeabi-v7a"; - private static final String ARCH_ARM64 = "arm64-v8a"; - private static final String ARCH_X86 = "x86"; - private static final String ARCH_X86_64 = "x86_64"; - - // Maps platforms to ABI architectures. - private static final Map PLATFORM_ARCH_MAP = [ - (PLATFORM_ARM32) : ARCH_ARM32, - (PLATFORM_ARM64) : ARCH_ARM64, - (PLATFORM_X86) : ARCH_X86, - (PLATFORM_X86_64) : ARCH_X86_64, - ] - - // The version code that gives each ABI a value. - // For each APK variant, use the following versions to override the version of the Universal APK. - // Otherwise, the Play Store will complain that the APK variants have the same version. - private static final Map ABI_VERSION = [ - (ARCH_ARM32) : 1, - (ARCH_ARM64) : 2, - (ARCH_X86) : 3, - (ARCH_X86_64) : 4, - ] - - // When split is enabled, multiple APKs are generated per each ABI. - private static final List DEFAULT_PLATFORMS = [ - PLATFORM_ARM32, - PLATFORM_ARM64, - ] - - // The name prefix for flutter builds. This is used to identify gradle tasks - // where we expect the flutter tool to provide any error output, and skip the - // standard Gradle error output in the FlutterEventLogger. If you change this, - // be sure to change any instances of this string in symbols in the code below - // to match. - static final String FLUTTER_BUILD_PREFIX = "flutterBuild" - private Path baseEnginePath private File flutterRoot private File flutterExecutable @@ -93,6 +49,29 @@ class FlutterPlugin implements Plugin { private File dynamicProfileFlutterJar private File dynamicReleaseFlutterJar + // The name prefix for flutter builds. This is used to identify gradle tasks + // where we expect the flutter tool to provide any error output, and skip the + // standard Gradle error output in the FlutterEventLogger. If you change this, + // be sure to change any instances of this string in symbols in the code below + // 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()) { @@ -101,16 +80,11 @@ class FlutterPlugin implements Plugin { return result } - private List getTargetPlatforms(Project project) { - if (!project.hasProperty('target-platform')) { - return DEFAULT_PLATFORMS - } - return project.property('target-platform').split(',').collect { - if (!PLATFORM_ARCH_MAP[it]) { - throw new GradleException("Invalid platform: $it.") - } - return it + private String getTargetPlatform(Project project) { + if (project.hasProperty('target-platform')) { + return project.property('target-platform') } + return 'android-arm-all'; } private Boolean getBuildShareLibrary(Project project) { @@ -120,13 +94,6 @@ class FlutterPlugin implements Plugin { return false; } - private Boolean splitPerAbi(Project project) { - if (project.hasProperty('split-per-abi')) { - return project.property('split-per-abi').toBoolean() - } - return false; - } - private String resolveProperty(Project project, String name, String defaultValue) { if (localProperties == null) { localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties")) @@ -144,55 +111,21 @@ class FlutterPlugin implements Plugin { return result } - /** - * Returns the platform that is used to extract the `libflutter.so` and the .class files. - * - * Note: This is only needed to add the .class files. - * Unfortunately, the engine artifacts include the .class and libflutter.so files. - */ - private String getBasePlatform(Project project) { - if (PLATFORM_ARM64 in getTargetPlatforms(project)) { - return PLATFORM_ARM64; - } - return PLATFORM_ARM32; - } - @Override void apply(Project project) { project.extensions.create("flutter", FlutterExtension) project.afterEvaluate this.&addFlutterTask - // By default, assembling APKs generates fat APKs if multiple platforms are passed. - // Configuring split per ABI allows to generate separate APKs for each abi. - // This is a noop when building a bundle. - if (this.splitPerAbi(project)) { - project.android { - splits { - abi { - // Enables building multiple APKs per ABI. - enable true - // Resets the list of ABIs that Gradle should create APKs for to none. - reset() - // Specifies that we do not want to also generate a universal APK that includes all ABIs. - universalApk false - } - } - } - } - this.getTargetPlatforms(project).each { targetArch -> - String abiValue = PLATFORM_ARCH_MAP[targetArch] + allTargetPlatforms.each { currentTargetPlatformValue, abi -> project.android { packagingOptions { - pickFirst "lib/${abiValue}/libflutter.so" - // Prevent the ELF library from getting corrupted. - doNotStrip "*/${abiValue}/libapp.so" - } - if (this.splitPerAbi(project)) { - splits { - abi { - include abiValue - } - } + 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" } } } @@ -240,7 +173,7 @@ class FlutterPlugin implements Plugin { baseEnginePath = Paths.get(engineOut.absolutePath) flutterJar = baseEnginePath.resolve("flutter.jar").toFile() if (!flutterJar.isFile()) { - throw new GradleException('Local engine jar not found: ' + flutterJar) + throw new GradleException('File not found: ' + flutterJar) } localEngine = engineOut.name @@ -253,14 +186,14 @@ class FlutterPlugin implements Plugin { dynamicProfileFlutterJar = flutterJar dynamicReleaseFlutterJar = flutterJar } else { - String basePlatformArch = getBasePlatform(project) - // This is meant to include the compiled classes only, however it will include `libflutter.so` as well. + String targetPlatform = getTargetPlatform(project) + String targetArch = targetPlatform == 'android-arm64' ? 'arm64' : 'arm' baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine") - debugFlutterJar = baseEnginePath.resolve("${basePlatformArch}").resolve("flutter.jar").toFile() - profileFlutterJar = baseEnginePath.resolve("${basePlatformArch}-profile").resolve("flutter.jar").toFile() - releaseFlutterJar = baseEnginePath.resolve("${basePlatformArch}-release").resolve("flutter.jar").toFile() - dynamicProfileFlutterJar = baseEnginePath.resolve("${basePlatformArch}-dynamic-profile").resolve("flutter.jar").toFile() - dynamicReleaseFlutterJar = baseEnginePath.resolve("${basePlatformArch}-dynamic-release").resolve("flutter.jar").toFile() + 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()) { @@ -276,7 +209,7 @@ class FlutterPlugin implements Plugin { // 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("${FLUTTER_BUILD_PREFIX}X86Jar", 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") { @@ -288,7 +221,7 @@ class FlutterPlugin implements Plugin { } // Add flutter.jar dependencies to all Api configurations, including custom ones // added after applying the Flutter plugin. - project.android.buildTypes.each { + project.android.buildTypes.each { addFlutterJarApiDependency(project, it, debugX86JarTask) } project.android.buildTypes.whenObjectAdded { @@ -405,19 +338,6 @@ class FlutterPlugin implements Plugin { return "release" } - private static String getEngineArtifactDirName(buildType, targetArch) { - if (buildType.name == "profile") { - return "${targetArch}-profile" - } else if (buildType.name == "dynamicProfile") { - return "${targetArch}-dynamic-profile" - } else if (buildType.name == "dynamicRelease") { - return "${targetArch}-dynamic-release" - } else if (buildType.debuggable) { - return "${targetArch}" - } - return "${targetArch}-release" - } - private void addFlutterTask(Project project) { if (project.state.failure) { return @@ -475,24 +395,13 @@ class FlutterPlugin implements Plugin { extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options') } - def targetPlatforms = this.getTargetPlatforms(project) - def addFlutterDeps = { variant -> - if (this.splitPerAbi(project)) { - variant.outputs.each { output -> - // Assigns the new version code to versionCodeOverride, which changes the version code - // for only the output APK, not for the variant itself. Skipping this step simply - // causes Gradle to use the value of variant.versionCode for the APK. - // For more, see https://developer.android.com/studio/build/configure-apk-splits - def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) - if (abiVersionCode != null) { - output.versionCodeOverride = - abiVersionCode * 1000 + variant.versionCode - } - } - } + Boolean buildSharedLibraryValue = this.getBuildShareLibrary(project) + String targetPlatformValue = this.getTargetPlatform(project) + def addFlutterDeps = { variant -> String flutterBuildMode = buildModeFor(variant.buildType) - if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) { + + if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) { Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac") if (task) { task.dependsOn project.flutterBuildX86Jar @@ -504,10 +413,22 @@ class FlutterPlugin implements Plugin { } def flutterTasks = [] - targetPlatforms.each { targetArch -> - String abiValue = PLATFORM_ARCH_MAP[targetArch] - String taskName = "compile${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}${targetArch.replace('android-', '').capitalize()}" - FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { + 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: "compile${flutterBuildPrefix}${variant.name.capitalize()}${currentTargetPlatformValue}", + type: FlutterTask) { flutterRoot this.flutterRoot flutterExecutable this.flutterExecutable buildMode flutterBuildMode @@ -523,53 +444,56 @@ class FlutterPlugin implements Plugin { createPatch createPatchValue buildNumber buildNumberValue baselineDir baselineDirValue - targetPlatform targetArch + buildSharedLibrary buildSharedLibraryValue + targetPlatform currentTargetPlatformValue sourceDir project.file(project.flutter.source) - intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}") + 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/${variant.name}/libs.jar") - Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { + + def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/libs.jar") + Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packFlutterSnapshotsAndLibs${flutterBuildPrefix}${variant.name.capitalize()}", type: Jar) { destinationDir libJar.parentFile archiveName libJar.name - targetPlatforms.each { targetArch -> - // This check prevents including `libflutter.so` twice, since it's included in the base platform jar. - // Unfortunately, the `pickFirst` setting in `packagingOptions` does not work when the project `:flutter` - // is included as an implementation dependency, which causes duplicated `libflutter.so`. - if (getBasePlatform(project) != targetArch) { - def engineArtifactSubdir = getEngineArtifactDirName(variant.buildType, targetArch); - // Include `libflutter.so`. - // TODO(blasten): The libs should be outside `flutter.jar` when the artifacts are downloaded. - from(project.zipTree("${flutterRoot}/bin/cache/artifacts/engine/${engineArtifactSubdir}/flutter.jar")) { - include 'lib/**' - } + 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 ELF library. - flutterTasks.each { flutterTask -> + // Add the snapshots and rename them as `lib/{abi}/*.so`. + flutterTasks.each { flutterTask -> from(flutterTask.intermediateDir) { - include '*.so' - rename { String filename -> - return "lib/${flutterTask.abi}/lib${filename}" + 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/`. - project.dependencies { - String configuration; - if (project.getConfigurations().findByName("api")) { - configuration = buildType.name + "Api"; - } else { - configuration = buildType.name + "Compile"; + 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 + }) } - 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") @@ -586,8 +510,12 @@ class FlutterPlugin implements Plugin { variant.mergeAssets.mustRunAfter("clean${variant.mergeAssets.name.capitalize()}") into variant.mergeAssets.outputDir } - flutterTasks.each { flutterTask -> + 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) { @@ -648,6 +576,8 @@ abstract class BaseFlutterTask extends DefaultTask { @Optional @Input String baselineDir @Optional @Input + Boolean buildSharedLibrary + @Optional @Input String targetPlatform @Input String abi @@ -692,10 +622,8 @@ abstract class BaseFlutterTask extends DefaultTask { args "build", "aot" args "--suppress-analytics" args "--quiet" - args "--build-shared-library" args "--target", targetPath args "--output-dir", "${intermediateDir}" - args "--target-platform", "${targetPlatform}" if (trackWidgetCreation) { args "--track-widget-creation" } @@ -705,6 +633,14 @@ abstract class BaseFlutterTask extends DefaultTask { if (extraGenSnapshotOptions != null) { args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" } + if (buildSharedLibrary) { + args "--build-shared-library" + } + if (targetPlatform == null) { + args "--target-platform", "android-arm" + } else { + args "--target-platform", "${targetPlatform}" + } args "--${buildMode}" } } @@ -719,7 +655,6 @@ abstract class BaseFlutterTask extends DefaultTask { args "build", "bundle" args "--suppress-analytics" args "--target", targetPath - args "--target-platform", "${targetPlatform}" if (verbose) { args "--verbose" } @@ -753,6 +688,9 @@ abstract class BaseFlutterTask extends DefaultTask { if (extraGenSnapshotOptions != null) { args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}" } + if (targetPlatform != null) { + args "--target-platform", "${targetPlatform}" + } if (buildMode == "release" || buildMode == "profile") { args "--precompiled" } else { @@ -793,7 +731,14 @@ class FlutterTask extends BaseFlutterTask { from "${intermediateDir}" if (buildMode == 'release' || buildMode == 'profile') { - include "app.so" + if (buildSharedLibrary) { + include "app.so" + } else { + include "vm_snapshot_data" + include "vm_snapshot_instr" + include "isolate_snapshot_data" + include "isolate_snapshot_instr" + } } } } @@ -860,7 +805,7 @@ class FlutterEventLogger extends BuildAdapter implements TaskExecutionListener { void buildFinished(BuildResult result) { if (result.failure != null) { - if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.FLUTTER_BUILD_PREFIX)) { + if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.flutterBuildPrefix)) { result.rethrowFailure() } } diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index f2151243a7..e062da5e3d 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -388,27 +388,18 @@ class AndroidDevice extends Device { if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion()) return LaunchResult.failed(); - - if (!debuggingOptions.buildInfo.isDebug && - !debuggingOptions.buildInfo.isDynamic) { - printError('Profile and release builds are only supported.'); + final TargetPlatform devicePlatform = await targetPlatform; + if (!(devicePlatform == TargetPlatform.android_arm || + devicePlatform == TargetPlatform.android_arm64) && + !(debuggingOptions.buildInfo.isDebug || + debuggingOptions.buildInfo.isDynamic)) { + printError('Profile and release builds are only supported on ARM targets.'); return LaunchResult.failed(); } - final TargetPlatform devicePlatform = await targetPlatform; - - AndroidArch androidArch; - switch (devicePlatform) { - case TargetPlatform.android_arm: - androidArch = AndroidArch.armeabi_v7a; - break; - case TargetPlatform.android_arm64: - androidArch = AndroidArch.arm64_v8a; - break; - default: - printError('ARM targets are only supported.'); - return LaunchResult.failed(); - } + BuildInfo buildInfo = debuggingOptions.buildInfo; + if (buildInfo.targetPlatform == null && devicePlatform == TargetPlatform.android_arm64) + buildInfo = buildInfo.withTargetPlatform(TargetPlatform.android_arm64); if (!prebuiltApplication || androidSdk.licensesAvailable && androidSdk.latestVersion == null) { printTrace('Building APK'); @@ -416,9 +407,7 @@ class AndroidDevice extends Device { await buildApk( project: project, target: mainPath, - androidBuildInfo: AndroidBuildInfo(debuggingOptions.buildInfo, - targetArchs: [androidArch] - ), + buildInfo: buildInfo, ); // Package has been built, so we can get the updated application ID and // activity name from the .apk. diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart index 908c535f7d..8b5b8d9914 100644 --- a/packages/flutter_tools/lib/src/android/apk.dart +++ b/packages/flutter_tools/lib/src/android/apk.dart @@ -16,7 +16,7 @@ import 'gradle.dart'; Future buildApk({ @required FlutterProject project, @required String target, - @required AndroidBuildInfo androidBuildInfo, + BuildInfo buildInfo = BuildInfo.debug, }) async { if (!project.android.isUsingGradle) { throwToolExit( @@ -33,7 +33,7 @@ Future buildApk({ await buildGradleProject( project: project, - androidBuildInfo: androidBuildInfo, + buildInfo: buildInfo, target: target, isBuildingBundle: false, ); diff --git a/packages/flutter_tools/lib/src/android/app_bundle.dart b/packages/flutter_tools/lib/src/android/app_bundle.dart index 4f304c1a56..c1e94ab6cb 100644 --- a/packages/flutter_tools/lib/src/android/app_bundle.dart +++ b/packages/flutter_tools/lib/src/android/app_bundle.dart @@ -17,7 +17,7 @@ import 'gradle.dart'; Future buildAppBundle({ @required FlutterProject project, @required String target, - @required AndroidBuildInfo androidBuildInfo, + BuildInfo buildInfo = BuildInfo.debug, }) async { if (!project.android.isUsingGradle) { throwToolExit( @@ -42,7 +42,7 @@ Future buildAppBundle({ return buildGradleProject( project: project, - androidBuildInfo: androidBuildInfo, + buildInfo: buildInfo, target: target, isBuildingBundle: true, ); diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index bf5316652e..62f44751e0 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -15,7 +15,6 @@ import '../base/logger.dart'; import '../base/os.dart'; import '../base/platform.dart'; import '../base/process.dart'; -import '../base/terminal.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -319,7 +318,7 @@ void _exitIfNoAndroidSdk() { Future buildGradleProject({ @required FlutterProject project, - @required AndroidBuildInfo androidBuildInfo, + @required BuildInfo buildInfo, @required String target, @required bool isBuildingBundle, }) async { @@ -331,7 +330,7 @@ Future buildGradleProject({ // and can be overwritten with flutter build command. // The default Gradle script reads the version name and number // from the local.properties file. - updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); + updateLocalProperties(project: project, buildInfo: buildInfo); final String gradle = await _ensureGradle(project); @@ -343,7 +342,7 @@ Future buildGradleProject({ case FlutterPluginVersion.managed: // Fall through. Managed plugin builds the same way as plugin v2. case FlutterPluginVersion.v2: - return _buildGradleProjectV2(project, gradle, androidBuildInfo, target, isBuildingBundle); + return _buildGradleProjectV2(project, gradle, buildInfo, target, isBuildingBundle); } } @@ -392,12 +391,11 @@ String _calculateSha(File file) { Future _buildGradleProjectV2( FlutterProject flutterProject, String gradle, - AndroidBuildInfo androidBuildInfo, + BuildInfo buildInfo, String target, bool isBuildingBundle, ) async { final GradleProject project = await _gradleProject(); - final BuildInfo buildInfo = androidBuildInfo.buildInfo; String assembleTask; @@ -455,13 +453,12 @@ Future _buildGradleProjectV2( command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}'); if (buildInfo.fileSystemScheme != null) command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}'); - if (androidBuildInfo.splitPerAbi) - command.add('-Psplit-per-abi=true'); - if (androidBuildInfo.targetArchs.isNotEmpty) { - final String targetPlatforms = androidBuildInfo.targetArchs - .map(getPlatformNameForAndroidArch).join(','); - command.add('-Ptarget-platform=$targetPlatforms'); + if (buildInfo.buildSharedLibrary) { + command.add('-Pbuild-shared-library=true'); } + if (buildInfo.targetPlatform != null) + command.add('-Ptarget-platform=${getNameForTargetPlatform(buildInfo.targetPlatform)}'); + command.add(assembleTask); bool potentialAndroidXFailure = false; final Stopwatch sw = Stopwatch()..start(); @@ -511,27 +508,24 @@ Future _buildGradleProjectV2( flutterUsage.sendTiming('build', 'gradle-v2', Duration(milliseconds: sw.elapsedMilliseconds)); if (!isBuildingBundle) { - final Iterable apkFiles = _findApkFiles(project, androidBuildInfo); - if (apkFiles.isEmpty) + final File apkFile = _findApkFile(project, buildInfo); + if (apkFile == null) throwToolExit('Gradle build failed to produce an Android package.'); - // Copy the first APK to app.apk, so `flutter run`, `flutter install`, etc. can find it. - // TODO(blasten): Handle multiple APKs. - apkFiles.first.copySync(project.apkDirectory.childFile('app.apk').path); + // Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it. + apkFile.copySync(project.apkDirectory.childFile('app.apk').path); printTrace('calculateSha: ${project.apkDirectory}/app.apk'); final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1'); - apkShaFile.writeAsStringSync(_calculateSha(apkFiles.first)); + apkShaFile.writeAsStringSync(_calculateSha(apkFile)); - for (File apkFile in apkFiles) { - String appSize; - if (buildInfo.mode == BuildMode.debug) { - appSize = ''; - } else { - appSize = ' (${getSizeAsMB(apkFile.lengthSync())})'; - } - printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.', - color: TerminalColor.green); + String appSize; + if (buildInfo.mode == BuildMode.debug) { + appSize = ''; + } else { + appSize = ' (${getSizeAsMB(apkFile.lengthSync())})'; } + printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.'); + } else { final File bundleFile = _findBundleFile(project, buildInfo); if (bundleFile == null) @@ -543,38 +537,28 @@ Future _buildGradleProjectV2( } else { appSize = ' (${getSizeAsMB(bundleFile.lengthSync())})'; } - printStatus('Built ${fs.path.relative(bundleFile.path)}$appSize.', - color: TerminalColor.green); + printStatus('Built ${fs.path.relative(bundleFile.path)}$appSize.'); } } -Iterable _findApkFiles(GradleProject project, AndroidBuildInfo androidBuildInfo) { - final Iterable apkFileNames = project.apkFilesFor(androidBuildInfo); - if (apkFileNames.isEmpty) - return const []; - - return apkFileNames.map((String apkFileName) { - File apkFile = project.apkDirectory.childFile(apkFileName); - if (apkFile.existsSync()) - return apkFile; - final BuildInfo buildInfo = androidBuildInfo.buildInfo; - final String modeName = camelCase(buildInfo.modeName); - apkFile = project.apkDirectory - .childDirectory(modeName) - .childFile(apkFileName); - if (apkFile.existsSync()) - return apkFile; - if (buildInfo.flavor != null) { - // Android Studio Gradle plugin v3 adds flavor to path. - apkFile = project.apkDirectory - .childDirectory(buildInfo.flavor) - .childDirectory(modeName) - .childFile(apkFileName); - if (apkFile.existsSync()) - return apkFile; - } +File _findApkFile(GradleProject project, BuildInfo buildInfo) { + final String apkFileName = project.apkFileFor(buildInfo); + if (apkFileName == null) return null; - }); + File apkFile = fs.file(fs.path.join(project.apkDirectory.path, apkFileName)); + if (apkFile.existsSync()) + return apkFile; + final String modeName = camelCase(buildInfo.modeName); + apkFile = fs.file(fs.path.join(project.apkDirectory.path, modeName, apkFileName)); + if (apkFile.existsSync()) + return apkFile; + if (buildInfo.flavor != null) { + // Android Studio Gradle plugin v3 adds flavor to path. + apkFile = fs.file(fs.path.join(project.apkDirectory.path, buildInfo.flavor, modeName, apkFileName)); + if (apkFile.existsSync()) + return apkFile; + } + return null; } File _findBundleFile(GradleProject project, BuildInfo buildInfo) { @@ -583,12 +567,12 @@ File _findBundleFile(GradleProject project, BuildInfo buildInfo) { if (bundleFileName == null) return null; final String modeName = camelCase(buildInfo.modeName); - File bundleFile = project.bundleDirectory.childDirectory(modeName).childFile(bundleFileName); + File bundleFile = fs.file(fs.path.join(project.bundleDirectory.path, modeName, bundleFileName)); if (bundleFile.existsSync()) return bundleFile; if (buildInfo.flavor != null) { // Android Studio Gradle plugin v3 adds the flavor to the path. For the bundle the folder name is the flavor plus the mode name. - bundleFile = project.bundleDirectory.childDirectory(buildInfo.flavor + modeName).childFile(bundleFileName); + bundleFile = fs.file(fs.path.join(project.bundleDirectory.path, buildInfo.flavor + modeName, bundleFileName)); if (bundleFile.existsSync()) return bundleFile; } @@ -677,20 +661,13 @@ class GradleProject { return 'assemble${toTitleCase(productFlavor)}${toTitleCase(buildType)}'; } - Iterable apkFilesFor(AndroidBuildInfo androidBuildInfo) { - final String buildType = _buildTypeFor(androidBuildInfo.buildInfo); - final String productFlavor = _productFlavorFor(androidBuildInfo.buildInfo); + String apkFileFor(BuildInfo buildInfo) { + final String buildType = _buildTypeFor(buildInfo); + final String productFlavor = _productFlavorFor(buildInfo); if (buildType == null || productFlavor == null) - return const []; - + return null; final String flavorString = productFlavor.isEmpty ? '' : '-' + productFlavor; - if (androidBuildInfo.splitPerAbi) { - return androidBuildInfo.targetArchs.map((AndroidArch arch) { - final String abi = getNameForAndroidArch(arch); - return 'app$flavorString-$abi-$buildType.apk'; - }); - } - return ['app$flavorString-$buildType.apk']; + return 'app$flavorString-$buildType.apk'; } String bundleTaskFor(BuildInfo buildInfo) { diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 1908cb3950..4b46edb0ba 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -137,7 +137,7 @@ class AOTSnapshotter { final String aotSharedLibrary = fs.path.join(outputDir.path, 'app.so'); outputPaths.add(aotSharedLibrary); genSnapshotArgs.add('--snapshot_kind=app-aot-elf'); - genSnapshotArgs.add('--elf=$aotSharedLibrary'); + genSnapshotArgs.add('--assembly=$aotSharedLibrary'); } else { // Blob AOT snapshot. final String vmSnapshotData = fs.path.join(outputDir.path, 'vm_snapshot_data'); diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index e3460193ac..8379e8187c 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -17,6 +17,8 @@ class BuildInfo { this.compilationTraceFilePath, this.extraFrontEndOptions, this.extraGenSnapshotOptions, + this.buildSharedLibrary, + this.targetPlatform, this.fileSystemRoots, this.fileSystemScheme, this.buildNumber, @@ -48,6 +50,12 @@ class BuildInfo { /// Extra command-line options for gen_snapshot. final String extraGenSnapshotOptions; + /// Whether to prefer AOT compiling to a *so file. + final bool buildSharedLibrary; + + /// Target platform for the build (e.g. android_arm versus android_arm64). + final TargetPlatform targetPlatform; + /// Internal version number (not displayed to users). /// Each build must have a unique number to differentiate it from previous builds. /// It is used to determine whether one build is more recent than another, with higher numbers indicating more recent build. @@ -90,31 +98,15 @@ class BuildInfo { bool get supportsSimulator => isEmulatorBuildMode(mode); String get modeName => getModeName(mode); String get friendlyModeName => getFriendlyModeName(mode); -} -/// Information about an Android build to be performed or used. -class AndroidBuildInfo { - const AndroidBuildInfo( - this.buildInfo, { - this.targetArchs = const [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ], - this.splitPerAbi = false, - }); - - // The build info containing the mode and flavor. - final BuildInfo buildInfo; - - /// Whether to split the shared library per ABI. - /// - /// When this is false, multiple ABIs will be contained within one primary - /// build artifact. When this is true, multiple build artifacts (one per ABI) - /// will be produced. - final bool splitPerAbi; - - /// The target platforms for the build. - final Iterable targetArchs; + BuildInfo withTargetPlatform(TargetPlatform targetPlatform) => + BuildInfo(mode, flavor, + trackWidgetCreation: trackWidgetCreation, + compilationTraceFilePath: compilationTraceFilePath, + extraFrontEndOptions: extraFrontEndOptions, + extraGenSnapshotOptions: extraGenSnapshotOptions, + buildSharedLibrary: buildSharedLibrary, + targetPlatform: targetPlatform); } /// The type of build. @@ -262,13 +254,6 @@ enum IOSArch { arm64, } -enum AndroidArch { - armeabi_v7a, - arm64_v8a, - x86, - x86_64, -} - /// The default set of iOS device architectures to build for. const List defaultIOSArchs = [ IOSArch.arm64, @@ -350,51 +335,6 @@ TargetPlatform getTargetPlatformForName(String platform) { return null; } -AndroidArch getAndroidArchForName(String platform) { - switch (platform) { - case 'android-arm': - return AndroidArch.armeabi_v7a; - case 'android-arm64': - return AndroidArch.arm64_v8a; - case 'android-x64': - return AndroidArch.x86_64; - case 'android-x86': - return AndroidArch.x86; - } - assert(false); - return null; -} - -String getNameForAndroidArch(AndroidArch arch) { - switch (arch) { - case AndroidArch.armeabi_v7a: - return 'armeabi-v7a'; - case AndroidArch.arm64_v8a: - return 'arm64-v8a'; - case AndroidArch.x86_64: - return 'x86_64'; - case AndroidArch.x86: - return 'x86'; - } - assert(false); - return null; -} - -String getPlatformNameForAndroidArch(AndroidArch arch) { - switch (arch) { - case AndroidArch.armeabi_v7a: - return 'android-arm'; - case AndroidArch.arm64_v8a: - return 'android-arm64'; - case AndroidArch.x86_64: - return 'android-x64'; - case AndroidArch.x86: - return 'android-x86'; - } - assert(false); - return null; -} - HostPlatform getCurrentHostPlatform() { if (platform.isMacOS) return HostPlatform.darwin_x64; diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 5d9cbe8b4a..1d7fbffd20 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -5,9 +5,6 @@ import 'dart:async'; import '../android/apk.dart'; -import '../base/terminal.dart'; -import '../build_info.dart'; -import '../globals.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; import 'build.dart'; @@ -24,14 +21,12 @@ class BuildApkCommand extends BuildSubCommand { argParser ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) - ..addFlag('split-per-abi', + ..addFlag('build-shared-library', negatable: false, - help: 'Whether to split the APKs per ABIs.' - 'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split', + help: 'Whether to prefer compiling to a *.so file (android only).', ) - ..addMultiOption('target-platform', - splitCommas: true, - defaultsTo: ['android-arm', 'android-arm64'], + ..addOption('target-platform', + defaultsTo: 'android-arm', allowed: ['android-arm', 'android-arm64', 'android-x86', 'android-x64'], help: 'The target platform for which the app is compiled.', ); @@ -54,33 +49,10 @@ class BuildApkCommand extends BuildSubCommand { @override Future runCommand() async { - final BuildInfo buildInfo = getBuildInfo(); - final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo, - splitPerAbi: argResults['split-per-abi'], - targetArchs: argResults['target-platform'].map(getAndroidArchForName) - ); - - if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) { - final String targetPlatforms = argResults['target-platform'].join(', '); - - printStatus('You are building a fat APK that includes binaries for ' - '$targetPlatforms.', emphasis: true, color: TerminalColor.green); - printStatus('If you are deploying the app to the Play Store, ' - 'it\'s recommended to use app bundles or split the APK to reduce the APK size.', emphasis: true); - printStatus('To generate an app bundle, run:', emphasis: true, indent: 4); - printStatus('flutter build appbundle ' - '--target-platform ${targetPlatforms.replaceAll(' ', '')}',indent: 8); - printStatus('Learn more on: https://developer.android.com/guide/app-bundle',indent: 8); - printStatus('To split the APKs per ABI, run:', emphasis: true, indent: 4); - printStatus('flutter build apk ' - '--target-platform ${targetPlatforms.replaceAll(' ', '')} ' - '--split-per-abi', indent: 8); - printStatus('Learn more on: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',indent: 8); - } await buildApk( project: FlutterProject.current(), target: targetFile, - androidBuildInfo: androidBuildInfo, + buildInfo: getBuildInfo(), ); return null; } diff --git a/packages/flutter_tools/lib/src/commands/build_appbundle.dart b/packages/flutter_tools/lib/src/commands/build_appbundle.dart index 76c5ad1725..dcc9d4a53a 100644 --- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart @@ -5,7 +5,6 @@ import 'dart:async'; import '../android/app_bundle.dart'; -import '../build_info.dart'; import '../project.dart'; import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; @@ -21,11 +20,18 @@ class BuildAppBundleCommand extends BuildSubCommand { argParser ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) - ..addMultiOption('target-platform', - splitCommas: true, - defaultsTo: ['android-arm', 'android-arm64'], + ..addFlag( + 'build-shared-library', + negatable: false, + help: 'Whether to prefer compiling to a *.so file (android only).', + ) + ..addOption( + 'target-platform', allowed: ['android-arm', 'android-arm64'], - help: 'The target platform for which the app is compiled.', + 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', ); } @@ -41,13 +47,10 @@ class BuildAppBundleCommand extends BuildSubCommand { @override Future runCommand() async { - final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(getBuildInfo(), - targetArchs: argResults['target-platform'].map(getAndroidArchForName) - ); await buildAppBundle( project: FlutterProject.current(), target: targetFile, - androidBuildInfo: androidBuildInfo, + buildInfo: getBuildInfo(), ); return null; } diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 62535d03a3..9f36386da0 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -387,7 +387,8 @@ class IOSSimulator extends Device { final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor, trackWidgetCreation: buildInfo.trackWidgetCreation, extraFrontEndOptions: buildInfo.extraFrontEndOptions, - extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions); + extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions, + buildSharedLibrary: buildInfo.buildSharedLibrary); final XcodeBuildResult buildResult = await buildXcodeProject( app: app, diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 634d4a8dcc..b39e674d1d 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -320,6 +320,12 @@ abstract class FlutterCommand extends Command { } BuildInfo getBuildInfo() { + TargetPlatform targetPlatform; + if (argParser.options.containsKey('target-platform') && + argResults['target-platform'] != 'default') { + targetPlatform = getTargetPlatformForName(argResults['target-platform']); + } + final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') ? argResults['track-widget-creation'] : false; @@ -356,6 +362,10 @@ abstract class FlutterCommand extends Command { extraGenSnapshotOptions: argParser.options.containsKey(FlutterOptions.kExtraGenSnapshotOptions) ? argResults[FlutterOptions.kExtraGenSnapshotOptions] : null, + buildSharedLibrary: argParser.options.containsKey('build-shared-library') + ? argResults['build-shared-library'] + : false, + targetPlatform: targetPlatform, fileSystemRoots: argParser.options.containsKey(FlutterOptions.kFileSystemRoot) ? argResults[FlutterOptions.kFileSystemRoot] : null, fileSystemScheme: argParser.options.containsKey(FlutterOptions.kFileSystemScheme) diff --git a/packages/flutter_tools/test/android/gradle_test.dart b/packages/flutter_tools/test/android/gradle_test.dart index 7aafd64bf2..3940e9b181 100644 --- a/packages/flutter_tools/test/android/gradle_test.dart +++ b/packages/flutter_tools/test/android/gradle_test.dart @@ -157,102 +157,16 @@ someOtherTask }); test('should provide apk file name for default build types', () { final GradleProject project = GradleProject(['debug', 'profile', 'release'], [], fs.directory('/some/dir'),fs.directory('/some/dir')); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.debug)).first, 'app-debug.apk'); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.profile)).first, 'app-profile.apk'); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.release)).first, 'app-release.apk'); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue); + expect(project.apkFileFor(BuildInfo.debug), 'app-debug.apk'); + expect(project.apkFileFor(BuildInfo.profile), 'app-profile.apk'); + expect(project.apkFileFor(BuildInfo.release), 'app-release.apk'); + expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull); }); test('should provide apk file name for flavored build types', () { final GradleProject project = GradleProject(['debug', 'profile', 'release'], ['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir')); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'free'))).first, 'app-free-debug.apk'); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'paid'))).first, 'app-paid-release.apk'); - expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue); - }); - test('should provide apks for default build types and each ABI', () { - final GradleProject project = GradleProject(['debug', 'profile', 'release'], [], fs.directory('/some/dir'),fs.directory('/some/dir')); - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo.debug, - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ), - [ - 'app-armeabi-v7a-debug.apk', - 'app-arm64-v8a-debug.apk', - ]); - - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo.release, - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ), - [ - 'app-armeabi-v7a-release.apk', - 'app-arm64-v8a-release.apk', - ]); - - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo(BuildMode.release, 'unknown'), - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ).isEmpty, isTrue); - }); - test('should provide apks for each ABI and flavored build types', () { - final GradleProject project = GradleProject(['debug', 'profile', 'release'], ['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir')); - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo(BuildMode.debug, 'free'), - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ), - [ - 'app-free-armeabi-v7a-debug.apk', - 'app-free-arm64-v8a-debug.apk', - ]); - - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo(BuildMode.release, 'paid'), - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ), - [ - 'app-paid-armeabi-v7a-release.apk', - 'app-paid-arm64-v8a-release.apk', - ]); - - expect(project.apkFilesFor( - const AndroidBuildInfo( - BuildInfo(BuildMode.release, 'unknown'), - splitPerAbi: true, - targetArchs: [ - AndroidArch.armeabi_v7a, - AndroidArch.arm64_v8a, - ] - ) - ).isEmpty, isTrue); + expect(project.apkFileFor(const BuildInfo(BuildMode.debug, 'free')), 'app-free-debug.apk'); + expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'paid')), 'app-paid-release.apk'); + expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull); }); test('should provide bundle file name for default build types', () { final GradleProject project = GradleProject(['debug', 'profile', 'release'], [], fs.directory('/some/dir'),fs.directory('/some/dir')); diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart index 8c32fd9e6a..60d3724e9a 100644 --- a/packages/flutter_tools/test/base/build_test.dart +++ b/packages/flutter_tools/test/base/build_test.dart @@ -80,7 +80,7 @@ void main() { }); }); - group('Snapshotter - AOT', () { + group('Snapshotter - iOS AOT', () { const String kSnapshotDart = 'snapshot.dart'; String skyEnginePath; @@ -395,11 +395,10 @@ void main() { ]); }, overrides: contextOverrides); - testUsingContext('builds shared library for android-arm', () async { - fs.file('main.dill').writeAsStringSync('binary magic'); - + testUsingContext('returns failure if buildSharedLibrary is true but no NDK is found', () async { final String outputPath = fs.path.join('build', 'foo'); - fs.directory(outputPath).createSync(recursive: true); + + when(mockAndroidSdk.ndk).thenReturn(null); final int genSnapshotExitCode = await snapshotter.build( platform: TargetPlatform.android_arm, @@ -410,45 +409,8 @@ void main() { buildSharedLibrary: true, ); - expect(genSnapshotExitCode, 0); - expect(genSnapshot.callCount, 1); - expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm); - expect(genSnapshot.snapshotType.mode, BuildMode.release); - expect(genSnapshot.additionalArgs, [ - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - '--no-sim-use-hardfp', - '--no-use-integer-division', - 'main.dill', - ]); - }, overrides: contextOverrides); - - testUsingContext('builds shared library for android-arm64', () async { - fs.file('main.dill').writeAsStringSync('binary magic'); - - final String outputPath = fs.path.join('build', 'foo'); - fs.directory(outputPath).createSync(recursive: true); - - final int genSnapshotExitCode = await snapshotter.build( - platform: TargetPlatform.android_arm64, - buildMode: BuildMode.release, - mainPath: 'main.dill', - packagesPath: '.packages', - outputPath: outputPath, - buildSharedLibrary: true, - ); - - expect(genSnapshotExitCode, 0); - expect(genSnapshot.callCount, 1); - expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm64); - expect(genSnapshot.snapshotType.mode, BuildMode.release); - expect(genSnapshot.additionalArgs, [ - '--deterministic', - '--snapshot_kind=app-aot-elf', - '--elf=build/foo/app.so', - 'main.dill', - ]); + expect(genSnapshotExitCode, isNot(0)); + expect(genSnapshot.callCount, 0); }, overrides: contextOverrides); testUsingContext('builds Android arm release AOT snapshot', () async { diff --git a/packages/flutter_tools/test/ios/xcodeproj_test.dart b/packages/flutter_tools/test/ios/xcodeproj_test.dart index 6a421627d6..63685c512c 100644 --- a/packages/flutter_tools/test/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/ios/xcodeproj_test.dart @@ -286,7 +286,7 @@ Information about project "Runner": platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); - const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); + const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, targetPlatform: TargetPlatform.ios); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties( project: project, @@ -304,7 +304,7 @@ Information about project "Runner": when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); - const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true); + const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true, targetPlatform: TargetPlatform.ios); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties( project: project, @@ -322,7 +322,7 @@ Information about project "Runner": when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm')); - const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); + const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, targetPlatform: TargetPlatform.ios); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties( project: project, @@ -340,7 +340,7 @@ Information about project "Runner": when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: anyNamed('mode'))).thenReturn('engine'); when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile')); - const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null); + const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, targetPlatform: TargetPlatform.ios); final FlutterProject project = FlutterProject.fromPath('path/to/project'); await updateGeneratedXcodeProperties(