🧹
【2024】composeプロジェクトのlint導入
対象読者
「@Composable や Modifier を見たことがある程度」以上の方を想定しています。
お品書き
- androidにおけるlintの概要
- slack compose lintの導入
- ktlintの導入
- CIの整備
用意するもの
- ktlintを設定したいAndroidのプロジェクト
- 本記事を学習目的・ハンズオン形式で進めたい場合は、以下のサンプルリポジトリを活用してください。
- https://github.com/TBSten/lintpractice
- 最後にgithub actionsで実行させる都合上、githubのリポジトリまで用意しておくと良いでしょう。
- [option] Composeなどのコードスタイルのガイドラインを読んでおくとスムーズかもしれません。
- VSCode系のエディタとgithub plugin。CIの設定のタイミングであると便利です。
⭐️ androidにおけるlintの概要
lintとは?
- よくないけど動くコードを怒ってくれる。
- 場合によってはコードが自動整形される。
- 守られていないといけない項目をルールと呼びます。
androidプロジェクトのlint
今回整備したいのは大きく2つです。
1. ktlint
- kotlinのlintです。あくまでkotlinのlintなので、Android固有の事情は考慮してくれません。
- デフォルトでは入ってないので、自分で導入する必要があります。
- ルールの例
2. android lint
- Androidのlintです。
- ルールの例
- 権限足りないよ!チェックするコード書いてね
- このコードはAPI Level XX じゃないといけないよ!
- Android SDKやAndroid Gradle Pluginの中に組み込まれているので、Androidプロジェクトでは導入の手間はありません。
- Android Gradle Pluginでは以下のコマンドを実行することでlintのチェックを走らせることができます。
ターミナル
./gradlew lint
- カスタムのlint ruleも作れるようです。
- 詳しくはこちら
- カスタムルールを作ったライブラリやモジュールを
lintChecks()
で追加していきます。
⭐️ android lintとslack compose-lints
- ルールの例
- slack compose-lintsは composeのチェックを android lintにカスタムのlint rule という形で追加して行きます。
- 以前はtwitterのcompose ruleが使われていた時期もあったようですが、開発が終了しているらしいので代替となるslack compose-lintsを利用して行きます。
slack compose-lintsを導入
app/build.gradle.kts
dependencies {
lintChecks("com.slack.lint.compose:compose-lint-checks:1.3.1")
}
- バージョンは都度 公式ドキュメント の右上を確認しながら都度変更してください。
⭐️ ktlint
導入
ktlint自体はコマンドラインツールとして公開されています。
今回は jlleitschuh/ktlint-gradle を導入していきます。
app/build.gradle.kts
plugins {
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
}
概要
- 今回使用したgradle pluginを導入するといくつかのgradleタスクが生えます。注目したいのは ktlintCheck タスクと ktlintFormat タスクです。これにより以下のコマンドが実行できるようになります。
ターミナル
// 警告やエラーが表示されます
./gradlew ktlintCheck
ターミナル
// 可能な限りフォーマット
./gradlew ktlintFormat
- ktlintでは
.editorconfig
ファイルで各ルールのon/offを切り替えることができます。- composeのプロジェクトでは以下のように
@Composable
がついている関数に対して[function-naming](https://pinterest.github.io/ktlint/latest/rules/standard/#function-naming)
をoffにしておくと良さそうです。
- composeのプロジェクトでは以下のように
// .editorconfig
ktlint_function_naming_ignore_when_annotated_with=Composable
- 私が以前作成したライブラリの .editorconfig や now in androidの .editorconfig を参考に各プロジェクトに合わせてカスタムしてください。
Android Studio向けの設定
Android Studio向けにpluginがあります。以下を参考にインストールして設定しましょう。保存時などに自動フォーマットしてくれたり、エディタ上に警告を表示してくれるようになります☺️
補足
- ググると公式の古いバージョンのものが出てくることがあるので注意
- ルール一覧
ここまでのまとめ
- androidプロジェクトのlintでは以下を導入すると良さそう
- anddroid lint
- slack compose lint
- ktlint
- anddroid lint
- それぞれセットアップすることで
-
./gradlew lint
で android lintを実行できる -
./gradlew ktlintCheck
や./gradlew ktlintFormat
でktlintの実行やフォーマット
-
⭐️ CI環境を構築しよう
- 上記で実装したチェックをgithub actionsでpull request時に走るようにしましょう。
- 今回は
reviewdog
を使ってCI環境を構築していきます。
概要
-
今回は
reviewdog
を使ってPull Requestにコメントします。そのためにandroid lintの出力やktlintの出力をreviewdogに決められた形で渡す必要があります。 -
android lint → reviewdogへはsarifという形式で渡します。
- そのためにandroid lintにsarifで出力するという設定が必要です。
- ktlint → reviewdogへはcheckstyleという形式で渡します。
- そのためにktlintにcheckstyleで出力するという設定が必要です。
各ツールで対応している形式
各ツールで使用できる形式はそれぞれ以下のように確認できます。
- reviewdogで対応している形式は
reviewdog -list
で一覧表示できます。詳しくはこちらを参照。 - ktlintは
plain
,json
,html
,checkstyle
に対応しています。詳しくはこちらを参照。 - android lintは
text
,xml
,html
,sarif
に対応しているようです。詳しくはこちらを参照。
CIを構築する
- ここからはVSCode系のIDEにGithubの拡張機能を入れて進めることをお勧めします。
- github actionsを動かすために以下を
.github/workflows/pullRequestCheck.yaml
に追加しましょう。
.github/workflows/pullRequestCheck.yaml
name: Pull Request Check
on: [pull_request]
env:
JAVA_VERSION: 17 # gradleに対応するものを選ぶ
GRADLE_VERSION: 8.2
permissions:
pull-requests: write
jobs:
android-lint:
runs-on: ubuntu-latest
steps:
# setup
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: ${{ env.GRADLE_VERSION }}
- name: Setup reviewdog
uses: reviewdog/action-setup@v1
with:
reviewdog_version: latest
# android lintを実行
- name: Android Lint
id: android-lint
run: ./gradlew lint
# report
- name: Comment PR by reviewdog
if: always()
run: |
find ./*/build/reports/android-lint/lintResults.sarif \
-type f \
-exec sh -c "cat {} | reviewdog -f=sarif -reporter=github-pr-review" \;
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ktlint:
runs-on: ubuntu-latest
steps:
# setup
- name: Checkout sources
uses: actions/checkout@v4
- name: ktlint
uses: ScaCap/action-ktlint@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
fail_on_error: true
次に app/build.gradle.kts に以下を追加します。マルチモジュール構成の場合は 存在するモジュール全てに 以下を設定してください。(頑張れば共通化もできなくはなさそうです)
app/build.gradle.kts
...
android {
...
lint {
sarifOutput = File(project.buildDir, "reports/android-lint/lintResults.sarif")
textOutput = File(project.buildDir, "reports/android-lint/lintResults.txt")
htmlOutput = File(project.buildDir, "reports/android-lint/lintResults.html")
xmlReport = false
}
}
...
各箇所の説明
1. android lint
.github/workflows/pullRequestCheck.yaml
# setup
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ env.JAVA_VERSION }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: ${{ env.GRADLE_VERSION }}
- name: Setup reviewdog
uses: reviewdog/action-setup@v1
with:
reviewdog_version: latest
- 各種ツールのセットアップです。
- JavaとGradleで使用するバージョンはenvで定義しています。AGPのバージョンに合わせたgradleのバージョン、gradleのバージョンに合わせたjavaのバージョンを指定してください。
.github/workflows/pullRequestCheck.yaml
# android lintを実行
- name: Android Lint
id: android-lint
run: ./gradlew lint
- android lintを実行します。
app/build.gradle.kts
android {
...
lint {
sarifOutput = File(project.buildDir, "reports/android-lint/lintResults.sarif")
textOutput = File(project.buildDir, "reports/android-lint/lintResults.txt")
htmlOutput = File(project.buildDir, "reports/android-lint/lintResults.html")
xmlReport = false
}
}
- android lintではbuild.gradle.ktsの
android.lint
ブロックに設定を書くことができます。今回はsarifOutput
textOutput
htmlOutput
の3つを指定してlintの出力結果の保存先を指定しています。この後sarifで指定したreports/android-lint/lintResults.sarif
を使用します。
.github/workflows/pullRequestCheck.yaml
# report
- name: Comment PR by reviewdog
if: always()
run: |
find ./*/build/reports/android-lint/lintResults.sarif \
-type f \
-exec sh -c "cat {} | reviewdog -f=sarif -reporter=github-pr-review" \;
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
reviewdog
コマンドを実行します。- reviewdogコマンドは | の右側の出力結果を後述のオプションに従って解釈し出力してくれます。
-
-f=sarif
はsarif形式で入力が来ることを指定しています。 -
-diff=
でdiffをとるコマンドを指定できます。
- findコマンドでごちゃごちゃやってるのは、出力されたすべてのlintResults.sarif全てに対してreviewdogするためです (マルチモジュールの対応)。
そのほかのreviewdogコマンドの使い方は公式ドキュメントを参照してください。
2. ktlint
.github/workflows/pullRequestCheck.yaml
- name: ktlint
uses: ScaCap/action-ktlint@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
fail_on_error: true
- ScaCap/action-ktlint を呼び出すだけで簡単に ktlintの実行からreviewdogによるPull Requestのコメントまで できます。便利👍
P.S. 2024.4.7
不正なコードを検出してもデフォルトの状態ではFailureになりません。
これでは不正なコードがあってもMergeできてしまうため、 fail_on_error
オプションでFailureするようにしておいた方が無難です。
with:
...
fail_on_error: true
この辺りのルールは適宜チーム内で相談する必要があるかもしれません。
⭐️ やってないこと (職務放棄)
- マルチモジュールプロジェクトでのlintの設定の共通化
- 現状だとモジュールが複数ある場合、すべてのモジュールのbuild.gradle.ktsにandroid.lintブロックやktlintブロックを追加する必要があります。これは非常に面倒です。
- buildSrcモジュールに gradle plugin を作るなどして共通化できると良さそうです。
Discussion