scalafmtやscalafix適用結果をGitHubのpull reqで自動でsuggestする

一言でいうとreviewdogの紹介をするだけなのですが、すごくタイミングよく罠にハマった?ので、それの暫定的な回避も書いておきます。

前提の解説

github.com

個人的にはここ数年、いくつかのprojectで

  • scalafmtはほぼ全部使ってる。projectによって、以下のようなことしてる
    • チェックはするだけで、修正は人間がやる
    • 全自動でformat後のものをbotがpull reqのbranchに強制push
    • pull reqに特定のコメントしたらbotが自動push
  • scalafix
    • チェックして、エラーにするか、警告出す
    • 警告の表示にreviewdog使ってる場合もある
    • cronで一定時間毎、あるいはpull reqで特定のコメントをトリガー、あるいはworkflow_dispatchでscalafix起動させる(適用したpull req出す)

などをやっているのですが、reviewdogのsuggestは機能使ってませんでした。

上記の運用でも大抵は十分なのと、動かし方理解するのと動作確認が面倒だっただけなのですが、やってみたら思った以上に特別な仕組み必要なくて簡単でした。

特別な仕組み必要なくて とは、つまり

「CI上で コード修正した状態 を作って(gitでdiff出る状態作って)、reviewdogの特定のコマンド呼ぶだけ」

でいいんですね。何かこちらで特別なフォーマットで頑張って出力する必要とかないので、言語やツールに全く関係なく使えます。

むしろ、suggestではなく、警告やエラーをインラインコメントするほうが、原理上、言語毎の警告やエラーの出力のparserが必要で面倒ですね。 *1

つまり、最低限scalafmtをsuggestするだけなら、以下で動きました。 *2

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10 # ここは適当に調整しましょう
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-java@v3
      with:
        java-version: 11 # あくまで例で、個々のprojectによって適切なversion
        distribution: adopt
    - run: sbt scalafmtAll scalafmtSbt # Checkの方ではない方を使うのがポイント
    - run: git diff --exit-code # このあたり雑だが、もう少し工夫していいかも
    - uses: reviewdog/action-suggester@v1
      if: failure()
      with:
        tool_name: scalafmt

reviewdog suggest

それで、これをいくつかに導入していこうかと思ったら、昨日まで動いていたのに、急に動かなくなりました。 reviewdogのinstall scriptがGitHub側の変更に影響受けたみたいです。

(reviewdogのscriptが、GitHubの普通のAPIじゃなくて、GitHub側の実装依存なものを呼び出してる?)

https://github.com/reviewdog/reviewdog/issues/1380

なので、reviewdogほぼ手動でinstallしてsuggest呼び出しも試したりしたので、それのパターンも貼っておきます。 正しいinstall方法が復活したとしても、原理的にはこのほうがversion固定で余計なアクセスしなくて安定するかも・・・? ubuntu-latestだと Linux_x86_64 なので、そこは固定してしまってますが、必要なら適当に変えましょう。 (細かいところ雑なので、そもままカレントディレクトリに直接展開してるが、必要ならもっと丁寧にやったほうが良い)

https://github.com/wartremover/wartremover/commit/913a7746a2223ac95809ad8514c576354e26ce53

    # ここより前は省略
    - if: ${{ (github.event_name == 'pull_request') && failure() }}
      run: |
        REVIEWDOG_VERSION="0.14.1"
        FILE_NAME="reviewdog_${REVIEWDOG_VERSION}_Linux_x86_64"
        wget -q https://github.com/reviewdog/reviewdog/releases/download/v${REVIEWDOG_VERSION}/${FILE_NAME}.tar.gz
        gunzip ${FILE_NAME}.tar.gz
        tar -xf ${FILE_NAME}.tar
        chmod +x ./reviewdog
        ./reviewdog -version
        export REVIEWDOG_GITHUB_API_TOKEN=${{ secrets.GITHUB_TOKEN }}
        TMPFILE=$(mktemp)
        git diff > "${TMPFILE}"
        ./reviewdog \
          -name="scalafmt" \
          -f=diff \
          -f.diff.strip=1 \
          -reporter="github-pr-review" \
          -filter-mode="diff_context" \
          -fail-on-error="false" \
          -level="warning" <"${TMPFILE}"

scalafmtの例で示しましたが、scalafixなら、単に警告出す系のlinterのruleではなく、直接書き換えるruleを scalafixAll で実行してしまって、その後のreviewdogの呼び出し方は完全に同じでいけるはずです。

-name="scalafmt" はsuggestの表示に使われる部分なので、適切に変えてください。

*1:大抵は用意されてるが。しかし、Scala 2から3のように、言語側の出力が変更されたら追従しないといけないし、parserがバグってたらそれを修正する必要などがあるが、suggestするだけならすごく仕組みがシンプルなので、そういった心配がない、ということ

*2:余談ですが、個々のgithub actionsは雑にメジャーversionのtag指定ですが、出来ればちゃんとhash指定しましょう