Skip to content

Add ability to get build settings for macOS Xcode project #146204

@vashworth

Description

@vashworth

Currently (from what I can tell), there's no way to get the build settings for macOS Xcode projects. We already have tooling to do this for iOS Xcode projects, but are not usable for macOS project. Would be great to make methods universal so they can be used for either macOS or iOS - but they would need to be tweaked to work.

/// 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();
_analytics.send(Event.flutterBuildInfo(
label: 'xcode-show-build-settings-timeout',
buildType: 'ios',
command: showBuildSettingsCommand.join(' '),
));
}
_logger.printTrace('Unexpected failure to get Xcode build settings: $error.');
return const <String, String>{};
} finally {
status.stop();
}
}

/// 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, {
String? scheme,
String? configuration,
String? target,
EnvironmentType environmentType = EnvironmentType.physical,
String? deviceId,
bool isWatch = false,
}) async {
if (!existsSync()) {
return null;
}
final XcodeProjectInfo? info = await projectInfo();
if (info == null) {
return null;
}
scheme ??= info.schemeFor(buildInfo);
if (scheme == null) {
info.reportFlavorNotFoundAndExit();
}
configuration ??= (await projectInfo())?.buildConfigurationFor(
buildInfo,
scheme,
);
return _buildSettingsForXcodeProjectBuildContext(
XcodeProjectBuildContext(
environmentType: environmentType,
scheme: scheme,
configuration: configuration,
target: target,
deviceId: deviceId,
isWatch: isWatch,
),
);
}
Future<Map<String, String>?> _buildSettingsForXcodeProjectBuildContext(XcodeProjectBuildContext buildContext) async {
if (!existsSync()) {
return null;
}
final Map<String, String>? currentBuildSettings = _buildSettingsByBuildContext[buildContext];
if (currentBuildSettings == null) {
final Map<String, String>? calculatedBuildSettings = await _xcodeProjectBuildSettings(buildContext);
if (calculatedBuildSettings != null) {
_buildSettingsByBuildContext[buildContext] = calculatedBuildSettings;
}
}
return _buildSettingsByBuildContext[buildContext];
}
final Map<XcodeProjectBuildContext, Map<String, String>> _buildSettingsByBuildContext = <XcodeProjectBuildContext, Map<String, String>>{};
Future<XcodeProjectInfo?> projectInfo() async {
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
if (!xcodeProject.existsSync() || xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
return null;
}
return _projectInfo ??= await xcodeProjectInterpreter.getInfo(hostAppRoot.path);
}
XcodeProjectInfo? _projectInfo;
Future<Map<String, String>?> _xcodeProjectBuildSettings(XcodeProjectBuildContext buildContext) async {
final XcodeProjectInterpreter? xcodeProjectInterpreter = globals.xcodeProjectInterpreter;
if (xcodeProjectInterpreter == null || !xcodeProjectInterpreter.isInstalled) {
return null;
}
final Map<String, String> buildSettings = await xcodeProjectInterpreter.getBuildSettings(
xcodeProject.path,
buildContext: buildContext,
);
if (buildSettings.isNotEmpty) {
// No timeouts, flakes, or errors.
return buildSettings;
}
return null;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listteam-iosOwned by iOS platform teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-iosTriaged by iOS platform team

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions