大きなsbtプロジェクトのScalaのバージョンアップを行いたいが、共通して依存されているモジュールがあるとそこがボトルネックになって結局全てのモジュールのScalaバージョンを上げなければならなくなってしまう。これをクロスコンパイルで解消したメモ。
サブプロジェクト
Scalaのビルドツールsbtにはサブプロジェクト機能がある。サブプロジェクトにすると、その単位でScalaのバージョンを切り替えたり、依存性を細かく切ることができる。おおまかに言えば、サブプロジェクトにした単位でJARファイルができるといってもよいはず。 コンパイルの単位も小さくできるので、大きめのプロジェクトでは重宝する機能だ。
例としてこのようなプロジェクト構造になっているとする:
flowchart TB; root -- depends --> moduleA; root -- depends --> moduleB; root -- depends --> moduleC; moduleB -- depends --> moduleD; moduleC -- depends --> moduleD;
仮に全てのScalaバージョンが2.12だとする。ここで部分的にScalaのバージョンを上げていく場合、依存グラフの末端にあるmoduleD
やmoduleA
から上げていくことになるはず。
ところが、例えばmoduleD
のScalaバージョンを2.13
に上げるとmoduleB
moduleC
のバージョンを同時に上げなければならなくなり、分割統治できない。
このような場合どうすればよいだろう?
クロスコンパイル
幸いにも、sbtにはクロスコンパイル機能が搭載されている。クロスコンパイルとは、異なるライブラリや処理系のバージョンごとにコンパイルして成果物を生成することであり、ここではScalaバージョンのクロスコンパイルを行うことができる。Scalaバージョンのクロスコンパイルを行うと、Scala 2.12向けの成果物とScala 2.13向けの成果物とが同時に得られるので、Scalaのバージョンのどっちを使っているプロジェクトからも成果物を依存できるようになる。
Scalaバージョンのクロスコンパイルを行うには、crossScalaVersion
キーにScalaバージョンのSeqを設定する:
val moduleD = (project in file ("moduleD")).settings ( crossScalaVersion := Seq("2.12.16", "2.13.1") )
こうすることにより、コンパイル時に自動的に複数のScalaでコンパイルが行なわて複数の成果物が用意される。ただし、コンパイルするときは+ moduleB / compile
のように頭に+
を付けて「クロスコンパイルしてください」と指示する必要がある。実用上は、開発時には普通のcompileを行い、リリース時に+ publishLocal
などして全バージョンでのビルドチェックを行うというのが生産的だろう。
クロスコンパイルしたプロジェクトに依存する
サブプロジェクトがクロスコンパイルしていても、それに依存する親プロジェクトからは普通にdependsOn
で依存性を追加できる:
val moduleB = (project in file ("moduleB")).dependsOn(moduleB)
ただし、dependsOn
で依存性を追加する前に、moduleB
自体のScalaバージョンを確定させておかないと、sbtはどちらの成果物を選べばよいかわからなくてコンパイルできなくなるのでこのように書いておく:
val moduleB = (project in file ("moduleB")) .settings(scalaVersion := "2.13.1") .dependsOn(moduleB)
これによりmoduleB
は2.13用の成果物に依存するようになり、ちょっとずつ順にScalaのバージョンを上げられるようになる。
flowchart TB; root -- depends --> moduleA; root -- depends --> moduleB_2.13; root -- depends --> moduleC_2.12; moduleB_2.13 -- depends --> moduleD_2.13; moduleC_2.12 -- depends --> moduleD_2.12;
まとめ
- sbtはサブプロジェクトによってプロジェクトとその成果物を分割でき、他のプロジェクトから参照できるようになる
- sbtはクロスコンパイル機能を持っており、ScalaやJavaのバージョンを切り替えつつ成果物を生成できる
- サブプロジェクトをScalaのバージョンごとにクロスコンパイルすることで、非互換な変更をサブプロジェクトごとに局所化しつつバージョンを上げていける