[flutter_tools] support --split-debug-info option in android builds (#49650)

diff --git a/packages/flutter_tools/bin/macos_assemble.sh b/packages/flutter_tools/bin/macos_assemble.sh
index c923cc6..b821c96 100755
--- a/packages/flutter_tools/bin/macos_assemble.sh
+++ b/packages/flutter_tools/bin/macos_assemble.sh
@@ -37,6 +37,12 @@
   flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
 fi
 
+# Provide location to split debug info
+split_debug_info_option=""
+if [[ -n "$SPLIT_DEBUG_INFO" ]]; then
+  split_debug_info_option="-dSplitDebugInfo=${SPLIT_DEBUG_INFO}"
+fi
+
 # Set the build mode
 build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
 
@@ -75,7 +81,8 @@
     -dTargetPlatform=darwin-x64                                             \
     -dTargetFile="${target_path}"                                           \
     -dBuildMode="${build_mode}"                                             \
-    -dFontSubset="${icon_tree_shaker_flag}"                                      \
+    "${split_debug_info_option}"                                            \
+    -dFontSubset="${icon_tree_shaker_flag}"                                 \
     --build-inputs="${build_inputs_path}"                                   \
     --build-outputs="${build_outputs_path}"                                 \
     --output="${ephemeral_dir}"                                             \
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 69a63df..ee764b9 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -603,6 +603,10 @@
         if (project.hasProperty('extra-gen-snapshot-options')) {
             extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
         }
+        String splitDebugInfoValue = null
+        if (project.hasProperty('split-debug-info')) {
+            splitDebugInfoValue = project.property('split-debug-info')
+        }
         Boolean treeShakeIconsOptionsValue = false
         if (project.hasProperty('tree-shake-icons')) {
             treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean()
@@ -641,6 +645,7 @@
                 intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")
                 extraFrontEndOptions extraFrontEndOptionsValue
                 extraGenSnapshotOptions extraGenSnapshotOptionsValue
+                splitDebugInfo splitDebugInfoValue
                 treeShakeIcons treeShakeIconsOptionsValue
             }
             File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
@@ -775,6 +780,8 @@
     @Optional @Input
     String extraGenSnapshotOptions
     @Optional @Input
+    String splitDebugInfo
+    @Optional @Input
     Boolean treeShakeIcons
 
     @OutputFiles
@@ -832,6 +839,9 @@
             if (extraFrontEndOptions != null) {
                 args "-dExtraFrontEndOptions=${extraFrontEndOptions}"
             }
+            if (splitDebugInfo != null) {
+                args "-dSplitDebugInfo=${splitDebugInfo}"
+            }
             if (treeShakeIcons == true) {
                 args "-dTreeShakeIcons=true"
             }
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 74bb007..9d386d5 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -338,6 +338,9 @@
   if (androidBuildInfo.fastStart) {
     command.add('-Pfast-start=true');
   }
+  if (androidBuildInfo.buildInfo.splitDebugInfoPath != null) {
+    command.add('-Psplit-debug-info=${androidBuildInfo.buildInfo.splitDebugInfoPath}');
+  }
   if (androidBuildInfo.buildInfo.treeShakeIcons) {
     command.add('-Ptree-shake-icons=true');
   }
diff --git a/packages/flutter_tools/lib/src/aot.dart b/packages/flutter_tools/lib/src/aot.dart
index 24d3b84..c3c5d8b 100644
--- a/packages/flutter_tools/lib/src/aot.dart
+++ b/packages/flutter_tools/lib/src/aot.dart
@@ -92,6 +92,7 @@
             extraGenSnapshotOptions: extraGenSnapshotOptions,
             bitcode: bitcode,
             quiet: quiet,
+            splitDebugInfo: null,
           ).then<int>((int buildExitCode) {
             return buildExitCode;
           });
@@ -128,6 +129,7 @@
           outputPath: outputPath,
           extraGenSnapshotOptions: extraGenSnapshotOptions,
           bitcode: false,
