Saltar a contenido

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