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 useful if you:
- 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
is designed to be opinionated and declarative, and consequently simpler.
Because you just declare what you want and the defaults are usually correct, it is less verbose
as well.
In your project/plugins.sbt
file, place this line:
addSbtPlugin("com.ossuminc" % "sbt-ossuminc" % "0.13.3")
In your build.sbt
place this line near the top:
enablePlugins(OssumIncPlugin)
These two things are required to activate the plugin for your build. There's more you can do, 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 these
common plugins:
// 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
// Helpers from other sources
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.12.0")
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")
// Scala specific from various places
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.1.0")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.13")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.4")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
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")
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
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 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/"),
maintainerEmail = "somebody@my_org.com",
startYr = 2024)
.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.
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.js("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.native
There's a lot going on in this example. Most of it is based on org.portable.scala
s 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_cp
is defined as aCrossProject
defined by those org.portable.scala plugins. It is named with the_cp
suffix to distinguish it as the CrossProject (thing that can build any of the variants) - The
CrossModule
object 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
,.configure
and.settings
calls 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
.jvmConfigure
and.jvmSettings
are analogous to the.configure
and.settings
call chain options, but they only apply to the JVM build. - Similarly, the
.jsConfigure
and.jsSettings
options only apply to the Javascript build. - To support terseness at the command line, we define three values:
passes
,passesJS
andpassesNAT
for each of the variants based on the recommendation by those crossproject plugins. This helps when you want to selectively run something likepassesJS/test
to 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.
Since the goal of sbt-ossuminc
is to be declarative, we want to specify just what we want to
include in the build. For this we have the With
options. These are sbt configuration functions
that transform a Project into another project with different settings. You can pass these names
directly into a .configure
call chain invocation per usual sbt
usage.
Here are descriptions of all the parameterless configuration functions you can use:
With.akka
- add the latests set of Akka dependencies to the projectWith.aliases
- add a set of useful command line aliases to the sbt command lineWith.build_info
- enable thesbt-buildinfo
plugin on the project to get an info object that provides info about your projectWith.dynver
- enable thesbt-dynver
plugin on the project for dynamic versioning based on git tagsWith.git
- enable issuing anygit
command from the sbt prompt so you don't need to use another terminal window.With.header
- enable thesbt-header
plugin for replacing code header comments with copyrightWith.java
-With.javascript
-With.misc
-With.noMiMa
- turn Migration Manager off for this project (prevents build-stopping errors)With.publishing
- configure the project to publish signed artifacts to Sonatype/MavenWith.release
- enable thesbt-release
pluginWith.resolvers
- enable a group of resolvers for resolving dependenciesWith.scala2
- configure the project to do the latest Scala 2 compilationWith.scala3
- configure the Project to do the most recent LTS Scala 3 compilationWith.scalaTest
- add ScalaTest dependencies to the probjectWith.scalaft
- add standardized ScalaFmt formatting helpWith.scoverage
- addsbt-scoverage
andsbt-coveralls
support to the projectWith.basic
- shorthand for adding these: aliases, dynver, git, header, resolversWith.typical
- shorthand for With.basic and scala3, scalaTest and publishingWith.everything
- shorthand for With.typical and java, misc, build_info, releaseWith.noPublishing
- turns publishing off making the project unpublishable, handy for Root projectWith.plugin
- makes the project produce an sbt autoplugin
Here are the descriptions of the configuration functions that take parameters:
-
With.coverage
- enable code coverage plugins with the following options:- percent: Double = 50.0d
-
With.js
- enable Scala.js Javascript compilation with the following options:- header: String = "no header"
- hasMain: Boolean = false,
- forProd: Boolean = true,
- withCommonJSModule: Boolean = false
-
With.laminar
- for Scala.js modules, include laminar and DOM support with the following options:- version: String = "17.1.0"
- domVersion: String = "2.8.0"
-
With.MiMa
- turns on Migration manager and you must specify at least the first option:- previousVersion: String // the previous version of the artifact/project to check against
- excludedClasses: Seq[String] = Seq.empty // classes to exclude from compatibility checks
- reportSignatureIssues: Boolean = false // full signature checks, including generic type parameters
-
With.native
- enable native code generation withscala-native
with the following options: buildTarget: String = "static" targetTriple: String = "arm64-apple-macosx11.0.0" gc: String = "commix" debug: Boolean = true noLTO: Boolean = false debugLog: Boolean = false verbose: Boolean = false ld64Path: String = "/opt/homebrew/opt/llvm/bin/ld64.lld" -
With.riddl
- for JVM or JS modules, include a specific version of RIDDL libraries with the following options:- forJS: Boolean
- version: String
-
With.unidoc
- enable thesbt-unidoc
plugin with the following options:- apiOutput: File = file("target/unidoc")
- baseURL: Option[String] = None
- inclusions: Seq[ProjectReference] = Seq.empty
- exclusions: Seq[ProjectReference] = Seq.empty
- logoPath: Option[String] = None
- externalMappings: Seq[Seq[String]] = Seq.empty