Scala 发布版本的二进制兼容性

语言

当两个版本的 Scala 二进制兼容时,可以在一个 Scala 版本上编译项目,并在运行时链接到另一个 Scala 版本。安全的运行时链接(仅限于此!)意味着,在混合场景中执行程序时,JVM 不会抛出(LinkageError 的子类),假设在编译和运行同一版本的 Scala 时不会出现此类错误。具体来说,这意味着您可以在运行时类路径上拥有使用与您编译时不同的 Scala 版本的外部依赖项,只要它们是二进制兼容的即可。换句话说,与在同一版本的 Scala 上编译和运行所有内容相比,在不同的二进制兼容版本上进行单独编译不会带来问题。

我们使用MiMa自动检查二进制兼容性。我们努力为标准库的行为(与仅链接相对)维持类似的不变性,但不会机械地检查这一点(Scala 不是证明助手,因此其类型系统无法实现这一点)。

请注意,对于 Scala.js 和 Scala Native,二进制兼容性问题会导致在构建时出现错误,而不是运行时异常。它们发生在各自的“链接”阶段:Scala.js 的 {fast,full}LinkJS 和 Scala Native 的 nativeLink

向前和向后

我们区分向前和向后兼容性(将它们视为版本序列的属性,而不是单个版本的属性)。保持向后兼容性意味着在旧版本上编译的代码将与使用较新版本编译的代码链接。向前兼容性允许您在新版本上编译并在旧版本上运行。

因此,向后兼容性排除了删除(非私有)方法,因为旧版本可以调用它们,而不知道它们会被删除,而向前兼容性不允许添加新的(非私有)方法,因为较新的程序可能依赖于它们,这将阻止它们在旧版本上运行(私有方法在此处也得到豁免,因为它们的定义和调用站点必须在同一编译单元中)。

保证和版本控制

对于 Scala 2,次要版本是版本中的第三个数字,例如 v2.13.10 中的 10。主要版本是第二个数字,在我们的示例中为 13。

Scala 2 保证在单个主要版本内的次要版本中实现向后和向前兼容性。

这些是严格的约束,但自 Scala 2.10.x 以来,它们对我们来说一直很有效。它们并没有阻止我们在次要版本中修复大量问题。到目前为止,该策略仍然适用于 Scala 2,尽管 存在一个提案来删除向前兼容性保证

对于 Scala 3,次要版本是版本中的第二个数字,例如 v3.2.1 中的 2。第三个数字是补丁版本。主要版本始终为 3。

Scala 3 保证在单个次要版本内的补丁版本中实现向后和向前兼容性。特别是,这适用于整个 长期支持 (LTS) 系列(从 Scala 3.3.x 开始)。

它还保证在整个 3.x 系列中实现向后兼容性,但不能向前兼容。这意味着使用任何 Scala 3.x 版本编译的库都可以用于使用任何 Scala 3.y 版本(其中 y >= x)编译的项目中。

此外,Scala 3.x 提供了与 Scala 2.13.y 相关的向后二进制兼容性。使用 Scala 2.13.y 编译的库可以在使用 Scala 3.x 的项目中使用。此策略不适用于实验性 Scala 2 特性,其中尤其包括

一般来说,这些保证都不适用于实验性特性和 API。

检查

对于 Scala 库工件(scala-libraryscala-reflectscala3-library),这些保证通过 MiMa 进行机械检查。

上述策略扩展到由特定 Scala 编译器版本编译的库。我们尽一切努力保留编译器生成工件的二进制兼容性。但是,这无法通过机械方式进行检查。因此,由于错误或不可预见的后果,使用不同编译器版本重新编译库可能会影响其二进制 API。我们无法保证这种情况永远不会发生。

我们建议库作者在发布之前使用 MiMa 自行验证小版本的兼容性。

TASTy 和 Pickle 兼容性

二进制兼容性是一个与目标平台(JVM、Scala.js 或 Scala Native)的链接时间相关的概念。TASTy 和 Pickle 兼容性类似,但适用于 Scala 编译器的编译时间。TASTy 适用于 Scala 3,Pickle 适用于 Scala 2。

如果某个库使用较旧版本的编译器编译,我们称该库具有向后 TASTy/Pickle 兼容性,如果它可以在使用较新编译器版本编译的应用程序中使用。同样,向前 TASTy/Pickle 兼容性则相反。

与二进制兼容性相同的策略适用于 TASTy/Pickle 兼容性,尽管它们未经过机械检查。

库作者可以使用 TASTy-MiMa 自动检查其库的 TASTy/Pickle 向后兼容性。免责声明:TASTy-MiMa 是一个新项目。在这一点上,你可能会遇到错误。请将其问题跟踪器报告你发现的问题。

具体来说

我们保证 "org.scala-lang" % "scala-library" % "2.N.x""org.scala-lang" % "scala-reflect" % "2.N.x" 工件的前向和后向兼容性,但以下情况除外

  • scala.reflect.internalscala.reflect.io 包,因为 scala-reflect 仍处于实验阶段,以及
  • scala.runtime 包,其中包含在运行时由生成代码使用的类。

我们还强烈不建议依赖 scala.concurrent.implscala.sys.process.*Implscala.reflect.runtime 的稳定性,尽管我们只会在此处针对严重错误中断兼容性。

我们保证 "org.scala-lang" % "scala3-library_3" % "3.x.y" 工件的向后兼容性。向前兼容性仅在给定的 N 中的 3.N.y 中得到保证。

我们为 模块(groupId org.scala-lang.modules 下的工件)强制执行向后(但不向前)二进制兼容性。由于它们是选择加入的,因此要求在类路径上拥有最新版本并不是什么负担。(如果没有向前兼容性,则工件的最新版本必须位于运行时类路径上,以避免链接错误。)

最后,从 Scala 2.11 到 Scala 2.13.0-M1,scala-library-all 聚合构成 Scala 版本的所有模块。请注意,这意味着它不提供向前二进制兼容性,而核心 scala-library 工件则提供。我们认为 "scala-library-all" % "2.N.x" 所依赖的模块版本是规范版本,它们是官方 Scala 发行版的一部分。(发行版本身由 scala-dist maven 工件定义。)

此页面的贡献者