+          splitDebugInfo: null,
         );
         if (snapshotExitCode != 0) {
           status?.cancel();
diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 42bef53..370e750 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -94,6 +94,7 @@
     DarwinArch darwinArch,
     List<String> extraGenSnapshotOptions = const <String>[],
     @required bool bitcode,
+    @required String splitDebugInfo,
     bool quiet = false,
   }) async {
     if (bitcode && platform != TargetPlatform.ios) {
@@ -157,11 +158,25 @@
       genSnapshotArgs.add('--no-use-integer-division');
     }
 
+    // The name of the debug file must contain additonal information about
+    // the architecture, since a single build command may produce
+    // multiple debug files.
+    final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
+    final String debugFilename = 'app.$archName.symbols';
+    if (splitDebugInfo != null) {
+      globals.fs.directory(splitDebugInfo)
+        .createSync(recursive: true);
+    }
+
     // Optimization arguments.
     genSnapshotArgs.addAll(<String>[
       // Faster async/await
       '--no-causal-async-stacks',
       '--lazy-async-stacks',
+      if (splitDebugInfo != null) ...<String>[
+        '--dwarf-stack-traces',
+        '--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}'
+      ]
     ]);
 
     genSnapshotArgs.add(mainPath);
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 3888954..edad71e 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -21,6 +21,7 @@
     this.fileSystemScheme,
     this.buildNumber,
     this.buildName,
+    this.splitDebugInfoPath,
     @required this.treeShakeIcons,
   });
 
@@ -62,6 +63,11 @@
   /// On Xcode builds it is used as CFBundleShortVersionString,
   final String buildName;
 
+  /// An optional directory path to save debugging information from dwarf stack
+  /// traces. If null, stack trace information is not stripped from the
+  /// executable.
+  final String splitDebugInfoPath;
+
   static const BuildInfo debug = BuildInfo(BuildMode.debug, null, treeShakeIcons: false);
   static const BuildInfo profile = BuildInfo(BuildMode.profile, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
   static const BuildInfo jitRelease = BuildInfo(BuildMode.jitRelease, null, treeShakeIcons: kIconTreeShakerEnabledDefault);
@@ -392,7 +398,7 @@
   return null;
 }
 
