By Alvin Alexander. Last updated: December 4, 2023
The Scala SBT build tool uses a file named build.sbt
to define your build. The SBT syntax can be a little hard to understand without examples, so I try to collect good examples here when I see them.
Seeing good examples rather than scrolling through reams of documentation has helped me many times!
To that end, this is a build.sbt
SBT build file example from the Kyo project:
val scala3Version = "3.3.1"
val scala2Version = "2.13.12"
val compilerOptions = Seq(
"-encoding",
"utf8",
"-feature",
"-unchecked",
"-deprecation",
"-language:implicitConversions"
// "-explain"
// "-Wvalue-discard",
// "-Vprofile",
)
scalaVersion := scala3Version
ThisBuild / sonatypeCredentialHost := "s01.oss.sonatype.org"
sonatypeRepository := "https://s01.oss.sonatype.org/service/local"
sonatypeProfileName := "io.getkyo"
publish / skip := true
lazy val `kyo-settings` = Seq(
fork := true,
scalacOptions ++= compilerOptions,
scalafmtOnCompile := true,
organization := "io.getkyo",
homepage := Some(url("https://getkyo.io")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
developers := List(
Developer(
"fwbrasil",
"Flavio Brasil",
"fwbrasil@gmail.com",
url("https://github.com/fwbrasil/")
)
),
ThisBuild / sonatypeCredentialHost := "s01.oss.sonatype.org",
sonatypeRepository := "https://s01.oss.sonatype.org/service/local",
sonatypeProfileName := "io.getkyo",
gen := {},
Test / testOptions += Tests.Argument("-oDG"),
ThisBuild / versionScheme := Some("early-semver")
)
lazy val gen = TaskKey[Unit]("gen", "")
lazy val genState: State => State = {
s: State =>
"gen" :: s
}
Global / onLoad := {
val old = (Global / onLoad).value
genState compose old
}
def transformFiles(path: File)(f: String => String): Unit =
if (path.isDirectory) path.listFiles.foreach(transformFiles(_)(f))
else {
var original = IO.read(path)
IO.write(path, f(original))
}
lazy val kyo =
crossProject(JVMPlatform)
.in(file("."))
.settings(
name := "kyo",
organization := "io.getkyo",
publishArtifact := false,
publish / skip := true,
Compile / packageBin / publishArtifact := false,
Compile / packageDoc / publishArtifact := false,
Compile / packageSrc / publishArtifact := false,
scalaVersion := scala3Version,
`kyo-settings`,
gen := {
val origin = new File("kyo-core/")
val dest = new File(s"kyo-core-opt/")
IO.delete(dest)
IO.copyDirectory(origin, dest)
transformFiles(dest) {
_.replaceAllLiterally(s"/*inline*/", "inline")
}
}
).aggregate(
`kyo-core`,
`kyo-core-opt`,
`kyo-direct`,
`kyo-stats-otel`,
`kyo-cache`,
`kyo-sttp`,
`kyo-tapir`,
`kyo-llm-macros`,
`kyo-llm`,
`kyo-bench`
)
val zioVersion = "2.0.19"
lazy val `kyo-core-settings` = `kyo-settings` ++ Seq(
libraryDependencies += "dev.zio" %%% "izumi-reflect" % "2.3.8",
libraryDependencies += "org.slf4j" % "slf4j-api" % "2.0.9",
libraryDependencies += "org.jctools" % "jctools-core" % "4.0.2",
libraryDependencies += "com.lihaoyi" %%% "sourcecode" % "0.3.1",
libraryDependencies += "dev.zio" %%% "zio-test" % zioVersion % Test,
libraryDependencies += "dev.zio" %%% "zio-test-magnolia" % zioVersion % Test,
libraryDependencies += "dev.zio" %%% "zio-test-sbt" % zioVersion % Test,
libraryDependencies += "dev.zio" %%% "zio-prelude" % "1.0.0-RC21" % Test,
libraryDependencies += "dev.zio" %%% "zio-laws-laws" % "1.0.0-RC21" % Test,
libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.16" % Test,
testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"),
Global / concurrentRestrictions := Seq(
Tags.limit(Tags.CPU, java.lang.Runtime.getRuntime().availableProcessors() - 1)
)
)
lazy val `without-cross-scala` = Seq(
scalaVersion := scala3Version,
crossScalaVersions := List(scala3Version)
)
lazy val `with-cross-scala` = Seq(
scalaVersion := scala3Version,
crossScalaVersions := List(scala3Version, scala2Version)
)
lazy val `kyo-core` =
crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-core"))
.settings(
`kyo-core-settings`,
`with-cross-scala`,
libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) => Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
case _ => Seq.empty
})
)
.jsSettings(`js-settings`)
lazy val `kyo-core-opt` =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file(s"kyo-core-opt"))
.settings(
`kyo-core-settings`,
`without-cross-scala`,
scalafmtOnCompile := false
)
lazy val `kyo-direct` =
crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("kyo-direct"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies ++= Seq(
"com.github.rssh" %%% "dotty-cps-async" % "0.9.19"
).filter(_ => scalaVersion.value.startsWith("3")),
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-library" % scalaVersion.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value,
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scalamacros" %% "resetallattrs" % "1.0.0"
).filter(_ => scalaVersion.value.startsWith("2"))
)
.jsSettings(`js-settings`)
lazy val `kyo-stats-otel` =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("kyo-stats-otel"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies += "io.opentelemetry" % "opentelemetry-api" % "1.32.0",
libraryDependencies += "io.opentelemetry" % "opentelemetry-sdk" % "1.32.0",
libraryDependencies += "io.opentelemetry" % "opentelemetry-exporters-inmemory" % "0.9.1" % Test
)
lazy val `kyo-cache` =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-cache"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.1.8"
)
lazy val `kyo-sttp` =
crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-sttp"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies += "com.softwaremill.sttp.client3" %%% "core" % "3.9.1"
)
.jsSettings(`js-settings`)
lazy val `kyo-tapir` =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("kyo-tapir"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.dependsOn(`kyo-sttp`)
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-core" % "1.8.4",
libraryDependencies += "com.softwaremill.sttp.tapir" %% "tapir-netty-server" % "1.8.4"
)
lazy val `kyo-llm-macros` =
crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-llm-macros"))
.dependsOn(`kyo-core` % "test->test;compile->compile")
.settings(
`kyo-settings`,
scalaVersion := scala3Version,
crossScalaVersions := List(scala2Version, scala3Version),
libraryDependencies += "dev.zio" %% "zio-schema" % "0.4.16",
libraryDependencies += "dev.zio" %% "zio-schema-derivation" % "0.4.16",
libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, _)) => Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
case _ => Seq.empty
})
)
.jsSettings(`js-settings`)
lazy val `kyo-llm` =
crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Full)
.in(file("kyo-llm"))
.dependsOn(`kyo-sttp`)
.dependsOn(`kyo-direct`)
.dependsOn(`kyo-core` % "test->test;compile->compile")
.dependsOn(`kyo-llm-macros`)
.jvmSettings(
libraryDependencies += "org.apache.lucene" % "lucene-core" % "9.8.0",
libraryDependencies += "org.apache.lucene" % "lucene-queryparser" % "9.8.0",
libraryDependencies += "com.formdev" % "flatlaf" % "3.2.5",
libraryDependencies += "com.vladsch.flexmark" % "flexmark-all" % "0.64.8",
libraryDependencies += "com.vladsch.flexmark" % "flexmark-java" % "0.64.8",
libraryDependencies += "com.knuddels" % "jtokkit" % "0.6.1"
)
.settings(
`kyo-settings`,
`with-cross-scala`,
libraryDependencies += "com.softwaremill.sttp.client3" %% "zio-json" % "3.9.1",
libraryDependencies += "dev.zio" %% "zio-schema" % "0.4.16",
libraryDependencies += "dev.zio" %% "zio-schema-json" % "0.4.16",
libraryDependencies += "dev.zio" %% "zio-schema-protobuf" % "0.4.16"
)
.jsSettings(`js-settings`)
lazy val `kyo-bench` =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("kyo-bench"))
.enablePlugins(JmhPlugin)
.dependsOn(`kyo-core-opt`)
.settings(
`kyo-settings`,
`without-cross-scala`,
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.2",
libraryDependencies += "org.typelevel" %% "log4cats-core" % "2.6.0",
libraryDependencies += "org.typelevel" %% "log4cats-slf4j" % "2.6.0",
libraryDependencies += "dev.zio" %% "zio-logging" % "2.1.16",
libraryDependencies += "dev.zio" %% "zio-logging-slf4j2" % "2.1.16",
libraryDependencies += "dev.zio" %% "zio" % zioVersion,
libraryDependencies += "dev.zio" %% "zio-concurrent" % zioVersion,
libraryDependencies += "com.softwaremill.ox" %% "core" % "0.0.14",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.16" % Test
)
lazy val rewriteReadmeFile = taskKey[Unit]("Rewrite README file")
addCommandAlias("checkReadme", ";readme/rewriteReadmeFile; readme/mdoc")
lazy val readme =
crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("target/readme"))
.enablePlugins(MdocPlugin)
.settings(
`kyo-settings`,
`without-cross-scala`,
mdocIn := new File("./../../README-in.md"),
mdocOut := new File("./../../README-out.md"),
rewriteReadmeFile := {
val readmeFile = new File("README.md")
val targetReadmeFile = new File("target/README-in.md")
val contents = IO.read(readmeFile)
val newContents = contents.replaceAll("```scala\n", "```scala mdoc:nest\n")
IO.write(targetReadmeFile, newContents)
}
)
.dependsOn(
`kyo-core`,
`kyo-direct`,
`kyo-cache`,
`kyo-sttp`,
`kyo-tapir`,
`kyo-llm`,
`kyo-bench`
)
import org.scalajs.jsenv.nodejs._
lazy val `js-settings` = Seq(
Compile / doc / sources := Seq.empty,
fork := false,
jsEnv := new NodeJSEnv(NodeJSEnv.Config().withArgs(List("--max_old_space_size=5120")))
)