前回の記事からはや3ヶ月・・・
TravisCIとfastlaneでiOSアプリのCI【途中】
その間何をやっていたかというと、、、特に何もしていませんでした
つい最近またCI熱が高まり、ついに使えるところまで持っていけたので、まとめて記事にします
前回の記事と方針が変わっているところもありますが、それはご愛嬌
この記事で実現できること、前提条件
ちょっと量が多いですが、大事なことなので書いておきます。
特にビルドコンフィギュレーションとプロビジョニングファイルについては把握しておかないと、CI上でアーカイブする時にCode Signing Errorで苦しむことになります(僕はなりました)。
AppleアカウントとかバンドルIDとかチームID
**一つしか使いません。**開発環境と本番環境で変えるとか、Developer CenterとiTunesConnectで違うアカウント使うとか、そういったことは対象外になります。
環境
**開発環境と本番環境の2つがあります。**アプリ側はビルドコンフィギュレーション(厳密にはフラグ)でつなぎ先を変えます。
static var domain: String {
#if DEBUG
return "https://開発ドメイン/"
#else
return "https://本番ドメイン/"
#endif
}
ビルドコンフィギュレーション
Xcodeで使用するビルドコンフィギュレーションは以下の4つとします。
|名前|フラグ|環境|プロビジョニングファイル種別|
|:--:|:--:|:--:|:--:|:--:|
|Debug|DEBUG|開発|Development|
|AdHoc_dev|DEBUG|開発|AdHoc|
|AdHoc_dis|なし|本番|AdHoc|
|Release|なし|本番|AppStore|
Gitブランチ
**CIで使用するブランチは以下の3つとします。**それ以外のブランチではCIが走らないようにします。
ブランチ名 | ビルドコンフィギュレーション | 環境 | プロビジョニングファイル種別 | デプロイ先 |
---|---|---|---|---|
deploy_debug | AdHoc_dev | 開発 | AdHoc | DeployGate |
deploy_release | AdHoc_dis | 本番 | AdHoc | DeployGate |
release | Release | 本番 | AppStore | iTunesConnect |
開発者証明書(certificate)
**手動で作ってリポジトリに含めます。**CI上ではリポジトリに含まれる証明書を使います。fastlaneのmatch
やcert
は使いません。
プロビジョニングファイル
**fastlaneのsigh
を使ってダウンロード/インストールします。Xcode上ではmanualで指定します。**XcodeのAutomatically manage signing
はチェックを外します。
TargetのGeneralタブは以下のようになります。
fastlaneの実行
**fastlaneは直で実行します。**bundleは使いません。
メタデータ/スクリーンショットの管理
管理はしません。deliver
でiTunesConnectにアップロードするのはビルドだけです。
デプロイツールと通知ツール
**DeployGateとslackを使います。**その他のツールについては扱いません。
Travis
リポジトリと連携
自身のアカウントページへ行き、下の方にスクロールすると、自分がアクセスできるリポジトリ一覧が表示されます。CIに載せたいリポジトリのスイッチをONにします。そして設定画面へ。
設定
環境変数にFASTLANE_PASSWORD
を追加します。値は使用するAppleアカウントのパスワードです。
後はお好みで。
プロジェクトルートに.travis.yml
を追加
エディタなどでファイルを生成し、プロジェクトルートに配置します。中身は以下の通り。
language: objective-c
osx_image: xcode9.1
notifications:
slack: slackで発行したトークン
cache: cocoapods
branches:
only:
- deploy_debug
- deploy_release
- release
script:
- if [ $TRAVIS_BRANCH == deploy_debug ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane deploy_debug; else true; fi
- if [ $TRAVIS_BRANCH == deploy_release ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane deploy_release; else true; fi
- if [ $TRAVIS_BRANCH == release ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane release; else true; fi
言語とXcodeのバージョン指定
language: objective-c
osx_image: xcode9.1
言語はSwiftであってもobjective-c
と指定します。
xcodeのバージョンは使いたいものを指定してください。
参考: Building an Objective-C or Swift Project
通知設定
notifications:
slack: slackで発行したトークン
結果をslackで通知したいので、トークンを設定します。
参考: Configuring Build Notifications
キャッシュ
cache: cocoapods
TravisはルートディレクトリにPodfileがある場合、自動でpod installを実行してくれます。
参考: [Building an Objective-C or Swift Project(Dependency Management)](Building an Objective-C or Swift Projecthttps://docs.travis-ci.com/user/languages/objective-c/#Dependency-Management)
ですが、毎回pod install
されると時間かかるのでキャッシュさせます。
参考: Caching Dependencies and Directories
ブランチを制限
branches:
only:
- deploy_debug
- deploy_release
- release
今回CIを走らせたいブランチは3つだけなので、それらを指定します。
参考: Customizing the Build(Building Specific Branches)
fastlaneを実行
script:
- if [ $TRAVIS_BRANCH == deploy_debug ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane deploy_debug; else true; fi
- if [ $TRAVIS_BRANCH == deploy_release ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane deploy_release; else true; fi
- if [ $TRAVIS_BRANCH == release ] && [ $TRAVIS_PULL_REQUEST == false ]; then fastlane release; else true; fi
ブランチに応じてfastlaneのlane
(後述)を実行します。
$TRAVIS_BRANCH
と$TRAVIS_PULL_REQUEST
はTravisの環境変数で、それぞれブランチ名とPRか否かを取得できます。
参考: Environment Variables
fastlane
init
を実行
プロジェクトルートでfastlane init
を実行します。
使うアカウントやプロジェクトによって若干の違いはありますが、ガイドに従ってアカウントやプロジェクトファイルorワークスペース、スキームの設定をします。deliver
でメタデータやスクリーンショットを管理するための処理も入ってしまいますが、今回は使いません。
$ fastlane init
[13:56:15]: Get started using a Gemfile for fastlane https://docs.fastlane.tools/getting-started/ios/setup/#use-a-gemfile
[13:56:18]: Detected iOS/Mac project in current directory...
[13:56:18]: This setup will help you get up and running in no time.
[13:56:18]: fastlane will check what tools you're already using and set up
[13:56:18]: the tool automatically for you. Have fun!
[13:56:18]: $ xcodebuild -list -workspace ./app.xcworkspace
...
Select Scheme:
1. hoge
2. fuga
3. Alamofire
5. Pods-hoge
? 1 // スキームが複数ある場合は選ぶ
[13:56:21]: $ xcodebuild -showBuildSettings -workspace ./app.xcworkspace -scheme hoge
...
[13:56:23]: Your Apple ID (e.g. fastlane@krausefx.com): some@account.com // IDを入力
[13:56:29]: Verifying that app is available on the Apple Developer Portal and iTunes Connect...
[13:56:29]: Starting login with user 'アカウントID'
+----------------+--------------------------------------+
| Detected Values |
+----------------+--------------------------------------+
| Apple ID | アカウントID |
| App Name | アプリ名 |
| App Identifier | バンドルID |
| Workspace | パス |
+----------------+--------------------------------------+
[13:56:40]: Note: If the values above are incorrect, it is possible the wrong scheme was selected
[13:56:40]: Please confirm the above values (y/n)
y // 問題無ければy
[13:56:49]: Created new file './fastlane/Appfile'. Edit it to manage your preferred app metadata information.
[13:56:49]: Loading up 'deliver', this might take a few seconds
[13:56:49]: Login to iTunes Connect (アカウントID)
[13:56:52]: Login successful
+--------------------------------------+------------------------+
| deliver 2.66.0 Summary |
+--------------------------------------+------------------------+
| run_precheck_before_submit | false |
| screenshots_path | ./fastlane/screenshots |
| metadata_path | ./fastlane/metadata |
| username | アカウントID |
| app_identifier | バンドルID |
| edit_live | false |
| platform | ios |
| skip_binary_upload | false |
| skip_screenshots | false |
| skip_metadata | false |
| skip_app_version_update | false |
| force | false |
| submit_for_review | false |
| automatic_release | false |
| dev_portal_team_id | チームID |
| overwrite_screenshots | false |
| precheck_default_rule_level | warn |
| ignore_language_directory_validatio | false |
| n | |
| precheck_include_in_app_purchases | true |
+--------------------------------------+------------------------+
...
[13:59:26]: Successfully downloaded all existing screenshots
[13:59:26]: Successfully created new Deliverfile at path './fastlane/Deliverfile'
[13:59:26]: 'snapshot' not enabled.
[13:59:26]: 'cocoapods' enabled.
[13:59:26]: 'carthage' not enabled.
[13:59:26]: Created new file './fastlane/Fastfile'. Edit it to manage your own deployment lanes.
[13:59:26]: fastlane will collect the number of errors for each action to detect integration issues
[13:59:26]: No sensitive/private information will be uploaded
[13:59:26]: Learn more at https://github.com/fastlane/fastlane#metrics
[13:59:26]: Successfully finished setting up fastlane
Appfile
fastlane init
で自動生成されます。アカウント情報を正しく入力できていれば変更する必要はありません。もし間違っている場合は手動で正しいIDを入力してください。
Fastfile
fastlane init
で自動生成されます。fastlaneが実行する処理をlaneという単位で記述します。
中身は以下の通り。
fastlane_version "2.61.0"
default_platform :ios
platform :ios do
BUNDLE_ID = "バンドルID"
SCHEME = "スキーム"
PLIST_PATH = "info.plistのパス"
VERSION = get_info_plist_value(path: PLIST_PATH, key: "CFBundleShortVersionString")
BUILD = get_info_plist_value(path: PLIST_PATH, key: "CFBundleVersion")
DEPLOYGATE_API_TOKEN = "APIトークン"
DEPLOYGATE_USER = "ユーザ/グループ名"
before_all do
ENV["SLACK_URL"] = "Incoming WebHooksのURL"
end
desc "archive release and upload to iTunesConnect"
lane :release do
import_cert
sigh
gym(
scheme: SCHEME,
)
deliver(
skip_screenshots: true,
skip_metadata: true,
)
release_tag
end
desc "archive release and deploy by DeployGate"
lane :deploy_release do
import_cert
sigh(
adhoc: true,
)
gym(
scheme: SCHEME,
configuration: "AdHoc_dis",
)
message = "本番環境 v#{VERSION}(#{BUILD})"
deploygate(
api_token: DEPLOYGATE_API_TOKEN,
user: DEPLOYGATE_USER,
message: message,
)
slack(
message: message
)
end
desc "archive debug and deploy by DeployGate"
lane :deploy_debug do
import_cert
sigh(
adhoc: true,
)
gym(
scheme: SCHEME,
configuration: "AdHoc_dev",
)
message = "開発環境 v#{VERSION}(#{BUILD})"
deploygate(
api_token: DEPLOYGATE_API_TOKEN,
user: DEPLOYGATE_USER,
message: message,
)
slack(
message: message
)
end
desc "add release tag"
private_lane :release_tag do |tag|
add_git_tag(
tag: "v#{VERSION}",
force: true,
)
push_git_tags(
force: true
)
end
desc "import cert"
private_lane :import_cert do
next unless Helper.is_ci?
KEYCHAIN_NAME = "キーチェーン名.keychain"
KEYCHAIN_PASSWORD = "パスワード"
create_keychain(
name: KEYCHAIN_NAME,
password: KEYCHAIN_PASSWORD,
default_keychain: true,
unlock: true,
timeout: 3600,
)
import_certificate(
certificate_path: "./certs/distribution.p12",
keychain_name: KEYCHAIN_NAME,
keychain_password: KEYCHAIN_PASSWORD,
)
end
error do |lane, exception|
next unless Helper.is_ci?
slack(
message: exception.message,
success: false
)
end
end
おやくそく
fastlane_version "2.61.0"
default_platform :ios
platform :ios do
....
end
バージョン、プラットフォームの指定を書きます。
使いまわす変数の宣言
BUNDLE_ID = "バンドルID"
SCHEME = "スキーム"
PLIST_PATH = "info.plistのパス"
VERSION = get_info_plist_value(path: PLIST_PATH, key: "CFBundleShortVersionString")
BUILD = get_info_plist_value(path: PLIST_PATH, key: "CFBundleVersion")
DEPLOYGATE_API_TOKEN = "APIトークン"
DEPLOYGATE_USER = "ユーザ/グループ名"
複数のlaneで使う変数は予め定義しておくと便利です。
get_info_plist_value
は名前の通りinfo.plist
の値を取得できる関数です。書いていて思いましたがバンドルIDもこれで取得できますね。。。
全てのlaneの前に実行される処理
before_all do
ENV["SLACK_URL"] = "Incoming WebHooksのURL"
end
fastlaneの結果もslackに流したいので、URLをセットしておきます。
エラー時の処理
error do |lane, exception|
next unless Helper.is_ci?
slack(
message: exception.message,
success: false
)
end
エラーが起きたときはその内容をslackに流します。
ただし、ローカルでfastlaneを実行する時はすぐにログを見れるので、CIでの実行時のみslackに通知します。
Helper.is_ci?
はCIでの実行か否かを返すアクションです。
キーチェンの生成と開発者証明書の取り込み
desc "import cert"
private_lane :import_cert do
next unless Helper.is_ci?
KEYCHAIN_NAME = "キーチェーン名.keychain"
KEYCHAIN_PASSWORD = "パスワード"
create_keychain(
name: KEYCHAIN_NAME,
password: KEYCHAIN_PASSWORD,
default_keychain: true,
unlock: true,
timeout: 3600,
)
import_certificate(
certificate_path: "./certs/distribution.p12",
keychain_name: KEYCHAIN_NAME,
keychain_password: KEYCHAIN_PASSWORD,
)
end
CI環境は開発者証明書が使える状態になっていないので、キーチェーンの生成とそのキーチェーンへ開発者証明書を取り込んでやります。
private_lane
は外部からの呼び出し不可なlaneの宣言です。このlaneは他のlaneから呼び出して使うのでprivateにしています。
create_keychain
はキーチェーンを生成するアクションです。オプションでunlock: true
を渡してやらないと、CIで実行した時にキーチェーンへのアクセス許可を求めるポップアップが出てしまい、タイムアウトしてしまいます。
import_certificate
はキーチェーンに開発者証明書を取り込むアクションです。上の例は証明書にパスワードを設定していないのですが、もしパスワードを設定している場合はcertificate_password
というオプションでパスワードを渡してやる必要があります。
ローカルでは既にキーチェーンに開発者証明書が取り込まれている前提なので、この処理は実行しません。
参考:
create_keychain
import_certificate
開発環境ビルドの配布
desc "archive debug and deploy by DeployGate"
lane :deploy_debug do
import_cert
sigh(
adhoc: true,
)
gym(
scheme: SCHEME,
configuration: "AdHoc_dev",
)
message = "開発環境 v#{VERSION}(#{BUILD})"
deploygate(
api_token: DEPLOYGATE_API_TOKEN,
user: DEPLOYGATE_USER,
message: message,
)
slack(
message: message
)
end
以下の流れになります。
- 開発者証明書の取り込み(import_cert)
- プロビジョニングファイルのダウンロード/インストール(sigh)
- ipaファイル生成(gym)
- DeployGateで配信(deploygate)
- slackに通知(slack)
import_cert
は上で定義したprivate_laneです。キーチェーンの生成と証明書の取り込みを行います。
sigh
はプロビジョニングファイルのダウンロード/インストールを行うアクションです。fastlane init
で入力したアカウント/バンドルIDに紐づくプロビジョニングファイルを、無ければ作成し、あればそれを取得します。デフォルトでAppStore
のプロビジョニングファイルを取得しようとするので、adhoc: true
をつけて、AdHocのものを取得させます。
gym
はipaを生成するアクションです。fastlane init
時に設定したプロジェクトorワークスペースとスキームでアーカイブします。そのはずなんですが、何故かスキームをうまく見つけてくれなかったので、scheme
オプションで指定しています。CIがスキームを見つけられるように、スキームはsharedにしておく必要があります。デフォルトのビルドコンフィギュレーションはRelease
です。今回はAdHoc_dev
でアーカイブさせたいので、configuration
オプションで指定します。
deploygate
はDeployGateにデプロイするアクションです。api_token
,user
,message
をオプションで指定します。
いずれかのアクションでエラーが発生すると、そこでlaneは中断され、error
が呼び出されます。
参考:
sigh
gym
deploygate
本番環境ビルドの配布
desc "archive release and deploy by DeployGate"
lane :deploy_release do
import_cert
sigh(
adhoc: true,
)
gym(
scheme: SCHEME,
configuration: "AdHoc_dis",
)
message = "本番環境 v#{VERSION}(#{BUILD})"
deploygate(
api_token: DEPLOYGATE_API_TOKEN,
user: DEPLOYGATE_USER,
message: message,
)
slack(
message: message
)
end
ほとんど開発環境ビルドの配布と同じです。違うのはアーカイブのときのコンフィギュレーションがAdHoc_dis
であることと、メッセージのみです。
リリースビルドのアップロード
desc "archive release and upload to iTunesConnect"
lane :release do
import_cert
sigh
gym(
scheme: SCHEME,
)
deliver(
skip_screenshots: true,
skip_metadata: true,
)
release_tag
end
desc "add release tag"
private_lane :release_tag do |tag|
add_git_tag(
tag: "v#{VERSION}",
force: true,
)
push_git_tags(
force: true
)
end
アーカイブまでは上の2つとほとんど同じです。今回はAppStore
のプロビジョニングファイルが必要なので、sigh
のadhoc
オプションはつけません。
deliver
はiTunesConnectにアップロードするためのアクションです。スクリーンショットやメタデータもアップロードできるのですが、今回はビルドのアップロードのみ使用したいので、その他の機能はオプションで無効化します。
release_tag
はタグ付けしてpush
するlaneです。githubにpush
するので、権限がある鍵をTravisに登録しておく必要があります。
参考:
deliver
add_git_tag
push_git_tags
おわりに
今まで手動でアーカイブしていたものが、特定のブランチにプッシュしたらそのうちDeployGateで配信されたり、iTunesConnectにアップロードされるというのは、非常に感動します
ただ、割とビルドに時間がかかるみたいなので、もっとビルド時間を短縮できたらなと思います
証明書とプロビジョニングファイルはmatch
を使うともう少しラクになりそうなので、そちらも挑戦してみたいです