Intro
sbt
is the most popular build tool for
Scala; however, there are other options if you are interested 1. If
you are not familiar with build tools, see the footnotes
2. If you are
familiar with maven, but not sbt, there are some big differences
3
In this post, I discuss the process of writing an sbt
plugin from
scratch. sbt
uses a fancy Scala DSL for configuration. I was
definitely turned off at first, and I had trouble figuring out how
things worked, until I read
sbt in Action.
We will write a plugin to send a message to Slack. This may be useful to notify a room when there is a new version of a library published, or when a task fails.
Hello World
First, we will start with the hello world of sbt
tasks with the
following requirements:
- We want to have a new task called
slackNotify
. - When we invoke the task, we want it to print “Hello World”
- It need not return anything.
We will start off with a blank directory to contain our project. In this
directory, we create a file build.sbt
, containing the following.
Sending to Slack
Running sbt slackNotify
from a command prompt will print Hello
World
. Instead of doing this, we want to send a message to slack. We
will use an “Incoming WebHook” to do this. This is a URL which we can
POST messages to in order to send a Slack message. The process of
setting up
Incoming WebHooks
is described in their help documentation.
A WebHook looks something like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
.
In order to submit a POST request, we can create a sendMessage
method
like this (and add it to build.sbt
):
This will send a message to the Slack room configured in the WebHook.
Next, we need to hook this up to our slackNotify
task:
That works, but it’s not that useful since we have to hardcode the message.
Let’s make the message an sbt
setting.
This is a ltitle better now, but we’ll change the message to something more useful.
We can use this whenever we publish a package.
Creating a Plugin
This is handy for a single project, but now we will create a plugin that
can be used by other projects. First, we create a new project with the
following build.sbt
:
Next, in src/main/scala/SlackNotify.scala
, we define a companion
object SlackNotifyPlugin
that extends from AutoPlugin
. This allows
sbt to automatically import the settings that we will define (rather
than having the user manually import them in the build.sbt
file).
At this point, you could package the plugin and use it in another project by
adding it to that project’s project/plugins.sbt
file, like:
Testing
Our plugin is fairly straightforward, but testing it would be fairly
onerous, since we would need to package it, then possibly bump the
version in a project, and test it manually. Rather than do that, we will
create an automated test using the
scripted test framework
.
We’ll follow the instructions on that page to first add the
scripted-plugin
dependency to project/scripted.sbt
:
and add the following to build.sbt
:
Then we’ll setup a sample project (that will use the plugin) in
src/sbt-test/sbt-notify/simple
. Finally, we’ll include a test
script in
that directory:
Finally, we can run the command scripted
in our plugin project to run
the test.
Publishing
In order for others to use our plugin, we can publish it to a maven repository. One such repository is Sonatype, which makes the Nexus repository software.
The process of signing up and publishing to Sonatype is described in the sbt docs. It’s basically as follows:
- Signup for Sonatype
- Setup your project to use pgp and sonatype
- Publish a signed version of your artifact
- Promote the release from staging
There are a few nuances to the way I did things that I will describe. Firstly, I use Keybase for key management. So, I want it to manage my PGP keys.
After setting up Keybase, you can create a new PGP key:
In order for us to use sbt-pgp
, we
should install gpg
locally.
And make sure to enable using gpg
rather than Bouncy castyle by adding
useGpg := true
to your build.sbt
file.
Finally, we copy our pgp key to our local gpg databse:
Although I used the sbt-pgp
plugin described in the sbt
documentation, version 1.1.1
didn’t work for
me. I used an older version 1.1.0
. When I was using the newer version,
I kept on getting error messages such as
Once this was setup, I was able to run the following to stage my signed artifact, and then release the artifact. My artifact appeared in Maven Central less than a day afterwards.
Conclusion
In this article, we built an sbt
plugin to send a Slack message. We
learned how to test sbt
plugins. We also pushed artifacts to Sonatype
for public consumption.
I’ve published this plugin on GitHub https://github.com/jamiely/sbt-slack-notify.
If you happen to want to use this plugin, you can do so by adding the
plugin to your project/plugins.sbt
file:
and then specifying some settings in your build.sbt
:
2: For those not familiar with build tools, they may provide one or a number of these capabilities:
- Dependency management
- Retrieving dependencies (and sub dependencies)
- Keeping track of which dependencies are used
- Building
- Can compile code
- May manage language versions
- Packaging
- Packages code into reusable library
- Deploys package to some repository for consumption
- Pluggable architecture
- Plugins can be easily created to add new features
In some ecosystems, these features are separated out into various tools. In others, they are combined into one. Often there are multiple viable options. In the Ruby ecosystem for example, there are separate tools:
- gem - A package format for libraries, and a way to retrieve and track dependencies.
- bundler - A way to manage gems at a project-level.
- rake - A make-like tool to specify build tasks and their dependencies
These all work together to provide the features above. In python, there is
-
easy_install - Installs
packages using a format called
egg
- pip - The preferred way to install Python packages
- virtualenv - Provides isolated python environments, so you can run a specific version of python with a specific set of packages per project.
In JavaScript, there is: npm, grunt, gulp, and webpack, among others. In Java, there is: ant, gradle, and maven, among others.
3: For those who haven’t used sbt
but have used mvn
(Maven), there are
two notable differences:
-
sbt
’s configuration language is Scala rather than XML. -
sbt
has an interactive shell.
Here are some common commands you can use with sbt
:
-
reload
- Reload thesbt
configuration file (usuallybuild.sbt
). -
update
- Pulls the most recent versions of the dependencies. This is useful for wildcard and SNAPSHOT dependencies. -
compile
- Compiles the code -
test
- Runs the tests -
publish
- Pushes code artifacts like the.jar
file to a repository.