不同版本的 Scala 可能在二進位檔案上不相容,即使它們保持來源碼相容性。此頁面說明如何使用 sbt
針對多個版本的 Scala 建置和發佈您的專案,以及如何使用已執行相同操作的函式庫。
如需交叉建置 sbt 外掛程式,另請參閱交叉建置外掛程式。
用來指出函式庫編譯時所針對的 Scala 版本的基礎機制,是在函式庫的名稱後附加 _<scala-binary-version>
。例如,當針對 Scala 2.12.0、2.12.1 或任何 2.12.x 版本進行編譯時,會使用成品名稱 dispatch-core_2.12
。這種相當簡單的方法允許與 Maven、Ant 和其他建置工具的使用者進行互通性。
對於 Scala 的預先發行版本 (例如 2.13.0-RC1),以及 2.10.x 之前的版本,則會使用完整版本作為後綴。
本頁面的其餘部分說明 sbt 如何在交叉編譯中為您處理此問題。
若要使用針對多個 Scala 版本建置的函式庫,請將內嵌相依性中的第一個 %
加倍為 %%
。這會告知 sbt
,它應該將目前用於建置函式庫的 Scala 版本附加至相依性的名稱。例如
libraryDependencies += "net.databinder.dispatch" %% "dispatch-core" % "0.13.3"
針對固定版本的 Scala,幾乎相等的替代方案是手動進行
libraryDependencies += "net.databinder.dispatch" % "dispatch-core_2.12" % "0.13.3"
雖然考量使用 sbt-projectmatrix (它能夠跨 Scala 版本和不同平台平行進行交叉建置),但在 sbt 中啟用交叉建置不需要外掛程式。
在 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)
會傳回 Option[(Int, Int)]
,其中包含 Scala 版本的前兩個區段。
如果您包含需要 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
檔案) 會寫入 crossTarget
目錄,其預設值為 target/scala-<scala binary version>
。
當交叉建置涉及純 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
中應該只有一個 Scala 版本,以避免重複發佈,通常為 scala212
。crossScalaVersions
中可以有多個 Scala 版本,但必須避免彙總 Java 子專案。您可以使用 ++ <version> [command]
暫時切換目前用於建置子專案的 Scala 版本,前提是 <version>
列在其 crossScalaVersions
中。
例如
> ++ 2.12.18
[info] Setting version to 2.12.18
> ++ 2.11.12
[info] Setting version to 2.11.12
> compile
<version>
應該是發佈到儲存庫的 Scala 版本,或是 Scala 主目錄的路徑,如 ++ /path/to/scala/home
中所示。如需詳細資料,請參閱命令列參考。
當將 [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/)
有時候,您可能想要強制切換 Scala 版本,而不管 crossScalaVersions
值為何。您可以將 ++ <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
變體。
相反地,我們有 CrossVersion.for2_13Use3
,當 scalaVersion
為 2.13.x 時,會使用函式庫的 _3
變體。
("a" % "b" % "1.0") cross CrossVersion.for2_13Use3
給函式庫作者的警告: 發布依賴 Scala 2.13 函式庫的 Scala 3 函式庫或反之,通常是不安全的。原因是為了防止您的最終使用者在他們的類別路徑中擁有同一個 x 函式庫的兩個版本 x_2.13
和 x_3
。
您可以透過在 ModuleID
上使用 cross
方法,來精細地控制不同 Scala 版本之間的行為。以下是等效的:
"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-release 和 sbt 1.x 進行交叉發布,請使用以下變通方法:
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
。