Scalaのcompiler pluginをCrossVersion.fullにするかCrossVersion.binaryにするか

昨日のwartremoverのやつの続き、というか、Scalaのcompiler pluginに関する別の話。

いきなり雑に説明に入りますが

  • Scalaの普通のlibraryは、scala-libraryのjarのみに依存する
  • scala-libraryのjarは、たとえば基本的に2.13.xなら、xの部分が変わっても、99%以上バイナリ互換を壊さない
  • マクロやリフレクションを使う場合はscala-reflectのjarに依存することになり、それはscala-libraryよりは互換壊す
  • compiler pluginなどのcompilerに依存するものは、scala-compilerのjarに依存することになるわけだが、それはおそらくscala-libraryやscala-reflectよりもわりと互換が壊れる
  • sbt側にはCrossVersionという概念がある
  • デフォルトではCrossVersion.binaryであり、通常のライブラリは "groupId" % "artifactId" % "version" とあったときに、artifactIdの末尾に "foo_2.13" などと、Scalaのversion(で一番末尾含まない)が付与されている
  • これらの仕組みによって、たとえば普通Scala 2.13.0使ってpublishしたライブラリは、Scala 2.13.1がリリースされても、publishし直す必要がない
  • しかし、scala-compilerなどに依存したものは、CrossVersion.fullにしてartifactIdを foo_2.13.1 などとしてpublishする方法もある。つまりscalaのversion毎に互換が壊れる可能性がある場合は、こちらにしておいたほうが安全である
  • 実際に2.12.9 => 2.12.10、2.13.0 => 2.13.1あたりで互換が壊れて、大抵のcompiler plugiinがCrossVersion.binaryでpublishされていたので問題になった
  • そのタイミングでwartremover本体もCorssVersion.fullでpubilshするように変えた
  • fullでpublishすると、原理的に互換が壊れない(compiler pluginならcompile時に変なエラーで死なない)、というメリットがある一方、Scalaの新しいversionがリリースされるたびに毎回リリースする必要が出てきて面倒である
  • 頑張って使う側でsbtの設定をすれば、CrossVersion.fullでも、新しいversionがリリースされてないときに、互換さえあれば微妙に古いものをあえてつかうようにするのは原理的には可能であるはず
  • 原理的には可能ではあるが、理解して設定するのはわりと難しい(?) https://github.com/wartremover/wartremover-contrib/issues/49#issuecomment-609609716
  • また、wartremoverとwartremover-contribの関係上「wartremover側はscala-compilerが互換壊したら明らかに毎回リリースする必要があるが、wartremover-contribは基本的にwartremoverのclassを参照して、scala-compiler内部の互換が壊れやすい部分あまり参照しないから、contribはCrossVersion.fullではなくCrossVersion.binaryでいいのでは?」みたいなところもある

され、これらを色々考慮した結果どうしたか?というと

などをやってみた状態なのですが、これも何が正解なのかのベストプラクティスが存在していなくて難しいですね・・・。上記で説明したような「fullとbinary両方でpublish」は、自分が知るかぎり、それをやっているライブラリは存在していない気がしますが、なにかもっといい感じの仕組みがあれば教えてください