🧹

【2024】composeプロジェクトのlint導入

2024/03/17に公開

対象読者

「@Composable や Modifier を見たことがある程度」以上の方を想定しています。

お品書き

  • androidにおけるlintの概要
  • slack compose lintの導入
  • ktlintの導入
  • CIの整備

用意するもの

⭐️ androidにおけるlintの概要

lintとは?

  • よくないけど動くコードを怒ってくれる。
  • 場合によってはコードが自動整形される。
  • 守られていないといけない項目をルールと呼びます。

androidプロジェクトのlint

今回整備したいのは大きく2つです。

1. ktlint

2. android lint

  • Androidのlintです。
  • ルールの例
    • 権限足りないよ!チェックするコード書いてね
    • このコードはAPI Level XX じゃないといけないよ!
  • Android SDKAndroid Gradle Pluginの中に組み込まれているので、Androidプロジェクトでは導入の手間はありません。
  • Android Gradle Pluginでは以下のコマンドを実行することでlintのチェックを走らせることができます。
ターミナル
./gradlew lint
  • カスタムのlint ruleも作れるようです。
    • 詳しくはこちら
    • カスタムルールを作ったライブラリやモジュールを lintChecks() で追加していきます。

⭐️ android lintとslack compose-lints

slack compose-lintsを導入

https://slackhq.github.io/compose-lints/#installation

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"
}

https://pinterest.github.io/ktlint/latest/install/integrations/#gradle-integration

https://github.com/jlleitschuh/ktlint-gradle

概要

  • 今回使用した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にしておくと良さそうです。
// .editorconfig
ktlint_function_naming_ignore_when_annotated_with=Composable

Android Studio向けの設定

Android Studio向けにpluginがあります。以下を参考にインストールして設定しましょう。保存時などに自動フォーマットしてくれたり、エディタ上に警告を表示してくれるようになります☺️

補足

  • ググると公式の古いバージョンのものが出てくることがあるので注意
  • ルール一覧
ここまでのまとめ
  • androidプロジェクトのlintでは以下を導入すると良さそう
    • anddroid lint
      • slack compose lint
    • ktlint
  • それぞれセットアップすることで
    • ./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で出力するという設定が必要です。

各ツールで対応している形式

各ツールで使用できる形式はそれぞれ以下のように確認できます。

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コマンドの使い方は公式ドキュメントを参照してください。
https://github.com/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