Edited at

Androidのコードを自動で解析し、GitHubのpull requestにコメントする

More than 3 years have passed since last update.


実現したいこと

フォーマットがぐちゃぐちゃのコードや、close漏れしてるコード、使ってないリソースが大量にあるようなプロジェクトをこの世から駆逐したい。

CheckstyleとかFindBugsをコミットする前に毎回確認し、それを修正していけば駆逐出来そうですが、

つい忘れてしまったり、時間がないからと後回しにしてしまいがちです。にんげんだもの。

また、コードレビューで、スペースの位置とかインデントとか細かいことを指摘したくない。

そんなの、人間の仕事じゃないので、コンピュータにやらせたい。

ということで、下図のようにGitHubのpull requestにレビューの形で書けば、実装者は対応せざるを得ないし、

対応後のコードを人間がレビューすることで、人間は本質的なレビューに専念できます。(きっと

自動レビューの内容としては下記の通り。


  • Checkstyle

  • FindBugs

  • PMD

  • CPD

  • Android Lint


類似プロジェクト


Dokumi

似たプロジェクトとして、CookpadのDokumiがあります。(紹介記事)

GitHubにコードも公開されており、これをそのまま利用することも検討したのですが、こちらはJenkinsの入っているサーバにいろいろ(Gemなど)インストールする形式のようでした。

Jenkinsサーバの管理コストがペイ出来るのであればこちらで良いと思いますが、インフラの面倒を見るのは面倒です。

既にCircleCIを利用していたため、その上で動作し、依存関係のインストールも短時間で済むようにしたかったので、独自に行いました。


SideCI

PaaSサービスとして、SideCIというものもあります。

こちらは、RubyなどのサーバサイドやJavaScriptなどのフロントエンドを対象にしているようです。

また、Privateリポジトリについては課金が必要になる可能性がありそうです。

(現在はbeta campaignとして、無料で利用できるようです)

※ UPDATE: https://www.sideci.com/ にPricingが設定されていました。Trialが終わると、Private Repoは課金が必要っぽいですね。

今回はAndroidの解析をしたかったので、対象外でした。


Saddler

類似というか、これを利用させてもらっていますが、Saddlerというものがあります。

ページ上では、rubocopの結果をGitHubのpull requestにコメントしていますが、checkstyleフォーマットのアウトプットがあれば動作するので、使わせてもらいました。


手順


前提


  • 該当のプロジェクトがGitHub上にある。

  • CircleCIを利用している。


CircleCIの環境変数を設定する

対象プロジェクトの設定画面より、"Environment variables"を選択、下記を設定する。

Name: GITHUB_ACCESS_TOKEN

Value: GitHubで取得したPersonal access token

このトークンを作成したアカウントでコメントされることになるので、BOTアカウントを作成、それにコメントしてもらったほうが混乱が無いと思います。


app/build.gradleにチェック項目を追加する


build.gradle

apply from: "https://raw.githubusercontent.com/monstar-lab/gradle-android-ci-check/1.1.0/ci.gradle"


checkタスクに、CheckstyleなどのタスクをdependsOnで指定しています。

詳細は https://github.com/monstar-lab/gradle-android-ci-check に。

Checkstyleの設定なども、弊社内の推奨に合わせてあったりするので、変更したい場合はforkするなり、READMEにあるように個別で設定するなり、ご自由にどうぞ。


circle.ymlにてtestをoverrideする


circle.yml

test:

override:
- ./scripts/saddler.sh

既に自動テストなどがある場合は、上記をprepostとするか、

行いたい自動テストも合わせて記述する必要があります。


scripts/saddler.shを作成する

※ファイルには実行権限を付けて下さい


scripts/saddler.sh

#!/usr/bin/env bash

echo "********************"
echo "* install gems *"
echo "********************"
gem install --no-document checkstyle_filter-git saddler saddler-reporter-github findbugs_translate_checkstyle_format android_lint_translate_checkstyle_format pmd_translate_checkstyle_format

if [ $? -ne 0 ]; then
echo 'Failed to install gems.'
exit 1
fi

echo "********************"
echo "* exec gradle *"
echo "********************"
./gradlew app:check

if [ $? -ne 0 ]; then
echo 'Failed gradle check task.'
exit 1
fi

echo "********************"
echo "* save outputs *"
echo "********************"

LINT_RESULT_DIR="$CIRCLE_ARTIFACTS/lint"

mkdir "$LINT_RESULT_DIR"
cp -v "app/build/reports/checkstyle/checkstyle.xml" "$LINT_RESULT_DIR/"
cp -v "app/build/reports/findbugs/findbugs.xml" "$LINT_RESULT_DIR/"
cp -v "app/build/reports/pmd/pmd.xml" "$LINT_RESULT_DIR/"
cp -v "app/build/reports/pmd/cpd.xml" "$LINT_RESULT_DIR/"
cp -v "app/build/outputs/lint-results.xml" "$LINT_RESULT_DIR/"

echo "********************"
echo "* select reporter *"
echo "********************"

if [ -z "${CI_PULL_REQUEST}" ]; then
# when not pull request
REPORTER=Saddler::Reporter::Github::CommitReviewComment
else
REPORTER=Saddler::Reporter::Github::PullRequestReviewComment
fi

echo "********************"
echo "* checkstyle *"
echo "********************"
cat app/build/reports/checkstyle/checkstyle.xml \
| checkstyle_filter-git diff origin/master \
| saddler report --require saddler/reporter/github --reporter $REPORTER

echo "********************"
echo "* findbugs *"
echo "********************"
cat app/build/reports/findbugs/findbugs.xml \
| findbugs_translate_checkstyle_format translate \
| checkstyle_filter-git diff origin/master \
| saddler report --require saddler/reporter/github --reporter $REPORTER

echo "********************"
echo "* PMD *"
echo "********************"
cat app/build/reports/pmd/pmd.xml \
| pmd_translate_checkstyle_format translate \
| checkstyle_filter-git diff origin/master \
| saddler report --require saddler/reporter/github --reporter $REPORTER

echo "********************"
echo "* PMD-CPD *"
echo "********************"
cat app/build/reports/pmd/cpd.xml \
| pmd_translate_checkstyle_format translate --cpd-translate \
| checkstyle_filter-git diff origin/master \
| saddler report --require saddler/reporter/github --reporter $REPORTER

echo "********************"
echo "* android lint *"
echo "********************"
cat app/build/outputs/lint-results.xml \
| android_lint_translate_checkstyle_format translate \
| checkstyle_filter-git diff origin/master \
| saddler report --require saddler/reporter/github --reporter $REPORTER



install gems

必要なGemのインストールを行っています。

(本来は、バージョンを固定するためにGemfileを作ったほうがいいのでしょうか。。。?)

GitHub(?)の不調の際に、Gemのインストールに失敗することがあり、そのまま次に進んでしまわないよう、

エラーの場合はここでexit 1しています。


exec gradle

checkタスクを実行します。

appというモジュールを前提にしています。)

これも、ここでエラーがあると、以降を実行する意味が無いので、exit 1しています。


save outputs

生成されるチェック結果をartifactsとして保存しています。


select reporter

使用しているSaddlerreporterを選択しています。

CircleCIはPUSHに反応して動作するため、そのタイミングではpull requestが作成されていない可能性があります。

その場合はコミットに対するコメント、pull requestがあるのであればそちらへのコメント、という切り替えを行っています。


checkstyle / findbugs / PMD / PMD-CPD / android lint

それぞれの結果を、下記のようにパイプでつなげて処理しています。

checkstyleフォーマットに変換→diffの取得→GitHubへのコメント


宣伝

株式会社モンスター・ラボでは、エンジニアを募集しています。

興味ある方は、Wantedlyから応募してもらえれば、気軽なお話から出来ると思います。