"scalafixAll --check" するだけのsbt pluginを作った

正確にはそれをするだけのつもりで作りはじめたら、微妙に思い出して追加のtask入れたのでそれだけではないのですが、とりあえず scalafixAll --check するだけの scalafixCheckAll taskの話をします。

https://github.com/xuwei-k/scalafix-check/commit/6b4127d96e0626b7493d71eb8cfa0c47f272136a

scalafixをsbtから使うなら、公式のsbt-scalafix pluginをほぼ必ず使うわけです。

sbt-scalafixは、いくつかのkeyが定義されていますが、割とInputKeyが多いです。InputKeyというのは引数を取るInputTask定義用のKeyです。引数を取らない普通のTask用のKey定義はTaskKeyです。

https://github.com/scalacenter/sbt-scalafix/blob/f04ca84283693ed0a2970226d46e36d9d03ae881/src/main/scala/scalafix/sbt/ScalafixPlugin.scala#L35-L90

https://www.scala-sbt.org/1.x/docs/Input-Tasks.html

InputKeyにして柔軟に色々やることによって、定義する側は無闇に大量にkeyを定義しなくて済むし、使う側も(定義する側がparser使ってると)タブ補完が効いたりもするし、かなり便利に柔軟に指定出来て便利です。

sbtの正しい良い使い方の例だと思います。

じゃあなぜわざわざそれをそのまま使わずに、あえて定義してsbt pluginにまでしたか?というと

「InputTaskのままでは、他のTaskと同時に指定して並列実行出来ない」

というのが1番の理由です。

同時に実行したい理由は、たいした差ではないけど、その方が原理上は速く終わる可能性があるからです。*1

つまり、lint系のtaskをまとめて実行したい場合にCIのyamlなどに

sbt scalafmtCheckAll "scalafixAll --check"

と書けば、直列実行になりますが、sbt標準の all というものを使って同時実行したくなりますが、そのままでは不可能ですね? --check なしで

sbt "all scalafmtCheckAll scalafixAll"

としても、以下のようなエラーになります

[error] Cannot mix input tasks with plain tasks/settings.  Input task(s):
[error]     ScopedKey(Scope(Select(ProjectRef(ここは省略), Zero, Zero, Zero),scalafixAll)
[error]  Task(s)/setting(s):
[error]     ScopedKey(Scope(Select(ProjectRef(ここは省略)), Zero, Zero, Zero),scalafmtCheckAll)
[error] 
[error] all scalafmtCheckAll scalafixAll
[error]                                 ^

これはこれで別にbuild.sbtなどに数行追加すればある意味Taskにするのは可能なわけですが

(実際にGitHubを検索した例)

とはいえ、最短で数行とはいえ、毎回書くのも面倒なので、あえてsbt pluginにしました。

また、厳密にはこれcommandのaliasとして定義するならともかく、複数のsub projectがある場合にTaskとして定義するとscalafixが有効になってるsub projectの全てに定義、的なことをしないといけないはずで、地味に面倒です。

また、ついでに、よく入れる細かい設定として

  • SemanticRuleは実行せずにSyntacticRuleだけ実行したい( "--syntactic" の引数渡す)
  • .scalafix.conf のruleはアルファベット順でsortして書く決まりにすることが多いが、それのチェックしたい

みたいなことがあったので、そういう地味なtaskを入れました。

sbt-scalafixに直接依存せずに無理やりリフレクションで動的にsbt-scalafixの依存があるかどうか?的な地味な工夫もしました (この工夫が必要だったのか?は若干謎)

*1:失敗するパターンにおいては、余計なtask実行されるデメリットもありますが