resolveSigningBundleIds static method

Future<Set<String>> resolveSigningBundleIds(
  1. String appRoot, {
  2. required String mainBundleId,
  3. String? flavor,
  4. ProcessRunner processRunner = const SystemProcessRunner(),
  5. bool? isMacOS,
})

Resolves every signed app or extension bundle ID in the Release scheme.

Implementation

static Future<Set<String>> resolveSigningBundleIds(
  String appRoot, {
  required String mainBundleId,
  String? flavor,
  ProcessRunner processRunner = const SystemProcessRunner(),
  bool? isMacOS,
}) async {
  if (!(isMacOS ?? Platform.isMacOS)) {
    throw const SmfError(
      'Apple signing-target discovery requires a macOS runner.',
      SmfErrorCode.macosRequired,
    );
  }
  SmfError.check(
    _isExplicitBundleId(mainBundleId),
    'The configured iOS bundle identifier is not an explicit bundle ID: '
    '$mainBundleId.',
    SmfErrorCode.bundleIdInvalid,
  );
  final projectRoot = p.normalize(p.absolute(appRoot));
  final iosDirectory = p.join(projectRoot, 'ios');
  final container = await _xcodeContainer(iosDirectory);
  final scheme = flavor ?? 'Runner';
  final arguments = <String>[
    container.option,
    container.path,
    '-scheme',
    scheme,
    '-configuration',
    'Release',
    '-sdk',
    'iphoneos',
    '-showBuildSettings',
    '-json',
  ];
  final result = await processRunner.run(
    'xcodebuild',
    arguments,
    options: RunOptions(workingDirectory: projectRoot),
  );
  Object? decoded;
  try {
    decoded = jsonDecode(result.stdout);
  } on FormatException catch (error) {
    throw SmfError(
      'Xcode returned malformed signing-target build settings.',
      SmfErrorCode.xcodeBuildSettingsInvalid,
      cause: error,
    );
  }
  if (decoded is! List<Object?>) {
    throw const SmfError(
      'Xcode signing-target build settings are not a list.',
      SmfErrorCode.xcodeBuildSettingsInvalid,
    );
  }
  final bundleIds = <String>{};
  final appBundleIds = <String>{};
  for (final item in decoded) {
    if (item is! Map<Object?, Object?>) continue;
    final settingsValue = item['buildSettings'];
    if (settingsValue is! Map<Object?, Object?>) continue;
    final wrapperExtension = settingsValue['WRAPPER_EXTENSION'];
    final signingAllowed = settingsValue['CODE_SIGNING_ALLOWED'];
    final signingRequired = settingsValue['CODE_SIGNING_REQUIRED'];
    if ((wrapperExtension != 'app' && wrapperExtension != 'appex') ||
        signingAllowed == 'NO' ||
        signingRequired == 'NO') {
      continue;
    }
    final bundleId = settingsValue['PRODUCT_BUNDLE_IDENTIFIER'];
    final targetValue = item['target'];
    final target = targetValue is String ? targetValue : 'unknown target';
    if (bundleId is! String || !_isExplicitBundleId(bundleId.trim())) {
      throw SmfError(
        'Xcode target "$target" does not resolve to an explicit Release '
        'bundle identifier.',
        SmfErrorCode.xcodeBundleIdInvalid,
      );
    }
    final resolved = bundleId.trim();
    bundleIds.add(resolved);
    if (wrapperExtension == 'app') appBundleIds.add(resolved);
  }
  SmfError.check(
    appBundleIds.contains(mainBundleId),
    appBundleIds.isEmpty
        ? 'Xcode did not report a signed application target for the Release '
              'scheme.'
        : 'The configured iOS bundle ID $mainBundleId does not match a signed '
              'application target in Xcode: ${appBundleIds.toList()..sort()}.',
    SmfErrorCode.bundleIdMismatch,
  );
  return bundleIds;
}