Move Flutter.framework to build directory instead of ios/Flutter (#70224)

diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb
index 455bdb3..87b495c 100644
--- a/packages/flutter_tools/bin/podhelper.rb
+++ b/packages/flutter_tools/bin/podhelper.rb
@@ -31,7 +31,19 @@
 
   # [target.deployment_target] is a [String] formatted as "8.0".
   inherit_deployment_target = target.deployment_target[/\d+/].to_i < 9
+
+  # This podhelper script is at $FLUTTER_ROOT/packages/flutter_tools/bin.
+  # Add search paths from $FLUTTER_ROOT/bin/cache/artifacts/engine.
+  artifacts_dir = File.join('..', '..', '..', '..', 'bin', 'cache', 'artifacts', 'engine')
+  debug_framework_dir = File.expand_path(File.join(artifacts_dir, 'ios'), __FILE__)
+  release_framework_dir = File.expand_path(File.join(artifacts_dir, 'ios-release'), __FILE__)
+
   target.build_configurations.each do |build_configuration|
+    # Profile can't be derived from the CocoaPods build configuration. Use release framework (for linking only).
+    configuration_engine_dir = build_configuration.type == :debug ? debug_framework_dir : release_framework_dir
+    build_configuration.build_settings['FRAMEWORK_SEARCH_PATHS'] = "\"#{configuration_engine_dir}\" $(inherited)"
+    build_configuration.build_settings['OTHER_LDFLAGS'] = '$(inherited) -framework Flutter'
+
     build_configuration.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO'
     build_configuration.build_settings['ENABLE_BITCODE'] = 'NO'
     # Suppress warning when pod supports a version lower than the minimum supported by Xcode (Xcode 12 - iOS 9).
@@ -68,28 +80,33 @@
   ios_application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file)
   raise 'Could not find iOS application path' unless ios_application_path
 
-  copied_flutter_dir = File.join(ios_application_path, 'Flutter')
-  copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
-  copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
+  copied_podspec_path = File.expand_path('Flutter.podspec', File.join(ios_application_path, 'Flutter'))
 
-  unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
-    # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
-    # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration,
-    # which can handle a local engine.
-    # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
+  # Generate a fake podspec to represent the Flutter framework.
+  # This is only necessary because plugin podspecs contain `s.dependency 'Flutter'`, and if this Podfile
+  # does not add a `pod 'Flutter'` CocoaPods will try to download it from the CoocaPods trunk.
+  File.open(copied_podspec_path, 'w') { |podspec|
+    podspec.write <<~EOF
+      #
+      # NOTE: This podspec is NOT to be published. It is only used as a local source!
+      #       This is a generated file; do not edit or check into version control.
+      #
 
-    # This podhelper script is at $FLUTTER_ROOT/packages/flutter_tools/bin.
-    # Copy frameworks from $FLUTTER_ROOT/bin/cache/artifacts/engine/ios (Debug).
-    debug_framework_dir = File.expand_path(File.join('..', '..', '..', '..', 'bin', 'cache', 'artifacts', 'engine', 'ios'), __FILE__)
-
-    unless File.exist?(copied_framework_path)
-      # Avoid the complication of dependencies like FileUtils.
-      system('cp', '-r', File.expand_path('Flutter.framework', debug_framework_dir), copied_flutter_dir)
-    end
-    unless File.exist?(copied_podspec_path)
-      system('cp', File.expand_path('Flutter.podspec', debug_framework_dir), copied_flutter_dir)
-    end
-  end
+      Pod::Spec.new do |s|
+        s.name             = 'Flutter'
+        s.version          = '1.0.0'
+        s.summary          = 'High-performance, high-fidelity mobile apps.'
+        s.homepage         = 'https://flutter.io'
+        s.license          = { :type => 'MIT' }
+        s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
+        s.source           = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s }
+        s.ios.deployment_target = '8.0'
+        # Framework linking is handled by Flutter tooling, not CocoaPods.
+        # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs.
+        s.vendored_frameworks = 'path/to/nothing'
+      end
+    EOF
+  }
 
   # Keep pod path relative so it can be checked into Podfile.lock.
   pod 'Flutter', :path => 'Flutter'
diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index e86fb3f..3ff708e 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -145,16 +145,12 @@
     bitcode_flag="true"
   fi
 
-  # TODO(jonahwilliams): move engine copying to build system.
+  # TODO(jmagman): use assemble copied engine in add-to-app.
   if [[ -e "${project_path}/.ios" ]]; then
     RunCommand rm -rf -- "${derived_dir}/engine"
     mkdir "${derived_dir}/engine"
     RunCommand cp -r -- "${flutter_podspec}" "${derived_dir}/engine"
     RunCommand cp -r -- "${flutter_framework}" "${derived_dir}/engine"
-  else
-    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
-    RunCommand cp -- "${flutter_podspec}" "${derived_dir}"
-    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
   fi
 
   RunCommand pushd "${project_path}" > /dev/null
