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.