Руководство по миграции с SBT 1.x на SBT 2.x
Примечание: Этот документ основан на официальной документации sbt 2.0 change summary и Migrating from sbt 1.x, а также на опыте миграции репозитория dicechess-reference-bot, где уже используется SBT 2.0.4.
📌 Обзор изменений
SBT 2.x — это значительное обновление, которое вносит много изменений в архитектуру и синтаксис. Основные изменения:
| Аспект | SBT 1.x | SBT 2.x |
|---|---|---|
| Язык для build.sbt | Scala 2.12 | Scala 3.8.x (метабилд) |
| Синтаксис | Поддержка старого синтаксиса (0.13) | Только slash-синтаксис |
| Кэширование | Опционально | Все задачи кэшируются по умолчанию |
| Тесты | Полный запуск каждый раз | Инкрементальные тесты (testFull для полного запуска) |
Пути к target | <project>/target/ | target/out/jvm/scala-<ver>/<project>/ |
ThisBuild | Использовался для общих настроек | Заменён на “common settings” |
IntegrationTest | Поддерживался | Удалён |
exportJars | false по умолчанию | true по умолчанию |
| Плагины | .sbt или .scala | Требуют суффикс _sbt2_3 |
🚀 Подготовка к миграции
1. Обновите версию SBT
В файле project/build.properties измените версию SBT:
- sbt.version=1.12.14
+ sbt.version=2.0.42. Обновите плагины
Плагины для SBT 2.x публикуются с суффиксом _sbt2_3. Пример из dicechess-reference-bot:
// project/plugins.sbt
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.7")⚠️ Важно: Проверьте совместимость версий плагинов с SBT 2.x на их страницах в репозиториях.
🔧 Основные изменения в коде
1. Scala 3 в метабилде
SBT 2.x использует Scala 3.8.x для DSL в build.sbt. Это означает:
- Импорты: Используйте
import sbt.{ given, * }для импорта всех необходимых сущностей. - Postfix-нотация: Избегайте postfix-нотации (например,
withSources()). Используйте точечную нотацию:- libraryDependencies += "org.example" "foo" % "1.0").withSources().withJavadoc()
2. Common Settings (Общие настройки)
В SBT 1.x настройки без ThisBuild применялись только к корневому проекту. В SBT 2.x они применяются ко всем подпроектам.
Проблема:
// SBT 2.x: это применится ко ВСЕМ подпроектам!
name := "root"
publish / skip := trueРешение:
- Используйте
LocalRootProjectдля настроек только корневого проекта:LocalRootProject / name := "root" LocalRootProject / publish / skip := true - Или явно определяйте настройки для каждого подпроекта.
Миграция ThisBuild:
В SBT 2.x ThisBuild больше не нужен для общих настроек. Они автоматически становятся “common settings”.
3. Slash-синтаксис
SBT 2.x требует единого slash-синтаксиса для всех ключей. Старый синтаксис (key in (project, config)) больше не поддерживается.
Примеры:
- scalacOptions in Compile += "-Xlint"
+ Compile / scalacOptions += "-Xlint"
- fork in Test := true
+ Test / fork := true
- version in ThisBuild := "1.0.0"
+ ThisBuild / version := "1.0.0"В консоли SBT:
- sbt:hello> test:compile
+ sbt:hello> Test/compile🔧 Инструмент: Используйте Scalafix-правило для полуавтоматической миграции:
scalafix --rules=https://gist.githubusercontent.com/eed3si9n/57e83f5330592d968ce49f0d5030d4d5/raw/7f576f16a90e432baa49911c9a66204c354947bb/Sbt0_13BuildSyntax.scala *.sbt project/*.scala
4. Кэширование задач
В SBT 2.x все задачи кэшируются по умолчанию. Это означает:
- Результаты задач сохраняются на диск и могут быть восстановлены.
- Если тип результата задачи не поддерживает сериализацию (например,
ClassLoader,Seq[PathMapping]), задача не скомпилируется.
Решения:
- Определите
JsonFormatдля вашего типа (рекомендуется для производительности). - Отключите кэширование для задачи:
myTask := Def.uncached { // Код задачи, который возвращает несериализуемый тип } - Используйте плагин
sbt2-compatдля совместимости с SBT 1.x:addSbtPlugin("com.github.sbt" % "sbt2-compat" % "<version>")
⚠️ Внимание: Кэширование пропускает побочные эффекты (например, запись файлов). Если задача должна выполнять побочные эффекты при каждом запуске, используйте
Def.uncached.
5. Инкрементальные тесты
В SBT 2.x задача test стала инкрементальной и кэшируемой:
- Тесты запускаются только если что-то изменилось с последнего запуска.
- Для полного запуска всех тестов используйте
testFull.
Пример:
# Запустить только изменённые тесты
sbt test
# Запустить ВСЕ тесты
sbt testFullФильтрация тестов:
# Запустить только тесты из ExampleTest
sbt "test ...ExampleTest"6. Изменения в target/
В SBT 2.x структура каталога target унифицирована:
- SBT 1.x:
<project>/target/ - SBT 2.x:
target/out/jvm/scala-<version>/<project>/
Пример:
- target/streamz/managed/src_managed/foo.txt
+ target/out/jvm/scala-3.8.4/streamz/managed/src_managed/foo.txtДля скриптовых тестов:
Используйте глобальные выражения (**):
# Проверка наличия файла
$ exists target/**/src_managed/foo.txt
# Удаление файла
$ absent target/**/src_managed/foo.txt7. exportJars по умолчанию true
В SBT 1.x exportJars был false по умолчанию. В SBT 2.x он стал true. Это может сломать:
getResource("/")resource.toURI
Решение:
Если ваш код ломается, установите exportJars := false:
exportJars := false8. Удаление IntegrationTest
Конфигурация IntegrationTest удалена. Миграция:
- Создайте отдельный подпроект.
- Реализуйте тесты как обычные тесты.
9. **Изменения в “ стал платформенно-осведомлённым:
- Для JVM: работает как раньше (кодирует суффикс версии Scala, например
_3). - Для Scala.js/Scala Native: кодирует и версию Scala, и суффикс платформы (
_sjs1,_native0.4и т.д.).
**Миграция `% “scalajs-dom” % “2.8.0”
- libraryDependencies += “org.scala-js” “foo” % “1.0”).platform(Platform.jvm)
### 10. **Изменения в `ModuleID`**
Некоторые методы `ModuleID` теперь требуют **точечной нотации** (не postfix):
```diff
- "org.example" "foo" % "1.0").withSources().withJavadoc()
🔄 Миграция CI/CD пайплайнов
1. Запуск последовательности команд
В SBT 2.x последовательность команд должна передаваться как одна строка с точкой с запятой:
- sbt clean compile test
+ sbt "clean ; compile ; test"2. sbt server и переменные окружения
В SBT 2.x sbt server переиспользуется между шагами CI/CD. Это означает:
- Все шаги используют переменные окружения, переданные в первом вызове
sbt. - Если ваш пайплайн полагается на разные переменные окружения (например,
JAVA_OPTS), вы должны:- Либо передавать все переменные на уровне задания.
- Либо явно завершать
sbtпосле каждого шага:sbt "clean ; compile ; test ; shutdown"
3. Пути к артефактам тестов
Артефакты тестов (например, отчёты) теперь хранятся в подкаталогах target/out. Обновите пути:
# GitHub Actions пример
path: target/out/**/test-reports/*.xml4. Глобальный базовый каталог
В SBT 2.x глобальный базовый каталог следует стандарту каталогов:
- Windows:
%LOCALAPPDATA%/sbt/2 - Unix:
$XDG_CONFIG_HOME/sbt/2или$HOME/.config/sbt/2
🛠️ Миграция плагинов
1. Кросс-компиляция плагинов
Если вы разрабатываете плагин для SBT 1.x и 2.x:
- Используйте
projectMatrixдля кросс-компиляции:lazy val plugin = (projectMatrix in file("plugin")) .enablePlugins(SbtPlugin) .settings( name := "sbt-myplugin", ) .jvmPlatform(scalaVersions = Seq("3.8.4", "2.12.20")) - Укажите версию SBT в зависимости от версии Scala:
(pluginCrossBuild / sbtVersion) := { scalaBinaryVersion.value match { case "2.12" => "1.5.8" case _ => "2.0.4" } }
2. Плагин sbt2-compat
Используйте sbt2-compat для совместимости между SBT 1.x и 2.x:
addSbtPlugin("com.github.sbt" % "sbt2-compat" % "<version>")Пример использования:
import sbtcompat.PluginCompat._
myTask := {
implicit val conv: FileConverter = fileConverter.value
val paths = toNioPaths((Compile / dependencyClasspath).value)
val files = toFiles((Compile / dependencyClasspath).value)
// ...
}3. Техника PluginCompat
Для API, которые сломались между SBT 1.x и 2.x, создайте шим PluginCompat:
// src/main/scala-2.12/PluginCompat.scala
package sbtfoo
object PluginCompat {
def someMethod(): Unit = ...
}
// src/main/scala-3/PluginCompat.scala
package sbtfoo
object PluginCompat {
def someMethod(): Unit = ...
}📝 Пример миграции: build.sbt
До (SBT 1.x):
import sbt._
import Keys._
// Общие настройки для всех подпроектов
ThisBuild / version := "1.0.0"
ThisBuild / scalaVersion := "3.8.4"
// Настройки для корневого проекта
name := "my-project"
organization := "com.example"
// Подпроект
lazy val core = (project in file("core"))
.settings(
name := "my-project-core",
libraryDependencies += "org.typelevel" "cats-core" % "2.10.0").withSources()
)
// Тесты (IntegrationTest удалён)
Test / fork := true
// Кэширование
myCustomTask := Def.uncached {
// Код с побочными эффектами
}🧪 Тестирование миграции
-
Локальное тестирование:
- Запустите
sbt compileи убедитесь, что проект компилируется. - Запустите
sbt testи проверьте, что тесты проходят. - Проверьте пути к
target/и артефактам.
- Запустите
-
Тестирование в CI/CD:
- Обновите скрипты CI/CD согласно изменениям выше.
- Убедитесь, что все шаги работают с новой версией SBT.
-
Проверка кэширования:
- Запустите задачу дважды и убедитесь, что второй раз она использует кэш.
- Проверьте, что
Def.uncachedработает для задач с побочными эффектами.
📚 Полезные ресурсы
- Официальная документация: sbt 2.0 change summary
- Официальная документация: Migrating from sbt 1.x
- Плагин sbt2-compat
- Блог: Migrating sbt plugins to sbt 2 with sbt2-compat
- Пример миграции: dicechess-reference-bot
✅ Чеклист миграции
- Обновлена версия SBT в
project/build.properties - Обновлены плагины (суффикс
_sbt2_3) - Заменён старый синтаксис на slash-синтаксис
- Проверены
ThisBuildнастройки (заменены на common settings илиLocalRootProject) - Удалены все упоминания
IntegrationTest - Проверено кэширование задач (
Def.uncachedдля несериализуемых типов) - Обновлены пути к
target/в скриптах и CI/CD - Проверено поведение
exportJars - Обновлены CI/CD пайплайны (последовательность команд, переменные окружения)
- Протестирована кросс-компиляция (если применимо)
- Проверена работа плагинов с SBT 2.x
💡 Совет: Начните миграцию с небольшого проекта (например,
dicechess-bot-scala), чтобы понять все нюансы, прежде чем мигрировать более сложные проекты, такие какdicechess-engine-scalaилиdicechess-analytics.