@@ -297,20 +293,11 @@
   # Embed the actual Flutter.framework that the Flutter app expects to run against,
   # which could be a local build or an arch/type specific build.
 
-  # Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
-  # doesn't exist.
-  local flutter_ios_engine_folder="${project_path}/.ios/Flutter/engine"
-  if [[ ! -d ${flutter_ios_engine_folder} ]]; then
-    flutter_ios_engine_folder="${project_path}/ios/Flutter"
-  fi
-
-  AssertExists "${flutter_ios_engine_folder}"
-
   # Copy Xcode behavior and don't copy over headers or modules.
-  RunCommand rsync -av --delete --filter "- .DS_Store/" --filter "- Headers/" --filter "- Modules/" "${flutter_ios_engine_folder}/Flutter.framework" "${xcode_frameworks_dir}/"
+  RunCommand rsync -av --delete --filter "- .DS_Store/" --filter "- Headers/" --filter "- Modules/" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
   if [[ "$ACTION" != "install" || "$ENABLE_BITCODE" == "NO" ]]; then
     # Strip bitcode from the destination unless archiving, or if bitcode is disabled entirely.
-    RunCommand "${DT_TOOLCHAIN_DIR}"/usr/bin/bitcode_strip "${flutter_ios_engine_folder}/Flutter.framework/Flutter" -r -o "${xcode_frameworks_dir}/Flutter.framework/Flutter"
+    RunCommand "${DT_TOOLCHAIN_DIR}"/usr/bin/bitcode_strip "${BUILT_PRODUCTS_DIR}/Flutter.framework/Flutter" -r -o "${xcode_frameworks_dir}/Flutter.framework/Flutter"
   fi
 
   # Sign the binaries we moved.
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 4bd146a..eba8cdc 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:meta/meta.dart';
+
 import '../../artifacts.dart';
 import '../../base/build.dart';
 import '../../base/common.dart';
@@ -145,6 +147,7 @@
 
   @override
   List<Target> get dependencies => const <Target>[
+    ReleaseUnpackIOS(),
     KernelSnapshot(),
   ];
 }
@@ -179,6 +182,7 @@
 
   @override
   List<Target> get dependencies => const <Target>[
+    ProfileUnpackIOS(),
     KernelSnapshot(),
   ];
 }
@@ -192,6 +196,7 @@
 
   @override
   List<Target> get dependencies => const <Target>[
+    DebugUnpackIOS(),
     KernelSnapshot(),
   ];
 
@@ -226,6 +231,95 @@
   }
 }
 
