An sbt plugin that can be used for a wide range of projects. This plugin is the only requirement for every project at Ossum Inc. and is maintained by and for that company. However, it is likely quite useful for other companies because of its modularity and ability to override the Ossum Inc. defaults.
sbt-ossuminc is likely most helpful if you:
- Develop software in Scala (why else would you use
sbt? :) ) - Believe in using mono-repos containing many subprojects
- Need to support JVM, JS, and Native targets for your Scala code
- Work at the sbt command line and want lots of utilities there
sbt-ossuminc embraces a functional, minimalist, Don't-Repeat-Yourself (DRY) approach to build configuration:
Instead of writing imperative sbt settings, you declare what you want using composable configuration helpers. The plugin handles the details.
// ❌ Imperative (verbose, repetitive)
lazy val myModule = project
.settings(scalaVersion := "3.3.7")
.settings(scalacOptions ++= Seq("-deprecation", "-feature"))
.settings(libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test)
.enablePlugins(GitPlugin, DynVerPlugin)
// ... 20 more lines of boilerplate
// ✅ Declarative (concise, clear intent)
lazy val myModule = Module("my-module")
.configure(With.typical)Configuration helpers are pure functions (Project => Project) that compose naturally:
Module("my-lib")
.configure(With.typical) // Scala 3 + testing + versioning + git
.configure(With.coverage(80)) // Add code coverage with 80% threshold
.configure(With.GithubPublishing) // Publish to GitHub Packages
.dependsOn(otherModule)Defaults are chosen for modern Scala development but can always be overridden:
- Scala 3.3.7 LTS by default (override with
With.Scala3(version = Some("3.4.0"))) - Cross-platform ready (JVM, JS, Native with one declaration)
- Dynamic versioning from git tags (no manual version management)
- Automatic header management (keep license headers current)
Define once, use everywhere. No copy-paste configuration across subprojects:
// Define your standard configuration once
val standardModule = (p: Project) => p
.configure(With.typical, With.coverage(70), With.GithubPublishing)
// Apply it to multiple modules
lazy val moduleA = Module("module-a").configure(standardModule)
lazy val moduleB = Module("module-b").configure(standardModule)
lazy val moduleC = Module("module-c").configure(standardModule)In your project/plugins.sbt file, add the GitHub Packages resolver and the plugin:
// GitHub Packages resolver for sbt-ossuminc
resolvers += "GitHub Packages" at "https://maven.pkg.github.com/ossuminc/sbt-ossuminc"
addSbtPlugin("com.ossuminc" % "sbt-ossuminc" % "1.2.0")You must also set up your credentials file as a global .sbt file. This file permits you to read public repositories from GitHub Package Repository (such as those published by Ossum Inc.) and managing private repositories in your organization(s).
We recommend placing the credentials in your private home directory at ~/.sbt/1.0/github.sbt.
It should have content like:
credentials += Credentials(
"GitHub Package Registry",
"maven.pkg.github.com",
"your-github-user-name-here",
"your-github-token-here"
)
where:
your-github-user-name-hereis replaced with your Github user nameyour-github-token-hereis replaced with the GitHub Personal Access token (classic) that you have generated with therepoandread:packagesprivilege enabled.
In your build.sbt, place this line near the top to enable everything sbt-ossuminc supports:
enablePlugins(OssumIncPlugin)The above three things are required to activate the plugin for your build. There's more you can do, as described below.
Using that single plugin causes several other plugins to be adopted.
While you can use other addSbtPlugin declarations in project/plugins.sbt,
chances are you don't need to. The one line above also brings in all
the plugins listed in the sections below. These dependencies of
sbt-ossuminc are regularly updated with help from
Scala Steward so all you have
to keep up to date is your version of sbt-ossuminc which Scala Steward
can also help you within your project.
// Generic plugins from github.sbt project
addSbtPlugin("com.github.sbt" % "sbt-dynver" % "5.0.1")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.10.4")
addSbtPlugin("com.github.sbt" % "sbt-git" % "2.0.1")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
addSbtPlugin("com.github.sbt" % "sbt-release" % "1.4.0")
addSbtPlugin("com.github.sbt" % "sbt-unidoc" % "0.5.0")
addDependencyTreePlugin- sbt-dynver - dynamic versioning based on git tags, commits, and date stamp
- sbt-native-packager - packaging your compilation results into a package for various platforms
- sbt-git - git commands from the sbt prompt
- sbt-pgp - artifact signing for publishing to Sonatype
- sbt-release - full control of the release process for your project
- sbt-unidoc - unifying the documentation output from your programming language from several sub-projects
addDependencyTreePlugin- adds a plugin so you can use the dependency commands at sbt prompt
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.11.1")
addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.9.2")- sbt-buildinfo - Your program can know all kinds of things about your build
- sbt-header - Keep those file headers up to date with your project license
- sbt-updates - Check for dependency updates
- sbt-sonatype - Publishing to Sonatype and Maven Central
- sbt-github-packages - Publishing to Github Package Repository
- sbt-paradox - Markdown documentation generator
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.14.5")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.6")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.1")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.15")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.9")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.1")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
addSbtPlugin("org.portable-scala" % "sbt-platform-deps" % "1.0.2")
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.4")
addSbtPlugin("ch.epfl.scala" % "sbt-tasty-mima" % "1.2.0")
addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta44")
addSbtPlugin("org.jetbrains.scala" % "sbt-idea-plugin" % "5.0.4")- sbt-scalafix - Code refactoring and linting
- sbt-scalafmt - Code formatting
- sbt-scoverage - Code coverage measurement
- sbt-coveralls - Upload coverage to Coveralls.io
- sbt-scala-native - Compile Scala to native code
- sbt-scalajs - Compile Scala to JavaScript
- sbt-scalajs-crossproject - Cross-platform builds (JVM/JS)
- sbt-scala-native-crossproject - Cross-platform builds (JVM/Native)
- sbt-platform-deps - Platform-specific dependencies
- sbt-mima-plugin - Binary compatibility checking
- sbt-tasty-mima - TASTy compatibility checking
- sbt-converter - Generate Scala.js facades from TypeScript
- sbt-idea-plugin - IntelliJ IDEA plugin development
Without any further definitions in your build.sbt, this plugin provides
various features that we like at Ossum Inc.:
- Git commands at the sbt prompt
- Dynamic versioning based on your git tag and updated on each sbt reload
- Automatic placement and update of your source file header comments
- Standardized Scala code formatting with scalameta from a single configuration file
- Automatic updates of dependencies with sbt-updates
- sbt-unidoc for collation of sub-project documentation into a single site
- sbt-native-packager for output packaging
- sbt-sonatype for publishing signed artifacts to Sonatype/Maven
- sbt-scoverage and sbt-coveralls for code coverage tracking
The sbt-ossuminc plugin automatically defines some top level objects you can
use to define your subprojects. The sub-sections below cover each of these
lightly. For more details see the scaladoc for the plugin.
Use this when you want to have a root project that aggregates all the other
sub-projects. When you've selected the root project (sbt command: project root)
then your commands get passed down to the sub-projects.
For example, this:
lazy val riddl: Project = Root(
ghRepoName = "my-project",
ghOrgName = "my-organization",
orgPackage = "com.my_org.my_proj",
orgName = "My Organization",
orgPage = url("https://my_org.com/"),
startYr = 2024,
projectId = "root" // Optional: customize sbt project ID (default: "root")
)
.configure(With.noPublishing, With.git, With.dynver)
.aggregate(
module0, // a sub-component of your project
module1,
module2
)defines a top-level Root project in the top level directory that aggregates the
three modules listed in the .aggregate call. The parameters to Root define the
basic identifiers about the project so you don't have to set them as sbt settings
elsewhere. You must define a Root in your build.sbt as it provides basic information
about your project that are used by other features of sbt-ossuminc.
So how do module0, module1, and module2 get specified? With the Module
object of course! Like this:
lazy val module0: Project = Module(dirName = "module0", modName = "proj-mod-0")
.configure(With.typical, With.coverage(30))
.configure(With.publishing)
.settings(
coverageExcludedPackages := "<empty>;$anon",
description := "An example of a module sub-project",
libraryDependencies ++= Seq(
"org.scala-js" %% "scalajs-stubs" % "1.1.0" % "provided"
)
)
.dependsOn(module1)The above defines a module named proj-mod-0 in the directory named module0.
The module name will be used as the artifact name that the module compilation
produces. We've also asked for With.typical scala configuration and for code
coverage to be supported with at least 30% coverage, via With.coverage(30).
This module is also configured to be published by using With.publishing.
All the With configuration options are described in a section below.
As you can see, you can still override the configured settings for your
subprojects. In the .settings(...), it excludes empty packages from coverage, sets the module description
for publishing, and allows the scalajs annotations to be used, but just as stubs
If you want to build your module for more than just the JVM, you can use the CrossModule object in a pattern like this:
lazy val foo_cp: CrossProject = CrossModule(dirName = "foo", modName = "foo")(JVM, JS, Native)
.dependsOn(other_cp)
.configure(With.typical, With.publishing)
.settings(
scalacOptions ++= Seq("-explain", "--explain-types", "--explain-cyclic"),
description := "The fooness of existence"
)
.jvmConfigure(With.coverage(30))
.jvmSettings(
coverageExcludedPackages := "<empty>;$anon",
)
.jsConfigure(With.ScalaJS("RIDDL: passes", withCommonJSModule = true))
.jsSettings(
libraryDependencies += "com.foo" %%% "fooness" % "0.1.0"
)
val passes = passes_cp.jvm
val passesJS = passes_cp.js
val passesNAT = passes_cp.nativeThere's a lot going on in this example. Most of it is based on org.portable.scalas plugins
sbt-scalajs-crossproject" and sbt-scala-native-crossproject which are used to implement
the CrossModule object. From top to bottom, we see that:
- a lazy val named
foo_cpis defined as aCrossProjectdefined by those org.portable.scala plugins. It is named with the_cpsuffix to distinguish it as the CrossProject (thing that can build any of the variants) - The
CrossModuleobject is invoked. The first set of arguments are just like for a module. The module lives in the "foo" directory and its published artifact names will start with "foo". The second argument list provides the kinds of targets to build. In this case all three:JVM,JS, andNative. - The
.dependsOn,.configureand.settingscalls in the call chain are the typical ones for any sbt project but in this context they apply to all variants of what is to be built. Doing something specific to one, like a JS libraryDepenedency, will break your JVM build. - The
.jvmConfigureand.jvmSettingsare analogous to the.configureand.settingscall chain options, but they only apply to the JVM build. - Similarly, the
.jsConfigureand.jsSettingsoptions only apply to the Javascript build. - To support terseness at the command line, we define three values:
passes,passesJSandpassesNATfor each of the variants based on the recommendation by those crossproject plugins. This helps when you want to selectively run something likepassesJS/testto run just the tests defined with the javascript variant
Use this to define an SBT plugin in a sub-project, like this:
lazy val plugin = Plugin(dirName = "sbt-plugin")
.configure(With.build_info)
.configure(With.scala2)
.configure(With.publishing)
.settings(
description := "An sbt plugin to help the build world along",
buildInfoObject := "SbtRiddlPluginBuildInfo",
buildInfoPackage := "com.ossuminc.riddl.sbt",
buildInfoUsePackageAsPath := true,
scalaVersion := "2.12.19"
)In this example we define that the sbt-plugin is in the eponymous directory. We configure that
project with three things: With.build_info, With.scala2, and With.publishing. This automatically
incorporates the scripted plugin for testing sbt plugins.
Use this to define an executable Program with a mainClass like this:
lazy val program = Program(dirName = "my-program", programName = "myprog", mainClass = Option("com.myprog.Main"))
.configure(With.typical, With.publishing)
.dependsOn(
module0,
module1,
module2,
)
.settings(
description := "The main program",
maintainer := "[email protected]",
)By now you should be able to figure out the above settings. It will yield a program executable named
myprog from the contents of directory my-program that must define a class named com.myprog.Main
and will also include module0, module1, and module2 in its classpath.
Use this top level definition to gather your api documentation into a web site. Here's an example from
the ossuminc/riddl repository:
lazy val docsite = DocSite(
dirName = "doc",
apiOutput = file("src") / "main" / "hugo" / "static" / "apidoc",
baseURL = Some("https://riddl.tech/apidoc"),
inclusions = Seq(utils, language, passes, diagrams, commands),
logoPath = Some("doc/src/main/hugo/static/images/RIDDL-Logo-128x128.png")
)
.settings(
name := "riddl-doc",
description := "Generation of the documentation web site",
libraryDependencies ++= Dep.testing
)
.configure(With.noMiMa)
.dependsOn(utils, language, passes, diagrams, commands)Since the goal of sbt-ossuminc is to be declarative, we want to specify what we want to
include in the build. For this, we have the With.* configuration helpers. These are
pure functions (Project => Project) that transform projects by adding settings and plugins.
You can pass these directly into a .configure() call chain.
These helpers take no parameters (or use all defaults):
With.aliases- Add useful command line aliases to the sbt shellWith.build_info- Enablesbt-buildinfoplugin (default configuration)With.dynver- Enablesbt-dynverfor git-based dynamic versioningWith.git- Enablesbt-gitto issue git commands from sbt promptWith.header- Enablesbt-headerfor automatic license header managementWith.java- Enable javac compiler for Java/Scala projectsWith.scalajs- Enable Scala.js compilation (default configuration)With.noMiMa- Disable binary compatibility checkingWith.noPublishing- Disable artifact publishing (useful for root aggregator projects)With.ScalaJavaTime()- Addscala-java-timedependency for cross-platformjava.timeAPIWith.ClassPathJar- Use classpath JAR for packaging (reduces command line length)With.UnmanagedJars- Use unmanaged JAR files fromlibs/directoryWith.ShellPrompt- Custom shell prompt showing project name, git branch, and versionWith.release- Enablesbt-releasepluginWith.resolvers- Add standard resolvers (Maven Local, JCenter, Typesafe)With.scala2- Configure for Scala 2.13 (latest)With.scala3- Configure for Scala 3.3.7 LTS (default)With.scoverage- Enable code coverage with sbt-scoverage and sbt-coveralls
With.Publishing- Configure publishing (defaults to GitHub Packages)With.Publishing.github- Explicitly configure GitHub Packages publishingWith.Publishing.sonatype- Configure publishing to Sonatype/Maven CentralWith.GithubPublishing- Alias forWith.Publishing.githubWith.SonatypePublishing- Alias forWith.Publishing.sonatype
Note: Do not combine GitHub and Sonatype publishing in the same project.
Shortcuts that combine multiple helpers:
With.basic- Combines:aliases,dynver,git,header,resolversWith.typical- Combines:basic,scala3,Scalatest()With.everything- Combines:typical,java,release
These helpers accept parameters for customization:
Add Akka dependencies to the project. Akka requires a commercial license and repository token since 2024.
release: Akka version ("24.10"or"25.10"(latest))
Module("my-actor-system")
.configure(With.Akka.forRelease("25.10"))Note: Akka repository access requires a token. Configure per Akka's instructions at https://akka.io/key
Configure AsciiDoc document generation for static websites and PDFs.
sourceDir: Directory containing AsciiDoc source files (default:"src/asciidoc")enablePdf: Enable PDF generation (default:true)enableDiagrams: Enable diagram support for PlantUML, Graphviz, etc. (default:false)attributes: Custom AsciiDoc attributes for document processing
Features:
- HTML5 website generation with customizable attributes
- PDF generation with asciidoctorj-pdf
- Diagram support via asciidoctorj-diagram (PlantUML, Graphviz, etc.)
- Customizable source directories and output locations
DocSite("docs", "project-docs")
.configure(With.AsciiDoc(
sourceDir = "src/docs/asciidoc",
enablePdf = true,
enableDiagrams = true,
attributes = Map("toc" -> "left", "icons" -> "font")
))Note: For full HTML generation via sbt-site, add to
project/plugins.sbt:addSbtPlugin("com.github.sbt" % "sbt-site-asciidoctor" % "1.7.0")Then enable in
build.sbt:enablePlugins(AsciidoctorPlugin)
Customize BuildInfo generation.
buildInfoObject: Name of generated objectbuildInfoPackage: Package for generated codebuildInfoUsePackageAsPath: Use package as directory structure
Module("my-app")
.configure(With.BuildInfo(
buildInfoObject = "AppBuildInfo",
buildInfoPackage = "com.myapp.build"
))Enable code coverage with minimum threshold.
Module("my-lib")
.configure(With.coverage(80.0)) // Require 80% coverageConfigure IntelliJ IDEA plugin development.
name: Plugin namedescription: Plugin descriptionbuild: IntelliJ build version (e.g.,"243.x")platform:"Community"or"Ultimate"
Plugin("my-idea-plugin")
.configure(With.IdeaPlugin(
name = "My Cool Plugin",
build = "243.x",
platform = "Community"
))Configure Scala.js compilation.
header: JS file header commenthasMain: Enable main module initializerforProd: Enable optimizer (production mode)withCommonJSModule: Use CommonJS modules instead of ES modulesscalaJavaTimeVersion: Override scala-java-time version (default:"2.6.0")scalatestVersion: Override scalatest version (default:"3.2.19")
CrossModule("my-ui", "ui")(JVM, JS)
.jsConfigure(With.ScalaJS(
header = "My App UI v1.0",
hasMain = true,
forProd = true,
scalaJavaTimeVersion = "2.6.0"
))Note:
With.Javascriptis deprecated and will be removed in 2.0. UseWith.ScalaJSinstead.
Add Laminar reactive UI dependencies (Scala.js).
version: Laminar versiondomVersion: Scala.js DOM versionwaypointVersion: Waypoint router version (optional)laminextVersion: Laminext utilities version (optional)laminextModules: Specific Laminext modules to include
CrossModule("frontend", "app-frontend")(JS)
.jsConfigure(With.Laminar(
version = "17.1.0",
domVersion = "2.8.0"
))Enable binary compatibility checking.
previousVersion: Version to check compatibility against (required)excludedClasses: Classes to exclude from checksreportSignatureIssues: Include generic type parameter checks
Module("my-stable-api")
.configure(With.MiMa(
previousVersion = "1.0.0",
excludedClasses = Seq("com.myapp.internal.*")
))Configure Scala Native compilation.
mode: Compilation mode ("debug","fast","full","size","release")buildTarget: Build type ("application","dynamic","static")gc: Garbage collector to uselto: Link-time optimization ("none","thin","full")debugLog: Enable debug loggingverbose: Verbose compilation outputtargetTriple: Target platform triple (optional)linkOptions: Additional linker optionsscalatestVersion: Override scalatest version (default:"3.2.19")
CrossModule("cli-tool", "tool")(JVM, Native)
.nativeConfigure(With.Native(
mode = "release",
buildTarget = "application",
scalatestVersion = "3.2.19"
))Create universal (zip/tgz) packages.
maintainerEmail: Package maintainer emailpkgName: Package namepkgSummary: One-line summarypkgDescription: Full description
Program("my-app", "app", Some("com.myapp.Main"))
.configure(With.Packaging.universal(
maintainerEmail = "[email protected]",
pkgName = "my-app",
pkgSummary = "My Application",
pkgDescription = "A useful application"
))Create Docker images.
maintainerEmail: Package maintainer emailpkgName: Docker image namepkgSummary: Image summarypkgDescription: Image description
Program("my-service", "service", Some("com.myapp.Service"))
.configure(With.Packaging.docker(
maintainerEmail = "[email protected]",
pkgName = "my-service"
))Create GraalVM native images.
pkgName: Executable namepkgSummary: Summarynative_image_path: Path to native-image executable
Add RIDDL library dependencies.
version: RIDDL version to usenonJVM: Use%%%(true) or%%(false) for dependency resolution
Module("my-riddl-app")
.configure(With.Riddl(version = "0.50.0"))Configure Scala 3 with custom version, compiler options, and documentation.
version: Scala version (defaults to"3.3.7")scala3Options: Additional compiler optionsprojectName: Project name for scaladoc output (optional)docSiteRoot: Root directory for documentation site (optional)docBaseURL: Base URL for API documentation (optional)
Module("my-experimental")
.configure(With.Scala3(
version = Some("3.4.0"),
scala3Options = Seq("-experimental")
))
// With documentation settings
Module("my-lib")
.configure(With.Scala3(
projectName = Some("MyLib"),
docSiteRoot = Some("docs/api"),
docBaseURL = Some("https://myproject.org/api")
))Generate Scala.js facades from TypeScript definitions.
Add ScalaTest dependencies with custom version.
version: ScalaTest version
Module("my-tests")
.configure(With.Scalatest(version = "3.2.19"))Generate unified API documentation.
apiOutput: Output directorybaseURL: Base URL for documentationinclusions: Projects to includeexclusions: Projects to excludelogoPath: Path to logo imageexternalMappings: External API mappings
DocSite(
dirName = "docs",
apiOutput = file("docs/api"),
baseURL = Some("https://myproject.org/api"),
inclusions = Seq(moduleA, moduleB, moduleC)
)In 1.1.0, CrossModule automatically included testing and time dependencies. In 1.2.0, these are now opt-in for cleaner, more explicit builds.
// Old (1.1.0) - dependencies were automatic
CrossModule("foo", "bar")(JVM, JS)
// New (1.2.0) - explicitly add what you need
CrossModule("foo", "bar")(JVM, JS)
.configure(With.Scalatest()) // Add if you need testing
.configure(With.ScalaJavaTime()) // Add if you need java.time API-
With.Publishing- Generic publishing helper (defaults to GitHub Packages)- Use
With.Publishingfor default (GitHub) publishing - Use
With.Publishing.githubto explicitly use GitHub Packages - Use
With.Publishing.sonatypefor Sonatype/Maven Central
- Use
-
With.ScalaJavaTime()- Addscala-java-timedependency for cross-platform date/time support -
With.ClassPathJar- Use classpath JAR to reduce command line length on Windows -
With.UnmanagedJars- Configure unmanaged JAR files fromlibs/directory -
With.ShellPrompt- Custom sbt shell prompt with project, branch, and version info
Root()project ID is now configurable - UseprojectIdparameter to customize (default:"root")- Parameterized versions -
With.ScalaJS()andWith.Native()now accept version parameters to override defaults