個人的に、すごい細かい使い捨て含めるとおそらくもう1000個くらいはrule書いたことあるので、おそらく現状では日本一scalafix rule書いていると思うのですが、 慣れるとそのくらい気軽に書けてすぐ役に立って便利なので、既存の他人が書いたruleを使うだけではなく、独自に書くことを強くすすめていきたいです。
しかし、それにあたって、sbtのproject構成が思ったより面倒なので、それの解説をします。
タイトルの通りあくまで「同じsbt project内部に置く」場合の話をします。 gitやsbtのprojectそのものを完全に分けてしまえば、もちろん考えることが減って楽になる点もありますが、分けることによるデメリットもあるので、個人的には(scalafixに限らないですが)わりとmonorepoというか、同じprojectで頑張ることを推奨したいです。
(OSSにはしないような社内共通ruleを複数repoで参照したいんだ〜、という需要があるなら話が別ですが)
前提としてややこしいのが
- これを書いてる2024年6月のscalafix version 0.12.1時点で、scalafixのruleは必ずScala 2.12か2.13で書く必要がある
- もちろん将来的にはScala 3対応されるはず(詳細時期未定?)
- しかし、ruleを書くときのversionと、それを使って解析や書き換えする対象のscalaコードのversionは異なっていて良い
- 余談ですが、同じlinterでもwartremoverは必ずscala versionが一致している必要があるため、そこは明らかにscalafixとwartremoverで異なる箇所です
表に示すとこう
2.10以前 | 2.11 | 2.12 | 2.13 | 3 | |
---|---|---|---|---|---|
2.11以前 | ❌ | ❌ | ❌ | ❌ | ❌ |
2.12 | 🔺 | ⭕ | ⭕ | ⭕ | ⭕ |
2.13 | 🔺 | ⭕ | ⭕ | ⭕ | ⭕ |
3 | ❌ | ❌ | ❌ | ❌ | ❌ |
改めて表の説明
- 2.12で書くか、2.13で書くか?に関係なく、解析や書き換えする対象のコードに対しての対応versionは全く同じです
- 自分が知らないだけで、実はすごく細かい違いがある場合は教えてください
- 繰り返しになりますが、2.12か2.13以外のversionでruleを書くのは今は不可能です
- Scala 2.10以前が🔺なのは、semanticdbがpublishされていないので、SemanticRuleを書くのがおそらく不可能です
- https://repo1.maven.org/maven2/org/scalameta/ ここの「
semanticdb-scalac_ここにScalaバージョン
」がsemanticdb
- https://repo1.maven.org/maven2/org/scalameta/ ここの「
- Scala 2.10以前でも、parseさえ可能なら、実質2.8や2.9の太古のversionだろうとも、原理上はSyntacticRuleなら適用可能です。よって🔺としています
さて、ここまである程度前置きで、ここからが本題なのですが、皆さんは(scalafix導入したいprojectにおいて)どのscala versionで書いていますか? それによって同じproject内でscalafixを導入する際に、sbtのproject構成が大きく変わります。 さらに言い方を変えるというか具体的な話としては、特定のversionの組み合わせの場合は、以下のsbt plugin使うのが実質必須というか、個人的には強く推奨することになります
sbt-projectmatrixを使わなくても済む場合の例 (使わなくて済むが、とはいえ、どうせなら使った方がいい場合もある)
- Scala 2.12あるいは2.13の範囲でcross build、あるいはそれのどちらか単体でbuild
- Scala 3あるいはScala 2.11だが、一切複数のscala versionでcross buildする予定がない
- ただし、結構ややこしいので、この場合でもsbt-projectmatrix導入は割と推奨ではある
- Scala 2.13と3でcross buildしてるが、Scala 2.13の場合だけscalafix実行できればいいや、という場合
- 絶妙な状態ではあるが、まぁそれでいいなら、ひとまずsbt-projectmatrix回避可能だが、それでもScala 3の場合にscalafix関連を無理やり無効化する特別な設定が必要だから、結局sbtの知識必要
- 自分が関わっているある程度のprojectでこれをやってる場合があるが、最初からsbt-projectmatrix導入すればよかったか?今からでも導入するか?と悩んでる
sbt-projectmatrixがほぼ必須な場合の例
- Scala 2.13と3でcross buildしていて(あるいはそれに追加で2.12もあって)、なおかつ全てのscala versionで必ずしっかりscalafix実行したい
- Scala 2.12と2.13でcross buildしているだけだが、ruleは2.13のみで記述して、2.12では記述したくない
そもそもsbt-projectmatrixの説明をしていませんが、その前に現在のsbt 1.xの標準のcross buildの方法から簡単に振り返りましょう。
sbt 1.xというかそれ以前の0.x時代からですが、 ++ 2.13.14
という ++
に続けてScala versionを入力すると、そのproject全体のscala versionを切り替える機能があります。
場合によっては
「簡単に切り替えれて便利〜〜〜」
と思うかもしれませんが、これは状態を変えることになり、単純には複数のscala versionのbuildを並列実行できない、必要ない場所まで変わったりする、などのデメリットがあり、特定の用途でかなり不便です。 例えば、sbt pluginは2.12でのみbuildしたいというかする必要があるけれど、他の部分は2.12、2.13、3全部でcross buildしたい、といった場合ですね。
独自のrootを作ってそこに特定のprojectだけaggregateして〜、などを例えば現状のscalikejdbcなどでは実施していますが、
結局不便なことも多く、 そういう面倒な場面に遭遇したら、ある意味ではさっさとsbt-projectmatrixを導入した方が結果的には楽になるかもしれません。
sbt標準の ++
には、その他細かい機能というか言及するべきことがあるのですが、自分の経験上、どう頑張っても不便なときは不便なので ++
などのsbt標準の機能だけで無理やり?頑張るならば、さっさとsbt-projectmatrixを導入した方がいいと思います。
sbt-projectmatrixは、状態を持たせることをやめて、半自動でscalaのversionごとにsub projectを大量に作成します。 すると
「このprojectは2.12と2.13、このprojectは2.12だけ、このprojectは2.12と2.13と3、このprojectは3だけ」
といった、cross buildするscala versionが、sub projectごとにどんなに複雑になっていても、 とにかく、自動生成される全てをaggregateするrootでcompileやtestすれば、全部一気にbuildその他が可能なので、色々と楽になります。
このplugin機能相当がsbt 2.xからは標準搭載される話があるらしいです。
さて、ある程度前提知識のある人は?頭のいい人は?ここまでの説明で、多少scalafix導入時にsbt-projectmatrixが必要になることに察しがついたかもしれません。
つまり
「Scala 3でscalafix ruleは書けないのに、 ++ 3.3.3
で一気に全部をScala 3に切り替えたら、rule記述部分のsub projectがbuildエラーになりますよね???」
といった事態に遭遇します。もちろん、すごく工夫すれば、scalafixのruleのprojectだけ2.13にしたまま他を3に上げるのは原理上は可能ですが、それをやるのはとてもややこしく辛いので、繰り返しになりますが全くお勧めしません。
ここで
「Scala 3あるいはScala 2.11だが、一切複数のscala versionでcross buildする予定がない」
という場合にsbt-projectmatrixを使わなくて済むかもしれない件の説明ですが
- rule記述のprojectでは
scalaVersion := "2.13.14"
を設定 - それ以外の普通のprojectでは
scalaVersion := "3.3.3"
を設定 ++
などを使って一切scala version切り替えを行わない。build.sbtで指定したversionでずっと固定である
という状態なら、おそらく可能だと思います。
さて、大体書きたいことは書いたというか、原理や理屈を説明して、具体的なコード例を示していないですが、そもそもscalafix公式のgiter8 templateがsbt-projectmatrix使うようになっていますし、
一旦今回の説明はこれで終わりにします。
(同じproject内部で)scalafixを導入するにあたって、sbt-projectmatrix以外にも細かいbuild設定のテクニックはあるのですが、それはまた気が向いたら別の時に説明します。