やっと半年ほど前からfastlaneとCicle CIを使い初めた。
自身が書いたビルドフローを晒すことによって誰かに突っついてもらえたらラッキーというモチベーションで晒してみる。
ビルドに関わるサードパーティ製品・ライブラリ
- Circle CI Enterprise
- Github Enterprise
- Crashlytics Beta
- fastlane(bundlerからフック)
- Gradleスクリプト
circle.yml
machine:
timezone: Asia/Tokyo
java:
version: oraclejdk8
environment:
JAVA_OPTS: "-Xms256m -Xmx1024m -XX:MaxPermSize=512m"
ADB_INSTALL_TIMEOUT: 60
dependencies:
cache_directories:
- ~/.android
- /usr/local/android-sdk-linux
pre:
- mkdir -p $ANDROID_HOME/licenses
- cp -f ./android-sdk-license $ANDROID_HOME/licenses
- echo y | android update sdk --no-ui --all --filter "platform-tools, tools"
- echo y | android update sdk --no-ui --all --filter "android-25, build-tools-25.0.2"
- echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"
- echo y | android update sdk --no-ui --all --filter "extra-android-support"
- echo y | android update sdk --no-ui --all --filter "extra-google-m2repository"
test:
override:
- ./gradlew testDevelopmentDebugUnitTest -PdisablePreDex -PdisableDevMinSdk
- ./gradlew testDevelopmentReleaseUnitTest -PdisabltePreDex
deployment:
deliver-develop:
branch: develop
commands:
- bundle exec fastlane dev type:Debug
- cp -r app/build/outputs $CIRCLE_ARTIFACTS
deliver-staging:
branch: /release\/.*/
commands:
- bundle exec fastlane stg
- cp -r app/build/outputs $CIRCLE_ARTIFACTS
create-tag:
branch: master
commands:
- ./gradlew assembleStandbyRelease -PdisabltePreDex -PdisableDevMinSdk
- ./gradlew assembleProductionRelease -PdisabltePreDex -PdisableDevMinSdk
- ./gradlew assemblePlaystoreRelease -PdisabltePreDex -PdisableDevMinSdk
- git fetch origin --prune 'refs/tags/*:refs/tags/*'
- git config user.email "circleci@"
- git config user.name "CircleCI bot"
- bundle exec fastlane tag
deliver-production:
tag: /builds\/androidtag\/.*/
commands:
- bundle exec fastlane stby # publish to crashlytics beta
- bundle exec fastlane prd # publish to crashlytics beta
- bundle exec fastlane store # make archive apk for PlayStore
- cp -r app/build/outputs $CIRCLE_ARTIFACTS
環境(Product Flavor)
- development
- staging
- standby
- production
- store
standby → プロジェクトがBlueGreenのため、待機しているサーバーにアクセスできるアプリを用意している。
production → ストアにリリースする前に触るアプリ。コンテンツを公開用アプリと同様のサーバーに向けて見れるようにしている。デバッグログなどが取れるもの
store → 公開用アプリ。
↑は各々のプロジェクトで変わると思いますが、ほとんどのプロジェクトではdev, stg, storeは最低でもあると思います。
ビルドのピタゴラスイッチ
Circle CIのブランチにマッチさせてビルドルールを変えるという概念はシンプルで
git-flowのようなブランチ名がルール化されている業務フローととても相性がいいです。
ブランチ名の条件には、正規表現が使えるので柔軟に分岐させることが出来ます。
ビルドルールリスト
ビルドルール | ビルド |
---|---|
branch: develop |
developブランチに変更があればビルドを行う。develop向けのアプリをCrashlyticsにデプロイする |
branch: /release\/.*/ |
release/ をプレフィックスとするブランチに変更があればビルドを行う。staging向けのアプリをCrashlyticsにデプロイする |
branch: master |
masterブランチに変更があればビルドを行う。タグ作成とGHEへのプッシュを行う |
tag: /builds\/androidtag\/.*/ |
builds/androidtag/ をプレフィックスとするタグが作成されたらビルドを行う。タグ作成とGHEへのプッシュを行う |
全体フロー
-
feature/.*
ブランチで開発を行いdevelopにプルリク - developアプリがデプロイされる
- 開発が終わったら
release/.*
ブランチを作成する。 - stagingアプリがデプロイされる
- stagingのリグレッションテストが一通り終わる
-
release/.*
ブランチをmasterにマージする -
builds/androidtag/.*
タグがアプリのバージョンなどの情報を元に生成される - タグが出来たので、standby, productionがデプロイ、storeアプリがアーカイブされる
ということで、ブランチの操作をするだけで本番アプリができるようになっています。
Fastfile
途中からFastfileが肥大化してしまい、見通しが悪くなったため、Fastfileを役割毎に切り出しています。
ファイル | 役割 |
---|---|
Env | SlackのWebhookURL, テスターグループ, CrashlyticsのAPIトークンをオブジェクトとして定義しておく |
App | アプリのビルドlaneを定義 |
Release | バージョンのbumpupスクリプトをフックするlaneを定義 |
Fastfile | フックされるファイル。Envをload, AppとReleaseをimportする。before_all , after_all , error のハンドリングを共通で定義 |
依存関係はこんな感じです
Env
↓オブジェクトのload
Fastfile
↑laneのimport
[App, Release]
下記に示すのはApp
の中身です。
=begin
# Lane for build application
=end
class AppContext
@@flavor_hash = {
'store' => 'playstore',
'stby' => 'standby',
'prd' => 'production',
'stg' => 'staging',
'dev' => 'development'
}
def self.validate_env(env)
unless @@flavor_hash.has_key?(env)
raise [
"Invalid environment: #{env}\n",
"Valid environment is one of them: #{@@flavor_hash.keys}",
].join("\n")
end
end
def initialize(env, build_type)
AppContext.validate_env(env)
@env = env
@build_type = build_type
end
def task
'assemble'
end
def flavor
@@flavor_hash[@env]
end
def apk_path
"app/build/outputs/apk/app-#{flavor}-#{@build_type.downcase}.apk"
end
def build_type
@build_type
end
end
platform :android do
#=Test
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
#=Build apps each environment
desc "Build store app and create apk"
lane :store do |options|
options[:env] = 'store'
beta(options)
end
desc "Build stby app, create apk and deploy to beta"
lane :stby do |options|
options[:env] = 'stby'
beta(options)
end
desc "Build prd app, create apk and deploy to beta"
lane :prd do |options|
options[:env] = 'prd'
beta(options)
end
desc "Build stg app, create apk and deploy to beta"
lane :stg do |options|
options[:env] = 'stg'
beta(options)
end
desc "Build dev app, create apk and deploy to beta"
lane :dev do |options|
options[:env] = 'dev'
beta(options)
end
#=Actual build operation
desc "Build app for current env, create apk and deploy to beta"
private_lane :beta do |options|
archive(options)
crashlytics(groups: Fabric.tester_group)
end
desc "Build app for current env create apk"
private_lane :archive do |options|
env = options.fetch(:env, 'dev')
build_type = options.fetch(:type, 'Release')
begin
AppContext.validate_env(env)
rescue => e
UI.user_error!(e.message)
Kernel.abort
end
context = AppContext.new(env, build_type)
gradle(
task: context.task,
flavor: context.flavor,
build_type: context.build_type,
flags: "-PdisablePreDex -PdisableDevMinSdk"
)
copy_artifacts(
target_path: "artifacts",
artifacts: [context.apk_path]
)
end
end
# vim: ft=ruby sw=2 ts=2 sts=2
普段のビルドはminSDKを21に設定。CIでは16にする
archiveレーンのgradleをフックしている所で以下のフラグを追加しています。
-PdisableDevMinSdk
これはgradleの中で開発用のminSdkをdisableにするというフラグで、自前で定義したフラグです。
このプロジェクトではminSdkが16
なのですが、フラグを立てない時(つまり、Android Studioでのビルド時)にminSdkを21
にすることによって普段のビルドをなるべく高速化させるという試みです。
理由はこちらの2016年、KotlinでAndroid開発する方へで述べられています。
設定を反映するbuild.gradleは以下の通りです。こうすることによってAndroid Studioで開発している時は21, CIではにするというフラグ16、他のBuildVariantsでは16のようなスイッチが実現しています。大まかな実装は21で。互換性の確認は16にして・・・という形で開発しています。
プロジェクトの開発の方針によって変わってくるところなので一概にコレが正解とは言えないでしょう。
def getDevMinSdk() {
return hasProperty('disableDevMinSdk') ? 16 : 21
}
android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
}
productFlavors {
development {
minSdkVersion = devMinSdk
}
}
}
AppContextクラス
gradleをフックするための変数を一括管理しておくクラスを作っています。
また、その変数からビルドのパスが一意に決まるので、文字列の解決をメソッド化しておくことによってprivate_lane :archive
をスッキリ書けるようにしています
まとめ
- circle.ymlはブランチ名の分岐には正規表現で自動化を
- Fastlaneのlaneはrubyで便利に
- Fastfileから切り出してlaneを書いたり、環境変数を定義するとスッキリする
-
minSdk
はフラグ1個追加すれば簡単に切り替え出来る