sbt tips and tricks

In previous posts (#1, #2) I described a bit of theory about using sbt. However, besides long, heavy topics, there are also some smaller pieces of knowledge that you can find useful. Most of them should be obvious for people who have been using sbt a little longer, but I decided to gather these tips here nonetheless.

Choosing sbt version yourself

To make sure that everyone has the same sbt version, set it in the project/build.properties file (remember to commit it to the repo!):

sbt.version=1.1.6

If you generated a project from a template, it should be there, but if you are writing things from scratch you must remember to add it yourself (it’s also worth knowing where to look in case you want to update something). This also applies to other hints in this article.

Setting JVM options

You can change JVM options using SBT_OPTS and JVM_OPTS - putting them into these variables works the same as passing them to the sbt/java command. With that, you can change memory sizes, the garbage collector algorithm, etc. Tools like Travis CI use them to provide some better values than the defaults.

In your local environment you can use slightly saner defaults just by installing Paul Phillips’ wrapper. Add it as an alias to your local dev environment and on CI and you might avoid some trouble once the project size grows.

If you want to put them into a file, the wrapper’s default file location is .jvmopts at the root of the project.

Global scope

If you want some plugins/settings to be available for every project on your computer (but not necessarily as an actual part of each project), you might look into the $HOME/.sbt directory. There:

  • ~/.sbt/1.0/plugins/plugins.sbt is a nice location for all plugins that you use as part of your workflow, which might not necessarily be useful for building, testing and deploying (e.g. checking updates, displaying a dependency graph, generating statistics),
  • ~/.sbt/1.0/build.sbt is where you can put global settings (e.g. settings for global plugins, repo credentials).

I’ll list a few nice plugins later on.

Debugging sbt

The first thing you have to do is make it compile the build files and start the shell. Once you are there, things get easier. (If sbt crashed at this step… just comment out all troublesome parts and continue).

Not sure how to configure this new plugin, or what options you can use in general? A few hints:

  • Import the project into IntelliJ with sbt sources included. This will allow you to easily jump into definitions and see what keys are defined,
  • for auto-plugins it’s worth knowing that it will always be something like: com.package.PluginName.autoPlugin - the contents of autoPlugin are automatically imported into .sbt files, so once you find it, you can check what you can use (or what to import in .scala files).

The sbt console can also tell you a lot of things. With the show command you can display the current values of settings to see if they have the values you expect.

For tasks, you can get a lot of insight from the inspect command. It can show you the key’s description, tell you about a task’s dependencies as well as changes made to the value. It is one of the most powerful commands when it comes to sbt debugging (if not the most powerful), so take a look what it can do.

To override a setting from the shell you can use set. It can shorten the change-reload-run-compare cycle a bit.

Personally, I also find it helpful not to name all .sbt files build.sbt. One of the first things I noticed when I migrated from 0.13 to 1.0 was the printing of the loading order of .sbt files. I could see how they were loaded and if one of them failed, I could see which one. With several .sbt files I no longer had to play a guessing game in order to figure out which one broke the build. That is, once I gave each of them a unique name.

It is also a good thing to extract the version into version.sbt. If you want to use some script to bump the version, it can be as simple as echo $NEW_VERSION > version.sbt.

More than just calling a task once

With sbt you might be used to running a task, waiting for it to finish, and then running another one if the previous one succeeds. You might be tired of doing it manually, and try to exit the shell and call sbt task1 task to avoid that. Well, yes, except you just killed the JVM and it needs to warm up from scratch and recalculate the build.

Instead, you might run ;task1;task2. It will run tasks sequentially and stop at the first failure. Notice that you need to start with ; - using it only as a separator is not enough.

Play users also know about this trick, ~task: it will start the task, but instead of finishing it, sbt will wait and check if any dependencies it used have changed, and rerun it on change. It is used for things like ~compile or ~run to recompile or restart an application when you edit the code. It will give you a way to get out of this mode.

These two features don’t necessarily mix well. If you run ;~task1;task2, you will not get to task2 as sbt would wait for you to unlock task1. Thus, it defeats the purpose of using ; in the first place. (However, ;task1;~task2 would not have this issue - it’s just a matter of being aware of what exactly is happening).

If you find yourself calling such sequences often enough (and don’t want to make a separate task for it), or maybe you just have some set of parameters you are passing for a task, you might need an alias:

addCommandAlias("myAlias", "commandToRun")

Useful plugins

This is a list of plugins I am fond of. Some of them I use for most of my projects, some I have put into a global scope to have them available at all times.

Build

Plugins that supplement the build process:

  • sbt-assembly and sbt-native-packager in general - while sbt gives you a way to build and test your application, it doesn’t ship with a way for you to deploy it, even as a simple fat JAR. These plugins are a must if you want to make your project actually useful.
  • sbt-git - I use it to generate a project’s version number from the current git commit. This way I avoid the issue of relying on a snapshot everywhere else.

Linting and style-checking

They probably deserve their own post, but since I have already written one a long time ago, I’ll just list them:

  • sbt-scalastyle - a basic style checker for Scala.
  • sbt-scalafmt (and its predecessor sbt-scalariform) - the sooner you start using formatter the better. Personally, I configure it to run on compile, but most people just want to check in CI whether sources are well formatted. A long time ago scalariform was what I used (and it was fast), but ever since scalafmt appeared and was improved enough for me to jump on the bandwagon.
  • sbt-wartremover and/or sbt-scapegoat - while I wouldn’t like to enable all warts, enabling most of them should increase your codebase’s quality, especially if you strongly prefer a functional approach to Java-in-Scala style. I learned about wartremover first, but later on, I was shown that a few rules appear in scapegoat that do not appear in wartremover. Pick your poison.
  • sbt-scoverage - while pursuing high coverage blindly is stupid, I like to be able to easily check which parts of my code are not tested, and so might need more attention.

Development

Plugins that don’t affect the build or testing, but still make development a little bit easier:

  • sbt-revolver - not every application is a Play application that you can easily terminate with Ctrl+D. Even some terminal applications refuse to exit after you send EOF. Yet, you would like to be able to kill them without killing sbt. Revolver lets you reStart the application in a fork and return control to sbt immediately. Then you can reStop it any time you want without killing sbt.
  • clippy - better error messages than vanilla scalac provides.
  • sbt-errors-summary - gathers compilation errors by file.
  • sbt-bloop - if you want to use bloop for compilation.

Build checking and improvement

Useful for providing additional information about your project, and dependencies in particular:

  • sbt-dependency-check - checks whether your dependencies might have some vulnerabilities you should be aware of (uses the OWASP list).
  • sbt-dependency-update - easily check whether some of your dependencies can be updated.
  • sbt-dependency-graph - can visualize the dependency graph. I use dependencyBrowseGraph quite often to check what transitive dependencies were introduced by that small library and dependencyStats to see how much it affected the fat JAR’s size.
  • sbt-stats - for checking how much code I wrote (not useful, but pleasant).

Task run in a fork

Sometimes you want to run a program that exits and does some cleanup on program shutdown, and you cannot rely on sbt-revolver because it happens in the console.

fork           := true // run program in forked JVM - so exiting it won't kill sbt
trapExit       := false // sbt don't prevent process from exiting
connectInput   := true // forked process still reads from std-in
outputStrategy := Some(StdoutOutput) // prevent output from prepending [info]

And if you want to be able to kill the currently running task without killing sbt:

Global / cancelable := true

This one is also handy if you see that compilation will fail, but apparently the compiler would run for a long time before it exits with an error message.

Summary

While none of these is a discovery, I think it might be useful to someone who has just started using sbt. If you know about some other nice-to-know tips let me know in the comments!

Update 18.06.17 — added Task run in fork.