Thin iOS app frameworks to the target architecture (#7913)

* Support thinning iOS frameworks to supported architectures

When building against frameworks that are distributed as
multi-architecture fat binaries, we want to strip the frameworks we
distribute down to only the architectures specified in $ARCHS.

This patch adds:
* The ability to specify commands to xcode_backend.sh (if none is
  specified, run BuildApp for backward compatibility).
* A 'thin' command that invokes lipo to thin down the distributed as
  described above.

* Add framework thinning step to iOS build

Invokes xcode_backend.sh thin on the build application.

* Limit architectures to arm64 in Xcode template

Flutter does not yet support armv7 iOS devices. Limit the $ARCHS build
variable to arm64 until then.
diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index a4db9cd..d4d7b3f 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -121,4 +121,77 @@
   return 0
 }
 
-BuildApp
+# Returns the CFBundleExecutable for the specified framework directory.
+GetFrameworkExecutablePath() {
+  local framework_dir="$1"
+
+  local plist_path="${framework_dir}/Info.plist"
+  local executable="$(defaults read "${plist_path}" CFBundleExecutable)"
+  echo "${framework_dir}/${executable}"
+}
+
+# Destructively thins the specified executable file to include only the
+# specified architectures.
+LipoExecutable() {
+  local executable="$1"
+  shift
+  local archs="$@"
+
+  # Extract architecture-specific framework executables.
+  local all_executables=()
+  for arch in $archs; do
+    local output="${executable}_${arch}"
+    lipo -output "${output}" -extract "${arch}" "${executable}"
+    if [[ $? == 0 ]]; then
+      all_executables+=("${output}")
+    else
+      echo "Failed to extract ${arch} for ${executable}. Running lipo -info:"
+      lipo -info "${executable}"
+      exit 1
+    fi
+  done
+
+  # Merge desired architectures.
+  local merged="${executable}_merged"
+  lipo -output "${merged}" -create "${all_executables[@]}"
+
+  # Replace the original executable with the thinned one and clean up.
+  cp -f "${merged}" "${executable}" > /dev/null
+  rm -f "${merged}" "${all_executables[@]}"
+}
+
+# Destructively thins the specified framework to include only the specified
+# architectures.
+ThinFramework() {
+  local framework_dir="$1"
+  shift
+  local archs="$@"
+
+  local plist_path="${framework_dir}/Info.plist"
+  local executable="$(GetFrameworkExecutablePath "${framework_dir}")"
+  LipoExecutable "${executable}" "$archs"
+}
+
+ThinAppFrameworks() {
+  local app_path="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
+  local frameworks_dir="${app_path}/Frameworks"
+
+  [[ -d "$frameworks_dir" ]] || return 0
+  for framework_dir in "$(find "${app_path}" -type d -name "*.framework")"; do
+    ThinFramework "$framework_dir" "$ARCHS"
+  done
+}
+
+# Main entry point.
+
+if [[ $# == 0 ]]; then
+  # Backwards-comptibility: if no args are provided, build.
+  BuildApp
+else
+  case $1 in
+    "build")
+      BuildApp ;;
+    "thin")
+      ThinAppFrameworks ;;
+  esac
+fi
diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index 6f8cc14..a8c30a3 100644
--- a/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -144,13 +144,14 @@
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
 				AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */,
-				9740EEB61CF901F6004384FC /* ShellScript */,
+				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */,
 				532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
 			);
 			buildRules = (
 			);
@@ -240,18 +241,33 @@
 			shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
-		9740EEB61CF901F6004384FC /* ShellScript */ = {
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
 			);
+			name = "Thin Binary";
 			outputPaths = (
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "/bin/sh $FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh";
+			shellScript = "/bin/sh $FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh $FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh build";
 		};
 		AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -396,6 +412,7 @@
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
 			buildSettings = {
+				ARCHS = arm64;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -417,6 +434,7 @@
 			isa = XCBuildConfiguration;
 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
+				ARCHS = arm64;
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (