Skip to content

Reevaluate getting and parsing of iOS/macOS build settings #134669

Description

@vashworth

We use xcodebuild -showBuildSettings to get the build settings for iOS and macOS projects.

/// Asynchronously retrieve xcode build settings. This one is preferred for
/// new call-sites.
///
/// If [scheme] is null, xcodebuild will return build settings for the first discovered
/// target (by default this is Runner).
Future<Map<String, String>> getBuildSettings(
String projectPath, {
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 String? target = buildContext.target;
final String? deviceId = buildContext.deviceId;
final List<String> showBuildSettingsCommand = <String>[
...xcrunCommand(),
'xcodebuild',
'-project',
_fileSystem.path.absolute(projectPath),
if (scheme != null)
...<String>['-scheme', scheme],
if (configuration != null)
...<String>['-configuration', configuration],
if (target != null)
...<String>['-target', target],
if (buildContext.environmentType == EnvironmentType.simulator)
...<String>['-sdk', 'iphonesimulator'],
'-destination',
if (buildContext.isWatch && buildContext.environmentType == EnvironmentType.physical)
'generic/platform=watchOS'
else if (buildContext.isWatch)
'generic/platform=watchOS Simulator'
else if (deviceId != null)
'id=$deviceId'
else if (buildContext.environmentType == EnvironmentType.physical)
'generic/platform=iOS'
else
'generic/platform=iOS Simulator',
'-showBuildSettings',
'BUILD_DIR=${_fileSystem.path.absolute(getIosBuildDirectory())}',
...environmentVariablesAsXcodeBuildSettings(_platform),
];
try {
// 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.
final RunResult result = await _processUtils.run(
showBuildSettingsCommand,
throwOnError: true,
workingDirectory: projectPath,
timeout: timeout,
timeoutRetries: 1,
);
final String out = result.stdout.trim();
return parseXcodeBuildSettings(out);
} on Exception catch (error) {
if (error is ProcessException && error.toString().contains('timed out')) {
BuildEvent('xcode-show-build-settings-timeout',
type: 'ios',
command: showBuildSettingsCommand.join(' '),
flutterUsage: _usage,
).send();
}
_logger.printTrace('Unexpected failure to get Xcode build settings: $error.');
return const <String, String>{};
} finally {
status.stop();
}
}

However, there appears to be some potential problems with this functionality:

  1. If both -target and -scheme are used, it will error
  2. If the scheme has multiple targets, it will use the build settings of whichever target it lists last, unless the setting is not found in the last target

For example, if you set two targets to Run for the scheme
Screenshot 2023-09-13 at 10 44 01 AM

And then did xcodebuild -project xxx -configuration xxx -scheme Runner -showBuildSettings, it would show something like:

Build settings for action build and target Runner:
...
Build settings for action build and target RunnerTests:
...

This has caused uncertainty in what to use, as seen in #134571 (comment).

We should make this functionality safer and easier to use.

Activity

  1. added
    platform-iosiOS applications specifically
    toolAffects the "flutter" command-line tool. See also t: labels.
    P2Important issues not at the top of the work list
    c: tech-debtTechnical debt, code quality, testing, etc.
    team-iosOwned by iOS platform team
    on Sep 13, 2023
  2. alperduzgun commented on Apr 28, 2026

    @alperduzgun

    Adding a data point on this with Xcode 26 + SWBBuildService, where the performance of xcodebuild -showBuildSettings on Pods.xcodeproj has regressed significantly on large projects.

    Reproduction

    • Xcode 26.4.1 (Build 17E202) — stable
    • macOS 26 (Apple Silicon)
    • Flutter 3.35.4
    • Production Flutter project with 122 CocoaPods targets (Firebase, GoogleMaps, OneSignal, etc.)

    Measurement

    Timings from a flutter run --verbose --flavor development against a booted iPhone 16e simulator:

    Phase Duration
    Runner.xcodeproj -showBuildSettings (generic/iOS + iphonesimulator) ~11s
    Pods.xcodeproj -alltargets -showBuildSettings ~208s
    Actual xcodebuild compile (122 pods + app) ~159s
    rsync app to simulator + simctl install ~51s
    Total wall clock to first frame ~432s (~7m12s)

    So pluginsBuildSettingsOutput (called via pluginsSupportArmSimulator() in xcode_project.dart) is the single largest contributor to flutter run time — ~48% of the total wall clock. This runs on every flutter run, not just after pod install.

    Root cause observation

    On Xcode 26, SWBBuildService opens a fresh build graph per pod target when -alltargets is passed. With 122 targets that's ~1.7s/target on this machine, totaling ~208s. On Xcode 16.x the same project's -alltargets query completed in ~30s, so this looks like a regression tied to SWBBuildService's per-target graph initialization cost rather than the historical developerservices2.apple.com network delay (which was fixed in Xcode 26.0).

    Functional result

    The check exists to decide whether to append arm64 to EXCLUDED_ARCHS for the simulator. On this project, no pod excludes arm64 (verified by grepping the captured -showBuildSettings output and Pods.xcodeproj/project.pbxproj for EXCLUDED_ARCHS.*arm64 — zero matches), so the 208s is spent to produce a result the build would have produced anyway.

    Workaround (local patch)

    I patched xcode_project.dart locally to cache the result of pluginsSupportArmSimulator() at build/ios/.arm64_sim_support_cache keyed off Podfile.lock's mtime. Cache invalidates on pod install (Podfile.lock rewrite) and on flutter clean (build dir cleared). After the first run seeds the cache, Running Xcode build... now starts at ~13s instead of ~222s — a ~209s saving per run. Full flutter run wall clock dropped from ~432s to ~225s.

    Happy to turn this into a PR if a Podfile.lock-keyed disk cache is an acceptable approach. An alternative would be to avoid the xcodebuild invocation entirely by reading the relevant build settings directly from the xcconfig files CocoaPods writes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: tech-debtTechnical debt, code quality, testing, etc.platform-iosiOS applications specificallyteam-iosOwned by iOS platform teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-iosTriaged by iOS platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions