Scala 3 迁移指南

手动移植 sbt 项目

语言

本教程是为 sbt 编写的。然而,只要它支持 Scala 3,该方法对任何其他构建工具都非常相似。

在跳到 Scala 3 之前,请确保您使用的是最新的 Scala 2.13.x 和 sbt 1.5.x 版本。

现在,让我们了解将整个项目移植到 Scala 3 所需的步骤。

1. 检查项目先决条件

确保您的项目已准备好移植

  • 它不得依赖尚未移植到 Scala 3 的宏库。
  • 它不得使用 Scala 3 中没有等效项的编译器插件。
  • 它不得依赖scala-reflect

这些先决条件在前一页中有更详细的描述。

2. 选择一个模块

由于 Scala 2.13 和 Scala 3 之间的互操作性,您可以从任何模块开始。然而,从依赖项最少的模块开始可能更简单。

如果您在内部使用宏定义或宏注释,则必须首先移植它们。

3. 设置交叉构建

代码库迁移的两个主要挑战是

  • 使代码编译
  • 确保运行时行为保持不变

我们推荐交叉构建策略,即使用 Scala 3 和 Scala 2.13 编译代码。背后的逻辑是能够在每次修复后使用 Scala 2.13 运行测试,从而确保运行时行为保持不变。这对于避免在修复不兼容性时可能发生的错误至关重要。

在 sbt 中配置交叉构建就像

scalaVersion := "3.3.1"
crossScalaVersions ++= Seq("2.13.11", "3.3.1")

此配置表示

  • 默认版本是 3.3.1
  • 可以通过运行 ++2.13.11 命令加载 2.13.11。
  • 可以通过运行 ++3.3.1 命令加载 3.3.1。

请注意,reload 命令将始终加载默认版本,此处为 3.3.1。

4. 准备依赖项

在此阶段,如果您运行 compile,sbt 可能会抱怨找不到某些依赖项。这是因为依赖项的声明版本未针对 Scala 3 发布。

您需要将依赖项升级到较新版本,或告诉 sbt 使用该库的 Scala 2.13 版本。

当您更改库依赖项时,请确保在项目的各个模块中应用相同的更改。

检查是否有该库的可用 Scala 3 版本。为此,您可以在 Scaladex 中使用版本矩阵。转到库的项目页面,单击版本矩阵按钮,按 Scala 3 和 Scala 2.13 筛选。

1. 有该库的 Scala 3 版本

我们强烈建议使用一个可用版本。确保您选择的版本不会带来任何重大更改。

2. 没有该库的 Scala 3 版本

您可以使用该库的 Scala 2.13 版本。语法为

("com.lihaoyi" %% "os-lib" % "0.7.7").cross(CrossVersion.for3Use2_13)

或对于 Scala.js 依赖项

("com.lihaoyi" %%% "os-lib" % "0.7.7").cross(CrossVersion.for3Use2_13)

修复所有未解决的依赖项后,您可以检查测试是否仍在 Scala 2.13 中通过

sbt:example> ++2.13.11
[info] Setting Scala version to 2.13.11 on 1 project.
...
sbt:example> example / test
...
[success]

5. 配置 Scala 3 编译器

Scala 3 编译器选项不同于 Scala 2.13 选项:一些选项已重命名,另一些选项尚未得到支持。您可以参考 编译器选项查找 页面,以调整 scalacOptions 列表以适应 Scala 3。

您应该提出一个通用选项列表、一个特定于 Scala 2.13 的选项列表和一个特定于 Scala 3 的选项列表。

典型的配置如下所示

scalacOptions ++= {
  Seq(
    "-encoding",
    "UTF-8",
    "-feature",
    "-language:implicitConversions",
    // disabled during the migration
    // "-Xfatal-warnings"
  ) ++ 
    (CrossVersion.partialVersion(scalaVersion.value) match {
      case Some((3, _)) => Seq(
        "-unchecked",
        "-source:3.0-migration"
      )
      case _ => Seq(
        "-deprecation",
        "-Xfatal-warnings",
        "-Wunused:imports,privates,locals",
        "-Wvalue-discard"
      )
    })
}

添加 -source:3.0-migration 选项以启用 Scala 3 迁移模式
您还应该禁用 -Xfatal-warnings 以充分利用迁移模式和自动重写。

6. 解决不兼容性

现在是尝试在 Scala 3 中编译的时候了

sbt:example> ++3.3.1
[info] Setting Scala version to 3.3.1 on 1 project.
...
sbt:example> example / compile
...
sbt:example> example / Test / compile

example / compile 编译示例项目的 main 源。它严格等同于 example / Compile / compile

example / Test / compile 编译 test 源。

编译器生成两个不同级别的诊断

  • 迁移警告:这些警告可以通过编译器使用 -rewrite 选项自动修补。
  • 错误:一段代码无法再编译。

您可以忽略迁移警告,因为编译器会自动修复它们。但是必须手动处理不兼容错误。

许多已知的不兼容性列在 不兼容性表 中。您可以在其中找到错误的描述和一些建议的解决方案。

如果可能,您应该尝试找到最能保留代码二进制兼容性的修复方法。如果您的项目是已发布的库,这一点尤其重要。

宏不兼容性无法轻松解决。必须从头开始重写大量代码。请参阅 元编程

修复不兼容性后,您可以通过在 Scala 2.13 中运行测试来验证解决方案。

sbt:example> ++2.13.11
[info] Setting Scala version to 2.13.11 on 1 project.
...
sbt:example> example / test
...
[success]

考虑定期提交您的更改。

修复所有错误后,您应该能够在 Scala 3 中成功编译。只有迁移警告仍然存在。您可以通过使用 -source:3.0-migration -rewrite 选项进行编译来自动修补它们。

sbt:example> ++3.3.1
sbt:example> set example / scalacOptions += "-rewrite"
sbt:example> example / compile
...
[info] [patched file /example/src/main/scala/app/Main.scala]
[warn] two warnings found
[success]

你现在应该移除 -source:3.0-migration 选项,并且可以再次添加 -Xfatal-warnings 选项。不要忘记重新加载。

7. 验证迁移

在极少数情况下,不同的隐式值可能会被解析并改变程序的运行时行为。良好的测试是防止此类错误不被注意到的唯一保证。

确保测试在 Scala 2.13 和 Scala 3 中都通过。

sbt:example> ++2.13.11
sbt:example> example / test
...
[success]
sbt:example> ++3.3.1
sbt:example> example / test
...
[success]

如果你有持续集成管道,现在是为 Scala 3 设置它的时候了。

8. 完成迁移

恭喜!你已成功将模块移植到 Scala 3。可以对每个模块重复相同的过程,直到项目完全迁移到 Scala 3。

你可以保留或删除 Scala 2.13 交叉构建配置,具体取决于是否要交叉发布你的程序。

sbt 项目迁移的演练到此结束。

此页面贡献者