LoginSignup
0
1

detekt の特定の Issue がプルリク差分から検出されたら、CI を失敗にする(シェル芸)

Last updated at Posted at 2024-05-06

経緯

Kotlin 言語で作られた大規模なプロジェクトがあり、その中に不適切な実装(該当 Issue)が1種類ですが多くの箇所にありました。それらを修正したいですが、すべてを修正する工数は取れなそうです。よって新機能開発や機能改修のついでに、そこも修正できるチーム開発体制を作ろうと思います。Kotlin の静的解析ツール detekt に該当 Issue を検出させて、reviewdogDanger でプルリク差分にそれがあればコメントで指摘する GitHub Actions の設定を行いました。しかしコメントも見落とされる可能性があるので、プルリク差分の中に該当 Issue があれば CI を失敗にして、強制力を持たせてみようと思います。

注意点

  • 言うまでも無く、このような設定を行うときは、チームの合意を取りましょう。
  • この記事の内容は個人の見解です。私の所属先でこのような設定を行っているわけではないです。

detekt について

今回、プルリク差分の中にあれば指摘したい該当 Issue の検出は detekt で行います。detekt の設定やカスタムルールの開発方法の説明は、こちらの記事に譲ります。

実際に検出したい該当 Issue は違いますが、上記の記事でカスタムルールのサンプルとして紹介されていた ContextOrder という Issue がプルリク差分にあれば、CI を失敗にしてみようと思います。

// これは context: Context が第1引数なので良し
fun okFunc(context: Context, bar: String) {
    // 何かの実装
}

// これは context: Context が第2引数なので detekt の Issue になる。
// これがプルリク差分の中にあれば CI が失敗になるように設定することが、この記事の本題。
fun ngFunc(bar: String, context: Context) {
    // 何かの実装
}

関連して Konsist の紹介

今回はプルリク差分の中に該当 Issue があれば CI を失敗にしますが、プロジェクト全体に1件でも該当 Issue があれば CI を失敗にしたい場合は、Konsist が使えます。Konsist は JVM 環境の単体テストで実行時型情報をチェックするツールです。Issue があれば単体テストを失敗にすることで CI を失敗に出来ます。

Konsist の使用例

表題の設定方法

detekt の特定の Issue がプルリク差分から検出されたら、CI を失敗にするためには、レポートを SARIF 形式で出力したあとに、いくつかの UNIX コマンドを組み合わせます。それらをひとつずつ紹介します。

レポートの出力

Gradle でレポートを出力します。

./gradlew detekt

find コマンド

マルチモジュール構成かつレポートを Gradle で統合していない場合は、レポートファイルがモジュールごとに出力されるので、find コマンドでそれらを列挙します。

find . -type f -name detekt.sarif
./kgsios/build/reports/detekt/detekt.sarif
./common/build/reports/detekt/detekt.sarif
./detekt-extensions/build/reports/detekt/detekt.sarif
./feature/home/build/reports/detekt/detekt.sarif
./androidApp/build/reports/detekt/detekt.sarif
./data/remote/build/reports/detekt/detekt.sarif

今回は -exec 引数も使っています。次の jq コマンドの {} + に列挙したファイルを渡しています。
(コメントで教えて頂きました。ありがとうございます。)

関連記事

find の -exec optionの末尾につく ; と + の違い。

jq コマンド

SARIF 形式は JSON ファイルです。detekt が出力した SARIF ファイルはこのような形式です。(大幅に省略しています)

{
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "results": [
        {
          "level": "warning",
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "file:///Users/takada/work/KmmGithubSearch/androidApp/src/main/java/com/tfandkusu/kgs/MainActivity.kt"
                },
                "region": {
                  "endColumn": 6,
                  "endLine": 24,
                  "startColumn": 5,
                  "startLine": 22
                }
              }
            }
          ],
          "message": {
            "text": "Context must be the first parameter"
          },
          "ruleId": "detekt.extra-rules.ContextOrder"
        }
      ]
    }
  ]
}

よって jq コマンドで該当 Issue が含まれている URI 一覧を得ます。

