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


## 実現したいこと

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

CheckstyleとかFindBugsをコミットする前に毎回確認し、それを修正していけば駆逐出来そうですが、
つい忘れてしまったり、時間がないからと後回しにしてしまいがちです。にんげんだもの。

また、コードレビューで、スペースの位置とかインデントとか細かいことを指摘したくない。
そんなの、人間の仕事じゃないので、コンピュータにやらせたい。

ということで、下図のようにGitHubのpull requestにレビューの形で書けば、実装者は対応せざるを得ないし、
対応後のコードを人間がレビューすることで、人間は本質的なレビューに専念できます。（きっと

![Kobito.Lg7V5m.png](https://qiita-image-store.s3.amazonaws.com/0/8708/1ce3c24b-1c45-6a76-d42d-cc61e40ef88f.png "Kobito.Lg7V5m.png")

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

- Checkstyle
- FindBugs
- PMD
- CPD
- Android Lint

## 類似プロジェクト

### Dokumi

似たプロジェクトとして、CookpadのDokumiがあります。([紹介記事](http://techlife.cookpad.com/entry/2015/06/04/dokumi-ja))
GitHubにコードも[公開](https://github.com/cookpad/dokumi)されており、これをそのまま利用することも検討したのですが、こちらはJenkinsの入っているサーバにいろいろ（Gemなど）インストールする形式のようでした。
Jenkinsサーバの管理コストがペイ出来るのであればこちらで良いと思いますが、インフラの面倒を見るのは面倒です。
既にCircleCIを利用していたため、その上で動作し、依存関係のインストールも短時間で済むようにしたかったので、独自に行いました。

### SideCI

PaaSサービスとして、[SideCI](https://www.sideci.com/)というものもあります。
こちらは、RubyなどのサーバサイドやJavaScriptなどのフロントエンドを対象にしているようです。
また、Privateリポジトリについては課金が必要になる可能性がありそうです。
（現在はbeta campaignとして、無料で利用できるようです）
※ UPDATE: https://www.sideci.com/ にPricingが設定されていました。Trialが終わると、Private Repoは課金が必要っぽいですね。
今回はAndroidの解析をしたかったので、対象外でした。

### Saddler

類似というか、これを利用させてもらっていますが、[Saddler](https://github.com/packsaddle/ruby-saddler)というものがあります。
ページ上では、rubocopの結果をGitHubのpull requestにコメントしていますが、checkstyleフォーマットのアウトプットがあれば動作するので、使わせてもらいました。

## 手順

### 前提
- 該当のプロジェクトがGitHub上にある。
- CircleCIを利用している。

### CircleCIの環境変数を設定する
対象プロジェクトの設定画面より、"Environment variables"を選択、下記を設定する。
Name: `GITHUB_ACCESS_TOKEN`
Value: GitHubで取得したPersonal access token

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

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

```groovy: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する

```yaml:circle.yml
test:
  override:
    - ./scripts/saddler.sh
```

既に自動テストなどがある場合は、上記を`pre`や`post`とするか、
行いたい自動テストも合わせて記述する必要があります。

### `scripts/saddler.sh`を作成する

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

```bash: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](https://github.com/packsaddle/ruby-saddler)の`reporter`を選択しています。
CircleCIはPUSHに反応して動作するため、そのタイミングではpull requestが作成されていない可能性があります。
その場合はコミットに対するコメント、pull requestがあるのであればそちらへのコメント、という切り替えを行っています。

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

それぞれの結果を、下記のようにパイプでつなげて処理しています。
checkstyleフォーマットに変換→diffの取得→GitHubへのコメント

## 宣伝

[株式会社モンスター・ラボ](http://monstar-lab.com/)では、エンジニアを募集しています。
興味ある方は、[Wantedly](https://www.wantedly.com/projects/4271)から応募してもらえれば、気軽なお話から出来ると思います。