sbt-scala3-migrate
是一个 sbt 插件,可帮助你在将 sbt 项目迁移到 Scala 3 期间提供帮助。它包含四个 sbt 命令
migrateDependencies
帮助你更新libraryDependencies
列表migrateScalacOptions
帮助你更新scalacOptions
列表migrateSyntax
修复了 Scala 2.13 和 Scala 3 之间的一些语法不兼容性migrateTypes
尝试通过推断类型并在需要时解析隐式内容将你的代码编译为 Scala 3。
下面将详细介绍每条命令。
要求
- Scala 2.13,建议使用 2.13.13
- sbt 1.5 或更高版本
- 免责声明:此工具无法迁移包含宏的库。
建议
在迁移之前,将
-Xsource:3
添加到 scalac 选项中,以在 Scala 2 编译器中启用 Scala 3 迁移警告。有关更多详细信息,请参阅页面 Scala 2 with -Xsource:3。
在本教程中,我们将迁移 scalacenter/scala3-migration-example 中的项目。要了解迁移并进行培训,可以克隆此存储库并按照教程步骤进行操作。
1. 安装
在 sbt 项目的 project/plugins.sbt
文件中添加 sbt-scala3-migrate
。
// project/plugins.sbt
addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.6.1")
2. 选择一个模块
如果项目包含多个模块,第一步是选择要首先迁移的模块。
由于 Scala 2.13 和 Scala 3 之间的互操作性,你可以从任何模块开始。但是,从依赖项最少的模块开始可能更简单。
sbt-scala3-migrate
一次处理一个模块。确保你选择的模块不是聚合。
3. 迁移依赖项
本教程中的所有命令都必须在 sbt shell 中运行。
用法: migrateDependencies <project>
出于本教程的目的,我们将考虑以下构建配置
//build.sbt
lazy val main = project
.in(file("."))
.settings(
scalaVersion := "2.13.11",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.4.0",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
"org.scalameta" %% "parsers" % "4.8.9",
"org.scalameta" %% "munit" % "0.7.23" % Test,
"com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test
),
addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)),
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
)
运行 migrateDependencies main
输出
sbt:main> migrateDependencies main
[info]
[info] Starting migration of libraries and compiler plugins of project main
[info]
[info] Valid dependencies:
[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12"
[warn]
[warn] Versions to update:
[warn] "org.typelevel" %% "cats-core" % "2.6.1" (Other versions: 2.7.0, ..., 2.10.0)
[warn] "org.scalameta" %% "munit" % "0.7.25" % Test (Other versions: 0.7.26, ..., 1.0.0-M8)
[warn]
[warn] For Scala 3 use 2.13:
[warn] ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13)
[warn]
[warn] Integrated compiler plugins:
[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
[warn] replaced by scalacOptions += "-Ykind-projector"
[error]
[error] Incompatible Libraries:
[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test (Macro Library)
[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") (Compiler Plugin)
[info]
[success] Total time: 0 s, completed Aug 28, 2023 9:18:04 AM
让我们仔细看看此输出消息的每个部分。
有效依赖项
有效依赖项与 Scala 3 兼容,原因是它们是标准 Java 库,或者已交叉发布到 Scala 3。
[info] Valid dependencies:
[info] "io.github.java-diff-utils" % "java-diff-utils" % "4.12"
你可以保持原样。
需要更新的版本
这些库已在更高版本中交叉发布到 Scala 3。你需要更新它们的版本。
[warn] Versions to update:
[warn] "org.typelevel" %% "cats-core" % "2.6.1" (Other versions: 2.7.0, ..., 2.10.0)
[warn] "org.scalameta" %% "munit" % "0.7.25" % Test (Other versions: 0.7.26, ..., 1.0.0-M8)
在给定的示例中,我们需要将 cats-core 的版本升级到 2.6.1,并将 munit 的版本升级到 0.7.25。
输出消息的
其他版本
部分指示 Scala 3 中有哪些其他版本可用。如果你愿意,可以升级到最新版本之一,但请注意选择一个源兼容版本。根据 语义版本控制方案,修补程序或次要版本升级是安全的,但主要版本升级则不安全。
对于 Scala 3,使用 2.13
这些库尚未交叉发布到 Scala 3,但它们是交叉兼容的。你可以使用它们的 2.13 版本来编译到 Scala 3。
在库中添加 .cross(CrossVersion.for3Use2_13)
以告诉 sbt 使用 _2.13
后缀,而不是 _3
。
[warn] For Scala 3 use 2.13:
[warn] ("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13)
关于
CrossVersion.for3Use2_13
的免责声明
- 它可能会导致传递依赖项的
_2.13
和_3
后缀发生冲突。在这种情况下,sbt 将无法解析依赖项,并会显示一条明确的错误消息。- 发布依赖于 Scala 2.13 库的 Scala 3 库通常是不安全的。否则,库的用户可能会在同一依赖项上遇到冲突的
_2.13
和_3
后缀。
集成的编译器插件
一些编译器插件已集成到 Scala 3 编译器本身中。在 Scala 3 中,你无需将它们解析为依赖项,但可以使用编译器标志来激活它们。
[warn] Integrated compiler plugins:
[warn] addCompilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
[warn] replaced by scalacOptions += "-Ykind-projector"
例如,你可以通过将 -Ykind-projector
添加到 scalacOptions
列表中来激活 kind-projector。
在迁移过程中,保持与 Scala 2.13 的兼容性非常重要。后来的 migrateSyntax
和 migrateTypes
命令将使用 Scala 2.13 编译来自动重写代码的某些部分。
你可以像这样以交叉兼容的方式配置 kind-projector
// add kind-projector as a dependency on Scala 2
libraryDependencies ++= {
if (scalaVersion.value.startsWith("3.")) Seq.empty
else Seq(
compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
)
},
// activate kind-projector in Scala 3
scalacOptions ++= {
if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector")
else Seq.empty
}
不兼容的库
一些宏库或编译器插件与 Scala 3 不兼容。
[error] Incompatible Libraries:
[error] "com.softwaremill.scalamacrodebug" %% "macros" % "0.4.1" % Test (Macro Library)
[error] addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1") (Compiler Plugin)
要解决这些不兼容性,你可以
- 向维护者咨询他们是否计划将它们移植到 Scala 3,并可能帮助他们这样做。
- 从你的构建中删除这些依赖项并相应地调整代码。
更新的构建
更新构建后,它应该如下所示
//build.sbt
lazy val main = project
.in(file("."))
.settings(
scalaVersion := "2.13.11",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.6.1",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
("org.scalameta" %% "parsers" % "4.8.9").cross(CrossVersion.for3Use2_13),
"org.scalameta" %% "munit" % "0.7.25" % Test
),
libraryDependencies ++= {
if (scalaVersion.value.startsWith("3.")) Seq.empty
else Seq(
compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full))
)
},
scalacOptions ++= {
if (scalaVersion.value.startsWith("3.")) Seq("-Ykind-projector")
else Seq.empty
}
)
重新加载 sbt,检查项目是否已编译(到 Scala 2.13),检查测试是否成功运行,并提交更改。你现在可以迁移编译器选项了。
4. 迁移编译器选项
用法: migrateScalacOptions <project>
Scala 3 编译器不包含与 Scala 2 编译器完全相同的选项集。你可以查看 编译器选项表 以获得所有编译器选项的完整比较。
migrateScalacOptions
将帮助你更新构建中的 scalacOptions
列表。
出于本教程的目的,我们将考虑以下构建配置
lazy val main = project
.in(file("."))
.settings(
scalaVersion := "2.13.11",
scalacOptions ++= Seq(
"-encoding",
"UTF-8",
"-target:jvm-1.8",
"-Xsource:3",
"-Wunused:imports,privates,locals",
"-explaintypes"
)
)
运行 migrateScalacOptions main
输出
sbt:main> migrateScalacOptions main
[info]
[info] Starting migration of scalacOptions in main
[info]
[info] Valid scalacOptions:
[info] -encoding UTF-8
[info] -Wunused:imports,privates,locals
[warn]
[warn] Renamed scalacOptions:
[warn] -target:jvm-1.8 -> -Xunchecked-java-output-version:8
[warn] -explaintypes -> -explain
[warn]
[warn] Removed scalacOptions:
[warn] -Xsource:3
[warn] -Yrangepos
[success] Total time: 0 s, completed Aug 29, 2023 2:00:57 PM
一些 scalac 选项仍然有效,一些必须重命名,一些必须删除。
一些选项可能出现在
migrateScalacOptions
的输出中,但不会出现在你的build.sbt
中。它们是由 sbt 或某些 sbt 插件添加的。确保使用最新版本的 sbt 和 sbt 插件。它们应该能够自动将添加的编译器选项调整到 Scala 版本。
同样重要的是,保持与 Scala 2.13 的兼容性,因为 migrateSyntax
和 migrateTypes
命令将使用 Scala 2.13 编译自动应用一些补丁。
以下是我们可以如何更新 scalacOptions 列表
lazy val main = project
.in(file("."))
.settings(
scalaVersion := "2.13.11",
scalacOptions ++= {
if (scalaVersion.value.startsWith("3.")) scala3Options
else scala2Options
}
)
lazy val sharedScalacOptions =
Seq("-encoding", "UTF-8", "-Wunused:imports,privates,locals")
lazy val scala2Options = sharedScalacOptions ++
Seq("-target:jvm-1.8", "-Xsource:3", "-explaintypes")
lazy val scala3Options = sharedScalacOptions ++
Seq("-Xunchecked-java-output-version:8", "-explain")
重新加载 sbt,检查项目是否已编译(到 Scala 2.13),检查测试是否成功运行,并提交更改。你现在可以迁移语法了。
5. 迁移语法
用法: migrateSyntax <project>
此命令运行许多 Scalafix 规则来修补一些废弃的语法。
应用的 Scalafix 规则列表是
- ProcedureSyntax
- fix.scala213.ExplicitNullaryEtaExpansion
- migrate.ParensAroundLambda
- fix.scala213.ExplicitNonNullaryApply
- fix.scala213.Any2StringAdd
- ExplicitResultTypes
有关 Scala 2.13 和 Scala 3 之间语法更改的更多信息,你可以参考 不兼容性表。
不兼容性表 中列出的某些不兼容性不会被 migrateSyntax 修复。它们中的大多数并不频繁,并且可以轻松手动修复。如果你想使用 Scalafix 重写规则做出贡献,我们非常乐意在
migrateSyntax
命令中添加它。
运行 migrateSyntax main
输出
sbt:main> migrateSyntax main
[info] Starting migration of syntax in main
[info] Run syntactic rules in 7 Scala sources successfully
[info] Applied 3 patches in src/main/scala/example/SyntaxRewrites.scala
[info] Run syntactic rules in 8 Scala sources successfully
[info] Applied 1 patch in src/test/scala/example/SyntaxRewritesTests.scala
[info] Migration of syntax in main succeeded.
[success] Total time: 2 s, completed Aug 31, 2023 11:23:51 AM
查看应用的更改,检查项目是否仍在编译,检查测试是否成功运行并提交更改。下一步也是最后一步是迁移类型。
6. 迁移类型
用法: migrateTypes <project>
Scala 3 编译器使用略微不同的类型推断算法。它有时无法推断出与 Scala 2 编译器相同的类型,这可能导致编译错误。此最后一步将添加所需的类型注释,以使代码编译为 Scala 3。
运行 migrateTypes main
输出
sbt:main> migrateTypes main
[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/classes ...
[warn] 1 deprecation; re-run with -deprecation for details
[warn] one warning found
[info] compiling 8 Scala sources to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-2.13/test-classes ...
[warn] 2 deprecations; re-run with -deprecation for details
[warn] one warning found
[success] Total time: 7 s, completed Aug 31, 2023 11:26:25 AM
[info] Defining scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others.
[info] Run `last` for details.
[info] Reapplying settings...
[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/)
[info]
[info] Migrating types in main / Compile
[info]
[info] Found 3 patches in 1 Scala source
[info] Starting migration of src/main/scala/example/TypeIncompat.scala
[info] 3 remaining candidates
[info] 1 remaining candidate
[info] Found 1 required patch in src/main/scala/example/TypeIncompat.scala
[info] Compiling to Scala 3 with -source:3.0-migration -rewrite
[info] compiling 1 Scala source to /home/piquerez/github/scalacenter/scala3-migration-example/target/scala-3.3.1/classes ...
[info]
[info] Migrating types in main / Test
[info]
[info] Found 4 patches in 1 Scala source
[info] Starting migration of src/test/scala/example/TypeIncompatTests.scala.scala
[info] 4 remaining candidates
[info] 3 remaining candidates
[info] 2 remaining candidates
[info] Found 1 required patch in src/test/scala/example/TypeIncompatTests.scala.scala
[info] Compiling to Scala 3 with -source:3.0-migration -rewrite
[info]
[info] You can safely upgrade main to Scala 3:
[info] scalaVersion := "3.3.1"
[success] Total time: 18 s, completed Aug 31, 2023 11:26:45 AM
[info] Defining scalaVersion
[info] The new value will be used by Compile / bspBuildTarget, Compile / dependencyTreeCrossProjectId and 68 others.
[info] Run `last` for details.
[info] Reapplying settings...
[info] set current project to main (in build file:/home/piquerez/github/scalacenter/scala3-migration-example/)
sbt:main>
migrateTypes main
找到 2 个必需的补丁:一个在 src/test/scala/example/TypeIncompatTests.scala.scala
中,另一个在 src/main/scala/example/TypeIncompat.scala
中。它应用了这些补丁,然后使用 -source:3.0-migration -rewrite
编译为 Scala 3 以完成迁移。
恭喜!您的项目现在可以编译为 Scala 3。
接下来做什么?
如果您的项目仅包含一个模块,则可以设置 scalaVersion := 3.3.1
。
如果您有多个模块,则可以从 3. 迁移依赖项 重新开始,使用另一个模块。
完成所有模块后,您可以从 project/plugins.abt
中删除 sbt-scala3-migrate
,以及所有与 Scala 2.13 相关的设置。
欢迎提供反馈和贡献
每条反馈都将帮助我们改进 sbt-scala3-migrate
:错别字、更清晰的日志消息、更好的文档、错误报告、功能创意。请随时打开 GitHub 问题。