実現したいこと
フォーマットがぐちゃぐちゃのコードや、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
にチェック項目を追加する
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する
test:
override:
- ./scripts/saddler.sh
既に自動テストなどがある場合は、上記をpre
やpost
とするか、
行いたい自動テストも合わせて記述する必要があります。
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
使用しているSaddlerのreporter
を選択しています。
CircleCIはPUSHに反応して動作するため、そのタイミングではpull requestが作成されていない可能性があります。
その場合はコミットに対するコメント、pull requestがあるのであればそちらへのコメント、という切り替えを行っています。
checkstyle / findbugs / PMD / PMD-CPD / android lint
それぞれの結果を、下記のようにパイプでつなげて処理しています。
checkstyleフォーマットに変換→diffの取得→GitHubへのコメント
宣伝
株式会社モンスター・ラボでは、エンジニアを募集しています。
興味ある方は、Wantedlyから応募してもらえれば、気軽なお話から出来ると思います。