PAYATHON 2026

Azure DevOps 归档 iOS 时提示缺少 App Groups 签名配置,如何修复?

数据小陈

结论

这两条错误表示,归档时 Xcode 没有为 Elders 和 EldersWidget 找到或选中满足各自能力要求的 provisioning profile。

证书和 profile 有效,只能说明它们没有过期等问题,不能证明 profile 与当前 target 的 Bundle ID、Team、签名证书和 entitlements 匹配。

流水线安装了两个 profile,但归档命令只指定了 CODE_SIGN_STYLE=Manual、签名身份和 Team,没有显示两个 target 分别使用哪个 profile。如果项目的 Release 配置也没有正确指定,安装成功后仍然会出现这类错误。

仅凭现有日志,无法判断是 profile 缺少能力,还是 Xcode 没有选中正确的 profile,需要分别核对。

为什么主应用和 Widget 都会报错?

原始日志分别指出:

  • EldersWidget 需要支持 App Groups 的 profile。
  • Elders 需要满足 App Groups、In-App Purchase 和 Push Notifications 能力要求的 profile。

主应用和 Widget extension 是两个独立的签名目标,通常具有不同的 Bundle ID,因此需要分别匹配各自的 App ID 和 provisioning profile。

即使两个 target 共享同一个 App Group,也不能因此共用主应用的 profile。共享的是 App Group 标识,例如 group.com.example.shared,各自的应用标识仍然不同。

当前报错发生在 bash 中的 xcodebuild archive 阶段。后续导出步骤使用的 exportOptions.plist 无法修复归档阶段的签名选择错误。

1. 检查 App ID 和 profile 包含的能力

在 Apple Developer 后台分别检查主应用和 Widget 的 App ID,确认 Bundle ID 与项目一致,且属于预期的 Team。

对于 App Groups:

  1. 主应用和 Widget 的 App ID 都应启用 App Groups。
  2. 如果两者通过 App Group 共享数据,应关联同一个实际使用的 Group ID。
  3. 两个 target 的 entitlements 文件应声明各自需要的 Group ID,拼写必须一致。

主应用还需要核对 Push Notifications 和 In-App Purchase 的能力配置。In-App Purchase 不应简单理解为在 entitlements 文件中补一个同名键,还需要检查 App ID 和项目中的对应能力设置。

如果修改了 App ID 的能力或 App Group 关联,应重新生成并下载受影响的 provisioning profiles,再更新 Azure DevOps Secure Files 中的文件。已经下载的旧 profile 不会随后台设置自动更新。

如果项目确实不再需要某项能力,可以在确认功能需求后移除相应配置;不要仅为消除报错,删除仍在使用的 App Groups 或 Push Notifications。

2. 解码实际上传的 profile

对主应用和 Widget 的 .mobileprovision 文件分别执行以下命令,将示例路径替换成实际文件路径:

security cms -D -i /path/to/profile.mobileprovision > /tmp/profile.plist

/usr/libexec/PlistBuddy -c 'Print :Name' /tmp/profile.plist
/usr/libexec/PlistBuddy -c 'Print :UUID' /tmp/profile.plist
/usr/libexec/PlistBuddy -c 'Print :TeamIdentifier' /tmp/profile.plist
/usr/libexec/PlistBuddy -c 'Print :ExpirationDate' /tmp/profile.plist
/usr/libexec/PlistBuddy -c 'Print :Entitlements' /tmp/profile.plist

重点核对:

检查项需要确认的内容
application-identifier应用标识与对应 target 的 Bundle ID 匹配
TeamIdentifier与项目和流水线使用的 Team 一致
com.apple.security.application-groups包含该 target 实际请求的 App Group
aps-environment对使用 Push Notifications 的主应用,存在且与签名用途相符
DeveloperCertificates包含与流水线安装的签名身份对应的证书
Name、UUID用于确认项目实际选择的是哪个 profile

.mobileprovision 的文件名和文件内部的 Name 是两回事。配置 PROVISIONING_PROFILE_SPECIFIER 时,不要直接假设上传文件名就是 profile 名称。

.p12 还需要包含对应私钥,并成功导入构建使用的 keychain。安装证书后,可以查看可用签名身份:

security find-identity -v -p codesigning

3. 为两个 target 分别配置 Release 签名

当前归档命令明确使用 Release,因此需要检查 Release 配置,不能只检查本地调试使用的 Debug 配置。

在 Xcode 中打开 workspace,分别选择 Elders 和 EldersWidget,检查 Signing & Capabilities 以及 Build Settings。继续使用当前的手动签名方案时,应满足:

设置EldersEldersWidget
CODE_SIGN_STYLEManualManual
DEVELOPMENT_TEAM对应 Team对应 Team
PRODUCT_BUNDLE_IDENTIFIER主应用 Bundle IDWidget Bundle ID
PROVISIONING_PROFILE_SPECIFIER主应用 profile 的内部名称Widget profile 的内部名称
CODE_SIGN_ENTITLEMENTS主应用的 entitlements 文件Widget 的 entitlements 文件

