アプリ開発者は、改善を意識しアプリ開発を行なっております。多くの開発者は既存アプリに新規機能追加をすることになり、運が良い開発者であれば新規の開発に携われるかもしれません。いずれにせよ、新しい開発者をオンボーディングしたり、新規プロジェクトを立ち上げることは難しいことではあります。もし慎重に行われなければ技術的負債が開発者、マネジャー、セールスおよび(最も重要)クライアントの悩みの種にになるかもしれません。
Git-flow: 簡単な概要
Git-flow は、アジャイルだけでなく従来のウォーターフォールのプロジェクトでも使われています。
継続的インテグレーション/継続的デリバリーの実施を始める前に、git branching modelを決めることが重要です。CI/CD ツールを利用することはみなさんの開発ワークフローと似ているはずです。
Git branching model
この記事はモバイルアプリケーションに焦点を当てています。従って、今回は、その目的を果たすために、以下のgit branching modelを使用します。
Git-flow は本質的に merge-based の解決策です。それはブランチを rebase しません。また、利用するタグ名のフォーマットを『vX.Y.Z-beta-N』にして changelog 自動化するユーティリティを使うともっと便利になります。
その最も純粋な形の Github-Flow と Git-Flow は、活発な開発ワークフローを採用することを手助けできていません。 Github の現代の傾向は、通常、最低 2 つの branch ー develop、および master を持っているようです。develop はいつもアクティブな開発で、master は最新のリリースをタグを付けてを保持しています。
Git-flow(GitHub-flow ひねりによる)は、CI と CD の具体的な解決策を見つけやすくしてくれます。もちろん、チームサイズによって違う流れを選べますが、、どうしてもそのフローは上記のGit Branching Modelとも似ているはずです。
Implementing Continous Integration & Delivery
定義上、、共有リポジトリにコード変更を統合する、単一のプロジェクトで共同作業する開発者によって採用される自動化されたプロセスまたはプラクティスです。そんな自動化処理は、テストコード、パッケージ化、および本番環境への配信とデプロイなどのステップで構成されています。
開発者はプロダクト開発中、QA チームまたはクライアントが機能をチェックしてテストするために、新しい IPA ファイルまたは APK ファイルをしばしば要求されます。手動で毎回 IPA/APK をビルドして別のチームへ渡すのは効率的ではありません。こっちで CI/CD を導入して開発関係ない作業(例:ユニットテストし、バイナリを配布)をかなり自動化して開発者は肩の荷を下ろすことができます。
Advantages of CI/CD
Git-flow と結合された CI/CD は、たとえば以下のような多くの利点があります:
- 自動化されたビルドをテストチームへ配信して、開発者の時間を節約する
- 保証されている純粋なビルドによるビルドの不整合(主に、ローカルなキャッシュのため起こされる)を除去する
- オープンなコミュニケーションおよび自由な情報の流れを促進することにより、チームのエンゲージメントを高いレベルに引き上げる
- チームメンバー間の知識共有を促進することにより、開発者の依存を減らす
- コードをマージする時の、開発者の自信を改善する
- 手動処理が原因で起こるかもしれないバグを排除する
小さいチームまたは個々の開発者が、オーバーヘッドを追加して CI を採用することにはデメリットがあるかもしれません。しかし、多くの人々と複数のプロジェクトに取り組んでいる開発者のために、CI は時間とお金についての非常に高いリターンを持つ賢明な投資となります。
Fastlane
チームのスキルによってShell Script
、Ruby
、Python
など利用してアプリビルドを実施できます。しかし、Fastlane (Ruby を元にしたツールだ!) は、CI/CD を実施するための一番おすすめのツールです。歴史的には、Fabric の一部になり、その後 2017 年に Google に買収されました。Fastlane のコミュニティは盛り上がり、ビジネスケースに合わせるにたくさんのオープンソースプラグインを見つけることができます。
Monstarlab では、私達は、私達の CI/CD 解決策を実施するために、Fastlane ツールを選びました。それはRuby
のいくらかの知識を必要としています。ただ、そのエキスパートである必要はないです。
Fastlane が初めての方は、この資料を参照してください。iOS と Android 両方の適度によいドキュメンテーションがあります。ただし、StackOverflow
のようなフォーラムで検索しても、いくつかの Android fastlane action の詳細と関連するものを見つけることに苦労しました。
Modeling Git Branching Model to Fastlane
普通に Git Branching Model のそれぞれの主要な branch は、下のテーブルに例示するように環境と一致しています:
Environment | Alias/Tag | Branch | Description |
---|---|---|---|
Development | alpha | develop | 次または遠方のリリースのすべての機能は通常、この環境で開発されています |
Staging | beta | release/ or hotfix/ | Pre-release 環境. |
Production | prod | master | master で作成されたビルドは常にこの環境を使用します。 |
より複雑なフローは環境分離の追加のレイヤーを持つこともできます。例えば、以下のように環境を分類できます:
# Fastfile
ALIAS_MAP = {
'alpha' => 'Develop',
'beta' => 'Staging',
'prod'=> 'Production'
}
...
desc 'Build alpha IPA'
lane :alpha do |options|
build_app # This will be replaced with custom `build_deploy` private lane later
end
desc 'Build beta IPA'
lane :beta do |options|
build_app # This will be replaced with custom `build_deploy` private lane later
end
desc 'Build production ipa'
lane :prod do |options|
build_app # This will be replaced with custom `build_deploy` private lane later
end
...
ℹ️ CI の YAML ファイルからコマンドライン引数を渡すために lane options
パラメータを使用する場合があります。
以下の通り、 build_app
アクションの構成をlane
ごとに Gymfile
に入れることも可能です。
# Gymfile
for_platform :ios do
include_bitcode true
include_symbols true
for_lane :alpha do
scheme '<YOUR_DEV_SCHEME>'
export_method 'development' # or 'enterprise' for in-house testing
end
for_lane :beta do
scheme '<YOUR_STAGING_SCHEME>'
export_method 'ad-hoc'
end
for_lane :prod do
scheme '<YOUR_PRODUCTION_SCHEME>'
export_method 'app-store' # or 'enterprise' for release
end
end
Gymfile
で利用可能な構成オプションについては、こちらをご参照ください。
Listing 2のコードをGymfile
に書くことで、scheme および export_method のような設定を以下のような build_app のパラメータに設定する必要はないです:
# Now you don't need to set parameters marked with 👈
# since it is handled in Gym file
build_app(
scheme: "Release", # 👈
export_options: { # 👈
method: "app-store" # 👈
}
)
ℹ️ 注:Listing 3 と異なり、bundle id と provisioning profile は xcconfig を使用して設定します。
Firebase App Distribution
ビルドされた IPA バイナリを Firebase に配布するには、Firebase アプリ配布の設定方法に関するこのドキュメントを読むことを強くお勧めします。
Listing 1において、ビルドを実行するために、build_app
を用いしました。こちらの action を別のbuild_deploy
というプライベートlane
に移動すると、3 つの main lane に重複するコードを削除できます。(これを確認するため Listing 7 をご参照ください。)
desc 'Build and deploy ipa'
private_lane :build_deploy do |options|
#1. Check if any new commits since last release
is_releasable = analyze_commits(match: "*#{options[:environment]}*")
unless is_releasable
UI.important "No changes since last one hence skipping build"
next
end
#2. Increment build number
increment_build_number(
build_number: lane_context[SharedValues::RELEASE_NEXT_VERSION] # set a specific number
)
#3. If you can use `match`, you use `match`.
setup_provisioning_profiles
#4. Build deploy
build_app
deploy_app options
end
Step 1. Check if any new commits since the last release
analyze_commits
はsemantic releaseを簡単に実装するのためのサードパーティ fastlane plugin です。このプラグインは最後のリリースから、どれだけの変化があるかがチェックしてくれます。存在する場合ビルドをトリガーしますし、なければ「No changes since the last one hence skipping build」メッセージを投げて停止します。これにより、CI マシンでのビルド時間を節約できます。
Step 2. Increment build number
Marketing Version と Internal Build Version を別個にしておくことができます。例えば、もし Xcode プロジェクトでAGV ツーリング有効にしているならば、increment_build_number
アクションを使うことができます。このアクションでターゲットのビルド番号が自動的に変更されます。
Step 3. Setup Provisioning Profiles
ここの Fastlanematch
コマンドを使うことができます。 もしもmatch
を利用できない( Apple Developer へのアクセスはない)場合、最初に、import_certificate
コマンドを使用して、それからFastlaneCore::ProvisioningProfile.install
を実行することによってそれをインストールします。
Step 4. Build and deploy
Listing 1にあるbuild_app
アクションを利用します。IPA ビルドを完成したら Firebase App Distribution へ配布するため別のプライベートlane
: deploy_app
を呼び出します。
Deploying
alpha
とbeta
は firebase へデプロイします。prod は、App Store connect に手動でアップロードできますし、upload_to_testflight アクションを使って自動化もできます。今回は、説明を簡略化するために、GitHub リリースに IPA ファイルを asset としてアップロードします。
deploy_app
lane は以下の Listing 5 のようになります。このプライベートlane
は 5 つのステップに分割ことができます。
private_lane :deploy_app do |options|
environment = options[:environment]
next if environment == 'prod' # Since `prod` is uploaded to testflight and app store
#1. Generate Change Log
notes = conventional_changelog(title: "Change Log", format: "plain")
#2. Get Google App ID
gsp_path = "SupportingFiles/GoogleService/#{ALIAS_MAP[environment]}-Info.plist"
google_app_id = get_info_plist_value(path: gsp_path, key: 'GOOGLE_APP_ID')
#3. Upload to firebase
firebase_app_distribution(
app: google_app_id,
# groups: tester_group,
release_notes: notes,
firebase_cli_path: FIREBASE_CLI_PATH
)
#4. Upload dSYM files to crashlytics
upload_symbols_to_crashlytics(gsp_path: gsp_path)
clean_build_artifacts
end
Step 1. Generating Change Log:
ログを生成するために、Fastlane のsemantic-releaseプラグインを使用しています。
conventional_changelog
はanalyze_commits
と連携して使われる必要があります(is_releasable
をチェックするために、build_deploy
レーンでconventional_changelog
を使用しました
)。analyze_commits
は、v1.0.1-beta5
のように 1 つ前の git-tag にマッチするためのmatch
regex 引数を渡して使っています。これは、最後のタグ、および現在のv1.0.1-beta6
の間だけのログを生成するのに役立ちます。
Step 2. Get Google App ID
3 つの環境向けに、それぞれのgoogle_app_id
が必要です。これは Firebase から Plist の形で取得できます。サンプルコードプロジェクトは、マルチ configuration の単一ターゲットの Xcode プロジェクトです。GoogleService plists を下記通り名前変えてGoogleService
ディレクトリに移動して整理しておきます。 - GoogleService/Develop-Info.plist - GoogleService/Staging-Info.plist - GoogleService/Production-Info.plist
まずはgsp_path
を取得して、そこからgoogle_app_id
を取ります。
Step 3. Upload to Firebase
firebase_app_distribution
を実行するため、Firebase CLIを環境上でインストールする必要があります。CircleCI のsetup
コマンドをご参照ください。そちらではfirebase-cli
を npm-package としてインストールしておきました。
firebase_app_distribution
で重要な引数はfirebase_cli_path
です。
Step 4. Upload dSYM files to crashlytics
最後に、dSYM
ファイルを Firebase へアップロードします。もしアプリがクラッシュしてしまった場合、再起動の時に firebase から送信されるレポートを、シンボルを解除して読みやすくするためです。
Note:
Xcode プロジェクトで bit code が有効になっている場合、App Store はコードを再コンパイルし、dSYM ファイルを提供します。 クラッシュレポートのシンボルを解除するには、そのファイルをダウンロードして Firebase Crashlytics にアップロードする必要があります。 したがって、製品版の場合のみ、この手順を実行する必要があります。 ** Fastlane の download_dsym
アクションをご参照ください。**
Github Release
release_on_github
はプライベートレーンであり、コミットに自動的にタグを付け、リリースノートを追加し、本番用の IPA ファイルを添付します。IPA ファイルは後で App StoreConnect にアップロードできます。
Firebase App Distribution には IPA ファイルをダウンロードするための API がないため、デバイスのみにインストールできます。deploygate
ような他のベータ版配信サービスを使用してクライアントに beta
リリースを提供したい場合、GitHub release にプレリリースとして IPA ファイルを保存したいかもしれません。
Uploading IPA as an asset
desc "Release on github"
private_lane :release_on_github do |options|
environment = options[:environment]
#1. Generate Change Log
notes = conventional_changelog(title: "Change Log")
#2. Get Version and Build Number
version_number = get_version_number
build_number = get_build_number
#3. Set Github Release
is_prerelease = environment == 'beta' ? true : false
name = "[#{ALIAS_MAP[environment]}] v#{version_number} Build: #{build_number}}"
set_github_release(
repository_name: "#{ENV['CIRCLE_PROJECT_USERNAME']}/#{ENV['CIRCLE_PROJECT_REPONAME']}",
name: name,
commitish: ENV['CIRCLE_SHA1'],
description: notes,
tag_name: "v#{version_number}-#{options[:environment]}-#{build_number}",
is_prerelease: is_prerelease,
upload_assets: [lane_context[SharedValues::IPA_OUTPUT_PATH]]
)
end
Step 1. Generate Change Log
conventional_changelog
はデフォルトのマークダウンスタイルのメモを自動生成してくれます。これは、自動リリースノートを作成するときに便利です。
Step 2. Get Version and Build Number
これらを使用して、リリースのタイトルを設定します。 get_version_number
と get_build_number
を使用するには、Xcode プロジェクトで AGV ツールを有効にする必要があります。
Step 3. Set Github Release
ベータ版がプレリリースとしてマークされているかどうかを確認します。環境に基づいて、リリースノートの名前/タイトルをフォーマットします。タグ名を v1.0.1-beta-1234
のような形式で設定し、ビルドされた IPA ファイルをアセットとしてリリースにアップロードします。
ℹ️ 注:環境では、個人トークンまたは CI 用 BOT のトークンを GITHUB_API_TOKEN
に設定する必要があります。 このトークンには、タグを作成する権限が必要です。
Revising Main Lanes in Listing 1
Github への「alpha」開発中に高頻度で頻繁に発生し、Firebase に保存されるため、スキップします。 これにより、GitHub のスペースを節約できます。
# Fastfile
ALIAS_MAP = {
'alpha' => 'Develop',
'beta' => 'Staging',
'prod'=> 'Production'
}
...
desc 'Build alpha IPA'
lane :alpha do |options|
build_deploy options
# Not releasing to Github since Firebase App Distribution
end
desc 'Build beta ipa'
lane :beta do |options|
build_deploy options
release_on_github options
end
desc 'Build production ipa'
lane :prod do |options|
build_deploy options
release_on_github options
end
...
Matching up CircleCI configuration with Fastlane
CircleCI はシンプルな YAML
config.yml の設定に従って、プルリクエストがマージされるか、コードがブランチにプッシュされると、config.yml の設定に従って、CircleCI ワークフローがトリガーされ、Fastlane で特定のレーンが起動されます。
Table 1の alias 列にある alpha
、beta
、および prod
と呼ばれる 3 つの環境引数があることに注意してください。以下の yaml ファイルのスニペットをご覧ください。
---
jobs:
deploy:
executor:
name: default
parameters:
build_type:
type: enum
enum: ["alpha", "beta", "prod"] # Corresponds to lanes
default: alpha
steps:
- attach_workspace:
at: /Users/distiller/project
- run:
name: Build ipa
command: bundle exec fastlane ios << parameters.build_type >>
- store_artifacts:
path: output
when: on_success
以下も同じ yaml ファイルのスニペットです。 ここで、 setup
は、rubygems、npm モジュール、cocoapod、firebase cli、carthage などの必要な依存関係をインストールします。
---
workflows:
main:
jobs:
- setup
- test:
requires:
- setup
- deploy:
name: build_deploy_alpha
build_type: alpha
requires:
- setup
filters:
branches:
only:
- develop # RegEx
- deploy:
name: build_deploy_beta
build_type: beta
requires:
- setup
filters:
branches:
only:
- /release\/.*/ # RegEx
- /hotfix\/.*/ # RegEx
- deploy:
name: build_deploy_prod
build_type: prod
requires:
- setup
filters:
branches:
only:
- master # RegEx
各ワークフローのフィルターにも特に注意してください。 develop
、/release\/.*/
、/hotfix\/.*/
、master
といった**正規表現が含まれています。**
Push On Branch | Lane executed | Environment |
---|---|---|
develop | bundle exec fastlane ios alpha | Development |
release/* & hotfix/* | bundle exec fastlane ios beta | Staging |
master | bundle exec fastlane ios prod | Production |
上記のスニペットによって、git ブランチモデル が、CircleCI YAML ファイルの構成と Fastlane 構成ファイルに、どのように反映されるかが理解いただけたかと思います。CircleCI Yaml の詳細については、こちらをご覧ください。
Conclusion
効果的な開発ワークフローと CI-CD の実装は、開発の時間を大幅に削減するのに役立ちます。 同様に、QA チームはバグや問題を特定のビルドにリンクすることができ、開発者とより生産的な会話をすることができるようになります。 特に、新しい開発者の立ち上がりまでの時間も大幅に短縮されます。
Resources
Sample Code Fastlane Tools Docs Firebase App Distribution Git Branching Model by Vincent Driessen AGV tooling enabled
Article Photo by Rasa Kasparaviciene