Как работает sbt? Что такое .sbt и .scala конфигурации?

sbt (Simple Build Tool, теперь просто "Scala Build Tool") — это основной инструмент сборки для проектов на Scala. Он управляет компиляцией, зависимостями, тестами, запуском и публикацией. sbt тесно интегрирован с экосистемой Scala и предоставляет мощную систему конфигурации через .sbt и .scala файлы.

Как работает sbt

sbt запускается в интерактивном режиме: он загружает проект, поднимает виртуальную машину (JVM), парсит конфигурации и отслеживает изменения в файлах. Благодаря инкрементальной компиляции, он перекомпилирует только те части, которые были изменены.

Основные этапы:

  1. Парсит конфигурационные файлы (build.sbt, project/*.scala, project/*.sbt).

  2. Загружает зависимости из Maven/ivy репозиториев.

  3. Выполняет указанные команды (compile, test, run, clean и т.д.).

  4. Мониторит изменения (если в интерактивном режиме — автоматически перекомпилирует при сохранении).

Структура проекта

project/
build.properties
plugins.sbt
MyBuild.scala (опционально)
src/
main/scala/
test/scala/
build.sbt

build.sbt

Это основной файл конфигурации сборки. Он используется для задания настроек проекта в декларативной форме.

Пример:

name := "my-awesome-app"
version := "0.1.0"
scalaVersion := "2.13.13"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.10.0",
"org.scalatest" %% "scalatest" % "3.2.17" % Test
)

Особенности:

  • .sbt — это Scala-выражения, но в специфическом DSL-стиле (однострочные выражения, без def, val, import).

  • Можно использовать := для установки значения, += / ++= для добавления.

  • Не поддерживает полноценных многострочных def или class — для них используется .scala.

project/plugins.sbt

Файл, в котором задаются плагины, используемые в проекте.

Пример:

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.10.0")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")

Этот файл — тоже DSL в виде .sbt, но обрабатывается отдельно (для загрузки плагинов).

project/build.properties

Определяет, какая версия sbt используется:

sbt.version=1.9.8

Этот файл критичен для совместимости между разработчиками и CI/CD.

.scala файлы в project/

Если нужно более гибкое поведение, используют .scala файлы внутри project/.

Они содержат полноценный код Scala, например:

import sbt._
import Keys._
object MyBuild extends Build {
lazy val myProject = Project(
id = "my-awesome-app",
base = file("."),
settings = Seq(
name := "My App",
scalaVersion := "2.13.13"
)
)
}

Сегодня такой стиль считается устаревшим в пользу build.sbt и AutoPlugin, но в сложных проектах ещё встречается.

Основные команды sbt

  • compile — компиляция src/main/scala

  • test — запуск тестов

  • run — запуск приложения

  • console — REPL со всеми зависимостями

  • update — загрузка зависимостей

  • reload — перезагрузка конфигурации

  • clean — удаление артефактов сборки

  • ~compile — "watch mode": перекомпиляция при изменении файлов

Зависимости и конфигурации

sbt использует механизм конфигураций:

  • Compile — основная сборка

  • Test — код тестов

  • Runtime, Provided, IntegrationTest и т.д.

Можно указать зависимости только для тестов:

libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.17" % Test

Или для разных подконфигураций:

configs += IntegrationTest
Defaults.itSettings
libraryDependencies += "com.h2database" % "h2" % "2.2.224" % IntegrationTest

Multi-project build

sbt позволяет организовывать несколько модулей в одном репозитории:

lazy val core = project.in(file("core"))
lazy val api = project.in(file("api")).dependsOn(core)

Это позволяет задавать общие зависимости, кэшировать сборку модулей и управлять иерархией.

Task'и и Settings

sbt устроен вокруг понятий SettingKey, TaskKey, InputKey.

  • SettingKey[T] — значение, определённое на этапе конфигурации (например, version).

  • TaskKey[T] — отложенное значение, вычисляется при вызове (например, compile, run).

  • InputKey[T] — задача с аргументами от пользователя (реже используется).

Пример:

val myTask = taskKey\[Unit\]("Prints Hello")
myTask := {
println("Hello from sbt task!")
}

Дополнительные фишки

  • sbt-shell: запоминает контекст, что позволяет быстро вызывать команды без перезапуска JVM.

  • autoImport: плагины и настройки, доступные в build.sbt.

  • crossScalaVersions: для сборки под разными версиями Scala.

  • scalacOptions: тонкая настройка компиляции.

scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xfatal-warnings")

Инкрементальная сборка

sbt использует собственный механизм кэширования и анализа зависимостей между файлами, что делает повторные сборки быстрыми. Он отслеживает:

  • изменения в файлах,

  • API изменений (например, сигнатуры методов),

  • изменения зависимостей.

sbt — мощный, но сложный инструмент, особенно при переходе от простых .sbt файлов к полным .scala-проектам. Он предоставляет масштабируемый способ управления сборкой, конфигурацией и зависимостями в Scala-проектах.