+/// Copy the iOS framework to the correct copy dir by invoking 'rsync'.
+///
+/// This class is abstract to share logic between the three concrete
+/// implementations. The shelling out is done to avoid complications with
+/// preserving special files (e.g., symbolic links) in the framework structure.
+abstract class UnpackIOS extends Target {
+  const UnpackIOS();
+
+  @override
+  List<Source> get inputs => <Source>[
+        const Source.pattern(
+            '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
+        Source.artifact(
+          Artifact.flutterFramework,
+          platform: TargetPlatform.ios,
+          mode: buildMode,
+        ),
+      ];
+
+  @override
+  List<Source> get outputs => const <Source>[
+        Source.pattern('{OUTPUT_DIR}/Flutter.framework/Flutter'),
+      ];
+
+  @override
+  List<Target> get dependencies => <Target>[];
+
+  @visibleForOverriding
+  BuildMode get buildMode;
+
+  @override
+  Future<void> build(Environment environment) async {
+    final String basePath = environment.artifacts.getArtifactPath(
+      Artifact.flutterFramework,
+      platform: TargetPlatform.ios,
+      mode: buildMode,
+    );
+
+    final ProcessResult result = environment.processManager.runSync(<String>[
+      'rsync',
+      '-av',
+      '--delete',
+      '--filter',
+      '- .DS_Store/',
+      basePath,
+      environment.outputDir.path,
+    ]);
+    if (result.exitCode != 0) {
+      throw Exception(
+        'Failed to copy framework (exit ${result.exitCode}:\n'
+        '${result.stdout}\n---\n${result.stderr}',
+      );
+    }
+  }
+}
+
+/// Unpack the release prebuilt engine framework.
+class ReleaseUnpackIOS extends UnpackIOS {
+  const ReleaseUnpackIOS();
+
+  @override
+  String get name => 'release_unpack_ios';
+
+  @override
+  BuildMode get buildMode => BuildMode.release;
+}
+
+/// Unpack the profile prebuilt engine framework.
+class ProfileUnpackIOS extends UnpackIOS {
+  const ProfileUnpackIOS();
+
+  @override
+  String get name => 'profile_unpack_ios';
+
+  @override
+  BuildMode get buildMode => BuildMode.profile;
+}
+
+/// Unpack the debug prebuilt engine framework.
+class DebugUnpackIOS extends UnpackIOS {
+  const DebugUnpackIOS();
+
+  @override
+  String get name => 'debug_unpack_ios';
+
+  @override
+  BuildMode get buildMode => BuildMode.debug;
+}
+
 /// The base class for all iOS bundle targets.
 ///
 /// This is responsible for setting up the basic App.framework structure, including:
diff --git a/packages/flutter_tools/lib/src/commands/clean.dart b/packages/flutter_tools/lib/src/commands/clean.dart
index 5e45dbb..6a4cc92 100644
--- a/packages/flutter_tools/lib/src/commands/clean.dart
+++ b/packages/flutter_tools/lib/src/commands/clean.dart
@@ -51,6 +51,7 @@
     deleteFile(flutterProject.ios.generatedXcodePropertiesFile);
     deleteFile(flutterProject.ios.generatedEnvironmentVariableExportScript);
     deleteFile(flutterProject.ios.deprecatedCompiledDartFramework);
+    deleteFile(flutterProject.ios.deprecatedProjectFlutterFramework);
 
     deleteFile(flutterProject.linux.ephemeralDirectory);
     deleteFile(flutterProject.macos.ephemeralDirectory);
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index f32a77a..9e36c46 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -689,6 +689,13 @@
       .childDirectory('Flutter')
       .childDirectory('App.framework');
 
+  /// No longer copied to this location.
+  ///
+  /// Used only for "flutter clean" to remove old references.
+  Directory get deprecatedProjectFlutterFramework => _flutterLibRoot
+      .childDirectory('Flutter')
+      .childDirectory('Flutter.framework');
+
   Directory get pluginRegistrantHost {
     return isModule
         ? _flutterLibRoot
diff --git a/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index 1ac8e06..1e69d7c 100644
--- a/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/app/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -300,16 +300,8 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
@@ -428,16 +420,8 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
@@ -451,16 +435,8 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
diff --git a/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index 5f6a5d4..6c82ff2 100644
--- a/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/app/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -289,16 +289,8 @@
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -421,16 +413,8 @@
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -448,16 +432,8 @@
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
diff --git a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl
index 8499e8b..326ad84 100644
--- a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.xcodeproj.tmpl/project.pbxproj.tmpl
@@ -297,17 +297,8 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-					"$(PROJECT_DIR)/Flutter/engine",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
@@ -425,17 +416,8 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-					"$(PROJECT_DIR)/Flutter/engine",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
@@ -448,17 +430,8 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
-				FRAMEWORK_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-					"$(PROJECT_DIR)/Flutter/engine",
-				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				LIBRARY_SEARCH_PATHS = (
-					"$(inherited)",
-					"$(PROJECT_DIR)/Flutter",
-				);
 				PRODUCT_BUNDLE_IDENTIFIER = {{iosIdentifier}};
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
index 8c005e6..08eb7d4 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
@@ -49,6 +49,7 @@
         projectUnderTest.ios.generatedXcodePropertiesFile.createSync(recursive: true);
         projectUnderTest.ios.generatedEnvironmentVariableExportScript.createSync(recursive: true);
         projectUnderTest.ios.deprecatedCompiledDartFramework.createSync(recursive: true);
+        projectUnderTest.ios.deprecatedProjectFlutterFramework.createSync(recursive: true);
 
         projectUnderTest.linux.ephemeralDirectory.createSync(recursive: true);
         projectUnderTest.macos.ephemeralDirectory.createSync(recursive: true);
@@ -69,6 +70,7 @@
         expect(projectUnderTest.ios.generatedXcodePropertiesFile.existsSync(), isFalse);
         expect(projectUnderTest.ios.generatedEnvironmentVariableExportScript.existsSync(), isFalse);
         expect(projectUnderTest.ios.deprecatedCompiledDartFramework.existsSync(), isFalse);
+        expect(projectUnderTest.ios.deprecatedProjectFlutterFramework.existsSync(), isFalse);
 
         expect(projectUnderTest.linux.ephemeralDirectory.existsSync(), isFalse);
         expect(projectUnderTest.macos.ephemeralDirectory.existsSync(), isFalse);
diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart
index bd99389..c553b3b 100644
--- a/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_system/targets/ios_test.dart
@@ -208,4 +208,31 @@
     ProcessManager: () => processManager,
     Platform: () => macPlatform,
   });
+
+  testWithoutContext('Unpack copies Flutter.framework', () async {
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final Directory outputDir = fileSystem.directory('output');
+    final Environment environment = Environment.test(
+      fileSystem.currentDirectory,
+      processManager: processManager,
+      artifacts: artifacts,
+      logger: logger,
+      fileSystem: fileSystem,
+      outputDir: outputDir,
+    );
+
+    processManager.addCommand(
+      FakeCommand(command: <String>[
+        'rsync',
+        '-av',
+        '--delete',
+        '--filter',
+        '- .DS_Store/',
+        'Artifact.flutterFramework.TargetPlatform.ios.debug',
+        outputDir.path,
+      ]),
+    );
+
+    await const DebugUnpackIOS().build(environment);
+  });
 }