Scalaの異なるバージョンは、ソース互換性を維持しているにもかかわらず、バイナリ互換性がない場合があります。このページでは、sbt
を使用して、複数のバージョンのScalaに対してプロジェクトをビルドおよび公開する方法と、同じことを行ったライブラリを使用する方法について説明します。
sbtプラグインのクロスビルドについては、プラグインのクロスビルドも参照してください。
ライブラリがコンパイルされたScalaのバージョンを示すために使用される基本的なメカニズムは、ライブラリの名前に_<scala-binary-version>
を付加することです。たとえば、Scala 2.12.0、2.12.1、または任意の2.12.xバージョンに対してコンパイルした場合、アーティファクト名dispatch-core_2.12
が使用されます。この非常にシンプルなアプローチにより、Maven、Ant、その他のビルドツールを使用するユーザーとの相互運用性が可能になります。
2.13.0-RC1などのScalaのプレリリースバージョンおよび2.10.xより前のバージョンでは、完全なバージョンがサフィックスとして使用されます。
このページの残りの部分では、sbtがクロスビルドの一部としてこれをどのように処理するかについて説明します。
複数のバージョンのScalaに対してビルドされたライブラリを使用するには、インライン依存関係の最初の%
を%%
に2倍にします。これにより、sbt
は、ライブラリのビルドに使用されている現在のScalaバージョンを依存関係の名前に付加する必要があることを認識します。例えば
libraryDependencies += "net.databinder.dispatch" %% "dispatch-core" % "0.13.3"
Scalaの固定バージョンに対するほぼ同等の手動による代替手段は次のとおりです。
libraryDependencies += "net.databinder.dispatch" % "dispatch-core_2.12" % "0.13.3"
sbtでクロスビルドを有効にするためにプラグインは必要ありませんが、Scalaバージョンおよび異なるプラットフォーム間で並行してクロスビルドできるsbt-projectmatrixの使用を検討してください。
crossScalaVersions
設定でビルド対象のScalaのバージョンを定義します。Scala 2.10.2以降のバージョンが許可されています。たとえば、.sbt
ビルド定義では、次のようになります。
lazy val scala212 = "2.12.18"
lazy val scala211 = "2.11.12"
lazy val supportedScalaVersions = List(scala212, scala211)
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := scala212
lazy val root = (project in file("."))
.aggregate(util, core)
.settings(
// crossScalaVersions must be set to Nil on the aggregating project
crossScalaVersions := Nil,
publish / skip := true
)
lazy val core = (project in file("core"))
.settings(
crossScalaVersions := supportedScalaVersions,
// other settings
)
lazy val util = (project in file("util"))
.settings(
crossScalaVersions := supportedScalaVersions,
// other settings
)
注:二重公開を避けるために、ルートプロジェクトではcrossScalaVersions
をNil
に設定する必要があります。
crossScalaVersions
にリストされているすべてのバージョンに対してビルドするには、実行するアクションに+
をプレフィックスとして付加します。例えば
> + test
この機能を使用する一般的な方法は、単一のScalaバージョン(+
プレフィックスなし)で開発を行い、クロスビルド(+
を使用)は時折およびリリース時に行うことです。
Scalaバージョンに応じていくつかの設定を変更する方法を次に示します。CrossVersion.partialVersion(scalaVersion.value)
は、Scalaバージョンの最初の2つのセグメントを含むOption[(Int, Int)]
を返します。
これは、たとえば、Scala 2.12でマクロパラダイスコンパイラプラグインが必要であり、Scala 2.13で-Ymacro-annotations
コンパイラオプションが必要な依存関係を含める場合に役立ちます。
lazy val core = (project in file("core"))
.settings(
crossScalaVersions := supportedScalaVersions,
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n <= 12 =>
List(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
case _ => Nil
}
},
Compile / scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, n)) if n <= 12 => Nil
case _ => List("-Ymacro-annotations")
}
},
)
src/main/scala/
ディレクトリに加えて、src/main/scala-<scala binary version>/
ディレクトリがソースディレクトリとして含まれます。たとえば、現在のサブプロジェクトのscalaVersion
が2.12.10の場合、src/main/scala-2.12
がScalaバージョン固有のソースとして含まれます。
crossPaths
をfalse
に設定することにより、Scalaバージョンソースディレクトリと_<scala-binary-version>
公開規約の両方をオプトアウトできます。これは、Scala以外のプロジェクトに役立つ場合があります。
同様に、*.class
ファイルなどのビルド成果物は、デフォルトではtarget/scala-<scala binary version>
であるcrossTarget
ディレクトリに書き込まれます。
クロスビルドに純粋なJavaプロジェクトが含まれる場合は、特別な注意が必要です。次の例では、network
がJavaプロジェクトであり、core
がnetwork
に依存するScalaプロジェクトであるとします。
lazy val scala212 = "2.12.18"
lazy val scala211 = "2.11.12"
lazy val supportedScalaVersions = List(scala212, scala211)
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := scala212
lazy val root = (project in file("."))
.aggregate(network, core)
.settings(
// crossScalaVersions must be set to Nil on the aggregating project
crossScalaVersions := Nil,
publish / skip := false
)
// example Java project
lazy val network = (project in file("network"))
.settings(
// set to exactly one Scala version
crossScalaVersions := List(scala212),
crossPaths := false,
autoScalaLibrary := false,
// other settings
)
lazy val core = (project in file("core"))
.dependsOn(network)
.settings(
crossScalaVersions := supportedScalaVersions,
// other settings
)
crossScalaVersions
は、ルートなどの集計プロジェクトではNil
に設定する必要があります。crossPaths
をfalseに設定する必要があります。これにより、_<scala-binary-version>
公開規約とScalaバージョン固有のソースディレクトリが無効になります。crossScalaVersions
に正確に1つのScalaバージョン(通常はscala212
)を設定する必要があります。crossScalaVersions
に複数のScalaバージョンを設定できますが、Javaサブプロジェクトを集計することは避ける必要があります。++ <version> [command]
を使用すると、<version>
がそのcrossScalaVersions
にリストされている場合、サブプロジェクトのビルドに使用されているScalaバージョンを一時的に切り替えることができます。
例えば
> ++ 2.12.18
[info] Setting version to 2.12.18
> ++ 2.11.12
[info] Setting version to 2.11.12
> compile
<version>
は、リポジトリに公開されたScalaのバージョン、または++ /path/to/scala/home
のようにScalaホームディレクトリへのパスのいずれかである必要があります。詳細については、コマンドラインリファレンスを参照してください。
[command]
が++
に渡されると、指定された<version>
をサポートするサブプロジェクトでコマンドが実行されます。
例えば
> ++ 2.11.12 -v test
[info] Setting Scala version to 2.11.12 on 1 projects.
[info] Switching Scala version on:
[info] core (2.12.18, 2.11.12)
[info] Excluding projects:
[info] * root ()
[info] network (2.12.18)
[info] Reapplying settings...
[info] Set current project to core (in build file:/Users/xxx/hello/)
場合によっては、crossScalaVersions
の値に関係なく、Scalaバージョンの切り替えを強制したい場合があります。そのためには、感嘆符付きで++ <version>!
を使用できます。
例えば
> ++ 2.13.0-M5! -v
[info] Forcing Scala version to 2.13.0-M5 on all projects.
[info] Switching Scala version on:
[info] * root ()
[info] core (2.12.18, 2.11.12)
[info] network (2.12.18)
+
の最終的な目的は、プロジェクトをクロス公開することです。つまり、次のようにすることで
> + publishSigned
さまざまなバージョンのScalaのユーザーがプロジェクトを利用できるようにします。プロジェクトの公開の詳細については、公開を参照してください。
このプロセスをできるだけ迅速にするために、異なるバージョンのScalaに対して、異なる出力および管理された依存関係ディレクトリが使用されます。たとえば、Scala 2.12.7に対してビルドする場合、
./target/
は./target/scala_2.12/
になります./lib_managed/
は./lib_managed/scala_2.12/
になりますパッケージ化されたjar、war、およびその他のアーティファクトには、上記の公開規約セクションで説明したように、通常のアーティファクトIDに_<scala-version>
が付加されています。
これは、各バージョンのScalaに対する各ビルドの出力が互いに独立していることを意味します。sbtは、各バージョンごとに依存関係を個別に解決します。これにより、たとえば、2.11.xビルドの場合は2.11に対してコンパイルされたDispatchのバージョン、2.12.xビルドの場合は2.12に対してコンパイルされたバージョンなどが得られます。
crossVersion
設定は、公開規約をオーバーライドできます
CrossVersion.disabled
(サフィックスなし)CrossVersion.binary
(_<scala-binary-version>
)CrossVersion.full
(_<scala-version>
)デフォルトは、crossPaths
の値に応じて、CrossVersion.binary
またはCrossVersion.disabled
のいずれかです。
(Scalaライブラリとは異なり)Scalaコンパイラはパッチリリース間で前方互換性がないため、コンパイラプラグインはCrossVersion.full
を使用する必要があります。
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
ライブラリ作成者への警告:一般的に、Scala 2.13ライブラリまたはその逆のライブラリに依存するScala 3ライブラリを公開することは安全ではありません。その理由は、エンドユーザーがクラスパスに同じxライブラリの2つのバージョンx_2.13
とx_3
を持つことを防ぐためです。
異なる Scala バージョンに対する挙動を細かく制御するには、ModuleID
の cross
メソッドを使用できます。これらは同等です。
"a" % "b" % "1.0"
("a" % "b" % "1.0").cross(CrossVersion.disabled)
これらは同等です。
"a" %% "b" % "1.0"
("a" % "b" % "1.0").cross(CrossVersion.binary)
これは、デフォルトをオーバーライドして、バイナリ Scala バージョンの代わりに常に完全な Scala バージョンを使用するようにします。
("a" % "b" % "1.0").cross(CrossVersion.full)
CrossVersion.patch
は、CrossVersion.binary
と CrossVersion.full
の中間に位置し、バイナリ互換の異なる Scala ツールチェーンビルドを区別するために使用される末尾の -bin-...
サフィックスを取り除きます。
("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"
定数のクロスバージョンは、クロスビルド時に、依存関係がすべての Scala バージョンで利用できない場合や、デフォルトとは異なる規約を使用する場合に主に使用されます。
("a" % "b" % "1.0") cross CrossVersion.constant {
scalaVersion.value match {
case "2.9.1" => "2.9.0"
case x => x
}
}
sbt-release は、sbt 0.13 の +
実装をコピーペーストすることでクロスビルドサポートを実装しました。そのため、少なくとも sbt-release 1.0.10 の時点では、sbt 1.x のクロスビルド (元々 sbt-doge としてプロトタイプ化されたもの) では正しく動作しません。
sbt 1.x で sbt-release を使用してクロス公開するには、次の回避策を使用してください。
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := scala212
import ReleaseTransformations._
lazy val root = (project in file("."))
.aggregate(util, core)
.settings(
// crossScalaVersions must be set to Nil on the aggregating project
crossScalaVersions := Nil,
publish / skip := true,
// don't use sbt-release's cross facility
releaseCrossBuild := false,
releaseProcess := Seq[ReleaseStep](
checkSnapshotDependencies,
inquireVersions,
runClean,
releaseStepCommandAndRemaining("+test"),
setReleaseVersion,
commitReleaseVersion,
tagRelease,
releaseStepCommandAndRemaining("+publishSigned"),
setNextVersion,
commitNextVersion,
pushChanges
)
)
これにより、テストと公開には実際のクロス (+
) 実装が使用されます。この手法は、James Roper の playframework#4520 と、後に releaseStepCommandAndRemaining
を発明したことに由来します。