尽管保持了源代码兼容性,但不同版本的 Scala 可能是二进制不兼容的。本页介绍如何使用 sbt
针对多个版本的 Scala 构建和发布项目,以及如何使用针对多个版本的 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 中不需要任何插件来启用交叉构建,但请考虑使用 sbt-projectmatrix,它能够跨 Scala 版本和不同平台并行交叉构建。
在 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 版本的前两个段的 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
文件)将写入 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 编译的 Dispatch 版本,用于您的 2.11.x 构建,针对 2.12 编译的版本,用于您的 2.12.x 构建,等等。
crossVersion
设置可以覆盖发布约定
CrossVersion.disabled
(无后缀)CrossVersion.binary
(_<scala-binary-version>
)CrossVersion.full
(_<scala-version>
)默认值是 CrossVersion.binary
或 CrossVersion.disabled
,具体取决于 crossPaths
的值。
因为(与 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
之间,它会剥离任何尾随的-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"
常量交叉版本主要用于交叉构建,并且依赖项并非所有 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
。