Move iOS codesigning into assemble build target (#77664)

diff --git a/packages/flutter_tools/bin/xcode_backend.sh b/packages/flutter_tools/bin/xcode_backend.sh
index 6e01855..008df97 100755
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -165,6 +165,11 @@
     code_size_directory="-dCodeSizeDirectory=${CODE_SIZE_DIRECTORY}"
   fi
 
+  local codesign_identity_flag=""
+  if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_REQUIRED:-}" != "NO" ]]; then
+    codesign_identity_flag="-dCodesignIdentity=${EXPANDED_CODE_SIGN_IDENTITY}"
+  fi
+
   RunCommand "${FLUTTER_ROOT}/bin/flutter"                                \
     ${verbose_flag}                                                       \
     ${flutter_engine_flag}                                                \
@@ -183,6 +188,7 @@
     -dTrackWidgetCreation="${TRACK_WIDGET_CREATION}"                      \
     -dDartObfuscation="${DART_OBFUSCATION}"                               \
     -dEnableBitcode="${bitcode_flag}"                                     \
+    "${codesign_identity_flag}"                                           \
     ${bundle_sksl_path}                                                   \
     ${code_size_directory}                                                \
     --ExtraGenSnapshotOptions="${EXTRA_GEN_SNAPSHOT_OPTIONS}"             \
@@ -214,15 +220,7 @@
 
   # 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.
-
-  # Copy Xcode behavior and don't copy over headers or modules.
-  RunCommand rsync -av --delete --filter "- .DS_Store" --filter "- Headers" --filter "- Modules" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
-
-  # Sign the binaries we moved.
-  if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" ]]; then
-    RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/App.framework/App"
-    RunCommand codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${xcode_frameworks_dir}/Flutter.framework/Flutter"
-  fi
+  RunCommand rsync -av --delete --filter "- .DS_Store" "${BUILT_PRODUCTS_DIR}/Flutter.framework" "${xcode_frameworks_dir}/"
 
   AddObservatoryBonjourService
 }
diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart
index 44c7614..0fcda20 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/common.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart
@@ -80,6 +80,12 @@
 /// An output directory where one or more code-size measurements may be written.
 const String kCodeSizeDirectory = 'CodeSizeDirectory';
 
+/// SHA identifier of the Apple developer code signing identity.
+///
+/// Same as EXPANDED_CODE_SIGN_IDENTITY Xcode build setting.
+/// Also discoverable via `security find-identity -p codesigning`.
+const String kCodesignIdentity = 'CodesignIdentity';
+
 /// Copies the pre-built flutter bundle.
 // This is a one-off rule for implementing build bundle in terms of assemble.
 class CopyFlutterBundle extends Target {
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 398cff9..be5ccca 100644
--- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart
+++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart
@@ -11,7 +11,6 @@
 import '../../base/common.dart';
 import '../../base/file_system.dart';
 import '../../base/io.dart';
-import '../../base/process.dart';
 import '../../build_info.dart';
 import '../../globals.dart' as globals hide fs, logger, processManager, artifacts;
 import '../../macos/xcode.dart';
@@ -231,14 +230,11 @@
       .childDirectory('App.framework')
       .childFile('App');
     environment.buildDir.createSync(recursive: true);
-    final RunResult createFrameworkResult = await createStubAppFramework(
+    await _createStubAppFramework(
       output,
-      environment.defines[kSdkRoot],
+      environment,
       iosArchNames,
     );
-    if (createFrameworkResult.exitCode != 0) {
-      throw Exception('Failed to create App.framework.');
-    }
   }
 }
 
@@ -283,18 +279,19 @@
     if (environment.defines[kBitcodeFlag] == null) {
       throw MissingDefineException(kBitcodeFlag, name);
     }
-    await _copyFramework(environment);
+    _copyFramework(environment);
 
     final File frameworkBinary = environment.outputDir.childDirectory('Flutter.framework').childFile('Flutter');
     final String frameworkBinaryPath = frameworkBinary.path;
     if (!frameworkBinary.existsSync()) {
       throw Exception('Binary $frameworkBinaryPath does not exist, cannot thin');
     }
-    await _thinFramework(environment, frameworkBinaryPath);
-    await _bitcodeStripFramework(environment, frameworkBinaryPath);
+    _thinFramework(environment, frameworkBinaryPath);
+    _bitcodeStripFramework(environment, frameworkBinaryPath);
+    _signFramework(environment, frameworkBinaryPath, buildMode);
   }
 
