SBT Cheatsheet
Sinopsis
SBT (Scala Build Tool) es una herramienta de construcción interactiva para Scala, Java y otros idiomas JVM. Utiliza Scala para crear definiciones y proporciona una compilación incremental, pruebas y despliegue.
Instalación
Administradores de paquetes
# macOS
brew install sbt
# Ubuntu/Debian
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main"|sudo tee /etc/apt/sources.list.d/sbt.list
curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x2EE0EA64E40A89B84B2DF73499E82A75642AC823"|sudo apt-key add
sudo apt update && sudo apt install sbt
# Windows (Chocolatey)
choco install sbt
# Manual installation
wget https://github.com/sbt/sbt/releases/download/v1.8.0/sbt-1.8.0.tgz
tar -xzf sbt-1.8.0.tgz
export PATH=$PATH:/path/to/sbt/bin
Verificación
sbt --version
Estructura del proyecto
Diseño estándar
project/
├── build.sbt
├── project/
│ ├── build.properties
│ ├── plugins.sbt
│ └── Dependencies.scala
├── src/
│ ├── main/
│ │ ├── scala/
│ │ ├── java/
│ │ └── resources/
│ └── test/
│ ├── scala/
│ ├── java/
│ └── resources/
├── target/
└── README.md
Construcción básica. Sbt
Ejemplo mínimo
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.13.10"
lazy val root = (project in file("."))
.settings(
name := "my-project",
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.15" % Test
)
)
Ejemplo completo
ThisBuild / version := "1.0.0"
ThisBuild / scalaVersion := "2.13.10"
ThisBuild / organization := "com.example"
// Global settings
ThisBuild / scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Xlint",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen"
)
ThisBuild / javacOptions ++= Seq(
"-source", "11",
"-target", "11",
"-Xlint:unchecked",
"-Xlint:deprecation"
)
// Dependencies
val catsVersion = "2.9.0"
val akkaVersion = "2.7.0"
val scalaTestVersion = "3.2.15"
lazy val commonDependencies = Seq(
"org.typelevel" %% "cats-core" % catsVersion,
"ch.qos.logback" % "logback-classic" % "1.4.5",
"org.scalatest" %% "scalatest" % scalaTestVersion % Test,
"org.scalatestplus" %% "mockito-4-6" % "3.2.15.0" % Test
)
// Root project
lazy val root = (project in file("."))
.aggregate(core, web, api)
.settings(
name := "my-multi-project",
publish / skip := true
)
// Core module
lazy val core = (project in file("core"))
.settings(
name := "my-project-core",
libraryDependencies ++= commonDependencies ++ Seq(
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion
)
)
// Web module
lazy val web = (project in file("web"))
.dependsOn(core)
.enablePlugins(PlayScala)
.settings(
name := "my-project-web",
libraryDependencies ++= commonDependencies ++ Seq(
"com.typesafe.play" %% "play" % "2.8.18",
"com.typesafe.play" %% "play-json" % "2.9.4"
)
)
// API module
lazy val api = (project in file("api"))
.dependsOn(core)
.settings(
name := "my-project-api",
libraryDependencies ++= commonDependencies ++ Seq(
"com.typesafe.akka" %% "akka-http" % "10.4.0",
"com.typesafe.akka" %% "akka-http-spray-json" % "10.4.0"
)
)
Dependencias
Sintaxis de dependencia
libraryDependencies ++= Seq(
// Scala library (cross-compiled)
"org.typelevel" %% "cats-core" % "2.9.0",
// Java library
"com.google.guava" % "guava" % "31.1-jre",
// Test dependency
"org.scalatest" %% "scalatest" % "3.2.15" % Test,
// Provided dependency
"javax.servlet" % "javax.servlet-api" % "4.0.1" % Provided,
// Runtime dependency
"mysql" % "mysql-connector-java" % "8.0.33" % Runtime,
// Optional dependency
"org.slf4j" % "slf4j-api" % "2.0.6" % Optional,
// Classifier
"org.apache.spark" %% "spark-core" % "3.3.2" classifier "tests",
// Exclude transitive dependencies
"com.typesafe.akka" %% "akka-actor" % "2.7.0" exclude("org.scala-lang", "scala-library")
)
Configuraciones de dependencia
// Custom configurations
lazy val IntegrationTest = config("it") extend Test
lazy val myProject = (project in file("."))
.configs(IntegrationTest)
.settings(
Defaults.itSettings,
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.15" % "it,test"
)
)
// Dependency overrides
dependencyOverrides ++= Seq(
"org.scala-lang" % "scala-library" % scalaVersion.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value
)
// Exclude specific dependencies
excludeDependencies ++= Seq(
ExclusionRule("commons-logging", "commons-logging"),
ExclusionRule("org.slf4j", "slf4j-log4j12")
)
Resolver
resolvers ++= Seq(
"Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots",
"Typesafe Repository" at "https://repo.typesafe.com/typesafe/releases/",
"Maven Central" at "https://repo1.maven.org/maven2/",
Resolver.mavenLocal
)
// Custom resolver
resolvers += "My Nexus" at "https://nexus.example.com/repository/maven-public/"
// Resolver with credentials
resolvers += "Private Repo" at "https://private.example.com/maven/" withCredentials Credentials(
"Sonatype Nexus Repository Manager",
"private.example.com",
"username",
"password"
)
Tareas y comandos
Comandos básicos
# Start SBT shell
sbt
# Compile
sbt compile
# Test
sbt test
# Run
sbt run
# Package
sbt package
# Clean
sbt clean
# Show dependencies
sbt dependencyTree
# Show project info
sbt projects
sbt show name
sbt show version
Shell interactivo
// In SBT shell
compile
test
run
package
clean
// Run specific main class
runMain com.example.MyApp
// Run with arguments
run arg1 arg2
// Test specific class
testOnly com.example.MySpec
// Test with pattern
testOnly *MySpec
// Continuous compilation
~compile
~test
~run
Comandos multiproyecto
# Compile all projects
sbt compile
# Compile specific project
sbt core/compile
sbt web/test
sbt api/run
# Run command on all projects
sbt "project core" compile "project web" compile
# Aggregate commands
sbt test # runs test on all projects
Ajustes y tareas
Ajustes personalizados
// Define custom setting
lazy val myCustomSetting = settingKey[String]("A custom setting")
// Define custom task
lazy val myCustomTask = taskKey[Unit]("A custom task")
lazy val root = (project in file("."))
.settings(
myCustomSetting := "Hello World",
myCustomTask := \\\\{
val value = myCustomSetting.value
println(s"Custom setting value: $value")
\\\\}
)
Dependencias de tareas
lazy val generateSources = taskKey[Seq[File]]("Generate source files")
lazy val processResources = taskKey[Unit]("Process resources")
lazy val root = (project in file("."))
.settings(
generateSources := \\\\{
val dir = (Compile / sourceManaged).value
val file = dir / "Generated.scala"
IO.write(file, "object Generated \\\\{ val value = 42 \\\\}")
Seq(file)
\\\\},
Compile / sourceGenerators += generateSources.taskValue,
processResources := \\\\{
val log = streams.value.log
log.info("Processing resources...")
// Resource processing logic
\\\\},
Compile / compile := (Compile / compile).dependsOn(processResources).value
)
Ajustes escocidos
lazy val root = (project in file("."))
.settings(
// Global setting
version := "1.0.0",
// Compile scope
Compile / scalacOptions ++= Seq("-deprecation", "-feature"),
// Test scope
Test / scalacOptions ++= Seq("-Yrangepos"),
// Runtime scope
Runtime / unmanagedClasspath += baseDirectory.value / "config",
// Custom configuration
IntegrationTest / testOptions += Tests.Argument("-oD")
)
Plugins
Plugins comunes
// project/plugins.sbt
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.8.18")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.10.4")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.11")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.6")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.0")
Configuración de plugin
// Enable plugins
lazy val root = (project in file("."))
.enablePlugins(JavaAppPackaging, DockerPlugin)
.settings(
// Native packager settings
packageName := "my-app",
maintainer := "user@example.com",
// Docker settings
dockerBaseImage := "openjdk:11-jre-slim",
dockerExposedPorts := Seq(8080),
// Assembly settings
assembly / assemblyMergeStrategy := \\\\{
case PathList("META-INF", xs @ _*) => MergeStrategy.discard
case x => MergeStrategy.first
\\\\}
)
// Scalafmt configuration
scalafmtOnCompile := true
// Scoverage configuration
coverageMinimumStmtTotal := 80
coverageFailOnMinimum := true
Pruebas
Configuración de prueba
lazy val root = (project in file("."))
.settings(
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.15" % Test,
"org.scalatestplus" %% "mockito-4-6" % "3.2.15.0" % Test,
"org.scalacheck" %% "scalacheck" % "1.17.0" % Test
),
// Test options
Test / testOptions ++= Seq(
Tests.Argument(TestFrameworks.ScalaTest, "-oD"),
Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/test-reports")
),
// Parallel execution
Test / parallelExecution := true,
Test / fork := true,
// JVM options for tests
Test / javaOptions ++= Seq(
"-Xmx1G",
"-XX:+CMSClassUnloadingEnabled"
)
)
Pruebas de integración
lazy val IntegrationTest = config("it") extend Test
lazy val root = (project in file("."))
.configs(IntegrationTest)
.settings(
Defaults.itSettings,
IntegrationTest / testOptions := Seq(Tests.Filter(s => s.endsWith("IntegrationSpec")))
)
Configuración de construcción
Ajustes de compilación
lazy val root = (project in file("."))
.settings(
// Scala compiler options
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "UTF-8",
"-feature",
"-unchecked",
"-Xlint",
"-Ywarn-dead-code",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard"
),
// Java compiler options
javacOptions ++= Seq(
"-source", "11",
"-target", "11",
"-Xlint:unchecked",
"-Xlint:deprecation"
),
// Incremental compilation
incOptions := incOptions.value.withNameHashing(true),
// Source directories
Compile / scalaSource := baseDirectory.value / "src" / "main" / "scala",
Test / scalaSource := baseDirectory.value / "src" / "test" / "scala"
)
Gestión de los recursos
lazy val root = (project in file("."))
.settings(
// Resource directories
Compile / resourceDirectory := baseDirectory.value / "src" / "main" / "resources",
Test / resourceDirectory := baseDirectory.value / "src" / "test" / "resources",
// Resource filtering
Compile / resourceGenerators += Def.task \\\\{
val file = (Compile / resourceManaged).value / "version.properties"
val contents = s"version=$\\\\{version.value\\\\}\nbuildTime=$\\\\{System.currentTimeMillis\\\\}"
IO.write(file, contents)
Seq(file)
\\\\}.taskValue,
// Include/exclude resources
Compile / unmanagedResources / includeFilter := "*.conf"||"*.properties",
Compile / unmanagedResources / excludeFilter := "*.tmp"
)
Publicación
Publicación básica
lazy val root = (project in file("."))
.settings(
// Publishing settings
publishTo := \\\\{
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value)
Some("snapshots" at nexus + "content/repositories/snapshots")
else
Some("releases" at nexus + "service/local/staging/deploy/maven2")
\\\\},
// Credentials
credentials += Credentials(Path.userHome / ".sbt" / "sonatype_credentials"),
// POM settings
pomIncludeRepository := \\\\{ _ => false \\\\},
publishMavenStyle := true,
publishArtifact in Test := false
)
Configuración completa de publicación
ThisBuild / organization := "com.example"
ThisBuild / organizationName := "Example Corp"
ThisBuild / organizationHomepage := Some(url("https://example.com/"))
ThisBuild / scmInfo := Some(
ScmInfo(
url("https://github.com/example/my-project"),
"scm:git@github.com:example/my-project.git"
)
)
ThisBuild / developers := List(
Developer(
id = "johndoe",
name = "John Doe",
email = "john@example.com",
url = url("https://github.com/johndoe")
)
)
ThisBuild / description := "My awesome Scala library"
ThisBuild / licenses := List("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt"))
ThisBuild / homepage := Some(url("https://github.com/example/my-project"))
// Remove all additional repository other than Maven Central from POM
ThisBuild / pomIncludeRepository := \\\\{ _ => false \\\\}
ThisBuild / publishTo := \\\\{
val nexus = "https://oss.sonatype.org/"
if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
\\\\}
ThisBuild / publishMavenStyle := true
Características avanzadas
Comandos Personalizados
// Define custom command
lazy val hello = Command.command("hello") \\\\{ state =>
println("Hello from custom command!")
state
\\\\}
// Add command to project
commands += hello
// Parameterized command
lazy val greet = Command.single("greet") \\\\{ (state, name) =>
println(s"Hello, $name!")
state
\\\\}
commands += greet
Información de construcción
// project/plugins.sbt
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")
// build.sbt
lazy val root = (project in file("."))
.enablePlugins(BuildInfoPlugin)
.settings(
buildInfoKeys := Seq[BuildInfoKey](
name,
version,
scalaVersion,
sbtVersion,
BuildInfoKey.action("buildTime") \\\\{
System.currentTimeMillis
\\\\}
),
buildInfoPackage := "com.example.buildinfo"
)
Versión dinámica
// Version from Git
version := \\\\{
import scala.sys.process._
"git describe --tags --always --dirty".!!.trim
\\\\}
// Snapshot versions
version := \\\\{
val base = "1.0.0"
if (isSnapshot.value) s"$base-SNAPSHOT"
else base
\\\\}
Buenas prácticas
Project Organization
// Use consistent naming
lazy val commonSettings = Seq(
organization := "com.example",
scalaVersion := "2.13.10",
scalacOptions ++= Seq("-deprecation", "-feature")
)
// Separate concerns
lazy val core = (project in file("core"))
.settings(commonSettings)
.settings(
name := "my-project-core",
libraryDependencies ++= coreDependencies
)
// Use dependency management
lazy val Dependencies = new \\\\{
val catsVersion = "2.9.0"
val akkaVersion = "2.7.0"
val cats = "org.typelevel" %% "cats-core" % catsVersion
val akkaActor = "com.typesafe.akka" %% "akka-actor-typed" % akkaVersion
\\\\}
Optimización del rendimiento
// Parallel compilation
Global / concurrentRestrictions := Seq(
Tags.limitAll(4)
)
// Incremental compilation
ThisBuild / incOptions := incOptions.value.withNameHashing(true)
// Fork JVM for tests
Test / fork := true
Test / javaOptions += "-Xmx2G"
// Disable documentation generation in development
Compile / doc / sources := Seq.empty
Compile / packageDoc / publishArtifact := false
Solución de problemas
Cuestiones comunes
# Clear cache and reload
sbt clean reload
# Show dependency conflicts
sbt evicted
# Debug dependency resolution
sbt "show dependencyTree"
# Check for updates
sbt dependencyUpdates
# Inspect settings
sbt "inspect compile"
sbt "inspect tree clean"
# Show classpath
sbt "show Runtime/fullClasspath"
Modo de depuración
# Enable debug logging
sbt -Dsbt.log.level=debug
# JVM debug options
sbt -J-Xdebug -J-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
# Memory settings
sbt -J-Xmx2G -J-XX:+CMSClassUnloadingEnabled
Recursos
- ** Documentación oficial**: scala-sbt.org
- Empezar: scala-sbt.org/1.x/docs/Getting-Started.html
- Plugin Index: scala-sbt.org/release/docs/Community-Plugins.html
- Las mejores prácticas: github.com/sbt/sbt/wiki/Best-Practices