-String getNameForTargetPlatform(TargetPlatform platform) {
+String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch darwinArch}) {
   switch (platform) {
     case TargetPlatform.android_arm:
       return 'android-arm';
@@ -403,6 +409,9 @@
     case TargetPlatform.android_x86:
       return 'android-x86';
     case TargetPlatform.ios:
+      if (darwinArch != null) {
+        return 'ios-${getNameForDarwinArch(darwinArch)}';
+      }
       return 'ios';
     case TargetPlatform.darwin_x64:
       return 'darwin-x64';
diff --git a/packages/flutter_tools/lib/src/build_system/targets/android.dart b/packages/flutter_tools/lib/src/build_system/targets/android.dart
index d2766cb..f7a8405 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/android.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/android.dart
@@ -204,6 +204,7 @@
   Future<void> build(Environment environment) async {
     final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
     final Directory output = environment.buildDir.childDirectory(_androidAbiName);
+    final String splitDebugInfo = environment.defines[kSplitDebugInfo];
     if (environment.defines[kBuildMode] == null) {
       throw MissingDefineException(kBuildMode, 'aot_elf');
     }
@@ -221,6 +222,7 @@
       outputPath: output.path,
       bitcode: false,
       extraGenSnapshotOptions: extraGenSnapshotOptions,
+      splitDebugInfo: splitDebugInfo,
     );
     if (snapshotExitCode != 0) {
       throw Exception('AOT snapshotter exited with code $snapshotExitCode');
diff --git a/packages/flutter_tools/lib/src/build_system/targets/dart.dart b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
index 7e46db2..9a03afb 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/dart.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/dart.dart
@@ -41,6 +41,9 @@
 /// This is expected to be a comma separated list of strings.
 const String kExtraGenSnapshotOptions = 'ExtraGenSnapshotOptions';
 
+/// Whether to strip source code information out of release builds and where to save it.
+const String kSplitDebugInfo = 'SplitDebugInfo';
+
 /// Alternative scheme for file URIs.
 ///
 /// May be used along with [kFileSystemRoots] to support a multi-root
@@ -259,6 +262,7 @@
       ?? const <String>[];
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
+    final String saveDebuggingInformation = environment.defines[kSplitDebugInfo];
     final int snapshotExitCode = await snapshotter.build(
       platform: targetPlatform,
       buildMode: buildMode,
@@ -267,6 +271,7 @@
       outputPath: outputPath,
       bitcode: false,
       extraGenSnapshotOptions: extraGenSnapshotOptions,
+      splitDebugInfo: saveDebuggingInformation
     );
     if (snapshotExitCode != 0) {
       throw Exception('AOT snapshotter exited with code $snapshotExitCode');
diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
index a25b1ef..e71258b 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
@@ -38,6 +38,7 @@
     final bool bitcode = environment.defines[kBitcodeFlag] == 'true';
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
+    final String splitDebugInfo = environment.defines[kSplitDebugInfo];
     final List<DarwinArch> iosArchs = environment.defines[kIosArchs]
       ?.split(' ')
       ?.map(getIOSArchForName)
@@ -60,6 +61,7 @@
         darwinArch: iosArch,
         bitcode: bitcode,
         quiet: true,
+        splitDebugInfo: splitDebugInfo,
       ));
     }
     final List<int> results = await Future.wait(pending);
diff --git a/packages/flutter_tools/lib/src/build_system/targets/macos.dart b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
index 2eeba00..7368512 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/macos.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/macos.dart
@@ -200,6 +200,7 @@
     if (buildMode == BuildMode.debug) {
       throw Exception('precompiled macOS framework only supported in release/profile builds.');
     }
+    final String splitDebugInfo = environment.defines[kSplitDebugInfo];
     final int result = await AOTSnapshotter(reportTimings: false).build(
       bitcode: false,
       buildMode: buildMode,
@@ -208,6 +209,7 @@
       platform: TargetPlatform.darwin_x64,
       darwinArch: DarwinArch.x86_64,
       packagesPath: environment.projectDir.childFile('.packages').path,
+      splitDebugInfo: splitDebugInfo,
     );
     if (result != 0) {
       throw Exception('gen shapshot failed.');
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 7e45e75..de01a1e 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -26,6 +26,7 @@
     usesBuildNumberOption();
     usesBuildNameOption();
     addShrinkingFlag();
+    addSplitDebugInfoOption();
     argParser
       ..addFlag('split-per-abi',
         negatable: false,
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index e3410f1..f4c857d 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -167,6 +167,11 @@
     xcodeBuildSettings.add('FLUTTER_TARGET=$targetOverride');
   }
 
+  // This is an optional path to split debug info
+  if (buildInfo.splitDebugInfoPath != null) {
+    xcodeBuildSettings.add('SPLIT_DEBUG_INFO=${buildInfo.splitDebugInfoPath}');
+  }
+
   // The build outputs directory, relative to FLUTTER_APPLICATION_PATH.
   xcodeBuildSettings.add('FLUTTER_BUILD_DIR=${buildDirOverride ?? getBuildDirectory()}');
 
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index cb03804..68f932a 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -107,6 +107,7 @@
   static const String kEnableExperiment = 'enable-experiment';
   static const String kFileSystemRoot = 'filesystem-root';
   static const String kFileSystemScheme = 'filesystem-scheme';
+  static const String kSplitDebugInfoOption = 'split-debug-info';
 }
 
 abstract class FlutterCommand extends Command<void> {
@@ -366,6 +367,20 @@
       help: 'Build a JIT release version of your app${defaultToRelease ? ' (default mode)' : ''}.');
   }
 
