<img height="1" width="1" src="https://www.facebook.com/tr?id=1076094119157733&amp;ev=PageView &amp;noscript=1">

Improving workflow with local SBT files

Posted by Aleksandr Ivanov on Thu, Dec 15, 2016

Did you know that you can create as many *.sbt configuration files as you need? Or that SBT has multiple configuration levels - global and project-specific? In this article i’d like to share the trick that helps me to improve productivity by using SBT’s multiple configuration files.

Global Configuration

As I mentioned earlier there are two types of configurations that you can apply — global and project-specific. Global configuration files are stored in versioned folder under ~/.sbt, e.g at the time of writing I have ~/.sbt/0.13/ folder. There you can find your account-wide SBT settings. The not-well-known feature is that SBT reads all your *.sbt files sorted by their lexical names. You can put multiple configuration files into ~/.sbt/0.13 and those will be applied globally. For clarity you can create a global.sbt file, but you are not constraint by the name.

As an example i’d recommend to try Ammonite REPL, great tool that improves your Scala REPL experience. You can put its configuration into any global *.sbt file (e.g ~/.sbt/0.13/global.sbt, or ~/.sbt/0.13/repl.sbt, etc…) to make it available across all your projects:

libraryDependencies += "com.lihaoyi" % "ammonite" % "0.8.0" % "test" cross CrossVersion.full
initialCommands in (Test, console) := """ammonite.Main().run()"""

Now all it takes to start an enhanced Ammonite REPL for you project is to run test:console. In case you are already an Ammonite’s user, running the amm command might be slightly more convenient. To simplify this, SBT has aliases that you can put either to your global configuration or as an alternative into ~/.sbtrc file:

alias amm = test:console

As a quick note, i’d like to mention that you can configure SBT plugin globally in the same manner adding them to ~/.sbt/0.13/plugins/*.sbt files. This gives you two benefits:

  1. No need to add the same plugin over and over for each new project
  2. You can use specific plugins of yours without storing them in you VCS repo.

Mine ~/.sbt/0.13/plugins/plugins.sbt has the following lines:

addSbtPlugin("org.ensime"      % "sbt-ensime"   % "1.9.1")
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-M14")

This way I can use Ensime and Coursier without enforcing anyone to deal with these dependencies.

Local Configuration

Project’s build configuration follows the same rules. SBT reads all *.sbt files and applies settings it resolves, overriding or extending global settings.
Using these rules you can define your project’s build structure in multiple ways.

The most common way is to have a single build.sbt file in the project’s root or multiple ones, to simplify a complex build (e.g projects.sbt, dependencies.sbt, configurations.sbt). Worth to mention that for now each *.sbt file has makes its own namespace and you can’t import definitions from one file to another. Alternative way to define multi-module SBT project, having module specific settings in the corresponding folders.

So, you may ask, what does it give you? What never occurred to me is that in conjunction with a VCS it becomes a powerful combination. As an example of such i’d like to share one use case for inspiration.

The only problem having a global configuration is that you can’t use project specific settings. Good example of this is Ammonite repl, since we have a global configuration we can’t add project-specific initial import statements that improve the console experience. Luckily it’s possible to override global settings on the project level, let’s say adding local.sbt file. But it’s something you want to keep on your machine, without sharing your private settings with other. We can easily solve it by ignoring local.sbt files in your VCS. Since i’m using Git, it’s easy enough to add it to .gitignore_global. If you have a complex, multimodule build, you can store these local.sbt files in each submodule to have submodule specific settings or create a single one in the project’s root. This way we can make a project-specific imports for Ammonite:

val start =
  """
    |import akka.actors._
    |implicit val system = ActorSystem()
  """.stripMargin

initialCommands in console := start

initialCommands in (Test, console) := s"""ammonite.Main(\"\"\"$start\"\"\").run()"""

Conclusion

Having too many configuration files in multiple places might confuse you, it may become hard to find where something is configured. One possible solution to this problem are commands inspect <cmd> and inspect tree <cmd>. Their output will help you to find the place where you’ve configured something and the dependencies between settings.

Obviously you are not limited by this scenario. SBT is powerful enough to be used as not only a build tool. If you don’t want to break your DevOps, but have something specific you can write a bunch of commands to pack your project’s distribution in a specific way or write some deployment scripts and control them from SBT. Grasping and mastering it all, especially when you have experience with other tools, is hard, but i hope that little tricks as this one with help you to improve you workflow and productivity. If you other have interesting ideas how to use what i described above to increase your productivity please don’t hesitate to share it.

Topics: Scala, Sbt

Recent Posts

Posts by Topic

see all

Subscribe to Email Updates