Push通知を含むiOSアプリのCD構築
はじめに
背景
最近では、iOSアプリのCI/CDをGitHub Actionsで構築する記事が多く見られるようになりました。
特にCD(継続的デリバリー)においては、TestFlightへの自動アップロードや証明書の自動管理など、有用な情報が数多く出回っています。
しかし、実際に私が構築してみると、「Push通知(APNs)を含んだビルド」に関する情報がほとんど見当たらないという問題につまずいたので、本記事を投稿しました。
直面した課題
GitHub ActionsでCDを構築し、TestFlightのアップロードまではできましたが、App Store Connectから以下の問題があると怒られました。
ITMS-90078: Missing potentially required entitlement - Your app, or a library that’s included in your app, uses Apple Push Notification service (APNs) registration APIs, but the APS Environment Entitlement isn’t included in the app signature’s entitlements. If your app uses APNs, make sure the App ID is enabled for push notifications in Certificates, Identifiers & Profiles, sign the app with a distribution provisioning profile that includes the APS Environment Entitlement, and upload a new build to ensure that push notifications function as intended.
(以下はDeepLによる翻訳)
ITMS-90078: Missing potentially required entitlement - アプリまたはアプリに含まれるライブラリが Apple Push Notification service (APNs) 登録 API を使用していますが、APS Environment Entitlement がアプリ署名のエンタイトルメントに含まれていません。アプリがAPNsを使用している場合は、証明書、識別子、およびプロファイルでApp IDがプッシュ通知用に有効になっていることを確認し、APS環境権限を含む配布プロビジョニングプロファイルでアプリに署名し、新しいビルドをアップロードして、プッシュ通知が意図したとおりに機能することを確認してください。
App Store Connectで、該当のTestFlight/ビルドのメタデータ/ストア情報/エンタイトルメントを見てみます。
確かにエンタイトルメントにaps-environment
が含まれていません。
以下は参考にさせていただいた記事です。GitHub Actionsを利用したCDの構築はできるものの、ITMS-90078: Missing Push Notification Entitlement
の解決には難航していそうでした。
CD(継続的デリバリー)の実装
確認事項
Automatically manage signing と Cloud signingを利用して、iOSアプリの証明書やプロビジョニングプロファイルの管理の問題は解決しました。
また、今回用意したアプリはPush通知を利用しているので、TARGETS
やRunner.entitlements
にPush通知を利用するのに必要な材料が揃っているかを確認します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
</dict>
</plist>
Cloud Signingを利用したワークフロー
iOSアプリの証明書等の管理はCloud Signingを採用しました。
App Store ConnectのAPIを利用することで証明書等の更新作業をしなくて済むようになります。
exportArchive
の際、ExportOptions.plistに以下が含まれていると、TestFlightにアップロードしてくれます。
<key>destination</key>
<string>upload</string>
deliver.yaml
name: Delivery
on:
workflow_dispatch:
jobs:
deliver:
runs-on: macos-latest
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version-file: pubspec.yaml
channel: 'stable'
cache: true
- name: Install flutter dependencies
shell: bash
run: |
flutter pub get
- name: Get App Store Connect API Key
run: |
mkdir -p ./private_keys
echo "${{ secrets.APP_STORE_CONNECT_API_KEY }}" > ./private_keys/AuthKey_${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}.p8
- name: Build ios(xcarchive)
run: |
flutter build ios \
--release \
--no-codesign
- name: Archive
run: |
xcodebuild archive \
-workspace ios/Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-sdk iphoneos \
-archivePath build/ios/Runner.xcarchive \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
- name: Export Archive with ExportOptions.plist
run: |
xcodebuild -exportArchive \
-archivePath build/ios/Runner.xcarchive \
-exportOptionsPlist ios/ExportOptions.plist \
-exportPath build/ios/ipa \
-allowProvisioningUpdates \
-authenticationKeyID ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} \
-authenticationKeyIssuerID ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} \
-authenticationKeyPath `pwd`/private_keys/AuthKey_${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}.p8
ExportOptions.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>upload</string>
<key>generateAppStoreInformation</key>
<false/>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<string>app-store-connect</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>${APPLE_TEAM_ID}</string>
<key>testFlightInternalTestingOnly</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
参考記事
Push通知のEntitlementの埋め込み
本題のPush通知のEntitlementをビルドに含める過程です。
ワークフローに以下のステップを、Cloud Signingを行うxcodebuild -exportArchive
の前に追加します。
- name: Embed entitlements
run: |
codesign \
--entitlements ios/Runner/Runner.entitlements \
--force \
--sign "-" build/ios/Runner.xcarchive/Products/Applications/Runner.app
各オプションの説明をします。
--entitlements path
指定されたパスの.entitlements
ファイルを取り出し、署名の際にエンタイトルメントのデータとして署名に埋め込みます。
--force
既に署名されている場合でも、強制的に署名を書き換えます。
再署名にはこのオプションが必要です。
--sign identity
コード署名を行うためのidentity
を指定するオプションです。
本来は開発者が誰であるかを証明する証明書(デジタルID)を指定します。
ここで証明書を指定するには、管理が複雑な証明書や秘密鍵が通常は必要です。
✅ ポイント
ここでのポイントは--sign
のidentity
に"-"
を指定していることです。
"-"
を指定すると何が嬉しいかというと、Ad-hoc署名が適用され、証明書や鍵を用意する必要がないことです。
このステップではコード署名を行わないようにし、続くステップのxcodebuild -exportArchive
で、App Store ConnectのAPIを用いたCloud Signingを行うため、証明書等の管理が不要です。
これで無事にEntitlementsの埋め込みができました。
参考
最終的なワークフロー
実行結果
無事にEntitlementに、aps-environment
が追加されました🚀
まとめ
いかがだったでしょうか。
TestFlightの配信をローカルから手動で実行する手間が省略できるのは、モバイルの開発者にとっては嬉しいことだと思います。
iOSのビルドやXcode関連のコマンドが複雑で理解に時間がかかったので、Cloud Signingで証明書の管理が不要になったように、今後触りやすくなっていけば嬉しいなと思いました。
記載内容に誤りや改善点があればコメントいただけると嬉しいです。
Discussion