-  Future<void> _copyFramework(Environment environment) async {
+  void _copyFramework(Environment environment) {
     final Directory sdkRoot = environment.fileSystem.directory(environment.defines[kSdkRoot]);
     final EnvironmentType environmentType = environmentTypeFromSdkroot(sdkRoot);
     final String basePath = environment.artifacts.getArtifactPath(
@@ -322,7 +319,7 @@
   }
 
   /// Destructively thin Flutter.framework to include only the specified architectures.
-  Future<void> _thinFramework(Environment environment, String frameworkBinaryPath) async {
+  void _thinFramework(Environment environment, String frameworkBinaryPath) {
     final String archs = environment.defines[kIosArchs];
     final List<String> archList = archs.split(' ').toList();
     final ProcessResult infoResult = environment.processManager.runSync(<String>[
@@ -368,7 +365,7 @@
   }
 
   /// Destructively strip bitcode from the framework, if needed.
-  Future<void> _bitcodeStripFramework(Environment environment, String frameworkBinaryPath) async {
+  void _bitcodeStripFramework(Environment environment, String frameworkBinaryPath) {
     if (environment.defines[kBitcodeFlag] == 'true') {
       return;
     }
@@ -460,6 +457,7 @@
     }
     final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
     final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
+    final String frameworkBinaryPath = frameworkDirectory.childFile('App').path;
     final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
     frameworkDirectory.createSync(recursive: true);
     assetDirectory.createSync();
@@ -470,7 +468,7 @@
       environment.buildDir
         .childDirectory('App.framework')
         .childFile('App')
-        .copySync(frameworkDirectory.childFile('App').path);
+        .copySync(frameworkBinaryPath);
 
       final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug);
       final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug);
@@ -482,7 +480,7 @@
           .copySync(assetDirectory.childFile('isolate_snapshot_data').path);
     } else {
       environment.buildDir.childDirectory('App.framework').childFile('App')
-        .copySync(frameworkDirectory.childFile('App').path);
+        .copySync(frameworkBinaryPath);
     }
 
     // Copy the assets.
@@ -512,6 +510,8 @@
       .copySync(environment.outputDir
       .childDirectory('App.framework')
       .childFile('Info.plist').path);
+
+    _signFramework(environment, frameworkBinaryPath, buildMode);
   }
 }
 
@@ -576,7 +576,7 @@
 /// This framework needs to exist for the Xcode project to link/bundle,
 /// but it isn't actually executed. To generate something valid, we compile a trivial
 /// constant.
-Future<RunResult> createStubAppFramework(File outputFile, String sdkRoot,
+Future<void> _createStubAppFramework(File outputFile, Environment environment,
     Set<String> iosArchNames) async {
   try {
     outputFile.createSync(recursive: true);
@@ -592,7 +592,8 @@
   static const int Moo = 88;
   ''');
 
-    return await globals.xcode.clang(<String>[
+    final String sdkRoot = environment.defines[kSdkRoot];
+    await globals.xcode.clang(<String>[
       '-x',
       'c',
       for (String arch in iosArchNames) ...<String>['-arch', arch],
@@ -616,4 +617,27 @@
       throwToolExit('Failed to create App.framework stub at ${outputFile.path}: $e');
     }
   }
+
+  _signFramework(environment, outputFile.path, BuildMode.debug);
+}
+
+void _signFramework(Environment environment, String binaryPath, BuildMode buildMode) {
+  final String codesignIdentity = environment.defines[kCodesignIdentity];
+  if (codesignIdentity == null || codesignIdentity.isEmpty) {
+    return;
+  }
+  final ProcessResult result = environment.processManager.runSync(<String>[
+    'codesign',
+    '--force',
+    '--sign',
+    codesignIdentity,
+    if (buildMode != BuildMode.release) ...<String>[
+      // Mimic Xcode's timestamp codesigning behavior on non-release binaries.
+      '--timestamp=none',
+    ],
+    binaryPath,
+  ]);
+  if (result.exitCode != 0) {
+    throw Exception('Failed to codesign $binaryPath with identity $codesignIdentity.\n${result.stderr}');
+  }
 }
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 5f30231..aad8c0e 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
@@ -95,6 +95,7 @@
     );
 
     await const DebugUniversalFramework().build(environment);
+    expect(processManager.hasRemainingExpectations, isFalse);
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
@@ -104,6 +105,7 @@
   testUsingContext('DebugIosApplicationBundle', () async {
     environment.inputs[kBundleSkSLPath] = 'bundle.sksl';
     environment.defines[kBuildMode] = 'debug';
+    environment.defines[kCodesignIdentity] = 'ABC123';
     // Precompiled dart data
 
     fileSystem.file(artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
@@ -120,7 +122,7 @@
     environment.buildDir.childFile('app.dill').createSync(recursive: true);
     // Stub framework
     environment.buildDir
-      .childDirectory('App.framework')
+        .childDirectory('App.framework')
       .childFile('App')
       .createSync(recursive: true);
     // sksl bundle
@@ -134,10 +136,23 @@
       }
     ));
 
-    await const DebugIosApplicationBundle().build(environment);
-
     final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
-    expect(frameworkDirectory.childFile('App'), exists);
+    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
+    processManager.addCommand(
+      FakeCommand(command: <String>[
+        'codesign',
+        '--force',
+        '--sign',
+        'ABC123',
+        '--timestamp=none',
+        frameworkDirectoryBinary.path,
+      ]),
+    );
+
+    await const DebugIosApplicationBundle().build(environment);
+    expect(processManager.hasRemainingExpectations, isFalse);
+
+    expect(frameworkDirectoryBinary, exists);
     expect(frameworkDirectory.childFile('Info.plist'), exists);
 
     final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
@@ -151,6 +166,7 @@
 
   testUsingContext('ReleaseIosApplicationBundle', () async {
     environment.defines[kBuildMode] = 'release';
+    environment.defines[kCodesignIdentity] = 'ABC123';
 
     // Project info
     fileSystem.file('pubspec.yaml').writeAsStringSync('name: hello');
@@ -165,10 +181,23 @@
       .childFile('App')
       .createSync(recursive: true);
 
-    await const ReleaseIosApplicationBundle().build(environment);
 
     final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
-    expect(frameworkDirectory.childFile('App'), exists);
+    final File frameworkDirectoryBinary = frameworkDirectory.childFile('App');
+    processManager.addCommand(
+      FakeCommand(command: <String>[
+        'codesign',
+        '--force',
+        '--sign',
+        'ABC123',
+        frameworkDirectoryBinary.path,
+      ]),
+    );
+
+    await const ReleaseIosApplicationBundle().build(environment);
+    expect(processManager.hasRemainingExpectations, isFalse);
+
+    expect(frameworkDirectoryBinary, exists);
     expect(frameworkDirectory.childFile('Info.plist'), exists);
 
     final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
@@ -205,6 +234,7 @@
         contains('release/profile builds are only supported for physical devices.'),
       )
     ));
+    expect(processManager.hasRemainingExpectations, isFalse);
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
@@ -233,19 +263,25 @@
       contains('required define SdkRoot but it was not provided'),
     )
     ));
+    expect(processManager.hasRemainingExpectations, isFalse);
   }, overrides: <Type, Generator>{
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
     Platform: () => macPlatform,
   });
 
-  group('copy, thin, and bitcode strip engine Flutter.framework', () {
+  group('copies Flutter.framework', () {
     Directory outputDir;
+    File binary;
     FakeCommand copyPhysicalFrameworkCommand;
+    FakeCommand lipoCommandNonFatResult;
+    FakeCommand lipoVerifyArm64Command;
+    FakeCommand bitcodeStripCommand;
 
     setUp(() {
       final FileSystem fileSystem = MemoryFileSystem.test();
       outputDir = fileSystem.directory('output');
+      binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
       copyPhysicalFrameworkCommand = FakeCommand(command: <String>[
         'rsync',
         '-av',
@@ -255,10 +291,31 @@
         'Artifact.flutterFramework.TargetPlatform.ios.debug.EnvironmentType.physical',
         outputDir.path,
       ]);
+
+      lipoCommandNonFatResult = FakeCommand(command: <String>[
+        'lipo',
+        '-info',
+        binary.path,
+      ], stdout: 'Non-fat file:');
+
+      lipoVerifyArm64Command = FakeCommand(command: <String>[
+        'lipo',
+        binary.path,
+        '-verify_arch',
+        'arm64',
+      ]);
+
+      bitcodeStripCommand = FakeCommand(command: <String>[
+        'xcrun',
+        'bitcode_strip',
+        binary.path,
+        '-m',
+        '-o',
+        binary.path,
+      ]);
     });
 
     testWithoutContext('iphonesimulator', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter');
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
         processManager: processManager,
@@ -273,7 +330,7 @@
         },
       );
 
-      processManager.addCommand(
+      processManager.addCommands(<FakeCommand>[
         FakeCommand(command: <String>[
           'rsync',
           '-av',
@@ -285,24 +342,14 @@
           ],
           onRun: () => binary.createSync(recursive: true),
         ),
-      );
-
-      processManager.addCommand(
-        FakeCommand(command: <String>[
-          'lipo',
-          '-info',
-          binary.path,
-        ], stdout: 'Non-fat file:'),
-      );
-
-      processManager.addCommand(
+        lipoCommandNonFatResult,
         FakeCommand(command: <String>[
           'lipo',
           binary.path,
           '-verify_arch',
           'x86_64',
         ]),
-      );
+      ]);
       await const DebugUnpackIOS().build(environment);
 
       expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
@@ -334,7 +381,7 @@
     });
 
     testWithoutContext('fails when requested archs missing from framework', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -350,16 +397,13 @@
         },
       );
 
-      processManager.addCommand(copyPhysicalFrameworkCommand);
-      processManager.addCommand(
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
         FakeCommand(command: <String>[
           'lipo',
           '-info',
           binary.path,
         ], stdout: 'Architectures in the fat file:'),
-      );
-
-      processManager.addCommand(
         FakeCommand(command: <String>[
           'lipo',
           binary.path,
@@ -367,7 +411,7 @@
           'arm64',
           'armv7',
         ], exitCode: 1),
-      );
+      ]);
 
       await expectLater(
           const DebugUnpackIOS().build(environment),
@@ -379,7 +423,7 @@
     });
 
     testWithoutContext('fails when lipo extract fails', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -395,16 +439,13 @@
         },
       );
 
-      processManager.addCommand(copyPhysicalFrameworkCommand);
-      processManager.addCommand(
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
         FakeCommand(command: <String>[
           'lipo',
           '-info',
           binary.path,
         ], stdout: 'Architectures in the fat file:'),
-      );
-
-      processManager.addCommand(
         FakeCommand(command: <String>[
           'lipo',
           binary.path,
@@ -412,9 +453,6 @@
           'arm64',
           'armv7',
         ]),
-      );
-
-      processManager.addCommand(
         FakeCommand(command: <String>[
           'lipo',
           '-output',
@@ -426,7 +464,7 @@
           binary.path,
         ], exitCode: 1,
         stderr: 'lipo error'),
-      );
+      ]);
 
       await expectLater(
         const DebugUnpackIOS().build(environment),
@@ -438,7 +476,7 @@
     });
 
     testWithoutContext('skips thin framework', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -454,23 +492,11 @@
         },
       );
 
-      processManager.addCommand(copyPhysicalFrameworkCommand);
-      processManager.addCommand(
-        FakeCommand(command: <String>[
-          'lipo',
-          '-info',
-          binary.path,
-        ], stdout: 'Non-fat file:'),
-      );
-
-      processManager.addCommand(
-        FakeCommand(command: <String>[
-          'lipo',
-          binary.path,
-          '-verify_arch',
-          'arm64',
-        ]),
-      );
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
+        lipoCommandNonFatResult,
+        lipoVerifyArm64Command,
+      ]);
       await const DebugUnpackIOS().build(environment);
 
       expect(logger.traceText, contains('Skipping lipo for non-fat file output/Flutter.framework/Flutter'));
@@ -479,7 +505,7 @@
     });
 
     testWithoutContext('thins fat framework', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -495,16 +521,13 @@
         },
       );
 
-      processManager.addCommand(copyPhysicalFrameworkCommand);
-      processManager.addCommand(
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
         FakeCommand(command: <String>[
           'lipo',
           '-info',
           binary.path,
         ], stdout: 'Architectures in the fat file:'),
-      );
-
-      processManager.addCommand(
         FakeCommand(command: <String>[
           'lipo',
           binary.path,
@@ -512,9 +535,6 @@
           'arm64',
           'armv7',
         ]),
-      );
-
-      processManager.addCommand(
         FakeCommand(command: <String>[
           'lipo',
           '-output',
@@ -525,14 +545,14 @@
           'armv7',
           binary.path,
         ]),
-      );
+      ]);
 
       await const DebugUnpackIOS().build(environment);
       expect(processManager.hasRemainingExpectations, isFalse);
     });
 
     testWithoutContext('fails when bitcode strip fails', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -550,17 +570,8 @@
 
       processManager.addCommands(<FakeCommand>[
         copyPhysicalFrameworkCommand,
-        FakeCommand(command: <String>[
-          'lipo',
-          '-info',
-          binary.path,
-        ], stdout: 'Non-fat file:'),
-        FakeCommand(command: <String>[
-          'lipo',
-          binary.path,
-          '-verify_arch',
-          'arm64',
-        ]),
+        lipoCommandNonFatResult,
+        lipoVerifyArm64Command,
         FakeCommand(command: <String>[
           'xcrun',
           'bitcode_strip',
@@ -583,7 +594,7 @@
     });
 
     testWithoutContext('strips framework', () async {
-      final File binary = outputDir.childDirectory('Flutter.framework').childFile('Flutter')..createSync(recursive: true);
+      binary.createSync(recursive: true);
 
       final Environment environment = Environment.test(
         fileSystem.currentDirectory,
@@ -601,23 +612,88 @@
 
       processManager.addCommands(<FakeCommand>[
         copyPhysicalFrameworkCommand,
+        lipoCommandNonFatResult,
+        lipoVerifyArm64Command,
+        bitcodeStripCommand,
+      ]);
+      await const DebugUnpackIOS().build(environment);
+
+      expect(processManager.hasRemainingExpectations, isFalse);
+    });
+
+    testWithoutContext('fails when codesign fails', () async {
+      binary.createSync(recursive: true);
+
+      final Environment environment = Environment.test(
+        fileSystem.currentDirectory,
+        processManager: processManager,
+        artifacts: artifacts,
+        logger: logger,
+        fileSystem: fileSystem,
+        outputDir: outputDir,
+        defines: <String, String>{
+          kIosArchs: 'arm64',
+          kSdkRoot: 'path/to/iPhoneOS.sdk',
+          kBitcodeFlag: '',
+          kCodesignIdentity: 'ABC123',
+        },
+      );
+
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
+        lipoCommandNonFatResult,
+        lipoVerifyArm64Command,
+        bitcodeStripCommand,
         FakeCommand(command: <String>[
-          'lipo',
-          '-info',
+          'codesign',
+          '--force',
+          '--sign',
+          'ABC123',
+          '--timestamp=none',
           binary.path,
-        ], stdout: 'Non-fat file:'),
+        ], exitCode: 1, stderr: 'codesign error'),
+      ]);
+
+      await expectLater(
+          const DebugUnpackIOS().build(environment),
+          throwsA(isA<Exception>().having(
+                (Exception exception) => exception.toString(),
+            'description',
+            contains('Failed to codesign output/Flutter.framework/Flutter with identity ABC123.\ncodesign error'),
+          )));
+
+      expect(processManager.hasRemainingExpectations, isFalse);
+    });
+
+    testWithoutContext('codesigns framework', () async {
+      binary.createSync(recursive: true);
+
+      final Environment environment = Environment.test(
+        fileSystem.currentDirectory,
+        processManager: processManager,
+        artifacts: artifacts,
+        logger: logger,
+        fileSystem: fileSystem,
+        outputDir: outputDir,
+        defines: <String, String>{
+          kIosArchs: 'arm64',
+          kSdkRoot: 'path/to/iPhoneOS.sdk',
+          kBitcodeFlag: '',
+          kCodesignIdentity: 'ABC123',
+        },
+      );
+
+      processManager.addCommands(<FakeCommand>[
+        copyPhysicalFrameworkCommand,
+        lipoCommandNonFatResult,
+        lipoVerifyArm64Command,
+        bitcodeStripCommand,
         FakeCommand(command: <String>[
-          'lipo',
-          binary.path,
-          '-verify_arch',
-          'arm64',
-        ]),
-        FakeCommand(command: <String>[
-          'xcrun',
-          'bitcode_strip',
-          binary.path,
-          '-m',
-          '-o',
+          'codesign',
+          '--force',
+          '--sign',
+          'ABC123',
+          '--timestamp=none',
           binary.path,
         ]),
       ]);
diff --git a/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart b/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart
index 35553f8..c5aefde 100644
--- a/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart
+++ b/packages/flutter_tools/test/integration.shard/ios_content_validation_test.dart
@@ -119,9 +119,6 @@
 
           expect(vmSnapshot.existsSync(), buildMode == BuildMode.debug);
 
-          expect(outputFlutterFramework.childDirectory('Headers'), isNot(exists));
-          expect(outputFlutterFramework.childDirectory('Modules'), isNot(exists));
-
           // Archiving should contain a bitcode blob, but not building.
           // This mimics Xcode behavior and prevents a developer from having to install a
           // 300+MB app.