Android
Kotlin
TravisCI
Danger

Danger + ktlintでPull RequestのKotlinコードスタイルを自動チェックさせる (Travis CI編)

TL;DR

  • GitHubのPull Request (PR) を作成したときに、PRのDiffに含まれるKotlinコードのスタイルを自動的にチェックして、問題があればPRに自動的にコメントさせる方法の説明です
  • 以下のツールを組み合わせて使っています
    • ktlint: KotlinコードのLinter
    • Danger: コードレビューbotのフレームワーク
  • サンプル: Pull Request #3 · kafumi/android-danger-ktlint-sample

導入のきっかけ

  • Android Studioでフォーマッターをかける (Command + Alt + L) ことをチーム内のルールにして、コードスタイルの統一を図っているのですが、ただ、そこは悲しい人の性、フォーマッターをかけ忘れたコードがときどきコミットされて、コードレビュー時に指摘されたりします
    • コードスタイルに対して神経質なレビュワーは、レビュー対象コードをチェックアウトして、Android Studioでフォーマットし、差分がでないことを確かめたりしてます (私です)
  • コードスタイルなど、本質的でないことに対するレビューの労力は極力減らしたい!
  • 最近よく目にする Danger を使ってみたら、コードスタイルのチェックが自動化できて幸せになりました

仕組みの全体像

  1. GitHubでPRを作成したとき、Travis CIのビルドが走る
  2. Travis CIのビルドの中で、ktlintの実行、Dangerの実行を行う
    1. ktlintはGradleから実行する (ktlint-gradle プラグインを使う)
    2. ktlintが出力する Checkstyle 形式のレポートを、Dangerが danger-checkstyle_format プラグインを使って読み込む
    3. Dangerが、ktlintが報告した警告をGitHubのPRにコメントする

Dangerとは

ktlintとは

設定手順

環境

  • 今回は、筆者が実際に使っている環境を例に説明します
    • CIシステム: Travis CI
    • コードホスティングサービス: GitHub
    • Publicレポジトリで説明していますが、Privateレポジトリでもほぼ同じ方法で設定できます
      • Privateレポジトリの場合は、botアカウントがPrivateレポジトリにアクセスできるように、botアカウントの権限設定を少し変える必要があります (「GitHubアクセストークンの発行」のセクションで後述)
  • DangerがサポートしているCIシステム、コードホスティングサービスなら、どの組み合わせでも設定可能です
    • Dangerがサポートしているシステムは、Danger Webサイトの "WHAT MAGIC IS THIS" - "SUPPORTS" に列挙されています

Travis CIの準備

まずは、GitHubでPull Requestが作成されたときに、Travis CI でビルドを走らせる必要があります。

基本的には、Androidプロジェクトビルド用の .travis.yml ファイルを作成してリポジトリにコミットしておき、Travis CIの設定画面で当該リポジトリに対するビルドを有効にすればOKです。.travis.ymlは、典型的には次のようなファイルになります。

# .travis.yml

language: android 
branches: 
  only: 
    - master 
android: 
  components: 
    - tools 
    - platform-tools 
    - build-tools-26.0.2 
    - android-26 
    - extra-android-m2repository 
script: 
  - ./gradlew assembleDebug 
before_cache: 
  - rm -f  $HOME/.gradle/caches/modules-2/modules-2.lock 
  - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 
cache: 
  directories: 
    - $HOME/.gradle/caches/ 
    - $HOME/.gradle/wrapper/ 
    - $HOME/.android/build-cache 

この .travis.yml には、まだ、Dangerの実行は含まれていません。Dangerの実行を追加する方法は、この後のセクションで説明します。

もし、Travis CIを使ったことがなく、上記のYAMLファイルが何を設定しているのかわからない場合は、Travis CIの次のDocumentページを参考にしてみてください。この3つを読めばだいたい分かるようになると思います。

ktlintの設定

次に、ktlint の設定を行います。Androidプロジェクトに対してktlintを実行する場合、主に次の方法があります。

Gradleで完結していると実行環境の準備が楽チンなので、Gradleを使う方法、特に、Gradleプラグインを使う方法がおすすめです。この記事では、ktlint-gradle プラグインを使います。

ktlint-gradleプラグインの導入

JLLeitschuh/ktlint-gradleHow to use に書かれているとおり、ktlint-gradle をGradleの依存先に追加し、apply plugin すれば、ktlint-gradle プラグインが導入できます。下の例では、2017年12月10日現在で最新の 2.3.0 を使用しています。プラグインの最新バージョンは、Gradle Plugin Registry で確認できます。

// build.gradle

buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath "gradle.plugin.org.jlleitschuh.gradle:ktlint-gradle:2.3.0"
    }
}
// app/build.gradle

apply plugin: "org.jlleitschuh.gradle.ktlint"

ktlint-gradleプラグインの設定

次に、Dangerと組み合わせて使うために、Configuration で説明されている方法を使って ktlint-gradle プラグインの設定を変更します。また、あわせて、Android Kotlin Style Guide に準拠したLintを行うように設定します。

(2017年12月10日編集: ktlint-gradle プラグインのバージョンアップ2.1.1 → 2.3.0にあわせて、設定内容を変更しました)

// app/build.gradle

ktlint {
    version = "0.14.0"
    android = true
    reporter = "checkstyle"
    ignoreFailures = true
}
  • version: 利用するktlintのバージョンを指定します。ktlint-gradle プラグインの2.3.0は、デフォルトではktlintの 0.9.2 を使用するのですが、ktlint 0.9.2は Android Kotlin Style Guide に準拠したLintをサポートしていません (ktlint 0.12.0 で追加された機能です)。Android Kotlin Style Guide に準拠したLintを行いたい場合、0.12.0より新しいバージョンのktlintを使う必要があります。ここでは、2017年12月10日時点で最新の 0.14.0 を使うようにしています
  • android: ktlintの --android オプションを有効にするかどうかを指定します。--android オプションが有効な場合、ktlintは Android Kotlin Style Guide に準拠したスタイルチェックを行うようになります
  • reporter: レポートの出力形式を指定します。今回は、Dangerで扱えるようにCheckstyle形式の出力を得たいので、checkstyle を指定します
  • ignoreFailures: 警告があってもビルドを継続するかどうかを指定します。ktlint-gradle は、デフォルト設定だと、ktlintからの指摘が1件でもあるとGradleタスクの結果を失敗として報告するようになっています (ビルドがエラーで止まります)。一般的に、コードスタイルに関する指摘があっただけでビルド失敗にしたくないと思うので、指摘があってもビルドを継続する (Gradleタスクは成功とする) ために、true を指定します

以上の設定を行なうと、Gradleに ktlintCheckktlintFormat というタスクが追加されます。

  • ktlintCheck: ktlintによるKotlinコードのLintが実行される
    • Lintレポートは app/build/reports/ktlint/ktlint-<sourceSet>.xml に出力される
      • main ソースセットの場合、出力先は app/build/reports/ktlint/ktlint-main.xml となる
  • ktlintFormat: ktlintによるKotlinコードの自動フォーマットが実行される
    • 本記事ではこちらのタスクは使っていません

Dangerの設定

Danger公式サイトGetting Set Up に従って、Dangerを設定します。

なお、Gem danger をインストールした状態で bundle exec danger init を実行すると、テキストベースの設定ウィザードが実行できるのですが、Getting Set Up で説明されていることが表示されるだけなので、特に実行しなくて問題ありません (ただし、実行すると Dangerfile の雛形を作成してくれるという利点はあります)。

Gemfile を作成する

DangerはRuby製であり、プラグインも含めた依存関係を Gemfile に定義し、Bundler でインストールします。まずは、Androidアプリプロジェクトのルートディレクトリで bundle init を実行し、Gemfile の雛形を生成します。

$ bundle init

生成された Gemfile をエディターで開き、#gem 'rails' の部分を必要なGemで置き換えます。今回は、dangerdanger-checkstyle_format の2つのGemを追加します。

# Gemfile

# frozen_string_literal: true
source "https://rubygems.org"

gem "danger"
gem "danger-checkstyle_format"

上記の編集が終わったら、bundle init を実行したのと同じディレクトリで bundle install を実行し、上記のGemをインストールします。

$ bundle install

Dangerfile の作成

Dangerは、Dangerfile というファイルの内容にもとづいて検査を実行します。Dangerfile はRuby DSL形式のファイルで、DangerコアやDangerプラグインが提供するAPIを使って、実行内容を定義します。利用できるAPIや記述例は、以下のページで紹介されています。

  • Dangerコアが提供する機能: Danger - Reference
  • Dangerプラグインが提供する機能: Danger の "PLUGINS" セクション

今回は、ktlintの指摘を報告する他に、Pull Requestのタイトルに [WIP] が入っていたらコメントで警告する (WIP中なのがわかりやすくなる) ように設定してみます。Gemfile と同じディレクトリに、 以下の内容で Dangerfile というファイルを作成します。

# Ignore inline messages which lay outside a diff's range of PR
github.dismiss_out_of_range_messages

# Make it more obvious that a PR is a work in progress and shouldn't be merged yet
warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]"