如果签名设置按 SDK 区分,还要检查 Any iOS SDK 或 iphoneos 对应的设置。

将这些配置保存到版本控制后,在流水线中安装对应的 profile,再执行归档。避免在整个 xcodebuild 命令上追加一个通用的 PROVISIONING_PROFILE_SPECIFIER:该命令会构建多个 target,单个值不能表达主应用与 Widget 的不同对应关系。

可以在 CI 中查看实际解析出的构建设置:

xcodebuild \
  -workspace mobile-app-eldersweather-ios/Elders.xcworkspace \
  -scheme Elders \
  -configuration Release \
  -sdk iphoneos \
  -showBuildSettings

在输出中分别找到两个 target,核对上述签名字段。还要确认 CODE_SIGN_ENTITLEMENTS 指向的文件存在,且内容与对应 profile 允许的 entitlements 匹配。

4. 安装两个 profile 后再归档

安装任务可以保留,建议分别命名,以便在日志中识别:

- task: InstallAppleProvisioningProfile@1
  name: installAppProfile
  displayName: 'Install main app provisioning profile'
  inputs:
    provisioningProfileLocation: 'secureFiles'
    provProfileSecureFile: 'Elders_Distubution_profile_App.mobileprovision'
    removeProfile: true

- task: InstallAppleProvisioningProfile@1
  name: installWidgetProfile
  displayName: 'Install Widget provisioning profile'
  inputs:
    provisioningProfileLocation: 'secureFiles'
    provProfileSecureFile: 'Elders_Distubution_profile_widget.mobileprovision'
    removeProfile: true

- bash: |
    set -euo pipefail
    cd mobile-app-eldersweather-ios

    xcodebuild \
      -workspace Elders.xcworkspace \
      -scheme Elders \
      -configuration Release \
      -sdk iphoneos \
      -archivePath "$(Build.ArtifactStagingDirectory)/Elders.xcarchive" \
      CODE_SIGN_STYLE=Manual \
      CODE_SIGN_IDENTITY="Apple Distribution" \
      DEVELOPMENT_TEAM="$(APPLE_TEAM_ID)" \
      archive
  displayName: 'Archive'

这个片段的前提是:证书安装步骤已经成功执行,两个 target 的 Release 配置已经分别指定正确的 profile,且流水线可以取得 APPLE_TEAM_ID 的实际值。给安装任务命名本身不会自动建立 target 与 profile 的绑定。

5. 归档成功后再处理 IPA 导出

手动签名导出时,exportOptions.plist 中的 provisioningProfiles 字典应覆盖主应用和 Widget,键使用各自的 Bundle ID,值使用对应 profile 的名称或 UUID。

将以下 XML 片段合并到现有 plist 的根字典中,并替换所有占位符。该片段不是完整的 plist 文件:

<key>signingStyle</key>
<string>manual</string>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>provisioningProfiles</key>
<dict>
    <key>YOUR_MAIN_APP_BUNDLE_ID</key>
    <string>YOUR_MAIN_APP_PROFILE_NAME</string>
    <key>YOUR_WIDGET_BUNDLE_ID</key>
    <string>YOUR_WIDGET_PROFILE_NAME</string>
</dict>

完整 plist 还应包含与所用 Xcode 和发布方式匹配的导出设置。现有材料没有提供发布方式和 plist 内容,因此不能直接确定应填写哪个 method。

如果需要单独导出 archive,可以用以下 bash 步骤替换当前导出任务:

- bash: |
    set -euo pipefail

    xcodebuild -exportArchive \
      -archivePath "$(Build.ArtifactStagingDirectory)/Elders.xcarchive" \
      -exportOptionsPlist "mobile-app-eldersweather-ios/Elders/exportOptions.plist" \
      -exportPath "$(Build.ArtifactStagingDirectory)/$(exportPath)"
  displayName: 'Export IPA'

该步骤只负责导出已有 archive,需要先解决前面的签名错误。

原始 YAML 还需要检查什么?

粘贴的 YAML 存在缩进缺失。例如,vmImage、变量的 value、任务的 inputs 和 bash 脚本正文都需要嵌套到对应节点下。另外,单独一行的 CocoaPods install & clean 如果是说明文字,应改成注释:

# CocoaPods install & clean

已有日志说明某次执行确实进入了 Xcode 构建阶段,因此这些格式问题可能来自复制过程。应以实际运行的 YAML 为准,不能用粘贴格式问题解释全部签名报错。

反复清理 DerivedData 或重新安装 CocoaPods,无法补齐 profile 中缺失的能力授权。

备注:内容仅供参考。