Aller au contenu

TBS Feuilles de chaleur

Aperçu général

SBT (Scala Build Tool) est un outil de construction interactif pour Scala, Java et d'autres langages JVM. Il utilise Scala pour construire des définitions et fournit une compilation, des tests et un déploiement progressifs.

Installation

Gestionnaires de paquets

# 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

Vérification

sbt --version
```_

## Structure du projet

### Norme-cadre

project/ ├── build.sbt ├── project/ │ ├── build.properties │ ├── plugins.sbt │ └── Dependencies.scala ├── src/ │ ├── main/ │ │ ├── scala/ │ │ ├── java/ │ │ └── resources/ │ └── test/ │ ├── scala/ │ ├── java/ │ └── resources/ ├── target/ └── README.md


## Construction de base. sbt

### Exemple minimal
```scala
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
    )
  )

Exemple complet

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"
    )
  )

Dépendances

Syntaxe de dépendance

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")
)

Configurations de dépendance

// 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")
)

Résolvez

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"
)

Tâches et commandes

Commandes de base

# 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 interactif

// 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

Commandes multi-projets

# 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

Paramètres et tâches

Paramètres personnalisés

// 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")
    \\\\}
  )

Dépendances des tâches

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
  )

Paramètres visés

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")
  )

Greffons

Greffons fréquents

// 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")

Configuration du 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

Essais

Configuration d'essai

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"
    )
  )

Essais d'intégration

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")))
  )

Configuration de construction

Paramètres de compilation

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"
  )

Gestion des ressources

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"
  )

Édition

Éditions de base

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
  )

Configuration complète de l'édition

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

Caractéristiques avancées

Commandes personnalisées

// 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

Créer des informations

// 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"
  )

Version dynamique

// 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
\\\\}

Meilleures pratiques

Organisation du projet

// 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
\\\\}

Optimisation des performances

// 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

Dépannage

Questions communes

# 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"

Mode de débogage

# 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

Ressources