クロスビルドの設定

このページではクロスビルドの設定を説明する。概要はクロスビルドを読んでほしい。

クロスビルドされたライブラリの使用

複数の Scala バージョン向けにビルドされたライブラリを使用するには、ModuleID の最初の %%% にする。これにより sbt はライブラリのビルドに使用中の Scala バージョンをライブラリ依存性のモジュール名に追加する。例:

libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.4"

これをほぼ等価の ModuleID に、特定の Scala に対して手で書き換えると以下のようになる:

libraryDependencies += "org.typelevel" % "cats-effect_3" % "3.5.4"

Scala 3 専用のクロスバージョン

Scala 3 でアプリケーションを開発している場合、Scala 2.13 のライブラリを使用できる:

("a" % "b" % "1.0").cross(CrossVersion.for3Use2_13)

%% を使用するのと等価だが、scalaVersion が 3.x.y のときにライブラリの _2.13 バリアントに解決する点が異なる。

逆に、scalaVersion が 2.13.x のときにライブラリの _3 バリアントを使用するには CrossVersion.for2_13Use3 がある:

("a" % "b" % "1.0").cross(CrossVersion.for2_13Use3)

Warning

ライブラリ作者への警告: Scala 2.13 ライブラリに依存する Scala 3 ライブラリ、またはその逆を公開するのは一般的に安全ではない。エンドユーザーのクラスパスに scala-xml_2.13scala-xml_3 のように同じライブラリの 2 バージョンが混在する可能性がある。

クロスビルドされたライブラリの使用について(詳細)

ModuleIDcross メソッドを使うと、異なる Scala バージョンに対する動作を細かく制御できる。以下は等価である:

"a" % "b" % "1.0"
("a" % "b" % "1.0").cross(CrossVersion.disabled)

以下も等価である:

"a" %% "b" % "1.0"
("a" % "b" % "1.0").cross(CrossVersion.binary)

Scala のバイナリバージョンではなくフルバージョンを常に使用するようにデフォルトをオーバーライドする:

("a" % "b" % "1.0").cross(CrossVersion.full)

CrossVersion.patchCrossVersion.binaryCrossVersion.full の中間的存在で、末尾の -bin-... サフィックスを削除して、バイナリ互換な Scala ツールチェーンの開発版をサポートする。

("a" % "b" % "1.0").cross(CrossVersion.patch)

CrossVersion.constant は固定値を指定する:

("a" % "b" % "1.0").cross(CrossVersion.constant("2.9.1"))

以下と等価である:

"a" % "b_2.9.1" % "1.0"

Project matrix

sbt 2.x では project matrix が導入され、クロスビルドをサブプロジェクトとして表すことで、並列実行できるようになった。

build.sbt

lazy val scala3 = "3.8.3"
lazy val scala2_13 = "2.13.18"

organization := "com.example"
scalaVersion := scala3
version      := "0.1.0-SNAPSHOT"

lazy val core = (projectMatrix in file("core"))
  .settings(
    name := "core",
  )
  .jvmPlatform(scalaVersions = Seq(scala3, scala2_13))
  .nativePlatform(scalaVersions = Seq(scala3, scala2_13))
  // .jsPlatform(scalaVersions = Seq(scala3))

// optional
lazy val core3 = core.jvm(scala3)
lazy val coreNative3 = core.native(scala3)

project/plugins.sbt

addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.11")

サプブロジェクトの生成

読み込み時に project matrix は、プラットフォームと Scala バージョンの組み合わせをサブプロジェクトへと展開する。

sbt:cross-root> projects
[info]     core
[info]     core2_13
[info]     coreNative
[info]     coreNative2_13
[info]   * cross-root

規約により、マトリックス中の JVM Scala 3 バリアントは、coreのように接尾辞を持たない名前が与えられる。

sbt:cross-root> core/run
[info] running (fork) example.main
Hello
[success] ok

build.sbt 内で、サブプロジェクトを参照するには、jvm(...)js(...), もしくは native(...) を呼ぶことができる:

// For Scala
lazy val core3 = core.jvm(scalaVersion = scala3)

// For Java
lazy val intf0 = intf.jvm(autoScalaLibrary = false)

Virtual axis

マトリックスの各組み合わせは ProjectRow と呼ばれ、ProjectRow は、VirtualAxis の列によって構成されている。

final class ProjectRow(
    val autoScalaLibrary: Boolean,
    val axisValues: Seq[VirtualAxis],
    val process: Project => Project
)

object VirtualAxis:

  /**
   * WeakAxis allows a row to depend on another row with Zero value.
   * For example, Scala version can be Zero for Java project, and it's ok.
   */
  abstract class WeakAxis extends VirtualAxis

  /** StrongAxis requires a row to depend on another row with the same selected value. */
  abstract class StrongAxis extends VirtualAxis
end VirtualAxis

VirtualAxis はさらに、WeakAxisStrongAxis に分岐する。Scala バージョンは、弱い軸の例で、ある Scala バージョンを持つ列は、Scala バージョンを持たない Java の列に依存することができる。一方、同一のプラットフォーム間でしか依存することができないため、プラットフォームは強い軸として定義される。

例えば、SparkAxis は以下のように定義できる:

project/Axis.scala

import sbt.*

case class SparkAxis(idSuffix: String, directorySuffix: String)
  extends VirtualAxis.WeakAxis

具体的な SparkAxis の使用方法に関しては VirtualAxis に対するクロスビルドのレシピ参照。

公開規約

ライブラリのコンパイルに使用した Scala のバージョンを示すため、Scala ABI(アプリケーションバイナリインターフェース)バージョンをサフィックスとして使用する。例えば、アーティファクト名 cats-effect_2.13 は Scala 2.13.x が使用されたことを意味し、cats-effect_3 は Scala 3.x が使用されたことを意味する。このシンプルな方式で Maven、Ant などのビルドツールユーザーとの相互運用が可能である。2.13.0-RC1 のようなプレリリース版の Scala では、フルバージョンが ABI バージョンとして扱われる。

crossVersion セッティングで公開規約をオーバーライドできる:

  • CrossVersion.disabled(サフィックスなし)
  • CrossVersion.binary (_<scala-abi-version>)
  • CrossVersion.full (_<scala-version>)

デフォルト値は crossPaths に応じて CrossVersion.binary または CrossVersion.disabled に設定される。ただし、Scala 標準ライブラリと異なり Scala コンパイラはパッチリリース間で前方互換性がないため、コンパイラプラグインは CrossVersion.full を採用することが推奨される。