# ktlint
checkstyle_format.base_path = Dir.pwd
checkstyle_format.report 'app/build/reports/ktlint/ktlint-main.xml'

上記の定義の意味は次の通りです。

  • github.dismiss_out_of_range_messages は、Pull RequestのDiff以外に対する指摘を無視する (PRに報告しないようにする) 設定です
  • warn で始まる行は、Pull Requestのタイトルに [WIP] が入っているかどうかで警告を出すための設定です
  • 最後の2行は、danger-checkstyle_format プラグインを使って、ktlintが出力したCheckstyle形式のレポートにもとづいてPRにコメントするための設定です
    • 2017年12月10日編集: ktlint-gradle プラグインのバージョンアップ2.1.1 → 2.3.0にあわせて、レポートファイルのパスを変更しました

GitHubアクセストークンの設定

DangerがPull Requestにコメントする際に使用するGitHubアカウント・アクセストークンを作成します。

GitHubアカウントの作成

Dangerが使うGitHubアカウントを作成します。自分のGitHubアカウントをDangerに使わせることもできますが、セキュリティのリスクを抑える (botアカウントなら権限を必要最小限にしぼっておける、アカウントの機密情報が漏洩したときにアカウント削除の手段がとりやすい、など) ために、別のアカウントを作成したほうがよいです。別アカウントだと、botによるコメントであることがわかりやすい、という利点もあります。

  • Dangerの検査対象がPublicレポジトリである場合: botアカウントを当該レポジトリのCollaboratorに追加する必要はありません。アカウントを作成するだけでOKです
  • Dangerの検査対象がPrivateレポジトリである場合: 作成したbotアカウントを当該レポジトリのCollaboratorや、レポジトリが属するOrganizationのメンバーに追加し、当該レポジトリへの "Write" 権限を与えてください

GitHubアクセストークンの発行

作成したbotアカウントでログインした状態で Generate new token のページにアクセスし、アクセストークンを発行します。

  • Dangerの検査対象がPublicレポジトリである場合: 権限 public_repo だけをトークンに付与します
  • Dangerの検査対象がPrivateレポジトリである場合: repo スコープの権限全てをトークンに付与します

発行されたトークンはこの後、環境変数に設定するときに使うので、必ず控えておいてください。

アクセストークンの環境変数への設定

Dangerは、環境変数 DANGER_GITHUB_API_TOKEN からアクセストークンを取得します。

Travis CIにおいて、機密情報を含む環境変数を設定する方法は2つあります。

  1. Travis CIの各リポジトリの設定ページ (https://travis-ci.org/[user]/[repo]/settings) で、環境変数を追加する
  2. travis コマンドで値を暗号化して、.travis.yml に環境変数として定義する

Dangerの Getting Set Up ページでは方法1が紹介されているのですが、Travis CIの設定情報を .travis.yml に集約しておきたい、という思いがあり、個人的には方法2が好みです。ここでは、方法2を紹介します。

まず、travis コマンドをインストールします (まだインストールしていない場合)。

$ gem install travis

次に、検査対象レポジトリのルートディレクトリに行き、travis encrypt コマンドを実行します。もし、検査対象レポジトリがPrivateレポジトリの場合は、ここで --pro オプションをつけてください。

$ travis encrypt DANGER_GITHUB_API_TOKEN=[アクセストークン]

暗号化に成功すると、secure: から始まる長い行が出力されるので、それを .travis.ymlenv.global 配列の要素として追加します。

# .travis.yml

env:
  global:
    - secure: "R+epV1sw... 中略 .../A0sGJ8="

Travis CIでのDangerの実行

これで、Travis CI実行環境で Danger を実行する準備が整いました。あとは、Travis CIのビルド中に、Dangerを実行するだけです。

まず、Dangerを実行する前に、必要なgemをインストールさせる必要があります。Travis CIの install フェーズで bundle install を実行します。

# .travis.yml

install:
  - bundle install

あとは、script フェーズで、Gradleの ktlintCheck タスクを実行してktlintを実行した後、Dangerを実行すれば、ktlintが見つけた問題がPull Requestにコメントされます。

# .travis.yml

script:
  - ./gradlew assembleDebug ktlintCheck
  - bundle exec danger

サンプルプロジェクト

ここまでの設定を終えて、作成物をレポジトリにコミット・プッシュすれば完了です。

実際にひととおりの設定を行い、ktlintで問題を報告させてみたサンプルプロジェクトをGitHubにコミットしてありますので、良ければ参考にしてみてください。

Appendix: 設定ファイルの例

今回、説明した設定ファイルは、サンプルプロジェクトのリポジトリ kafumi/android-danger-ktlint-sample にコミットしてあります。