Use cached Xcode build settings during iOS build (#80904)

diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 35d9b1d..eea1947 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -176,9 +176,15 @@
   }
 
   Map<String, String> autoSigningConfigs;
+
+  final Map<String, String> buildSettings = await app.project.buildSettingsForBuildInfo(
+        buildInfo,
+        environmentType: buildForDevice ? EnvironmentType.physical : EnvironmentType.simulator,
+      ) ?? <String, String>{};
+
   if (codesign && buildForDevice) {
     autoSigningConfigs = await getCodeSigningIdentityDevelopmentTeam(
-      buildSettings: await app.project.buildSettingsForBuildInfo(buildInfo),
+      buildSettings: buildSettings,
       processManager: globals.processManager,
       logger: globals.logger,
       config: globals.config,
@@ -353,45 +359,6 @@
   );
   globals.flutterUsage.sendTiming(xcodeBuildActionToString(buildAction), 'xcode-ios', Duration(milliseconds: sw.elapsedMilliseconds));
 
-  // Run -showBuildSettings again but with the exact same parameters as the
-  // build. showBuildSettings is reported to occasionally timeout. Here, we give
-  // it a lot of wiggle room (locally on Flutter Gallery, this takes ~1s).
-  // When there is a timeout, we retry once. See issue #35988.
-  final List<String> showBuildSettingsCommand = (List<String>
-      .of(buildCommands)
-      ..add('-showBuildSettings'))
-      // Undocumented behavior: xcodebuild craps out if -showBuildSettings
-      // is used together with -allowProvisioningUpdates or
-      // -allowProvisioningDeviceRegistration and freezes forever.
-      .where((String buildCommand) {
-        return !const <String>[
-          '-allowProvisioningUpdates',
-          '-allowProvisioningDeviceRegistration',
-        ].contains(buildCommand);
-      }).toList();
-  const Duration showBuildSettingsTimeout = Duration(minutes: 1);
-  Map<String, String> buildSettings;
-  try {
-    final RunResult showBuildSettingsResult = await globals.processUtils.run(
-      showBuildSettingsCommand,
-      throwOnError: true,
-      workingDirectory: app.project.hostAppRoot.path,
-      timeout: showBuildSettingsTimeout,
-      timeoutRetries: 1,
-    );
-    final String showBuildSettings = showBuildSettingsResult.stdout.trim();
-    buildSettings = parseXcodeBuildSettings(showBuildSettings);
-  } on ProcessException catch (e) {
-    if (e.toString().contains('timed out')) {
-      BuildEvent('xcode-show-build-settings-timeout',
-        type: 'ios',
-        command: showBuildSettingsCommand.join(' '),
-        flutterUsage: globals.flutterUsage,
-      ).send();
-    }
-    rethrow;
-  }
-
   if (buildResult.exitCode != 0) {
     globals.printStatus('Failed to build iOS app');
     if (buildResult.stderr.isNotEmpty) {
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index ea544e8..2691966 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -166,10 +166,12 @@
   /// target (by default this is Runner).
   Future<Map<String, String>> getBuildSettings(
     String projectPath, {
-    String? scheme,
+    required XcodeProjectBuildContext buildContext,
     Duration timeout = const Duration(minutes: 1),
   }) async {
     final Status status = _logger.startSpinner();
+    final String? scheme = buildContext.scheme;
+    final String? configuration = buildContext.configuration;
     final List<String> showBuildSettingsCommand = <String>[
       ...xcrunCommand(),
       'xcodebuild',
@@ -177,7 +179,12 @@
       _fileSystem.path.absolute(projectPath),
       if (scheme != null)
         ...<String>['-scheme', scheme],
+      if (configuration != null)
+        ...<String>['-configuration', configuration],
+      if (buildContext.environmentType == EnvironmentType.simulator)
+        ...<String>['-sdk', 'iphonesimulator'],
       '-showBuildSettings',
+      'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}',
       ...environmentVariablesAsXcodeBuildSettings(_platform)
     ];
     try {
@@ -201,7 +208,7 @@
           flutterUsage: _usage,
         ).send();
       }
-      _logger.printTrace('Unexpected failure to get the build settings: $error.');
+      _logger.printTrace('Unexpected failure to get Xcode build settings: $error.');
       return const <String, String>{};
     } finally {
       status.stop();
@@ -282,6 +289,29 @@
   return str.replaceAllMapped(_varExpr, (Match m) => xcodeBuildSettings[m[1]!] ?? m[0]!);
 }
 
+@immutable
+class XcodeProjectBuildContext {
+  const XcodeProjectBuildContext({this.scheme, this.configuration, this.environmentType = EnvironmentType.physical});
+
+  final String? scheme;
+  final String? configuration;
+  final EnvironmentType environmentType;
+
+  @override
+  int get hashCode => scheme.hashCode ^ configuration.hashCode ^ environmentType.hashCode;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    return other is XcodeProjectBuildContext &&
+        other.scheme == scheme &&
+        other.configuration == configuration &&
+        other.environmentType == environmentType;
+  }
+}
+
 /// Information about an Xcode project.
 ///
 /// Represents the output of `xcodebuild -list`.
@@ -365,7 +395,7 @@
     });
   }
 
