§Debugging your build
If you are having difficulties getting sbt to do what you want it to do, you may need to use some of the built in utilities that sbt provides to help you debug your build.
§Debugging dependencies
By default, sbt generates reports of all your dependencies, including dependency trees to show which dependencies transitively brought in other dependencies, and conflict resolution tables showing how sbt decided which version of a dependency it selected when multiple were requested.
The reports are generated into xml files, with an accompanying XSL stylesheet that allow browsers that support XSL to convert the XML reports into HTML. Browsers with this support include Firefox and Safari, and notably don’t include Chrome.
The reports can be found in the target/resolution-cache/reports
directory of your project, one is generated for each scope in your project, and are named organization-projectId_scalaVersion-scope.xml
, for example, com.example-my-first-app_2.11-compile.xml
. When opened in Firefox, this report looks something like this:
§Debugging settings
There are a few useful commands that sbt provides that can be used to understand your build and work out where things may be going wrong.
§The show command
The show command shows the return value from any sbt task. So for example, if you’re not sure if a certain source file is being compiled or not, you can run show sources
to see if sbt is including it in the sources:
[my-first-app] $ show sources
[info] ArrayBuffer(my-first-app/app/controllers/Application.scala,
my-first-app/target/scala-2.11/twirl/main/views/html/index.template.scala,
my-first-app/target/scala-2.11/twirl/main/views/html/main.template.scala,
my-first-app/target/scala-2.11/src_managed/main/routes_reverseRouting.scala,
my-first-app/target/scala-2.11/src_managed/main/routes_routing.scala,
my-first-app/target/scala-2.11/src_managed/main/controllers/routes.java)
The output above has been formatted to ensure it fits cleanly on the screen, you may need to copy it to an editor to make sense of it if the task you run returns a long list of items.
You can also specify a task a particular scope, eg test:sources
or compile:sources
, or for a particular project, my-project/compile:sources
, and in some cases, where tasks are scoped by another task, you can specify that scope too, for example, to see everything that will be packaged into your projects jar file, you want to show the mappings
task, scoped to the packageBin
task:
[my-first-app] $ show compile:packageBin::mappings
[info] List(
(my-first-app/target/scala-2.11/classes/application.conf,application.conf),
(my-first-app/target/scala-2.11/classes/controllers/Application.class,controllers/Application.class),
...
§The inspect command
The inspect command gives you detailed information about a task, including what it depends on, what depends on it, where it was defined, etc. It can be used like the show
command:
[my-first-app] $ inspect managedSources
[info] Task: scala.collection.Seq[java.io.File]
[info] Description:
[info] Sources generated by the build.
[info] Provided by:
[info] {file:my-first-app/}root/compile:managedSources
[info] Defined at:
[info] (sbt.Defaults) Defaults.scala:185
[info] Dependencies:
[info] compile:sourceGenerators
[info] Reverse dependencies:
[info] compile:sources
...
Here we’ve inspected the managedSources
command, it tells us that this is a task that produces a sequence of files, it has a description of Sources generated by the build.
You can see that it depends on the sourceGenerators
task, and the sources
task depends on it. You can also see where it was defined, in this case, it’s from sbt’s default task definitions, line 185.
§The inspect tree command
The inspect tree command shows a whole tree of task dependencies for a particular task. If we inspect the tree for the unmanagedSources
task, we can see it here:
[my-first-app] $ inspect tree unmanagedSources
[info] compile:unmanagedSources = Task[scala.collection.Seq[java.io.File]]
[info] +-*/*:sourcesInBase = true
[info] +-*/*:unmanagedSources::includeFilter = sbt.SimpleFilter@3dc46f24
[info] +-compile:unmanagedSourceDirectories = List(my-first-app/app, my-first-a..
[info] | +-compile:javaSource = app
[info] | | +-*:baseDirectory = my-first-app
[info] | | +-*:thisProject = Project(id root, base: my-first-app, configurations: List(compile,..
[info] | |
[info] | +-compile:scalaSource = app
[info] | +-*:baseDirectory = my-first-app
[info] | +-*:thisProject = Project(id root, base: my-first-app, configurations: List(compile,..
[info] |
[info] +-*:baseDirectory = my-first-app
[info] +-*/*:excludeFilter = sbt.HiddenFileFilter$@49e479da
This shows the whole tree of tasks that sbt uses to discover the sources in your project, including the filters to decide which files to be included or excluded. The inspect tree
command is particularly useful when you’re not sure how some part of your build is structured, and you want to find out how it all fits together so you can then dive in deeper.
§Debugging incremental compilation
A common problem that people have in Play is they find Play recompiles and reloads when they don’t expect it to. This is often caused by source generators or IDEs that inadvertently update elements of Play’s classpath, forcing a reload. To debug problems like this, we can look at the debug log from the compile task. When sbt runs a task it captures all the log output, whether it displays it or not, so that you can inspect it later if you want. It can be inspected using the last
command.
So, let’s say you compile
, and a file needs to be recompiled:
[my-first-app] $ compile
[info] Compiling 1 Scala source to my-first-app/target/scala-2.11/classes...
[success] Total time: 1 s, completed 07/04/2015 1:28:43 PM
You can get a full debug log of what happened during the compile command by running last compile
. This will dump a lot of output, but only the first part is what we are interested in, shown here:
[my-first-app] $ last compile
[debug]
[debug] Initial source changes:
[debug] removed:Set()
[debug] added: Set()
[debug] modified: Set(my-first-app/app/controllers/Application.scala)
[debug] Removed products: Set()
[debug] External API changes: API Changes: Set()
[debug] Modified binary dependencies: Set()
[debug] Initial directly invalidated sources: Set(my-first-app/app/controllers/Application.scala)
[debug]
[debug] Sources indirectly invalidated by:
[debug] product: Set()
[debug] binary dep: Set()
[debug] external source: Set()
What this tells us is that a recompile was triggered because my-first-app/app/controllers/Application.scala
was modified.