GitHub Actionsで最新の状態をテストフライトで配信する
iOSアプリを開発するなかで、開発中のパッケージを確認するためにテストフライトという仕組みを使います。
これをGitHub Actionsを使って自動化してみました。
「あるブランチにcommitされたら、最新の状態のビルドをテストフライトで配布」をGitHub Actionsで組む手順を紹介します。
実装の流れ
- fastlane matchを使った証明書管理を準備する
- fastlaneにテストフライトのためのレーンを実装する
- GitHub Actionsの設定をする
なお、fastlaneはすでにセットアップ済みであることを想定して書きます。
まだfastlaneを使ってない場合はこちらの記事などを参考にセットアップしてみてください。
fastlane matchを使った証明書管理を準備する
fastlane matchはfastlaneアクションの一つで、iOSの煩雑な証明書管理をシンプルに行うためのものです。開発メンバー間で異なる証明書を使ったり、CI用の証明書に困ったりする問題を解決できます。(公式ドキュメントはこちら)
まずはmatchの初期化を行い、
fastlane match init
証明書を作成します。
fastlane match appstore
今回はテストフライトのビルドで使う証明書が欲しいので、appstore
を指定します。
fastlane matchでは証明書管理のためのスペースを別で作成し、そこで証明書やプロビジョニングファイルを管理します。
コマンドの途中で管理方法やURLを聞かれるので入力しましょう。今回はGitのリポジトリを指定しました。
確認のためにfastlane/Matchfile
の中身をみてみましょう。
指定した方法(git)とリポジトリのURLが記述されていることが分かります。
git_url("https://github.com/username/repository_name.git")
storage_mode("git")
さらに、コマンドの途中で MATCH_PASSWORD
が尋ねられます。これは適当なものにしてOKですが、後でCIに設定するのでメモしておきましょう。
次に、以下のコマンドで作成した証明書を取得します。
fastlane match appstore --readonly
readonly
を指定することで証明書を取得できます。ひとりのオーナーが証明書を一回だけ作成し、残りのメンバーはその証明書を利用することで全員が共通の証明書を利用できます。
ダウンロードした証明書とプロビジョニングファイルの場所はfastlane match
の実行ログに表示されており、ファイルをダブルクリックしてimportできます。
Xcodeを開いて、証明書とプロビジョニングがセットできることを確認しておきましょう。また、Auto Signingはオフにしておくと良いです。
証明書の準備はここまでで完了です。
fastlaneコマンドに失敗する場合など、さらに詳しい情報が必要な場合は以下の記事が詳しく参考になります。
また、matchを使わない証明書管理をしたい場合は以下の記事が参考になると思います。
fastlaneにテストフライトのためのレーンを実装する
GitHub Actionsで実行されるfastlaneのレーンを用意します。ソースコードは以下のようになります。
lane :beta do
# 1. Keychainの準備
setup_ci(provider: "travis")
# 2. AppStoreConenct APIの準備
api_key = app_store_connect_api_key(
key_id: ENV['ASC_KEY_ID'],
issuer_id: ENV['ASC_ISSUER_ID'],
key_content: ENV['ASC_KEY_CONTENT'],
in_house: false
)
# 3. GitHubアクセスの準備
username = ENV['USERNAME']
personal_github_access_token = ENV["PERSONAL_GITHUB_ACCESS_TOKEN"]
authorization_token_str = "#{username}:#{personal_github_access_token}"
basic_authorization_token = Base64.strict_encode64(authorization_token_str)
# 4. fastlane matchを使って証明書をインストール
match(
git_basic_authorization:basic_authorization_token,
api_key: api_key,
app_identifier: 'com.example.myapp',
type: "appstore",
readonly: is_ci
)
# ビルド番号をインクリメント
increment_build_number(xcodeproj: "MyApp.xcodeproj")
# アプリをビルド
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_options: {
method: "app-store",
}
)
# 5. テストフライトで配布
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true
)
end
少し補足の説明を書きます。
1. Keychainの準備
setup_ci(provider: "travis")
いきなりTravisを指定してるのが変な感じがしますが、こちらはfastlaneのIssueで議論されていた方法を参考にして記述しました。内部的にはキーチェーンの準備をしているようなので、別の方法でも良いかもしれません。
ちなみにこの一行がないと'[CP] Embed Pods Frameworks'
で停止してビルドが進まなくなります。
2. AppStoreConenct APIの準備
api_key = app_store_connect_api_key(
key_id: ENV['ASC_KEY_ID'],
issuer_id: ENV['ASC_ISSUER_ID'],
key_content: ENV['ASC_KEY_CONTENT'],
in_house: false
)
かつて、fastlaneで申請をするときはCI上で二段階認証を越えるためにFASTLANE_SESSION
を毎回セットする必要があるなど手間がかかる部分が多くありました。
現在、fastlaneはAppStoreConnect APIをサポートしており、これを使うことでFASTLANE_SESSIONの更新などは必要なくシンプルに実現できます。詳しくは以下リンクをご参考ください。
API Keyを発行するために、以下の3つの情報が必要です。
ASC_KEY_ID
ASC_ISSUER_ID
ASC_KEY_CONTENT
これらを用意する方法はmogaさんの以下の記事がスクリーンショット付きで大変分かりやすいです。
AppStoreConenctから生成しましょう。後でCIのSecretsに設定するのでメモしておきましょう。
3. GitHubアクセスの準備
username = ENV['USERNAME']
personal_github_access_token = ENV["PERSONAL_GITHUB_ACCESS_TOKEN"]
authorization_token_str = "#{username}:#{personal_github_access_token}"
basic_authorization_token = Base64.strict_encode64(authorization_token_str)
fastlane match
では別のリポジトリにアクセスして証明書をダウンロードする必要があります。ここでは別のリポジトリにアクセスするための認証情報を用意しています。
ここで、 USERNAME
はGitHubアカウント名、 PERSONAL_GITHUB_ACCESS_TOKEN
はGitHubで発行できるPERSONAL ACCESS TOKENです。(PERSONAL ACCESS TOKENの発行方法はこちらから)
4. fastlane matchを使って証明書をインストール
match(
git_basic_authorization: basic_authorization_token,
api_key: api_key,
app_identifier: 'com.example.myapp',
type: "appstore",
readonly: is_ci
)
fastlane matchを使って証明書とプロビジョニングファイルをダウンロードします。
CIから証明書の作成はしたくないので、readonly: is_ci
を指定してリードオンリーで利用します。
5. テストフライトで配布
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true
)
ビルドしたアプリをテストフライトで配布します。FASTLANE_SESSION
がこれまで必要でしたが、api_keyを使った方法であれば2FAを越えられるため不要になっています。
GitHub Actionsでは利用時間に応じて料金がかかります。できるだけ時間を節約するためにskip_waiting_for_build_processing: true
をつけて、ビルドのアップロード完了を待たずに処理を終了しています。
ここまででfastlaneの準備は完了です。
GitHub Actionsの設定をする
それでは最後に、いま用意したfastlaneのレーンをGitHub Actionsから実行できるようにしましょう。
GitHub Actionsのワークフローは以下のように設定します。
name: Upload TestFlight
on:
workflow_dispatch:
push:
branches:
- master
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Select Xcode version
run: sudo xcode-select -s '/Applications/Xcode_12.3.app/Contents/Developer'
- name: Show Xcode version
run: xcodebuild -version
- name: Bundle Install
run: bundle install
- uses: actions/cache@v2
with:
path: Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Pod Install
if: steps.cache-cocoapods.outputs.cache-hit != 'true'
run: pod install
- name: Upload a new build to App Store Connect
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
PERSONAL_GITHUB_ACCESS_TOKEN: ${{ secrets.PERSONAL_GITHUB_ACCESS_TOKEN }}
USERNAME: ${{ secrets.USERNAME }}
run: bundle exec fastlane beta
Xcodeのバージョンの選択、CocoaPodsで管理しているライブラリのインストールから始まり、最後のステップで用意したレーンを実行します。
ここまでのステップで用意した環境変数をGitHubのSecretsに設定し、それをenvの項目に記載します。
GitHubでSecretsを設定する
発動のトリガーを以下のようにセットしているので、手動実行(workflow_dispatch
)、またはmaster
ブランチへのプッシュで自動でテストフライトが配布されます。
on:
workflow_dispatch:
push:
branches:
- master
最後に
fastlane + AppStoreConnect API + GitHub Actionsを使ってテストフライトの自動化を組んでみました。
GitHub Actionsはmacosで動かすとLinuxの10倍の時間を消費します。ヘビーな使い方をすると無料枠をすぐに超えてしまうので料金には注意してください。
必要に応じてTestFlightが必要になるようなシーンでは今回の構成で十分に便利だと思います。
Discussion