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:
- 主应用和 Widget 的 App ID 都应启用 App Groups。
- 如果两者通过 App Group 共享数据,应关联同一个实际使用的 Group ID。
- 两个 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。继续使用当前的手动签名方案时,应满足:
| 设置 | Elders | EldersWidget |
|---|---|---|
CODE_SIGN_STYLE | Manual | Manual |
DEVELOPMENT_TEAM | 对应 Team | 对应 Team |
PRODUCT_BUNDLE_IDENTIFIER | 主应用 Bundle ID | Widget 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 中缺失的能力授权。
备注:内容仅供参考。
