Skip to content

Instantly share code, notes, and snippets.

@jsuereth
Created June 15, 2015 14:56
Show Gist options
  • Save jsuereth/ad8524a56d7e54780fe6 to your computer and use it in GitHub Desktop.
Save jsuereth/ad8524a56d7e54780fe6 to your computer and use it in GitHub Desktop.
Sbt build structure

Here's a short little guide to sbt's build structure.

First off, the key you need to investigate how sbt is building things. Here's a snippet from a build.sbt:

import sbt.Keys._
val bs: sbt.BuildStructure = buildStructure.value

This will give you all the gory details of our build (or builds, as is the case).

Note: Sbt may have more than one build definition loaded, each with its own classpath of sorts (actually these get merged, but that's an implementation restriction really). If you use a source dependency, like ProjectRef(....) in your build, you may wind up with more than one sbt build getting loaded

Next, let's look at each of these loaded builds, we can grab that via "units" on the build structure. A single unit represents a "meta-build". e.g. the project/ directory of the current working directory represents one such "unit". If you have a source dependency (e.g. a git repository), that would be another "unit". Each unit is indexed by the source URI ->

> bs.units
res0: Map[java.net.URI,sbt.LoadedBuildUnit] = Map(file:/home/jsuereth/projects/sbt/sbt/ -> /home/jsuereth/projects/sbt/sbt)

You can see the "base" directory for the project here is my local checkout of sbt. This is a mapped lookup of all the sbt.LoadedBuiildUnit. A sbt.LoadedBuildUnit will give you everything you need to generate an ENSIME project that can autocomplete even the .sbt format.

Here's the useful bits:

> val testUnit = bs.units.values.head  // Hackery to just get the only one for now
testUnit: sbt.LoadedBuildUnit = /home/jsuereth/projects/sbt/sbt

1. Getting the classpath used for .sbt files

scala> testUnit.classpath
res10: Seq[java.io.File] = List(/home/jsuereth/projects/sbt/sbt/project/target/scala-2.10/sbt-0.13/classes, /home/jsuereth/.sbt/0.13/plugins/target/scala-2.10/sbt-0.13/classes, /home/jsuereth/.ivy2/cache/scala_2.10/sbt_0.13/com.github.mpeltonen/sbt-idea/jars/sbt-idea-1.6.0.jar, /home/jsuereth/.ivy2/cache/commons-io/commons-io/jars/commons-io-2.0.1.jar, /home/jsuereth/.ivy2/cache/scala_2.10/sbt_0.13/me.lessis/sbt-growl-plugin/jars/sbt-growl-plugin-0.1.3.jar, /home/jsuereth/.ivy2/cache/me.lessis/meow_2.10/jars/meow_2.10-0.1.1.jar, /home/jsuereth/.sbt/boot/scala-2.10.4/lib/scala-library.jar, /home/jsuereth/.ivy2/cache/scala_2.10/sbt_0.13/com.jsuereth/sbt-pgp/jars/sbt-pgp-1.0.0.jar, /home/jsuereth/.ivy2/cache/com.jsuereth/pgp-library_2.10/jars/pgp-library_2.10-1.0.0.jar, /home/jsuereth/.ivy...

2. Getting the imports used in .sbt files

scala> testUnit.imports
res12: Seq[String] = List(import sbt._, Keys._, dsl._, import _root_.org.sbtidea.SbtIdeaPlugin._, _root_.growl.GrowlingTests._, _root_.com.typesafe.sbt.SbtSite._, _root_.com.typesafe.sbt.SbtGhPages._, _root_.com.typesafe.sbt.SbtGit._, _root_.com.typesafe.sbt.SbtScalariform._, import _root_.com.typesafe.sbt.SbtPgp.autoImport._, _root_.com.typesafe.sbt.JavaVersionCheckPlugin.autoImport._, _root_.bintray.BintrayPlugin.autoImport._, import StatusPlugin.autoImport._, SbtLauncherPlugin.autoImport._, NightlyPlugin.autoImport._, import _root_.sbt.plugins.IvyPlugin, _root_.sbt.plugins.JvmPlugin, _root_.sbt.plugins.CorePlugin, _root_.sbt.plugins.JUnitXmlReportPlugin, _root_.com.typesafe.sbt.SbtPgp, _root_.com.typesafe.sbt.JavaVersionCheckPlugin, _root_.sbtdoge.DogePlugin, _root_.bintray.BintrayPl...

3. Getting all the "metaprojects" used to build your build

scala> testUnit.rootProjects
res13: Seq[String] = List(sbtRoot)

Note: Yes, people COULD have multi-module meta-builds. We do this as a testing trick for some plugins, although don't worry about supporting it for now, as i'm not sure a lot of people use that feature

4. Inspecting what possible autoplugins are resolved/enablable

scala> testUnit.unit.plugins.detected.autoPlugins
res26: Seq[sbt.DetectedAutoPlugin] = List(DetectedAutoPlugin(sbt.plugins.IvyPlugin,sbt.plugins.IvyPlugin,false), DetectedAutoPlugin(sbt.plugins.JvmPlugin,sbt.plugins.JvmPlugin,false), DetectedAutoPlugin(sbt.plugins.CorePlugin,sbt.plugins.CorePlugin,false), DetectedAutoPlugin(sbt.plugins.JUnitXmlReportPlugin,sbt.plugins.JUnitXmlReportPlugin,false), DetectedAutoPlugin(com.typesafe.sbt.SbtPgp,com.typesafe.sbt.SbtPgp,true), DetectedAutoPlugin(com.typesafe.sbt.JavaVersionCheckPlugin,com.typesafe.sbt.JavaVersionCheckPlugin,true), DetectedAutoPlugin(sbtdoge.DogePlugin,sbtdoge.DogePlugin,false), DetectedAutoPlugin(bintray.BintrayPlugin,bintray.BintrayPlugin,true), DetectedAutoPlugin(StatusPlugin,StatusPlugin,true), DetectedAutoPlugin(SbtLauncherPlugin,SbtLauncherPlugin,true), DetectedAutoPlugin...

Some of these may need to live in alternative indicies so you can provide smarter autocompletion for .sbt or build.scala files, possibly even prompting the user to have their build.scala include some of the auto-imports from AutoPlugins.

In any case, hope this helps you implement what you need. I'm still debating how much of that to publicize, as we're not locked down 100% on interfaces. You can be assured nothing i've documented here will change significantly, but there may be tweaks between 0.13 and 1.0 for removing old-school plugins and doing some general TLC/cleanup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment