この記事では、Bitriseを利用してiOSのCI/CDを構築します。
CI/CD環境の構築は日頃アプリを作るエンジニアにとっては慣れない作業であり、
利用するCIサービスによってやり方は異なるので、他のサービスを構築した知見が活きにくいものです。
また、チームの中でもCertificatesやProvisioning Profileといった、XcodeのCode Signingは経験がないとDeveloperであってもセットアップに苦慮するという状況にあります。
この記事で案内する方法は、BitriseのStepとApp Store Connect APIを利用します。
これによりCode Signingの管理を簡単にし、配布もTest Flightにすることで、できるだけミニマルにCI/CDを構築することができます。
2022/01/26 更新
ios-auto-provision-appstoreconnect
step がdeprecatedとなり、各ステップに automatic_code_signing: api-key
を指定するようになりました。
基本的なセットアップ
Bitriseの管理画面にある Add New App
の手順に従い進めます。
最初のビルドが終わった時点で次の手順に進みます。
特筆することは特にありません。
目指すもの
次の2つができれば、ひとまずやりたいことは満たせるでしょう。
- Unit Testを実行する
- Test FlightでStaging / Production環境のアプリを配布する
Unit Testの実行に必要なのはShared Scheme
Xcode上でSchemeを選択し、 Manage Schemes...
選択します。
CIで利用したいSchemeにSharedのチェックを入れます。
gitに含めてPushすることで、CI上でcloneしたときに利用できるようになります。
Schemeを作った場合は、Testが追加されていることを確認します。
ローカル環境に fastlane は必要かを考える
fastlaneはiOSアプリのビルドや配布を自動化するツールです。
iOSアプリのビルドや配信の自動化には xcodebuild
や xcrun
といった、Xcode同梱のコマンドラインツールを利用することができます。
fastlaneには、Certificate や Provisioning Profile を管理する仕組みも備わっています。
従来は fastlane なしでの開発は考えられないほど恩恵を大きく受けていました。
しかしながら、CI 環境で Ruby のセットアップが必要であったり、fastlane match の導入によりかえって証明書や Provisioning Profile の管理が煩雑になってしまっているのも事実です。
CocoaPodsを利用している場合はRubyのセットアップは求められるので問題ないですが、Swift Package Managerを利用している場合、fastlaneのためにRubyのセットアップをするのは少々ためらわれます。
Bitriseでは、xcodebuild
や xcrun
を利用したStepが作られてる他、内部ではfastlaneに依存しているものもあり、ローカル環境で fastlane を導入しなくとも fastlane の恩恵を受けることができます。
Bitrise に預けるのは Certificate と API Key
他のCI環境に比べて、Bitriseの開発体験がよいところがこのCertificateをアップロードするGUIが備わっていることです。
他のCIでは fastlane match
を利用してCertificateを管理すると記載があります。
例: Circle CI https://circleci.com/docs/2.0/ios-codesigning/
Note: CircleCI only officially supports Fastlane Match for code signing. Other methods may be used, but are not guaranteed to work and are unsupported.
厳密に管理がなされれば良いのは、Distribution Certificateのみで、App StoreやTest Flightで利用するapp-storeのProvisioning Profileは、なければ都度生成して問題ありません。
Bitriseの iOS Auto Provisioning
Stepではこの方式が取られており、とてもリーズナブルです。
Xcode archiveで automatic_code_signing
を指定するように変更となりました。
(すでに存在している場合はそのProvisioning Profileを利用します)
Automatic Signingに対応しているので、Provisioning Profileを固定する必要はありません。
背景と設定手順この記事にまとめましたAd hoc配信をやめてTestFlightで配信する
実はこれで終わり
実はこれ以上書くことはないほどにBitriseのセットアップは簡単です。
次に記載するyamlの内容で、Pull Requestが作成されたらTestが動いたり、main
ブランチにマージコミットされたらStaging用のTest Flightアプリが配布されるところまで実現できます。
Production用のアプリはBitriseのGUIから実行させたり、専用のトリガーを作って外部から起動させるという方法も取れます。
実際のbitrise.yml
関わっているプロジェクトで運用されているbitrise.ymlは次の通りです。
format_version: "11"
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: ios
workflows:
_finish:
steps:
- slack:
inputs:
- api_token: "$SLACK_API_TOKEN"
- webhook_url: "$SLACK_WEBHOOK_URL"
- cache-push: {}
_prepare:
steps:
- activate-ssh-key:
run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
- script:
inputs:
- content: |-
#!/usr/bin/env bash
set -ex
for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts
title: Add SSH
- git-clone: {}
- cache-pull: {}
- script:
title: Prepare CLI
run_if: ".IsCI"
inputs:
- is_debug: "yes"
- content: |-
#!/usr/bin/env bash
set -eux
make bootstrap
deliver-staging:
steps:
- ios-auto-provision-appstoreconnect:
inputs:
- distribution_type: app-store
- scheme: Staging
- api_issuer: "$APP_STORE_CONNECT_API_KEY_ISSUER_ID"
- api_key_path: "$BITRISEIO_APPSTORE_CONNECT_API_KEY_URL"
- set-xcode-build-number:
inputs:
- plist_path: "$BITRISE_SOURCE_DIR/App/iOS/Info.plist"
- xcode-archive:
inputs:
- export_method: app-store
- scheme: Staging
- team_id: "$BITRISE_APPSTORE_TEAM_ID"
- deploy-to-itunesconnect-application-loader:
inputs:
- api_key_path: "$BITRISEIO_APPSTORE_CONNECT_API_KEY_URL"
- api_issuer: "$APP_STORE_CONNECT_API_KEY_ISSUER_ID"
- team_id: "$BITRISE_APPSTORE_TEAM_ID"
before_run:
- _prepare
after_run:
- _finish
deliver-production:
steps:
- set-xcode-build-number:
inputs:
- plist_path: "$BITRISE_SOURCE_DIR/App/iOS/Info.plist"
- xcode-archive:
inputs:
- export_method: app-store
- scheme: App
- distribution_method: app-store
- automatic_code_signing: api-key
- team_id: "$BITRISE_APPSTORE_TEAM_ID"
- deploy-to-itunesconnect-application-loader:
inputs:
- api_key_path: "$BITRISEIO_APPSTORE_CONNECT_API_KEY_URL"
- api_issuer: "$APP_STORE_CONNECT_API_KEY_ISSUER_ID"
- team_id: "$BITRISE_APPSTORE_TEAM_ID"
before_run:
- _prepare
after_run:
- _finish
test:
steps:
- xcode-test:
inputs:
- scheme: AppFeature
- simulator_device: iPhone 12
- simulator_os_version: "14.5"
- project_path: "$BITRISE_PROJECT_PATH"
before_run:
- _prepare
after_run:
- _finish
app:
envs:
- opts:
is_expand: false
BITRISE_PROJECT_PATH: xxxxxxxx.xcworkspace
- opts:
is_expand: false
BITRISE_APPSTORE_TEAM_ID: XXXXXXXXXX
trigger_map:
- push_branch: main
workflow: deliver-staging
- pull_request_target_branch: "*"
workflow: test
FAQ
Q. デバイスをDeveloper Portalに追加する必要ないのですか?
TestFlightで配布する場合は、Provisioning Profileにデバイスを登録する必要がありません。
Q. fastlane deliverでメタデータのアップデートを行なってました
deploy-to-itunesconnect-deliver
を利用すると、中では fastlane deliver
を利用しているので、オプションにKey Valueを渡すことで同じように利用することができます。
筆者が試したときは、 deploy-to-itunesconnect-deliver
がうまく動かず、 deploy-to-itunesconnect-application-loader
でユースケースは満たせたので、こちらを利用しています。