Migrating from sbt 1.x
Changing build.sbt
DSL to Scala 3.x
As a reminder, users can build either Scala 2.x or Scala 3.x programs using either sbt 1.x or sbt 2.x. However, the Scala that underlies the build.sbt
DSL is determined by the sbt version. In sbt 2.0, we are migrating to Scala 3.x.
This means that if you implement custom tasks or sbt plugins for sbt 2.x, it must be done using Scala 3.x. See Scala 3.x incompatibility table and Scala 2 with -Xsource:3.
// This works on Scala 2.12.20 under -Xsource:3
import sbt.{ given, * }
Bare settings changes
version := "0.1.0"
scalaVersion := "3.3.3"
Bare settings, like the example above, are settings written directly in build.sbt
without settings(...)
.
In sbt 1.x bare settings were project settings that applied only to the root subproject. In sbt 2.x, the bare settings in build.sbt
are common settings that are injected to all subprojects.
name := "root" // every subprojects will be named root!
publish / skip := true
Migrating ThisBuild
In sbt 2.x, bare settings settings should no longer be scoped to ThisBuild
. One benefit of the new common settings over ThisBuild
is that it would act in a more predictable delegation. These settings are inserted between plugins settings and those defined in settings(...)
, meaning they can be used to define settings like Compile / scalacOptions
, which was not possible with ThisBuild
.
Migrating to slash syntax
sbt 1.x supported both the sbt 0.13 style syntax and the slash syntax. sbt 2.x removes the support for the sbt 0.13 syntax, so use the slash syntax for both sbt shell and in build.sbt
:
<project-id> / Config / intask / key
For example, test:compile
will no longer work on the shell. Use Test/compile
instead. See syntactic Scalafix rule for unified slash syntax for semi-automated migration of build.sbt
files.
Cross building sbt plugins
In sbt 2.x, if you cross build an sbt plugin with Scala 3.x and 2.12.x, it will automatically cross build against sbt 1.x and sbt 2.x:
// using sbt 2.x
lazy val plugin = (projectMatrix in file("plugin"))
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-vimquit",
)
.jvmPlatform(scalaVersions = Seq("3.3.3", "2.12.20"))
If you use projectMatrix
, make sure to move the plugin to a subdirectory like plugin/
. Otherwise, the synthetic root project will also pick up the src/
.
Cross building sbt plugin with sbt 1.x
Use sbt 1.10.2 or later, if you want to cross build using sbt 1.x.
// using sbt 1.x
lazy val scala212 = "2.12.20"
lazy val scala3 = "3.3.4"
ThisBuild / crossScalaVersions := Seq(scala212, scala3)
lazy val plugin = (project in file("plugin"))
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-vimquit",
(pluginCrossBuild / sbtVersion) := {
scalaBinaryVersion.value match {
case "2.12" => "1.5.8"
case _ => "2.0.0-M2"
}
},
)
Changes to %%
In sbt 2.x, ModuleID
's %%
operator has become platform-aware. For JVM subprojects, %%
works as before, encoding Scala suffix (for example _3
) on Maven repositories.
Migrating %%%
operator
When Scala.JS or Scala Native becomes available on sbt 2.x, %%
will encode both the Scala version (such as _3
) and the platform suffix (_sjs1
etc). As a result, %%%
can be replaced with %%
:
libraryDependencies += "org.scala-js" %% "scalajs-dom" % "2.8.0"
Use .platform(Platform.jvm)
in case where JVM libraries are needed.
The PluginCompat technique
To use the same *.scala
source but target both sbt 1.x and 2.x, we can create a shim, for example an object named PluginCompat
in both src/main/scala-2.12/
and src/main/scala-3/
.
Migrating Classpath type
sbt 2.x changed the Classpath
type to be an alias of the Seq[Attributed[xsbti.HashedVirtualFileRef]]
type. The following is a shim created to work with classpaths from both sbt 1.x and 2.x.
// src/main/scala-3/PluginCompat.scala
package sbtfoo
import java.nio.file.{ Path => NioPath }
import sbt.*
import xsbti.{ FileConverter, HashedVirtualFileRef, VirtualFile }
private[sbtfoo] object PluginCompat:
type FileRef = HashedVirtualFileRef
type Out = VirtualFile
def toNioPath(a: Attributed[HashedVirtualFileRef])(using conv: FileConverter): NioPath =
conv.toPath(a.data)
inline def toFile(a: Attributed[HashedVirtualFileRef])(using conv: FileConverter): File =
toNioPath(a).toFile()
def toNioPaths(cp: Seq[Attributed[HashedVirtualFileRef]])(using conv: FileConverter): Vector[NioPath] =
cp.map(toNioPath).toVector
inline def toFiles(cp: Seq[Attributed[HashedVirtualFileRef]])(using conv: FileConverter): Vector[File] =
toNioPaths(cp).map(_.toFile())
end PluginCompat
and here's for sbt 1.x:
// src/main/scala-2.12/PluginCompat.scala
package sbtfoo
private[sbtfoo] object PluginCompat {
type FileRef = java.io.File
type Out = java.io.File
def toNioPath(a: Attributed[File])(implicit conv: FileConverter): NioPath =
a.data.toPath()
def toFile(a: Attributed[File])(implicit conv: FileConverter): File =
a.data
def toNioPaths(cp: Seq[Attributed[File]])(implicit conv: FileConverter): Vector[NioPath] =
cp.map(_.data.toPath()).toVector
def toFiles(cp: Seq[Attributed[File]])(implicit conv: FileConverter): Vector[File] =
cp.map(_.data).toVector
}
Now we can import PluginCompat.*
and use toNioPaths(...)
etc to absorb the differences between sbt 1.x and 2.x. The above demonstrates how we can absorb the classpath type change, and convert it into a vector of NIO Paths.