はじめに
皆様こんにちは!ラクマでAndroidアプリの開発をしている @tarumzu です。
ラクマAndroidチームでは、開発に専念できるように自動化出来るところは可能な限り自動化しよう!を合言葉に様々なタスクをGithub ActionsやBitriseを使って自動化しています。本章ではそれら自動化施策をご紹介します。
自動化施策一覧
まず、どのようなことを行ってきたのかを表にしました。
表の 本章の対象 に○がついてある項目について順番に説明していきます。また、本章で紹介する施策はすべてGithub Actionsを利用したものになります。
※ 「Firebase App Distribution、Google Playへのaabアップロード」に関してはRAKUMA TECH BOOK vol1 「ラクマのiOSアプリを支えるCI/CD環境」で詳細に説明しています。基本的流れは同様のため本章では説明省きます。気になる方はぜひそちら読んでみてください。
トリガー | タスク | 本章の対象 |
---|---|---|
プルリクエストの作成・更新 | ・ktlint実行 ・AndroidLint実行 ・unit test実行 ・オープンソースライセンスチェック ・Milestoneが設定されているかチェック |
○ |
プルリクエストのオープン | ・Assignees設定 | ○ |
release, hotfixブランチの作成・更新 | ・master, developブランチへのプルリクエストを作成 (Git-flowで運用しているため) |
○ |
release, hotfixブランチを作成 | ・ブランチ名のバージョンからVersionNameを更新するプルリクを作成する | |
releaseブランチに対してのプルリクエストマージ | ・紐付けられたGithub Issuesをクローズ (Issuesに対するCloseキーワードはデフォルトブランチに対してのみ動作するため、デフォルトブランチ以外でもクローズできるようにしている) |
○ |
develop, master, releaseブランチへのマージ | ・ktlint実行 ・AndroidLint実行 ・unit test実行 |
|
SlackからBitriseワークフロー実行コマンドを叩く | ・Firebase App Distribution、Google Playへのaabアップロード |
自動化施策
プルリクエストの作成・更新時
ここでは一つのワークフローの中でktlint実行 、AndroidLint実行 、unit test実行、オープンソースライセンスの表記漏れがチェックを行っているのですが長いので各Jobに分けて説明します。
ktlint
まずは皆さんよく使っているであろうktlintです。プルリクエストを作成したらコードスタイルのチェックを行い、違反している箇所をコメントで指摘してくれるという流れになります。
ここではDanger用のActionとして MeilCli/danger-action
を利用してKtlintに必要なGemのみに絞ったGemfile、Dangerfile使うことで処理時間の最適化を図っています。微々たるものですがこういう積み重ねが大事!
name: check pr
on:
pull_request:
branches:
- 'master'
- 'develop'
- 'feature/**'
- 'release/**'
jobs:
ktlint_and_other:
runs-on: ubuntu-latest
timeout-minutes: 30 # デフォルトは360分となっていて長すぎるので30分を設定
strategy:
matrix:
ruby: [ '2.7' ]
steps:
# 連続でpushした際などに前回のジョブが残っていたら停止させる
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Cache Gradle
uses: actions/cache@v2
id: cache_gradle
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: actions/cache@v1
name: Cache Bundler
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('.github/danger/Gemfile') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ruby }}-gems-
- name: Run ktlintCheck
run: ./gradlew --no-daemon ktlintCheck
# MeilCli/danger-action使うことでGemfile、Dangerfileを分けることが出来る
- uses: MeilCli/danger-action@v5
if: always()
name: Run Danger
with:
plugins_file: '.github/danger/Gemfile'
install_path: 'vendor/bundle'
danger_file: '.github/danger/ktlint_and_other.Dangerfile'
danger_id: 'danger-ktlint'
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
...
AndroidLint
続いてはAndroidLintです。やっていることはKtlintとほぼ同じですが、最後にJobの結果を notify-result-action
というアクションを使ってプルリクエストにコメントで通知するようにしています。今までは起票者がCIでエラーになったことに気づきにくく、コードの修正が遅れることが多かったための施策です。
notify-result-action
に関しては次で説明します。
jobs:
...
android_lint:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
ruby: [ '2.7' ]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- name: Cache Gradle
uses: actions/cache@v2
id: cache_gradle
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: actions/cache@v1
name: Cache Bundler
with:
path: vendor/bundle
key: ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles('.github/danger/android_lint.Gemfile') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ruby }}-gems-
- name: run android lint
run: ./gradlew --no-daemon :app:lintProductionRelease
- uses: MeilCli/danger-action@v5
if: always()
name: Run Danger
with:
plugins_file: '.github/danger/android_lint.Gemfile'
install_path: 'vendor/bundle'
danger_file: '.github/danger/android_lint.Dangerfile'
danger_id: 'danger-android-lint'
env:
DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: notify result
if: always()
uses: ./.github/workflows/composite/notify-result-action
with:
job-title: 'Android Lint'
github-token: ${{secrets.GITHUB_TOKEN}}
...
Composite Action notify-result-action
について
AndroidLint, UnitTest, ライセンスチェック結果についてはプルリクエストにコメントとして通知するようにしています。ここではComposite Actionを利用して処理を再利用可能なstepにしています。
2021/08にusesが利用できるようになったことでようやく使えるレベルになったと思います。
name: "Notify result"
inputs: # 呼び出し元から値を受け取れるようにするため変数を定義
job-title:
required: true # 必須の変数にするためにrequiredはtrueに
github-token:
required: true
runs:
using: "composite"
steps:
# body-includesに指定したコメントを探す
- name: Find Comment
uses: peter-evans/find-comment@v1
id: comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: ${{ inputs.job-title }} results
# すでに実行結果がコメントされている場合はコメントを削除&新規作成することで起票者に通知させる
- uses: actions/github-script@v5
with:
github-token: ${{ inputs.github-token }}
script: |
if (process.env.COMMENT_ID === '') {
return;
}
github.rest.issues.deleteComment({
issue_number: ${{ github.event.pull_request.number }},
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: process.env.COMMENT_ID
})
env:
COMMENT_ID: ${{ steps.comment.outputs.comment-id }}
# job.statusをプルリクエストにコメントする
- name: ${{ inputs.job-title }} results
uses: peter-evans/create-or-update-comment@v1
with:
issue-number: ${{ github.event.pull_request.number }}
body: |
${{ inputs.job-title }} results: ${{ job.status }}!
edit-mode: append
notify-result-action
の処理の流れは次のようになっています。
-
peter-evans/find-comment
を使ってすでにコメントしてあるか確認 - すでにコメントされていたら
actions/github-script
を使ってコメントを削除1 -
peter-evans/create-or-update-comment
を使ってコメントを追加
Composite Actionを実際に使ってみて1点だけ残念な点がありました。それはif構文 jobs.<job_id>.steps[*].if
が使えないという点です。今回、すでにコメントがあったら削除するという判定をscript内に入れているのはそのためです。
if構文使えるようになれば最高なんですけどね〜。Githubさん対応してくれないかな?|ω・)チラ
if構文にも対応したそうです。
https://github.blog/changelog/2021-11-09-github-actions-conditional-execution-of-steps-in-actions/
Licenseチェック
cookpadさんのライブラリであるLicenseToolsPluginを使ってオープンソースライセンス表記の記載漏れがないかをチェックしています。ライブラリは使わせてもらっている立場なので抜けもれなく表記したいところです。
jobs:
...
check_licence:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v2
- name: Cache Gradle
uses: actions/cache@v2
id: cache_gradle
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: check license
run: ./gradlew --no-daemon checkLicenses
- name: notify result
if: always()
uses: ./.github/workflows/composite/notify-result-action
with:
job-title: 'Check License'
github-token: ${{secrets.GITHUB_TOKEN}}
...
UnitTest
ここでは各モジュールのテストを行ったあとに mikepenz/action-junit-report
を利用してテストのエラーケースをハイライトするようにしています。どこがエラーになっているかがひと目で分かるので便利です。
jobs:
...
unit_test:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out
uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: 'zulu'
java-version: 11
- name: Run unit test
run: |
./gradlew --no-daemon app:testProductionReleaseUnitTest \
network:testReleaseUnitTest \
data:repository:testReleaseUnitTest \
model:testReleaseUnitTest
- name: Publish Test Report
if: cancelled() != true
uses: mikepenz/action-junit-report@v2
with:
report_paths: '**/build/test-results/*/TEST-*.xml'
- name: notify result
if: always()
uses: ./.github/workflows/composite/notify-result-action
with:
job-title: 'Unit Test'
github-token: ${{secrets.GITHUB_TOKEN}}
プルリクエストのオープン・レビュー準備完了時
プルリクエストのオープン・レビュー準備完了時はAssigneesやReviewersを自動的に設定したいですよね。
塵も積もれば山となるということで、ここではhkusu/review-assign-action
を使って Assigneesにプルリクエストの作成者を自動設定するようにしています。
※ Reviewersの設定に関してはGithubのCODE OWNERSを使って自動設定しているので本章では触れません。
name: Review Assign
on:
pull_request:
types: [opened, ready_for_review]
jobs:
assign:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: hkusu/review-assign-action@v1.0.0
with:
assignees: ${{ github.actor }}
release, hotfixブランチの作成・更新時
release, hotfixブランチを作成したら何をするのか?そうです。master, developブランチへのプルリクエストを作ります。手動で作るのは面倒なのでここもCIで作成しちゃいましょう。
プルリクエストの作成には git-pr-release
というGemライブラリを利用します。
name: git-pr-release
on:
push:
branches:
- release/**
- hotfix/**
jobs:
create-release-pr:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
ruby: [ '2.7' ]
steps:
# 実行中のジョブをキャンセル
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
# リポジトリのチェックアウト
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
# ブランチ名の取得
- name: get branch name
id: vars
run: echo "::set-output name=branch_name::${GITHUB_REF#refs/heads/}"
# Releaseブランチがプッシュされた場合、Masterブランチに向けたプルリクエストを作成
- name: Create a release pull request for release branch
if: contains(steps.vars.outputs.branch_name, 'release')
env:
GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PR_RELEASE_BRANCH_PRODUCTION: master
GIT_PR_RELEASE_BRANCH_STAGING: ${{ steps.vars.outputs.branch_name }}
GIT_PR_RELEASE_LABELS: release
GIT_PR_RELEASE_TEMPLATE: .github/.git-pr-release-template
run: |
gem install -N git-pr-release -v "1.9.0"
git-pr-release --no-fetch
# Hotfixブランチがプッシュされた場合、Masterブランチに向けたプルリクエストを作成
- name: Create a release pull request for hotfix branch
if: contains(steps.vars.outputs.branch_name, 'hotfix')
env:
GIT_PR_RELEASE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_PR_RELEASE_BRANCH_PRODUCTION: master
GIT_PR_RELEASE_BRANCH_STAGING: ${{ steps.vars.outputs.branch_name }}
GIT_PR_RELEASE_LABELS: release
GIT_PR_RELEASE_TEMPLATE: .github/.git-pr-hotfix-template
run: |
gem install -N git-pr-release -v "1.9.0"
git-pr-release --no-fetch
# Release, Hotfixそれぞれから、Developブランチに向けたプルリクエストをドラフト状態で作成
# 差分がなくてもエラーなしで終了する
- name: Create a develop pull request
uses: repo-sync/pull-request@v2
with:
source_branch: ${{ steps.vars.outputs.branch_name }}
destination_branch: develop
pr_title: ${{ steps.vars.outputs.branch_name }} into develop
pr_body: release branch into develop
pr_label: release
pr_allow_empty: true
pr_draft: true
github_token: ${{ secrets.GITHUB_TOKEN }}
git-pr-release
はテンプレートファイルを用意してあげることでプルリクエストに記載する内容を定義出来ます。1行目がタイトル、pull_requests.eachでマージ対象のコミットの一覧を出力します。
Release <%= Time.now.strftime("%Y-%m-%d") %>
## コミット対象
各自が担当した修正が下記一覧にあるかチェックする。
<% pull_requests.each do |pr| -%>
<%= pr.to_checklist_item %>
<% end -%>
---
## リリース作業
...
releaseブランチに対してのプルリクエストマージ時
ラクマAndroidチームでは、バグや改善系などのプロジェクトに紐付かないタスクはGithub Issuesで管理しています。
プルリクエストとIssuesをcloseなどのキーワードを紐付けることで自動でクローズしてくれて便利なのですが、デフォルトブランチ以外に対するプルリクエストのマージでは自動クローズはされません。
されないならCIでクローズしちゃおう!ということで下記のワークフローです。
name: close issue for release branch
on:
pull_request:
types:
- closed
branches:
- 'release/**'
jobs:
# リリースブランチにマージされた Pull Request に Issue のリンクがあれば、Issue を閉じる。
close-issue:
runs-on: ubuntu-latest
timeout-minutes: 30
if: github.event.pull_request.merged == true
steps:
- name: Closes issue
uses: ldez/gha-mjolnir@v1.0.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
おわりに
今後もさまざまな処理の自動化を検討しています。例えばGoogle Play Consoleを使った段階公開をCI + slackワークフローの組み合わせでカジュアルに実行することや、テストカバレッジの導入など。
ラクマでは一緒にこれらの施策を行い、Androidアプリ開発してくれる仲間を絶賛募集中です!少しでも興味を持ってくれた方がいましたらTwitter(@tarumzu)などでもお気軽にお声がけください。
参考文献
- https://qiita.com/hkusu/items/39eb92dbd4d6db8a14d8
- https://blog.beachside.dev/entry/2021/09/24/220000
- https://github.com/x-motemen/git-pr-release
-
peter-evans/create-or-update-comment
はコメントの更新にも対応していますが都度メンションしたいので、コメントを削除、追記するようにしています。 ↩