-  void reportFlavorNotFoundAndExit() {
+  Never reportFlavorNotFoundAndExit() {
     _logger.printError('');
     if (definesCustomSchemes) {
       _logger.printError('The Xcode project defines schemes: ${schemes.join(', ')}');
@@ -377,7 +407,10 @@
 
   /// Returns unique build configuration matching [buildInfo] and [scheme], or
   /// null, if there is no unique best match.
-  String? buildConfigurationFor(BuildInfo buildInfo, String scheme) {
+  String? buildConfigurationFor(BuildInfo? buildInfo, String scheme) {
+    if (buildInfo == null) {
+      return null;
+    }
     final String expectedConfiguration = expectedBuildConfigurationFor(buildInfo, scheme);
     if (hasBuildConfigurationForBuildMode(expectedConfiguration)) {
       return expectedConfiguration;
diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart
index 6744c03..50f9e12 100644
--- a/packages/flutter_tools/lib/src/macos/cocoapods.dart
+++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart
@@ -248,6 +248,7 @@
     } else {
       final bool isSwift = (await _xcodeProjectInterpreter.getBuildSettings(
         runnerProject.path,
+        buildContext: const XcodeProjectBuildContext(),
       )).containsKey('SWIFT_VERSION');
       podfileTemplateName = isSwift ? 'Podfile-ios-swift' : 'Podfile-ios-objc';
     }
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 7f18902..0dea5ca 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -593,11 +593,10 @@
   /// The build settings for the host app of this project, as a detached map.
   ///
   /// Returns null, if iOS tooling is unavailable.
-  Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async {
+  Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo, { EnvironmentType environmentType = EnvironmentType.physical }) async {
     if (!existsSync()) {
       return null;
     }
-    _buildSettingsByScheme ??= <String, Map<String, String>>{};
     final XcodeProjectInfo info = await projectInfo();
     if (info == null) {
       return null;
@@ -608,9 +607,15 @@
       info.reportFlavorNotFoundAndExit();
     }
 
-    return _buildSettingsByScheme[scheme] ??= await _xcodeProjectBuildSettings(scheme);
+    final String configuration = (await projectInfo()).buildConfigurationFor(
+      buildInfo,
+      scheme,
+    );
+    final XcodeProjectBuildContext buildContext = XcodeProjectBuildContext(environmentType: environmentType, scheme: scheme, configuration: configuration);
+    return _buildSettingsByBuildContext[buildContext] ??= await _xcodeProjectBuildSettings(buildContext);
   }
-  Map<String, Map<String, String>> _buildSettingsByScheme;
+
+  final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
 
   Future<XcodeProjectInfo> projectInfo() async {
     if (!xcodeProject.existsSync() || !globals.xcodeProjectInterpreter.isInstalled) {
@@ -620,13 +625,14 @@
   }
   XcodeProjectInfo _projectInfo;
 
-  Future<Map<String, String>> _xcodeProjectBuildSettings(String scheme) async {
+  Future<Map<String, String>> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
     if (!globals.xcodeProjectInterpreter.isInstalled) {
       return null;
     }
+
     final Map<String, String> buildSettings = await globals.xcodeProjectInterpreter.getBuildSettings(
       xcodeProject.path,
-      scheme: scheme,
+      buildContext: buildContext,
     );
     if (buildSettings != null && buildSettings.isNotEmpty) {
       // No timeouts, flakes, or errors.