There’s a getting started page focused on using existing plugins, which you may want to read first.
A plugin is a way to use external code in a build definition.
A plugin can be a library used to implement a task (you might use
Knockoff to write a
markdown processing task). A plugin can define a sequence of sbt settings
that are automatically added to all projects or that are explicitly
declared for selected projects. For example, a plugin might add a
proguard
task and associated (overridable) settings. Finally, a plugin
can define new commands (via the commands
setting).
sbt 0.13.5 introduces auto plugins, with improved dependency management among the plugins and explicitly scoped auto importing. Going forward, our recommendation is to migrate to the auto plugins. The Plugins Best Practices page describes the currently evolving guidelines to writing sbt plugins. See also the general best practices.
A common situation is when using a binary plugin published to a repository.
You can create project/plugins.sbt
with all of the desired sbt plugins, any general dependencies, and any necessary repositories:
addSbtPlugin("org.example" % "plugin" % "1.0")
addSbtPlugin("org.example" % "another-plugin" % "2.0")
// plain library (not an sbt plugin) for use in the build definition
libraryDependencies += "org.example" % "utilities" % "1.3"
resolvers += "Example Plugin Repository" at "https://example.org/repo/"
Many of the auto plugins automatically add settings into projects, however, some may require explicit enablement. Here’s an example:
lazy val util = (project in file("util"))
.enablePlugins(FooPlugin, BarPlugin)
.disablePlugins(plugins.IvyPlugin)
.settings(
name := "hello-util"
)
See using plugins in the Getting Started guide for more details on using plugins.
A plugin definition is a project under project/
folder. This
project’s classpath is the classpath used for build definitions in
project/
and any .sbt
files in the project’s base
directory. It is also used for the eval
and set
commands.
Specifically,
project/
project are
retrieved and are available on the build definition classpath, just
like for a normal project.
project/lib/
are available to the build
definition, just like for a normal project.
project/
project are the build definition files and
are compiled using the classpath built from the managed and
unmanaged dependencies.
project/plugins.sbt
(similarly to build.sbt
file in a normal project) and will be available to the build
definitions.
The build definition classpath is searched for sbt/sbt.autoplugins
descriptor files containing the names of
sbt.AutoPlugin
implementations.
The reload plugins
command changes the current build to
the (root) project’s project/
build definition. This allows manipulating
the build definition project like a normal project. reload return
changes back
to the original build. Any session settings for the plugin definition
project that have not been saved are dropped.
An auto plugin is a module that defines settings to automatically inject into projects. In addition an auto plugin provides the following feature:
.sbt
files and the eval
and set
commands.
projectSettings
, buildSettings
, and globalSettings
as appropriate.
When a traditional plugin wanted to reuse some functionality from an existing plugin, it would pull in the plugin as a library dependency, and then it would either:
This becomes complicated as the number of plugins increase within an application, and becomes more error prone. The main goal of auto plugin is to alleviate this setting dependency problem. An auto plugin can depend on other auto plugins and ensure these dependency settings are loaded first.
Suppose we have the SbtLessPlugin
and the SbtCoffeeScriptPlugin
, which in turn depends on the SbtJsTaskPlugin
, SbtWebPlugin
, and JvmPlugin
. Instead of manually activating all of these plugins, a project can just activate the SbtLessPlugin
and SbtCoffeeScriptPlugin
like this:
lazy val root = (project in file("."))
.enablePlugins(SbtLessPlugin, SbtCoffeeScriptPlugin)
This will pull in the right setting sequence from the plugins in the right order. The key notion here is you declare the plugins you want, and sbt can fill in the gap.
A plugin implementation is not required to produce an auto plugin, however. It is a convenience for plugin consumers and because of the automatic nature, it is not always appropriate.
The $HOME/.sbt/1.0/plugins/
directory is treated as a global plugin
definition project. It is a normal sbt project whose classpath is
available to all sbt project definitions for that user as described
above for per-project plugins.
A minimal sbt plugin is a Scala library that is built against the version of Scala that sbt runs (currently, 2.12.18) or a Java library. Nothing special needs to be done for this type of library. A more typical plugin will provide sbt tasks, commands, or settings. This kind of plugin may provide these settings automatically or make them available for the user to explicitly integrate.
To make an auto plugin, create a project and enable SbtPlugin
.
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / homepage := Some(url("https://github.com/sbt/sbt-hello"))
lazy val root = (project in file("."))
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-hello",
pluginCrossBuild / sbtVersion := {
scalaBinaryVersion.value match {
case "2.12" => "1.2.8" // set minimum sbt version
}
}
)
Some details to note:
scalaVersion
, sbt will default to the Scala version suited for a plugin.
pluginCrossBuild / sbtVersion
is an optional setting to compile your plugin against an older version of sbt, which allows the plugin users to choose from a range of sbt versions.
Then, write the plugin code and publish your project to a repository. The plugin can be used as described in the previous section.
First, in an appropriate namespace, define your auto plugin object
by extending sbt.AutoPlugin
.
With auto plugins, all provided settings (e.g. assemblySettings
) are provided by the plugin directly via the projectSettings
method. Here’s an example plugin that adds a task named hello to sbt projects:
package sbthello
import sbt._
import Keys._
object HelloPlugin extends AutoPlugin {
override def trigger = allRequirements
object autoImport {
val helloGreeting = settingKey[String]("greeting")
val hello = taskKey[Unit]("say hello")
}
import autoImport._
override lazy val globalSettings: Seq[Setting[_]] = Seq(
helloGreeting := "hi",
)
override lazy val projectSettings: Seq[Setting[_]] = Seq(
hello := {
val s = streams.value
val g = helloGreeting.value
s.log.info(g)
}
)
}
If the plugin needs to append settings at the build-level (that is, in ThisBuild
) there’s a buildSettings
method. The settings returned here are guaranteed to be added to a given build scope only once
regardless of how many projects for that build activate this AutoPlugin.
override def buildSettings: Seq[Setting[_]] = Nil
The globalSettings
is appended once to the global settings (in Global
).
These allow a plugin to automatically provide new functionality or new defaults.
One main use of this feature is to globally add commands, such as for IDE plugins.
override def globalSettings: Seq[Setting[_]] = Nil
Use globalSettings
to define the default value of a setting.
Next step is to define the plugin dependencies.
package sbtless
import sbt._
import Keys._
object SbtLessPlugin extends AutoPlugin {
override def requires = SbtJsTaskPlugin
override lazy val projectSettings = ...
}
The requires
method returns a value of type Plugins
, which is a DSL for constructing the dependency list. The requires method typically contains one of the following values:
empty
(No plugins)
&&
operator (for defining multiple dependencies)
Some plugins should always be explicitly enabled on projects. we call these root plugins, i.e. plugins that are “root” nodes in the plugin dependency graph. An auto plugin is by default a root plugin.
Auto plugins also provide a way for plugins to automatically attach themselves to
projects if their dependencies are met. We call these triggered plugins,
and they are created by overriding the trigger
method.
For example, we might want to create a triggered plugin that can append commands automatically to the build. To do this, set the requires
method to return empty
, and override the trigger
method with allRequirements
.
package sbthello
import sbt._
import Keys._
object HelloPlugin2 extends AutoPlugin {
override def trigger = allRequirements
override lazy val buildSettings = Seq(commands += helloCommand)
lazy val helloCommand =
Command.command("hello") { (state: State) =>
println("Hi!")
state
}
}
The build user still needs to include this plugin in project/plugins.sbt
, but it is no longer needed to be included in build.sbt
. This becomes more interesting when you do specify a plugin with requirements. Let’s modify the SbtLessPlugin
so that it depends on another plugin:
package sbtless
import sbt._
import Keys._
object SbtLessPlugin extends AutoPlugin {
override def trigger = allRequirements
override def requires = SbtJsTaskPlugin
override lazy val projectSettings = ...
}
As it turns out, PlayScala
plugin (in case you didn’t know, the Play framework is an sbt plugin) lists SbtJsTaskPlugin
as one of its required plugins. So, if we define a build.sbt
with:
lazy val root = (project in file("."))
.enablePlugins(PlayScala)
then the setting sequence from SbtLessPlugin
will be automatically appended somewhere after the settings from PlayScala
.
This allows plugins to silently, and correctly, extend existing plugins with more features. It also can help remove the burden of ordering from the user, allowing the plugin authors greater freedom and power when providing feature for their users.
When an auto plugin provides a stable field such as val
or object
named autoImport
, the contents of the field are wildcard imported
in set
, eval
, and .sbt
files. In the next example, we’ll replace
our hello command with a task to get the value of greeting
easily.
In practice, it’s recommended to prefer settings or tasks to commands.
package sbthello
import sbt._
import Keys._
object HelloPlugin3 extends AutoPlugin {
object autoImport {
val greeting = settingKey[String]("greeting")
val hello = taskKey[Unit]("say hello")
}
import autoImport._
override def trigger = allRequirements
override lazy val buildSettings = Seq(
greeting := "Hi!",
hello := helloTask.value)
lazy val helloTask =
Def.task {
println(greeting.value)
}
}
Typically, autoImport
is used to provide new keys - SettingKey
s, TaskKey
s,
or InputKey
s - or core methods without requiring an import or qualification.
An example of a typical plugin:
build.sbt
:
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / homepage := Some(url("https://github.com/sbt/sbt-obfuscate"))
lazy val root = (project in file("."))
.enablePlugins(SbtPlugin)
.settings(
name := "sbt-obfuscate",
pluginCrossBuild / sbtVersion := {
scalaBinaryVersion.value match {
case "2.12" => "1.2.8" // set minimum sbt version
}
}
)
ObfuscatePlugin.scala
:
package sbtobfuscate
import sbt._
import sbt.Keys._
object ObfuscatePlugin extends AutoPlugin {
// by defining autoImport, the settings are automatically imported into user's `*.sbt`
object autoImport {
// configuration points, like the built-in `version`, `libraryDependencies`, or `compile`
val obfuscate = taskKey[Seq[File]]("Obfuscates files.")
val obfuscateLiterals = settingKey[Boolean]("Obfuscate literals.")
// default values for the tasks and settings
lazy val baseObfuscateSettings: Seq[Def.Setting[_]] = Seq(
obfuscate := {
Obfuscate(sources.value, (obfuscate / obfuscateLiterals).value)
},
obfuscate / obfuscateLiterals := false
)
}
import autoImport._
override def requires = sbt.plugins.JvmPlugin
// This plugin is automatically enabled for projects which are JvmPlugin.
override def trigger = allRequirements
// a group of settings that are automatically added to projects.
override val projectSettings =
inConfig(Compile)(baseObfuscateSettings) ++
inConfig(Test)(baseObfuscateSettings)
}
object Obfuscate {
def apply(sources: Seq[File], obfuscateLiterals: Boolean): Seq[File] = {
// TODO obfuscate stuff!
sources
}
}
A build definition that uses the plugin might look like. obfuscate.sbt
:
obfuscate / obfuscateLiterals := true
The simplest global plugin definition is declaring a library or plugin
in $HOME/.sbt/1.0/plugins/build.sbt
:
libraryDependencies += "org.example" %% "example-plugin" % "0.1"
This plugin will be available for every sbt project for the current user.
In addition:
$HOME/.sbt/1.0/plugins/lib/
and will be available to every build definition for the current user.
$HOME/.sbt/1.0/plugins/project/Build.scala
as described at
.scala build definition.
$HOME/.sbt/1.0/plugins/
, such as
$HOME/.sbt/1.0/plugins/MyPlugin.scala
.
$HOME/.sbt/1.0/plugins//build.sbt
should contain sbtPlugin := true
. This can be used for quicker
turnaround when developing a plugin initially:
reload
the project you want to use the modified plugin in
sbt will rebuild the plugin and use it for the project.
Additionally, the plugin will be available in other projects on
the machine without recompiling again. This approach skips the
overhead of publishLocal
and clean
ing the plugins directory of the
project using the plugin.
These are all consequences of $HOME/.sbt/1.0/plugins/
being a standard
project whose classpath is added to every sbt project’s build
definition.
As an example, we’ll add the Grizzled Scala library as a plugin. Although this does not provide sbt-specific functionality, it demonstrates how to declare plugins.
project/lib/
Edit project/plugins.sbt
to contain:
libraryDependencies += "org.clapper" %% "grizzled-scala" % "1.0.4"
If sbt is running, do reload
.
We can change to the plugins project in project/
using
reload plugins
.
$ sbt
> reload plugins
[info] Set current project to default (in build file:/Users/sbt/demo2/project/)
>
Then, we can add dependencies like usual and save them to
project/plugins.sbt
. It is useful, but not required, to run update
to verify that the dependencies are correct.
> set libraryDependencies += "org.clapper" %% "grizzled-scala" % "1.0.4"
...
> update
...
> session save
...
To switch back to the main project use reload return
:
> reload return
[info] Set current project to root (in build file:/Users/sbt/demo2/)
This variant shows how to use sbt’s external project support to declare a source dependency on a plugin. This means that the plugin will be built from source and used on the classpath.
Edit project/plugins.sbt
lazy val root = (project in file(".")).dependsOn(assemblyPlugin)
lazy val assemblyPlugin = RootProject(uri("git://github.com/sbt/sbt-assembly"))
If sbt is running, run reload
.
Note that this approach can be useful when developing a plugin. A
project that uses the plugin will rebuild the plugin on reload
. This
saves the intermediate steps of publishLocal
and update
. It can also
be used to work with the development version of a plugin from its
repository.
It is however recommended to explicitly specify the commit or tag by appending it to the repository as a fragment:
lazy val assemblyPlugin = uri("git://github.com/sbt/sbt-assembly#0.9.1")
One caveat to using this method is that the local sbt will try to run the remote plugin’s build. It is quite possible that the plugin’s own build uses a different sbt version, as many plugins cross-publish for several sbt versions. As such, it is recommended to stick with binary artifacts when possible.
Grizzled Scala is ready to be used in build definitions. This includes
the eval
and set
commands and .sbt
and project/*.scala
files.
> eval grizzled.sys.os
In a build.sbt
file:
import grizzled.sys._
import OperatingSystem._
libraryDependencies ++=
if(os == Windows)
Seq("org.example" % "windows-only" % "1.0")
else
Seq.empty
Plugins can be published like any other projects. When publishing your plugin to a Maven-layout repository, use sbt 1.9.x or above.
However, there is one caveat if you attempt to publish your plugin to a repository that follows the Maven layout.
If your artifacts repository expect artifacts to be compliant with Maven layout and rejects artifacts that do not adhere to it you can: 1. (recommended) If you and consumers of your plugin use sbt 1.9.x or above
Since sbt 1.9, it tries to publish any plugin with both the new and legacy Maven style (for backward compatibility). The legacy Maven style is not fully compatible with Maven layout.
You need to disable it with:
sbtPluginPublishLegacyMavenStyle := false
Notice that you won’t be able to consume this plugin with sbt older than 1.9, as it can only resolve the legacy Maven style (or you need to use the trick described in sbt-vspp).
3. If you use sbt < 1.9.x
You can use https://github.com/esbeetee/sbt-vspp/ 5. If you cannot use sbt 1.9.x and you cannot/don’t want to use sbt-vspp
There should be an option like Suppress POM Consistency Checks
in your artifactory settings that will allow you to submit artifacts even if they don’t fully follow Maven layout.
You can find more details about this in the following issue.
If you’re a plugin writer, please consult the Plugins Best Practices page; it contains a set of guidelines to help you ensure that your plugin is consistent and plays well with other plugins.
For cross building sbt plugins see also Cross building plugins.