jq -r ".runs[].results[] | select(.ruleId == \"detekt.extra-rules.ContextOrder\") | .locations[].physicalLocation.artifactLocation.uri" {} + # {} + は入力 SARIF ファイル名

-r--raw-output です。出力に "" が含まれなくなります。

.runs[].results[] の中から ruleIddetekt.extra-rules.ContextOrder の要素を探して、その .locations[].physicalLocation.artifactLocation.uri を得ています。

URI 形式で出力されます。

file:///Users/takada/work/KmmGithubSearch/feature/home/src/commonMain/kotlin/com/tfandkusu/kgs/feature/home/HomeActionCreator.kt
file:///Users/takada/work/KmmGithubSearch/androidApp/src/main/java/com/tfandkusu/kgs/MainActivity.kt

sed コマンド

sed コマンドによる文字列置き換えで、該当 Issue を含むファイルの URI をリポジトリルートからの相対パスに変換します。

sed "s|file://$PWD/||"

$PWD 環境変数でカレントディレクトリを取得しています。

file:///Users/takada/takada/KmmGithubSearch/androidApp/src/main/java/com/tfandkusu/kgs/MainActivity.kt

androidApp/src/main/java/com/tfandkusu/kgs/MainActivity.kt

sort コマンド

並び替えコマンドです。あとで join コマンドを使うたためにファイル一覧を昇順にします。

これまでの処理をパイプでつないで、テキストファイルに保存する

これまでの処理をパイプでつないで、テキストファイルに保存します。

find . -type f -name detekt.sarif -exec \
jq -r ".runs[].results[] | select(.ruleId == \"detekt.extra-rules.ContextOrder\") | .locations[].physicalLocation.artifactLocation.uri" {} + | \
sed "s|file://$PWD/||" | \
sort > error_files.txt

git コマンド、sort コマンド

main ブランチと差分があるファイル一覧を得て、昇順にしてからテキストファイルに保存します。

git diff --name-only origin/main | sort > change_files.txt

(ここで「プルリク差分」とは main ブランチとの差分と定義しています)

join コマンド

これまでのステップで作成された、error_files.txtchange_files.txt の共通する行を取得します。

join error_files.txt change_files.txt

この結果が空ならばプルリク差分には該当 Issue 無し。そうで無ければ該当 Issue 有りとなります。

grep コマンド

grep コマンドを使い、結果が空か否かを判定します。

! join error_files.txt change_files.txt | grep '.'

grep '.' ですべての文字列にマッチします。grep コマンドはマッチしたら成功、マッチしなければ失敗ですが、今回は結果を反転させたいです。そのため先頭に ! を付けています。

GitHub Actions のワークフローで、これまでのコマンドを実行する

GitHub Actions のワークフローでこれまでのコマンドを実行する設定はこのようになります。

check.yml
name: check
on:
  push:
    branches:
      - main
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
jobs:
  check:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v3
        with:
          distribution: 'adopt'
          java-version: '17'
      - run: ./gradlew deteKt
      - run: |
          find . -type f -name detekt.sarif -exec \
          jq -r ".runs[].results[] | select(.ruleId == \"detekt.extra-rules.ContextOrder\") | .locations[].physicalLocation.artifactLocation.uri" {} + | \
          sed "s|file://$PWD/||" | \
          sort > error_files.txt
      - run: git diff --name-only origin/main | sort > change_files.txt
      - name: ContextOrder 違反チェック
        run: "! join error_files.txt change_files.txt | grep '.'"

最後のコマンドについては name: フィールドを設定することで、CI 失敗理由を分かりやすくしています。また ! は YAML ファイルにおいてタグを表すので、ダブルクオテーションで囲んでいます。

まとめ

今回は新機能開発や機能改修のついでに、プルリク差分にある既存の問題点を修正する強制力を CI に持たせる設定方法(所謂シェル芸)を紹介しました。問題点は detekt で Issue として検出できるようにしておき、SARIF 形式のレポートファイルから jq, sed コマンドで該当 Issue を含むファイル一覧を得ます。git コマンドで main ブランチとの差分ファイル一覧を得たら、共通の行があるかを join コマンドで確認して、あれば CI を失敗にすることで、表題の設定ができることを解説しました。

0
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1