先日、MoveLogというiOSアプリをリリースしました。機能追加したいとは思いつつも、最近はテストコード書いたり足回りを整えたりしています。その中でも、アプリのリリース作業というものはとても面倒な作業で、普通にやろうとすると下記のような手作業で実施するかと思います。
- App Store Connect で新しいバージョンの提出準備をする
- XcodeでアプリをBuidして、ビルドしたアプリをアップロードする
- App Store Connectでメタ情報を修正してリリースする
App Store Connect とXcodeを行き来しないといけないし、Xcode で Buidを実行すると10分ほどかかって、その後に画面でぽちぽちしてアップロードしないといけないしで面倒ですよね。ビルド中はMacが悲鳴をあげるという厳しさもあります。
仕事でインフラ周りの整備をやってる身ということもあり、この作業を効率化しないといけないという使命感にかられたので、GitHub Actions と faslaneを使って部分的に自動化するようにしました。心が折れそうになるくらいハマったので備忘録として残します。主に証明書や2段階認証周りの設定がハマり尽くしました。
全体像
今回はアプリのビルドからApp Store Connectへのビルドファイルのアップロードをするだけとなります。 スクリーンショット取得やアプリの紹介情報の更新もfastlaneで出来るようですが、そこまでは実施していないです。
- アプリのコードをGitHubのリポジトリにPushする
- Release ブランチにPRがマージされると GitHub Actionsが発火する
- GitHub Actions の中でfastlaneを実行する
- fastlane match で ビルド用の証明書情報を取得する
- fastlane gymでアプリをビルドする
- faslane deliver でApp Store Connectにビルドしたアプリをアップロードする
fastlaneについて
fastlane はiOS,Android アプリのデプロイを自動化するツールです。
fastlane - App automation done right
今回は App Store Connectにビルドしたものをアップロードするまでが目的なので、 APP STORE DEPLOYMENT
と CODE SIGNING
のみを利用します。元々は APP STORE DEPLOYMENT
のみを利用しようと思ってましたが、CI環境を動かそうとすると証明書をどうするか問題があったので、証明書の管理も fastlane で実施する事にしました。
fastlane は actionsの組み合わせによって自動化出来る範囲を増やすことが出来ます。
Available Actions - fastlane docs
fastlane の 設定(fastfile)は下記のような感じになり、lane の中で指定されたアクションを定義していくといったものですね。
lane :release do build_app(scheme: "MyApp") end
この記事では、fastlane actions の下記のアクションを利用します。
fastlane match を使った証明書の管理
公式のドキュメントはこちら
元々は複数の開発者のMacでそれぞれ証明書の管理をする事が大変という事で、中央集権的な管理をするためのものらしいですね。個人開発者からすると若干オーバースペックな気はしますが、一旦は気にしない。
下記の流れで初期化をする事が出来ました。(2020/6/14時点)
- アプリとは異なる証明書管理専用の新しいリポジトリを作成する。
fastlane match init
を実行して初期化するfastlane match development
を実行して、開発用の証明書(Development)を作成するfastlane match appstore
を実行して、リリース用の証明書(Distribution)を作成する
初期化をした後には Xcode の Automatically manage signing
のチェックを外して matchで作成した開発用の証明書を指定すれば良いです。
fastlane gym でのアプリのビルド
公式のドキュメントはこちら。内部的には xcodebuild を利用しているようです。
設定したものは特に複雑な事はしていないかと思います。ライブラリの管理にPodを利用しているので .xcworkspace
を指定しています。
gym( workspace: "XXXX.xcworkspace", configuration: "Release", scheme: "XXXXX", export_method: "app-store", )
fastlane deliver でのリリース準備
公式のドキュメントはこちら。
App Store Connectとの連携をするアクションですね。今回は新しいバージョンの作成とビルドしたもののアップデートをしたいので、下記設定をしました。何も設定をしない場合は対話形となるため、CIで利用するためには force: true
の設定が必要です。
deliver( force: true, skip_metadata: true, skip_screenshots: true )
GitHub Actionsでの設定
職場ではCircle CI を 使っているので、GitHub Actionsは始めて触りました。macOS
の実行環境が無料で利用できるのはとても良いですね。単純に言えばLinuxの10倍お金かかるので、無料プランのビルド時間だとテストも常に回すようにすると無料だと厳しそうですね。 個人開発なら程よいくらいかも。
アプリのリポジトリに /.github/workflows/xxxx.yml
のような構造で設定ファイルを配置すれば利用できるようになるので楽ですね。ライブラリのキャッシュにも対応したのでとても便利になりました。
設定自体は下記のようになりました。timeout-minutes
を設定しない場合はデフォルト値が6時間なので、処理が固まった時にひたすら利用時間が消費されるので気をつけた方が良さそうです。あとはPodのライブラリはキャッシュするようにしてるので、2回目以降の実行は少し早くなるはずです。
name: iOSBuild on: push: branches: - release jobs: build: runs-on: macos-latest timeout-minutes: 900 steps: - uses: actions/checkout@v2 - name: Cache Pods uses: actions/cache@v2 with: path: Pods key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} - name: Pod Install if: steps.cache-cocoapods.outputs.cache-hit != 'true' run: pod install - name: Release run: fastlane release --verbose env: FASTLANE_USER: ${{ secrets.FASTLANE_USER }} FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} MATCH_PASSWORD: ${{secrets.MATCH_PASSWORD}} MATCH_GIT_BASIC_AUTHORIZATION: ${{secrets.MATCH_GIT_BASIC_AUTHORIZATION}} FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD}} FASTLANE_SESSION: ${{secrets.FASTLANE_SESSION}}
環境変数の詳細については後述しますが、GitHub ActionsではリポジトリのSecretsに秘匿情報を設定すれば環境変数として使う事が出来ます。
GitHub Actions 上で利用する証明書の設定
ローカルのMacで実行する分にはXcodeで指定している証明書が利用されるので、あまり気にしなくてよいのですが、CI環境で実行させようとするとかなりハマりました。
ビルドするための証明書を fastlane matchで設定したリポジトリから取得します。 sync_code_signing
のアクションを実行する事で取得する事が出来ます。
# Fastfile sync_code_signing( git_url: "<fastlane matchの設定で作成したリポジトリ>", git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"], type: "appstore", readonly: true )
環境変数 MATCH_PASSWORD
に fastlane match
で証明書を作成した際に設定したパスワードを設定します。
また、環境変数 MATCH_GIT_BASIC_AUTHORIZATION
がないと怒られます。
https://docs.fastlane.tools/actions/match/#git-storage-on-github
エラーが発生した時の内容
Exit status: 128 Error cloning certificates repo, please make sure you have read access to the repository you want to use Run the following command manually to make sure you're properly authenticated:
GitHub Actions は実行されたリポジトリへのアクセストークンは保有していますが、異なるリポジトリへのアクセスは出来ないので、認証情報を設定する必要があるようです。
また、この認証情報は <ユーザ名>:<GitHub Personal access tokens>
をbase64 フォーマットしたものを利用する必要があります。 そのため、下記のようなコマンドを実行して生成しました。私は echo
で改行コード含んだ状態でbase64にしてしまって動かないという初歩ミスして時間を溶かしました。
echo -n <ユーザ名>:<GitHub Personal access tokens> | base64
sync_code_signing
の設定をしただけだと、Xcodeで設定している証明書ファイル(match Development XXXX)が指定されてしまってビルドでこけるという問題も発生しました。
INFO [2020-06-14 05:01:18.70]: $ set -o pipefail && xcodebuild -workspace XXXX.xcworkspace -scheme XXXX -configuration Release -destination 'generic/platform=iOS' -archivePath /Users/runner/Library/Developer/Xcode/Archives/2020-06-14/XXXX\ 2020-06-14\ 05.01.18.xcarchive archive | tee /Users/runner/Library/Logs/gym/XXXX-XXXX.log | xcpretty INFO [2020-06-14 05:01:25.61]: ▸ ❌ error: No profile for team 'XXXX' matching 'match Development XXXX' found: Xcode couldn't find any provisioning profiles matching 'XXXX/match Development XXXX. Install the profile (by dragging and dropping it onto Xcode's dock item) or select a different one in the Signing & Capabilities tab of the target editor. (in target 'XXXX' from project 'XXXX') INFO [2020-06-14 05:01:25.61]: ▸ ** ARCHIVE FAILED **
下記のように証明書の設定をアップデートする事で通るようにはなりました。match で設定してたら自動でしてくれそうなものの、出来ないのかなと疑問に思ってるので良い方法を知ってる人がいたら教えてください。
update_code_signing_settings( code_sign_identity: "iPhone Distribution", profile_name: "match AppStore XXXX" )
App Store Connect への認証
App Store Connect にビルドしたものをfastlaneでアップロードする際には下記の環境変数の設定が必要となります。
https://docs.fastlane.tools/best-practices/continuous-integration/#environment-variables-to-set
また、App Store Connect にログインしようとする際には Apple ID の二段階認証が必須となったかと思います。CIからのログインで二段階認証が突破出来ないと下記のような事象に陥ります。
INFO [2020-06-13 06:10:33.91]: Login to App Store Connect (***) Reading keychain entry, because either user or password were empty Two-factor Authentication (6 digits code) is enabled for account '***' More information about Two-factor Authentication: https://support.apple.com/en-us/HT204915 If you're running this in a non-interactive session (e.g. server or CI) check out https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification Please enter the 6 digit code you received at +81 •••-••••-••XX:
エラーログ自体はおそらくはStringをパース出来ないとかで、関連性がなさそうなものが出ます。めっちゃハマりました。
➡️ [Swift] undefined method `each' for nil:NilClass - Cannot Create Group Within FastlaneSwiftRunner Project https://github.com/fastlane/fastlane/issues/15184 [open] 28 💬 a week ago /usr/local/lib/ruby/gems/2.6.0/gems/highline-1.7.10/lib/highline/question.rb:413:in `remove_whitespace': [!] undefined method `strip' for nil:NilClass (NoMethodError)
二段階認証を突破するためには下記の環境変数の設定が必要です。
FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
: Apple IDのApp用パスワードFASTLANE_SESSION
: fastlane spaceauth で実行した結果の文字列fastlane spaceauth -u <Apple IDのメールアドレス>
詳細な設定方法はドキュメントをご確認ください。
https://docs.fastlane.tools/best-practices/continuous-integration/#application-specific-passwords
fastlane/spaceship at master · fastlane/fastlane · GitHub
GitHub Actions特有の罠
一通りビルドが終わった後に Running script '[CP] Embed Pods Frameworks'
というログの後で動かなくなるという事象がありました。
具体的な原因は分かりませんが、setup_ci を 仮に "travis" として設定すれば良いようです。公式でGitHub Actionsに対応するのを期待する。
setup_ci( force: true, provider: "travis", )
最終的な 設定ファイル(fastfile)
最終的には fastfile
は下記のようになりました。大変だったわりにはシンプルな感じにはなりました。今回はビルド番号を自動でインクリメントするとか、Slack通知をするといった設定は割愛します。
default_platform(:ios) setup_ci( force: true, provider: "travis" ) platform :ios do desc "Push a new release build to the App Store" lane :release do sync_code_signing( git_url: "<fastlane matchの設定で作成したリポジトリ>", git_basic_authorization: ENV["MATCH_GIT_BASIC_AUTHORIZATION"], type: "appstore", readonly: true ) update_code_signing_settings( code_sign_identity: "iPhone Distribution", profile_name: "match AppStore XXXX" ) gym( workspace: "XXXX.xcworkspace", configuration: "Release", scheme: "XXXX", export_method: "app-store", ) deliver( force: true, skip_metadata: true, skip_screenshots: true ) end end
おわりに
分かってしまえばなんて事ない手順なんですが、証明書とか二段階認証を突破するのにかなり苦労しました。試行錯誤しているうちに無料枠も超えてしまったので課金しました。使用量の75% → 90% → 100% とメール通知がくるので、 どのくらい使ったかは分かりやすいですね。個人開発だし手動で良いのでは…と心がおれそうになりました。こんなにハマると思ってなかった