+  void addSplitDebugInfoOption() {
+    argParser.addOption(FlutterOptions.kSplitDebugInfoOption,
+      help: 'In a release build, this flag reduces application size by storing '
+        'Dart program symbols in a separate file on the host rather than in the '
+        'application. The value of the flag should be a directory where program '
+        'symbol files can be stored for later use. These symbol files contain '
+        'the information needed to symbolize Dart stack traces. For an app built '
+        'with this flag, the \'flutter symbolize\' command with the right program '
+        'symbol file is required to obtain a human readable stack trace. This '
+        'command is tracked by https://github.com/flutter/flutter/issues/50206',
+      valueHelp: '/project-name/v1.2.3/',
+    );
+  }
+
   void addTreeShakeIconsFlag() {
     argParser.addFlag('tree-shake-icons',
       negatable: true,
@@ -499,6 +514,9 @@
       buildName: argParser.options.containsKey('build-name')
           ? stringArg('build-name')
           : null,
+      splitDebugInfoPath: argParser.options.containsKey(FlutterOptions.kSplitDebugInfoOption)
+          ? stringArg(FlutterOptions.kSplitDebugInfoOption)
+          : null,
       treeShakeIcons: argParser.options.containsKey('tree-shake-icons')
           ? boolArg('tree-shake-icons')
           : kIconTreeShakerEnabledDefault,
diff --git a/packages/flutter_tools/templates/app/.gitignore.tmpl b/packages/flutter_tools/templates/app/.gitignore.tmpl
index ae1f183..78b3c2b 100644
--- a/packages/flutter_tools/templates/app/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/app/.gitignore.tmpl
@@ -33,5 +33,8 @@
 # Web related
 lib/generated_plugin_registrant.dart
 
+# Symbolication related
+app.*.symbols
+
 # Exceptions to above rules.
 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/packages/flutter_tools/templates/module/common/.gitignore.tmpl b/packages/flutter_tools/templates/module/common/.gitignore.tmpl
index 86f4691..1607ae6 100644
--- a/packages/flutter_tools/templates/module/common/.gitignore.tmpl
+++ b/packages/flutter_tools/templates/module/common/.gitignore.tmpl
@@ -40,3 +40,6 @@
 .ios/
 .flutter-plugins
 .flutter-plugins-dependencies
+
+# Symbolication related
+app.*.symbols
diff --git a/packages/flutter_tools/test/general.shard/base/build_test.dart b/packages/flutter_tools/test/general.shard/base/build_test.dart
index b50555b..baa51ee 100644
--- a/packages/flutter_tools/test/general.shard/base/build_test.dart
+++ b/packages/flutter_tools/test/general.shard/base/build_test.dart
@@ -266,6 +266,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       ), isNot(equals(0)));
     }, overrides: contextOverrides);
 
@@ -278,6 +279,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       ), isNot(0));
     }, overrides: contextOverrides);
 
@@ -290,6 +292,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       ), isNot(0));
     }, overrides: contextOverrides);
 
@@ -316,6 +319,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.armv7,
         bitcode: true,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -376,6 +380,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.armv7,
         bitcode: true,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -435,6 +440,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.armv7,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -463,6 +469,61 @@
       expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
     }, overrides: contextOverrides);
 
+    testUsingContext('builds iOS armv7 profile AOT snapshot with dwarf stack traces', () async {
+      globals.fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = globals.fs.path.join('build', 'foo');
+      globals.fs.directory(outputPath).createSync(recursive: true);
+
+      final String assembly = globals.fs.path.join(outputPath, 'snapshot_assembly.S');
+      genSnapshot.outputs = <String, String>{
+        assembly: 'blah blah\n.section __DWARF\nblah blah\n',
+      };
+      final String debugPath = globals.fs.path.join('foo', 'app.ios-armv7.symbols');
+
+      final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
+      when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+      when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.ios,
+        buildMode: BuildMode.profile,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        darwinArch: DarwinArch.armv7,
+        bitcode: false,
+        splitDebugInfo: 'foo',
+      );
+
+      expect(genSnapshotExitCode, 0);
+      expect(genSnapshot.callCount, 1);
+      expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
+      expect(genSnapshot.snapshotType.mode, BuildMode.profile);
+      expect(genSnapshot.additionalArgs, <String>[
+        '--deterministic',
+        '--snapshot_kind=app-aot-assembly',
+        '--assembly=$assembly',
+        '--strip',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        '--no-causal-async-stacks',
+        '--lazy-async-stacks',
+        '--dwarf-stack-traces',
+        '--save-debugging-info=$debugPath',
+        'main.dill',
+      ]);
+      verifyNever(mockXcode.cc(argThat(contains('-fembed-bitcode'))));
+      verifyNever(mockXcode.clang(argThat(contains('-fembed-bitcode'))));
+
+      verify(mockXcode.cc(argThat(contains('-isysroot')))).called(1);
+      verify(mockXcode.clang(argThat(contains('-isysroot')))).called(1);
+
+      final File assemblyFile = globals.fs.file(assembly);
+      expect(assemblyFile.existsSync(), true);
+      expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
+    }, overrides: contextOverrides);
+
     testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
       globals.fs.file('main.dill').writeAsStringSync('binary magic');
 
