Edited at

AndroidプロジェクトのCI状況を晒す

More than 1 year has passed since last update.

やっと半年ほど前から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へのプッシュを行う


全体フロー



  1. feature/.*ブランチで開発を行いdevelopにプルリク

  2. developアプリがデプロイされる

  3. 開発が終わったらrelease/.*ブランチを作成する。

  4. stagingアプリがデプロイされる

  5. stagingのリグレッションテストが一通り終わる


  6. release/.*ブランチをmasterにマージする


  7. builds/androidtag/.*タグがアプリのバージョンなどの情報を元に生成される

  8. タグが出来たので、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個追加すれば簡単に切り替え出来る