Extract shared library build from AOT snapshotting (#17166)

diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart
index 5818efd..975900d 100644
--- a/packages/flutter_tools/lib/src/base/build.dart
+++ b/packages/flutter_tools/lib/src/base/build.dart
@@ -336,38 +336,13 @@
     // On iOS, we use Xcode to compile the snapshot into a dynamic library that the
     // end-developer can link into their app.
     if (platform == TargetPlatform.ios) {
-      printStatus('Building App.framework...');
-      const List<String> commonBuildOptions = const <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
-
-      final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
-      await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', assembly, '-o', assemblyO]));
-
-      final String frameworkDir = fs.path.join(outputDir.path, 'App.framework');
-      fs.directory(frameworkDir).createSync(recursive: true);
-      final String appLib = fs.path.join(frameworkDir, 'App');
-      final List<String> linkArgs = commonBuildOptions.toList()..addAll(<String>[
-          '-dynamiclib',
-          '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
-          '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
-          '-install_name', '@rpath/App.framework/App',
-          '-o', appLib,
-          assemblyO,
-      ]);
-      await xcode.clang(linkArgs);
-    } else {
-      if (compileToSharedLibrary) {
-        // A word of warning: Instead of compiling via two steps, to a .o file and
-        // then to a .so file we use only one command. When using two commands
-        // gcc will end up putting a .eh_frame and a .debug_frame into the shared
-        // library. Without stripping .debug_frame afterwards, unwinding tools
-        // based upon libunwind use just one and ignore the contents of the other
-        // (which causes it to not look into the other section and therefore not
-        // find the correct unwinding information).
-        final String assemblySo = fs.path.join(outputDir.path, 'app.so');
-        await runCheckedAsync(<String>[androidSdk.ndkCompiler]
-            ..addAll(androidSdk.ndkCompilerArgs)
-            ..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assembly ]));
-      }
+      final RunResult result = await _buildIosFramework(assemblyPath: assembly, outputPath: outputDir.path);
+      if (result.exitCode != 0)
+        return result.exitCode;
+    } else if (compileToSharedLibrary) {
+      final RunResult result = await _buildAndroidSharedLibrary(assemblyPath: assembly, outputPath: outputDir.path);
+      if (result.exitCode != 0)
+        return result.exitCode;
     }
 
     // Compute and record build fingerprint.
@@ -375,6 +350,54 @@
     return 0;
   }
 
+  /// Builds an iOS framework at [outputPath]/App.framework from the assembly
+  /// source at [assemblyPath].
+  Future<RunResult> _buildIosFramework({
+    @required String assemblyPath,
+    @required String outputPath,
+  }) async {
+    printStatus('Building App.framework...');
+    const List<String> commonBuildOptions = const <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
+
+    final String assemblyO = fs.path.join(outputPath, 'snapshot_assembly.o');
+    final RunResult compileResult = await xcode.cc(commonBuildOptions.toList()..addAll(<String>['-c', assemblyPath, '-o', assemblyO]));
+    if (compileResult.exitCode != 0)
+      return compileResult;
+
+    final String frameworkDir = fs.path.join(outputPath, 'App.framework');
+    fs.directory(frameworkDir).createSync(recursive: true);
+    final String appLib = fs.path.join(frameworkDir, 'App');
+    final List<String> linkArgs = commonBuildOptions.toList()..addAll(<String>[
+        '-dynamiclib',
+        '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
+        '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
+        '-install_name', '@rpath/App.framework/App',
+        '-o', appLib,
+        assemblyO,
+    ]);
+    final RunResult linkResult = await xcode.clang(linkArgs);
+    return linkResult;
+  }
+
+  /// Builds an Android shared library at [outputPath]/app.so from the assembly
+  /// source at [assemblyPath].
+  Future<RunResult> _buildAndroidSharedLibrary({
+    @required String assemblyPath,
+    @required String outputPath,
+  }) async {
+    // A word of warning: Instead of compiling via two steps, to a .o file and
+    // then to a .so file we use only one command. When using two commands
+    // gcc will end up putting a .eh_frame and a .debug_frame into the shared
+    // library. Without stripping .debug_frame afterwards, unwinding tools
+    // based upon libunwind use just one and ignore the contents of the other
+    // (which causes it to not look into the other section and therefore not
+    // find the correct unwinding information).
+    final String assemblySo = fs.path.join(outputPath, 'app.so');
+    return await runCheckedAsync(<String>[androidSdk.ndkCompiler]
+        ..addAll(androidSdk.ndkCompilerArgs)
+        ..addAll(<String>[ '-shared', '-nostdlib', '-o', assemblySo, assemblyPath ]));
+  }
+
   /// Compiles a Dart file to kernel.
   ///
   /// Returns the output kernel file path, or null on failure.
diff --git a/packages/flutter_tools/test/base/build_test.dart b/packages/flutter_tools/test/base/build_test.dart
index c8b6542..d34c233 100644
--- a/packages/flutter_tools/test/base/build_test.dart
+++ b/packages/flutter_tools/test/base/build_test.dart
@@ -12,6 +12,8 @@
 import 'package:flutter_tools/src/base/build.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/process.dart';
 import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:flutter_tools/src/version.dart';
 import 'package:mockito/mockito.dart';
@@ -646,6 +648,10 @@
         fs.path.join(outputPath, 'snapshot.d'): '',
       };
 
+      final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
+      when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
+
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
         buildMode: BuildMode.profile,
@@ -688,6 +694,10 @@
         fs.path.join(outputPath, 'snapshot.d'): '',
       };
 
+      final RunResult successResult = new RunResult(new ProcessResult(1, 0, '', ''));
+      when(xcode.cc(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
+      when(xcode.clang(any)).thenAnswer((_) => new Future<RunResult>.value(successResult));
+
       final int genSnapshotExitCode = await snapshotter.build(
         platform: TargetPlatform.ios,
         buildMode: BuildMode.release,