Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

sbtでサブプロジェクトをクロスコンパイルする

大きな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のバージョンを上げていく場合、依存グラフの末端にあるmoduleDmoduleAから上げていくことになるはず。

ところが、例えばmoduleDのScalaバージョンを2.13に上げるとmoduleB moduleCのバージョンを同時に上げなければならなくなり、分割統治できない。

このような場合どうすればよいだろう?

クロスコンパイル

幸いにも、sbtにはクロスコンパイル機能が搭載されている。クロスコンパイルとは、異なるライブラリや処理系のバージョンごとにコンパイルして成果物を生成することであり、ここではScalaバージョンのクロスコンパイルを行うことができる。Scalaバージョンのクロスコンパイルを行うと、Scala 2.12向けの成果物とScala 2.13向けの成果物とが同時に得られるので、Scalaのバージョンのどっちを使っているプロジェクトからも成果物を依存できるようになる。

www.scala-sbt.org

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のバージョンごとにクロスコンパイルすることで、非互換な変更をサブプロジェクトごとに局所化しつつバージョンを上げていける
★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?