Generate ELF shared libraries and allow multi-abi libs in APKs and App bundles (#33696)

* Gradle generates ELF shared libraries instead of AOT snapshots.
* `flutter build apk/appbundle` supports multiple `--target-platform` and defaults to `android-arm` and `android-arm64`.
*  `flutter build apk` now has a flag called `--split-per-abi`.
diff --git a/dev/devicelab/bin/tasks/gradle_plugin_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_test.dart
index 15939ab..c71a01d 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
@@ -46,6 +47,263 @@
     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<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath);
+
+        _checkItContains<String>(<String>[
+          '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<String>(<String>[
+          '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: <String>['-Ptarget-platform=android-arm']);
+
+        if (!pluginProject.hasDebugApk)
+          throw TaskResult.failure(
+              'Gradle did not produce a debug apk file at: ${pluginProject.debugApkPath}');
+
+        final Iterable<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.debugApkPath);
+
+        _checkItContains<String>(<String>[
+          '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<String>(<String>[
+          '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<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath);
+
+        _checkItContains<String>(<String>[
+          '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<String>(<String>[
+          '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: <String>['-Ptarget-platform=android-arm']);
+
+        if (!pluginProject.hasReleaseApk)
+          throw TaskResult.failure(
+              'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}');
+
+        final Iterable<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath);
+
+        _checkItContains<String>(<String>[
+          'AndroidManifest.xml',
+          'classes.dex',
+          'lib/armeabi-v7a/libflutter.so',
+          'lib/armeabi-v7a/libapp.so',
+        ], apkFiles);
+
+        _checkItDoesNotContain<String>(<String>[
+          '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: <String>['-Ptarget-platform=android-arm64']);
+
+        if (!pluginProject.hasReleaseApk)
+          throw TaskResult.failure(
+              'Gradle did not produce a release apk file at: ${pluginProject.releaseApkPath}');
+
+        final Iterable<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath);
+
+        _checkItContains<String>(<String>[
+          'AndroidManifest.xml',
+          'classes.dex',
+          'lib/arm64-v8a/libflutter.so',
+          'lib/arm64-v8a/libapp.so',
+        ], apkFiles);
+
+        _checkItDoesNotContain<String>(<String>[
+          '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: <String>['-Ptarget-platform=android-arm,android-arm64']);
+
+        if (!pluginProject.hasReleaseApk)
+          throw TaskResult.failure(
+              'Gradle did not produce a release apk at: ${pluginProject.releaseApkPath}');
+
+        final Iterable<String> apkFiles = await pluginProject.getFilesInApk(pluginProject.releaseApkPath);
+
+        _checkItContains<String>(<String>[
+          '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<String>(<String>[
+          '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: <String>['-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<String> armApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArmApkPath);
+
+        _checkItContains<String>(<String>[
+          'AndroidManifest.xml',
+          'classes.dex',
+          'lib/armeabi-v7a/libflutter.so',
+          'lib/armeabi-v7a/libapp.so',
+        ], armApkFiles);
+
+        _checkItDoesNotContain<String>(<String>[
+          '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<String> arm64ApkFiles = await pluginProject.getFilesInApk(pluginProject.releaseArm64ApkPath);
+
+        _checkItContains<String>(<String>[
+          'AndroidManifest.xml',
+          'classes.dex',
+          'lib/arm64-v8a/libflutter.so',
+          'lib/arm64-v8a/libapp.so',
+        ], arm64ApkFiles);
+
+        _checkItDoesNotContain<String>(<String>[
+          '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<String> bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath);
+
+        _checkItContains<String>(<String>[
+          '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: <String>['-Ptarget-platform=android-arm']);
+
+        if (!pluginProject.hasReleaseBundle)
+          throw TaskResult.failure(
+              'Gradle did not produce a release aab file at: ${pluginProject.releaseBundlePath}');
+
+        final Iterable<String> bundleFiles = await pluginProject.getFilesInAppBundle(pluginProject.releaseBundlePath);
+
+        _checkItContains<String>(<String>[
+          'base/manifest/AndroidManifest.xml',
+          'base/dex/classes.dex',
+          'base/lib/armeabi-v7a/libapp.so',
+          'base/lib/armeabi-v7a/libflutter.so',
+        ], bundleFiles);
+
+        _checkItDoesNotContain<String>(<String>[
+          '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');
@@ -80,32 +338,9 @@
               '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');
+          final String sharedLibrary = path.join(androidArmSnapshotPath, 'app.so');
+          if (!File(sharedLibrary).existsSync()) {
+            throw TaskResult.failure('Shared library doesn\'t exist');
           }
         }
       });
@@ -183,6 +418,23 @@
   });
 }
 
+
+void _checkItContains<T>(Iterable<T> values, Iterable<T> collection) {
+  for (T value in values) {
+    if (!collection.contains(value)) {
+      throw TaskResult.failure('Expected to find `$value` in `$collection`.');
+    }
+  }
+}
+
+void _checkItDoesNotContain<T>(Iterable<T> values, Iterable<T> 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}');
@@ -288,12 +540,37 @@
   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<void> runGradleTask(String task, {List<String> options}) async {
     return _runGradleTask(workingDirectory: exampleAndroidPath, task: task, options: options);
   }
 
-  bool get hasDebugApk => File(debugApkPath).existsSync();
+  Future<Iterable<String>> getFilesInApk(String apk) async {
+    final Process unzip = await startProcess(
+      'unzip',
+      <String>['-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<Iterable<String>> getFilesInAppBundle(String bundle) {
+    return getFilesInApk(bundle);
+  }
 }
 
 Future<void> _runGradleTask({String workingDirectory, String task, List<String> options}) async {
diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml
index 2f56149..e8f090d 100644
--- a/dev/devicelab/pubspec.yaml
+++ b/dev/devicelab/pubspec.yaml
@@ -5,7 +5,7 @@
 
 environment:
   # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
-  sdk: ">=2.2.2 <3.0.0"
+  sdk: ">=2.3.0 <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 c06edba..66530cb 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -2,6 +2,7 @@
 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
@@ -36,6 +37,49 @@
 apply plugin: FlutterPlugin
 
 class FlutterPlugin implements Plugin<Project> {
+    // 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
@@ -49,29 +93,6 @@
     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()) {
@@ -80,11 +101,16 @@
         return result
     }
 
-    private String getTargetPlatform(Project project) {
-        if (project.hasProperty('target-platform')) {
-            return project.property('target-platform')
+    private List<String> getTargetPlatforms(Project project) {
+        if (!project.hasProperty('target-platform')) {
+            return DEFAULT_PLATFORMS
         }
-        return 'android-arm-all';
+        return project.property('target-platform').split(',').collect {
+            if (!PLATFORM_ARCH_MAP[it]) {
+                throw new GradleException("Invalid platform: $it.")
+            }
+            return it
+        }
     }
 
     private Boolean getBuildShareLibrary(Project project) {
@@ -94,6 +120,13 @@
         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"))
@@ -111,21 +144,55 @@
         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
 
-        allTargetPlatforms.each { currentTargetPlatformValue, abi ->
+        // 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]
             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"
+                    pickFirst "lib/${abiValue}/libflutter.so"
+                    // Prevent the ELF library from getting corrupted.
+                    doNotStrip "*/${abiValue}/libapp.so"
+                }
+                if (this.splitPerAbi(project)) {
+                    splits {
+                        abi {
+                            include abiValue
+                        }
+                    }
                 }
             }
         }
@@ -173,7 +240,7 @@
             baseEnginePath = Paths.get(engineOut.absolutePath)
             flutterJar = baseEnginePath.resolve("flutter.jar").toFile()
             if (!flutterJar.isFile()) {
-                throw new GradleException('File not found: ' + flutterJar)
+                throw new GradleException('Local engine jar not found: ' + flutterJar)
             }
 
             localEngine = engineOut.name
@@ -186,14 +253,14 @@
             dynamicProfileFlutterJar = flutterJar
             dynamicReleaseFlutterJar = flutterJar
         } else {
-            String targetPlatform = getTargetPlatform(project)
-            String targetArch = targetPlatform == 'android-arm64' ? 'arm64' : 'arm'
+            String basePlatformArch = getBasePlatform(project)
+            // This is meant to include the compiled classes only, however it will include `libflutter.so` as well.
             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()
+            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()
         }
 
         if (!debugFlutterJar.isFile()) {
@@ -209,7 +276,7 @@
 
         // 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) {
+        Task debugX86JarTask = project.tasks.create("${FLUTTER_BUILD_PREFIX}X86Jar", Jar) {
             destinationDir flutterX86Jar.parentFile
             archiveName flutterX86Jar.name
             from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
@@ -221,7 +288,7 @@
         }
         // Add flutter.jar dependencies to all <buildType>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 {
@@ -338,6 +405,19 @@
         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
@@ -395,13 +475,24 @@
             extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
         }
 
-        Boolean buildSharedLibraryValue = this.getBuildShareLibrary(project)
-        String targetPlatformValue = this.getTargetPlatform(project)
-
+        def targetPlatforms = this.getTargetPlatforms(project)
         def addFlutterDeps = { variant ->
-            String flutterBuildMode = buildModeFor(variant.buildType)
+            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
+                    }
+                }
+            }
 
-            if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) {
+            String flutterBuildMode = buildModeFor(variant.buildType)
+            if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) {
                 Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
                 if (task) {
                     task.dependsOn project.flutterBuildX86Jar
@@ -413,22 +504,10 @@
             }
 
             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: "compile${flutterBuildPrefix}${variant.name.capitalize()}${currentTargetPlatformValue}", 
-                    type: FlutterTask) {
+            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) {
                     flutterRoot this.flutterRoot
                     flutterExecutable this.flutterExecutable
                     buildMode flutterBuildMode
@@ -444,56 +523,53 @@
                     createPatch createPatchValue
                     buildNumber buildNumberValue
                     baselineDir baselineDirValue
-                    buildSharedLibrary buildSharedLibraryValue
-                    targetPlatform currentTargetPlatformValue
+                    targetPlatform targetArch
                     sourceDir project.file(project.flutter.source)
-                    intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${currentTargetPlatformValue}")
+                    intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/${targetArch}")
                     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: "packFlutterSnapshotsAndLibs${flutterBuildPrefix}${variant.name.capitalize()}", type: Jar) {
+            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) {
                 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/**'
+                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/**'
+                        }
                     }
                 }
-
                 dependsOn flutterTasks
-                // Add the snapshots and rename them as `lib/{abi}/*.so`.
-                flutterTasks.each { flutterTask -> 
+                // Add the ELF library.
+                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 '*.so'
+                        rename { String filename ->
+                            return "lib/${flutterTask.abi}/lib${filename}"
                         }
                     }
                 }
             }
-
             // 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
-                    })
+            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")
@@ -510,12 +586,8 @@
                     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) {
@@ -576,8 +648,6 @@
     @Optional @Input
     String baselineDir
     @Optional @Input
-    Boolean buildSharedLibrary
-    @Optional @Input
     String targetPlatform
     @Input
     String abi
@@ -622,8 +692,10 @@
                 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"
                 }
@@ -633,14 +705,6 @@
                 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}"
             }
         }
@@ -655,6 +719,7 @@
             args "build", "bundle"
             args "--suppress-analytics"
             args "--target", targetPath
+            args "--target-platform", "${targetPlatform}"
             if (verbose) {
                 args "--verbose"
             }
@@ -688,9 +753,6 @@
             if (extraGenSnapshotOptions != null) {
                 args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
             }
-            if (targetPlatform != null) {
-                args "--target-platform", "${targetPlatform}"
-            }
             if (buildMode == "release" || buildMode == "profile") {
                 args "--precompiled"
             } else {
@@ -731,14 +793,7 @@
             from "${intermediateDir}"
 
             if (buildMode == 'release' || buildMode == 'profile') {
-                if (buildSharedLibrary) {
-                    include "app.so"
-                } else {
-                    include "vm_snapshot_data"
-                    include "vm_snapshot_instr"
-                    include "isolate_snapshot_data"
-                    include "isolate_snapshot_instr"
-                }
+                include "app.so"
             }
         }
     }
@@ -805,7 +860,7 @@
 
     void buildFinished(BuildResult result) {
         if (result.failure != null) {
-            if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.flutterBuildPrefix)) {
+            if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.FLUTTER_BUILD_PREFIX)) {
                 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 e062da5..f215124 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -388,18 +388,27 @@
     if (!await _checkForSupportedAdbVersion() || !await _checkForSupportedAndroidVersion())
       return LaunchResult.failed();
 
-    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.');
+
+    if (!debuggingOptions.buildInfo.isDebug &&
+          !debuggingOptions.buildInfo.isDynamic) {
+      printError('Profile and release builds are only supported.');
       return LaunchResult.failed();
     }
 
-    BuildInfo buildInfo = debuggingOptions.buildInfo;
-    if (buildInfo.targetPlatform == null && devicePlatform == TargetPlatform.android_arm64)
-      buildInfo = buildInfo.withTargetPlatform(TargetPlatform.android_arm64);
+    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();
+    }
 
     if (!prebuiltApplication || androidSdk.licensesAvailable && androidSdk.latestVersion == null) {
       printTrace('Building APK');
@@ -407,7 +416,9 @@
       await buildApk(
           project: project,
           target: mainPath,
-          buildInfo: buildInfo,
+          androidBuildInfo: AndroidBuildInfo(debuggingOptions.buildInfo,
+            targetArchs: <AndroidArch>[androidArch]
+          ),
       );
       // 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 8b5b8d9..908c535 100644
--- a/packages/flutter_tools/lib/src/android/apk.dart
+++ b/packages/flutter_tools/lib/src/android/apk.dart
@@ -16,7 +16,7 @@
 Future<void> buildApk({
   @required FlutterProject project,
   @required String target,
-  BuildInfo buildInfo = BuildInfo.debug,
+  @required AndroidBuildInfo androidBuildInfo,
 }) async {
   if (!project.android.isUsingGradle) {
     throwToolExit(
@@ -33,7 +33,7 @@
 
   await buildGradleProject(
     project: project,
-    buildInfo: buildInfo,
+    androidBuildInfo: androidBuildInfo,
     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 c1e94ab..4f304c1 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 @@
 Future<void> buildAppBundle({
   @required FlutterProject project,
   @required String target,
-  BuildInfo buildInfo = BuildInfo.debug,
+  @required AndroidBuildInfo androidBuildInfo,
 }) async {
   if (!project.android.isUsingGradle) {
     throwToolExit(
@@ -42,7 +42,7 @@
 
   return buildGradleProject(
     project: project,
-    buildInfo: buildInfo,
+    androidBuildInfo: androidBuildInfo,
     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 62f4475..bf53166 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -15,6 +15,7 @@
 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';
@@ -318,7 +319,7 @@
 
 Future<void> buildGradleProject({
   @required FlutterProject project,
-  @required BuildInfo buildInfo,
+  @required AndroidBuildInfo androidBuildInfo,
   @required String target,
   @required bool isBuildingBundle,
 }) async {
@@ -330,7 +331,7 @@
   // 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: buildInfo);
+  updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
 
   final String gradle = await _ensureGradle(project);
 
@@ -342,7 +343,7 @@
     case FlutterPluginVersion.managed:
       // Fall through. Managed plugin builds the same way as plugin v2.
     case FlutterPluginVersion.v2:
-      return _buildGradleProjectV2(project, gradle, buildInfo, target, isBuildingBundle);
+      return _buildGradleProjectV2(project, gradle, androidBuildInfo, target, isBuildingBundle);
   }
 }
 
@@ -391,11 +392,12 @@
 Future<void> _buildGradleProjectV2(
   FlutterProject flutterProject,
   String gradle,
-  BuildInfo buildInfo,
+  AndroidBuildInfo androidBuildInfo,
   String target,
   bool isBuildingBundle,
 ) async {
   final GradleProject project = await _gradleProject();
+  final BuildInfo buildInfo = androidBuildInfo.buildInfo;
 
   String assembleTask;
 
@@ -453,12 +455,13 @@
     command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}');
   if (buildInfo.fileSystemScheme != null)
     command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
-  if (buildInfo.buildSharedLibrary) {
-    command.add('-Pbuild-shared-library=true');
+  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.targetPlatform != null)
-    command.add('-Ptarget-platform=${getNameForTargetPlatform(buildInfo.targetPlatform)}');
-
   command.add(assembleTask);
   bool potentialAndroidXFailure = false;
   final Stopwatch sw = Stopwatch()..start();
@@ -508,24 +511,27 @@
   flutterUsage.sendTiming('build', 'gradle-v2', Duration(milliseconds: sw.elapsedMilliseconds));
 
   if (!isBuildingBundle) {
-    final File apkFile = _findApkFile(project, buildInfo);
-    if (apkFile == null)
+    final Iterable<File> apkFiles = _findApkFiles(project, androidBuildInfo);
+    if (apkFiles.isEmpty)
       throwToolExit('Gradle build failed to produce an Android package.');
-    // Copy the APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
-    apkFile.copySync(project.apkDirectory.childFile('app.apk').path);
+    // 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);
 
     printTrace('calculateSha: ${project.apkDirectory}/app.apk');
     final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1');
-    apkShaFile.writeAsStringSync(_calculateSha(apkFile));
+    apkShaFile.writeAsStringSync(_calculateSha(apkFiles.first));
 
-    String appSize;
-    if (buildInfo.mode == BuildMode.debug) {
-      appSize = '';
-    } else {
-      appSize = ' (${getSizeAsMB(apkFile.lengthSync())})';
+    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);
     }
-    printStatus('Built ${fs.path.relative(apkFile.path)}$appSize.');
-
   } else {
     final File bundleFile = _findBundleFile(project, buildInfo);
     if (bundleFile == null)
@@ -537,28 +543,38 @@
     } else {
       appSize = ' (${getSizeAsMB(bundleFile.lengthSync())})';
     }
-    printStatus('Built ${fs.path.relative(bundleFile.path)}$appSize.');
+    printStatus('Built ${fs.path.relative(bundleFile.path)}$appSize.',
+        color: TerminalColor.green);
   }
 }
 
-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));
+Iterable<File> _findApkFiles(GradleProject project, AndroidBuildInfo androidBuildInfo) {
+  final Iterable<String> apkFileNames = project.apkFilesFor(androidBuildInfo);
+  if (apkFileNames.isEmpty)
+    return const <File>[];
+
+  return apkFileNames.map<File>((String apkFileName) {
+    File apkFile = project.apkDirectory.childFile(apkFileName);
     if (apkFile.existsSync())
       return apkFile;
-  }
-  return null;
+    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;
+    }
+    return null;
+  });
 }
 
 File _findBundleFile(GradleProject project, BuildInfo buildInfo) {
@@ -567,12 +583,12 @@
   if (bundleFileName == null)
     return null;
   final String modeName = camelCase(buildInfo.modeName);
-  File bundleFile = fs.file(fs.path.join(project.bundleDirectory.path, modeName, bundleFileName));
+  File bundleFile = project.bundleDirectory.childDirectory(modeName).childFile(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 = fs.file(fs.path.join(project.bundleDirectory.path, buildInfo.flavor + modeName, bundleFileName));
+    bundleFile = project.bundleDirectory.childDirectory(buildInfo.flavor + modeName).childFile(bundleFileName);
     if (bundleFile.existsSync())
       return bundleFile;
   }
@@ -661,13 +677,20 @@
     return 'assemble${toTitleCase(productFlavor)}${toTitleCase(buildType)}';
   }
 
-  String apkFileFor(BuildInfo buildInfo) {
-    final String buildType = _buildTypeFor(buildInfo);
-    final String productFlavor = _productFlavorFor(buildInfo);
+  Iterable<String> apkFilesFor(AndroidBuildInfo androidBuildInfo) {
+    final String buildType = _buildTypeFor(androidBuildInfo.buildInfo);
+    final String productFlavor = _productFlavorFor(androidBuildInfo.buildInfo);
     if (buildType == null || productFlavor == null)
-      return null;
+      return const <String>[];
+
     final String flavorString = productFlavor.isEmpty ? '' : '-' + productFlavor;
-    return 'app$flavorString-$buildType.apk';
+    if (androidBuildInfo.splitPerAbi) {
+      return androidBuildInfo.targetArchs.map<String>((AndroidArch arch) {
+        final String abi = getNameForAndroidArch(arch);
+        return 'app$flavorString-$abi-$buildType.apk';
+      });
+    }
+    return <String>['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 4b46edb..1908cb3 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -137,7 +137,7 @@
       final String aotSharedLibrary = fs.path.join(outputDir.path, 'app.so');
       outputPaths.add(aotSharedLibrary);
       genSnapshotArgs.add('--snapshot_kind=app-aot-elf');
-      genSnapshotArgs.add('--assembly=$aotSharedLibrary');
+      genSnapshotArgs.add('--elf=$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 8379e81..e346019 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -17,8 +17,6 @@
     this.compilationTraceFilePath,
     this.extraFrontEndOptions,
     this.extraGenSnapshotOptions,
-    this.buildSharedLibrary,
-    this.targetPlatform,
     this.fileSystemRoots,
     this.fileSystemScheme,
     this.buildNumber,
@@ -50,12 +48,6 @@
   /// 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.
@@ -98,15 +90,31 @@
   bool get supportsSimulator => isEmulatorBuildMode(mode);
   String get modeName => getModeName(mode);
   String get friendlyModeName => getFriendlyModeName(mode);
+}
 
-  BuildInfo withTargetPlatform(TargetPlatform targetPlatform) =>
-      BuildInfo(mode, flavor,
-          trackWidgetCreation: trackWidgetCreation,
-          compilationTraceFilePath: compilationTraceFilePath,
-          extraFrontEndOptions: extraFrontEndOptions,
-          extraGenSnapshotOptions: extraGenSnapshotOptions,
-          buildSharedLibrary: buildSharedLibrary,
-          targetPlatform: targetPlatform);
+/// Information about an Android build to be performed or used.
+class AndroidBuildInfo {
+  const AndroidBuildInfo(
+    this.buildInfo, {
+    this.targetArchs = const <AndroidArch>[
+      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<AndroidArch> targetArchs;
 }
 
 /// The type of build.
@@ -254,6 +262,13 @@
   arm64,
 }
 
+enum AndroidArch {
+  armeabi_v7a,
+  arm64_v8a,
+  x86,
+  x86_64,
+}
+
 /// The default set of iOS device architectures to build for.
 const List<IOSArch> defaultIOSArchs = <IOSArch>[
   IOSArch.arm64,
@@ -335,6 +350,51 @@
   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 1d7fbff..5d9cbe8 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -5,6 +5,9 @@
 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';
@@ -21,12 +24,14 @@
 
     argParser
       ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
-      ..addFlag('build-shared-library',
+      ..addFlag('split-per-abi',
         negatable: false,
-        help: 'Whether to prefer compiling to a *.so file (android only).',
+        help: 'Whether to split the APKs per ABIs.'
+              'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',
       )
-      ..addOption('target-platform',
-        defaultsTo: 'android-arm',
+      ..addMultiOption('target-platform',
+        splitCommas: true,
+        defaultsTo: <String>['android-arm', 'android-arm64'],
         allowed: <String>['android-arm', 'android-arm64', 'android-x86', 'android-x64'],
         help: 'The target platform for which the app is compiled.',
       );
@@ -49,10 +54,33 @@
 
   @override
   Future<FlutterCommandResult> runCommand() async {
+    final BuildInfo buildInfo = getBuildInfo();
+    final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
+      splitPerAbi: argResults['split-per-abi'],
+      targetArchs: argResults['target-platform'].map<AndroidArch>(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,
-      buildInfo: getBuildInfo(),
+      androidBuildInfo: androidBuildInfo,
     );
     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 dcc9d4a..76c5ad1 100644
--- a/packages/flutter_tools/lib/src/commands/build_appbundle.dart
+++ b/packages/flutter_tools/lib/src/commands/build_appbundle.dart
@@ -5,6 +5,7 @@
 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';
@@ -20,18 +21,11 @@
 
     argParser
       ..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
-      ..addFlag(
-        'build-shared-library',
-        negatable: false,
-        help: 'Whether to prefer compiling to a *.so file (android only).',
-      )
-      ..addOption(
-        'target-platform',
+      ..addMultiOption('target-platform',
+        splitCommas: true,
+        defaultsTo: <String>['android-arm', 'android-arm64'],
         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',
+        help: 'The target platform for which the app is compiled.',
       );
   }
 
@@ -47,10 +41,13 @@
 
   @override
   Future<FlutterCommandResult> runCommand() async {
+    final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(getBuildInfo(),
+      targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName)
+    );
     await buildAppBundle(
       project: FlutterProject.current(),
       target: targetFile,
-      buildInfo: getBuildInfo(),
+      androidBuildInfo: androidBuildInfo,
     );
     return null;
   }
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 9f36386..62535d0 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -387,8 +387,7 @@
     final BuildInfo debugBuildInfo = BuildInfo(BuildMode.debug, buildInfo.flavor,
         trackWidgetCreation: buildInfo.trackWidgetCreation,
         extraFrontEndOptions: buildInfo.extraFrontEndOptions,
-        extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions,
-        buildSharedLibrary: buildInfo.buildSharedLibrary);
+        extraGenSnapshotOptions: buildInfo.extraGenSnapshotOptions);
 
     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 b39e674..634d4a8 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -320,12 +320,6 @@
   }
 
   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;
@@ -362,10 +356,6 @@
       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 3940e9b..7aafd64 100644
--- a/packages/flutter_tools/test/android/gradle_test.dart
+++ b/packages/flutter_tools/test/android/gradle_test.dart
@@ -157,16 +157,102 @@
     });
     test('should provide apk file name for default build types', () {
       final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
-      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);
+      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);
     });
     test('should provide apk file name for flavored build types', () {
       final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
-      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);
+      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(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
+      expect(project.apkFilesFor(
+        const AndroidBuildInfo(
+          BuildInfo.debug,
+            splitPerAbi: true,
+            targetArchs: <AndroidArch>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ),
+        <String>[
+          'app-armeabi-v7a-debug.apk',
+          'app-arm64-v8a-debug.apk',
+        ]);
+
+      expect(project.apkFilesFor(
+        const AndroidBuildInfo(
+          BuildInfo.release,
+            splitPerAbi: true,
+            targetArchs: <AndroidArch>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ),
+        <String>[
+          'app-armeabi-v7a-release.apk',
+          'app-arm64-v8a-release.apk',
+        ]);
+
+      expect(project.apkFilesFor(
+        const AndroidBuildInfo(
+          BuildInfo(BuildMode.release, 'unknown'),
+            splitPerAbi: true,
+            targetArchs: <AndroidArch>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ).isEmpty, isTrue);
+    });
+    test('should provide apks for each ABI and flavored build types', () {
+      final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
+      expect(project.apkFilesFor(
+        const AndroidBuildInfo(
+          BuildInfo(BuildMode.debug, 'free'),
+            splitPerAbi: true,
+            targetArchs: <AndroidArch>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ),
+        <String>[
+          '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>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ),
+        <String>[
+          '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>[
+                AndroidArch.armeabi_v7a,
+                AndroidArch.arm64_v8a,
+              ]
+            )
+          ).isEmpty, isTrue);
     });
     test('should provide bundle file name for default build types', () {
       final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], 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 60d3724..8c32fd9 100644
--- a/packages/flutter_tools/test/base/build_test.dart
+++ b/packages/flutter_tools/test/base/build_test.dart
@@ -80,7 +80,7 @@
     });
   });
 
-  group('Snapshotter - iOS AOT', () {
+  group('Snapshotter - AOT', () {
     const String kSnapshotDart = 'snapshot.dart';
     String skyEnginePath;
 
@@ -395,10 +395,11 @@
       ]);
     }, overrides: contextOverrides);
 
-    testUsingContext('returns failure if buildSharedLibrary is true but no NDK is found', () async {
-      final String outputPath = fs.path.join('build', 'foo');
+    testUsingContext('builds shared library for android-arm', () async {
+      fs.file('main.dill').writeAsStringSync('binary magic');
 
-      when(mockAndroidSdk.ndk).thenReturn(null);
+      final String outputPath = fs.path.join('build', 'foo');
+      fs.directory(outputPath).createSync(recursive: true);
 
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.android_arm,
@@ -409,8 +410,45 @@
         buildSharedLibrary: true,
       );
 
-      expect(genSnapshotExitCode, isNot(0));
-      expect(genSnapshot.callCount, 0);
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.android_arm);
+      expect(genSnapshot.snapshotType.mode, BuildMode.release);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--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, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-elf',
+        '--elf=build/foo/app.so',
+        'main.dill',
+      ]);
     }, 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 63685c5..6a42162 100644
--- a/packages/flutter_tools/test/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/ios/xcodeproj_test.dart
@@ -286,7 +286,7 @@
           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, targetPlatform: TargetPlatform.ios);
+      const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null);
       final FlutterProject project = FlutterProject.fromPath('path/to/project');
       await updateGeneratedXcodeProperties(
         project: project,
@@ -304,7 +304,7 @@
       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, targetPlatform: TargetPlatform.ios);
+      const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null, trackWidgetCreation: true);
       final FlutterProject project = FlutterProject.fromPath('path/to/project');
       await updateGeneratedXcodeProperties(
         project: project,
@@ -322,7 +322,7 @@
       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, targetPlatform: TargetPlatform.ios);
+      const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null);
       final FlutterProject project = FlutterProject.fromPath('path/to/project');
       await updateGeneratedXcodeProperties(
         project: project,
@@ -340,7 +340,7 @@
       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, targetPlatform: TargetPlatform.ios);
+      const BuildInfo buildInfo = BuildInfo(BuildMode.debug, null);
 
       final FlutterProject project = FlutterProject.fromPath('path/to/project');
       await updateGeneratedXcodeProperties(