@@ -485,6 +546,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.arm64,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -524,6 +586,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.armv7,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -565,6 +628,7 @@
         outputPath: outputPath,
         darwinArch: DarwinArch.arm64,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -595,6 +659,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -614,6 +679,42 @@
       ]);
     }, overrides: contextOverrides);
 
+    testUsingContext('builds shared library for android-arm with dwarf stack traces', () async {
+      globals.fs.file('main.dill').writeAsStringSync('binary magic');
+
+      final String outputPath = globals.fs.path.join('build', 'foo');
+      final String debugPath = globals.fs.path.join('foo', 'app.android-arm.symbols');
+      globals.fs.directory(outputPath).createSync(recursive: true);
+
+      final int genSnapshotExitCode = await snapshotter.build(
+        platform: TargetPlatform.android_arm,
+        buildMode: BuildMode.release,
+        mainPath: 'main.dill',
+        packagesPath: '.packages',
+        outputPath: outputPath,
+        bitcode: false,
+        splitDebugInfo: 'foo',
+      );
+
+      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',
+        '--strip',
+        '--no-sim-use-hardfp',
+        '--no-use-integer-division',
+        '--no-causal-async-stacks',
+        '--lazy-async-stacks',
+        '--dwarf-stack-traces',
+        '--save-debugging-info=$debugPath',
+        'main.dill',
+      ]);
+    }, overrides: contextOverrides);
+
     testUsingContext('builds shared library for android-arm64', () async {
       globals.fs.file('main.dill').writeAsStringSync('binary magic');
 
@@ -627,6 +728,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
@@ -665,6 +767,7 @@
         packagesPath: '.packages',
         outputPath: outputPath,
         bitcode: false,
+        splitDebugInfo: null,
       );
 
       expect(genSnapshotExitCode, 0);
diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart
index fcd28ee..5db5da7 100644
--- a/packages/flutter_tools/test/general.shard/build_info_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_info_test.dart
@@ -78,4 +78,11 @@
       expect(() => BuildMode.fromName('foo'), throwsArgumentError);
     });
   });
+
+  test('getNameForTargetPlatform on Darwin arches', () {
+    expect(getNameForTargetPlatform(TargetPlatform.ios, darwinArch: DarwinArch.arm64), 'ios-arm64');
+    expect(getNameForTargetPlatform(TargetPlatform.ios, darwinArch: DarwinArch.armv7), 'ios-armv7');
+    expect(getNameForTargetPlatform(TargetPlatform.ios, darwinArch: DarwinArch.x86_64), 'ios-x86_64');
+    expect(getNameForTargetPlatform(TargetPlatform.android), isNot(contains('ios')));
+  });
 }
diff --git a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
index 31a6370..d467790 100644
--- a/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/build_apk_test.dart
@@ -230,6 +230,35 @@
       ProcessManager: () => mockProcessManager,
     });
 
+    testUsingContext('--split-debug-info is enabled when an output directory is provided', () async {
+      final String projectPath = await createProject(tempDir,
+          arguments: <String>['--no-pub', '--template=app']);
+
+      await expectLater(() async {
+        await runBuildApkCommand(projectPath, arguments: <String>['--split-debug-info=${tempDir.path}']);
+      }, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
+
+      verify(mockProcessManager.start(
+        <String>[
+          gradlew,
+          '-q',
+          '-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
+          '-Ptrack-widget-creation=true',
+          '-Pshrink=true',
+          '-Ptarget-platform=android-arm,android-arm64,android-x64',
+          '-Psplit-debug-info=${tempDir.path}',
+          'assembleRelease',
+        ],
+        workingDirectory: anyNamed('workingDirectory'),
+        environment: anyNamed('environment'),
+      )).called(1);
+    },
+    overrides: <Type, Generator>{
+      AndroidSdk: () => mockAndroidSdk,
+      FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
+      ProcessManager: () => mockProcessManager,
+    });
+
     testUsingContext('shrinking is disabled when --no-shrink is passed', () async {
       final String projectPath = await createProject(tempDir,
           arguments: <String>['--no-pub', '--template=app']);