Perhaps it's due to my weird work on build tool plugins that do weird stuff (git-commit-id-maven-plugin, sbt-jmh, sbt-jol, sbt-jcstress and more recently sbt-multi-release-jar ...) yet I find myself very often in need of formulating the following as a task / goal or whatnot:

Override some task (e.g. test), with some other "special-test", followed by the executing the tasks original implementation.

Using .dependsOn to model task dependencies

This can easily become a bit mind bending if you're not used to it, though the solution, at least in sbt, is very simple. Tasks in sbt form a DAG that sbt then can analyse and execute the right things in the right order. The basic operator here is to "depend on" some other task's output. This makes a lot of sense - the test task, depends on the output of compilation (compile), so if we want to test things, we'll first have to get the results of the compile task. sbt can figure all this out thanks to the dependencies between tasks.

How do we, however express the, "I depend on myself (and some new things)" concept?

I've been using the following pattern for a while now, which uses the task.dependsOn(task1, task2, task3, ...): returns the new task combinator:

  compile in Compile := // TODO blog about this 
    (compile in MultiReleaseJar).dependsOn(
      compile in Compile

So the "special task" depends on the "normal task". This means that the "normal task" (compile in Compile) will be executed first, and then our "special task" will be executed. This makes sense of course, however it is a bit backwards when we want to reason about a bunch of tasks, and look at it from the perspective "what will run first?", and especially when we actually need to re-use results from the previous tasks.

To complete the description of the above snippet: We bind this new task, that represents the dependencies explained like this to the compile in Compile which now effectively has replaced the compile (or more specifically compile:compile command in sbt).

Depending on results of tasks by using .value

While the above snippet is correct and works fine, it may be tricky to understand when first looking at such advanced sbt magics. sbt has over the last years drastically improved its user-friendliness - gone are the days of magical un-named operators, and most tasks nowadays can be achieved by using just := ("binding some value/task (rhs) to a key (lhs)". Thanks to Eugene chiming in in a recent PR towards the Multi Release Jar support plugin for sbt, I was reminded once more of how powerful and simple sbt has become.

When I was starting this plugin, I thought to myself "I'll want to execute my special tests, and then the normal tests." Turns out, that when using the .value (macro) operator, the implementation of that requirement would be exactly that:

test in Test := {  
  val _ = (test in MultiReleaseJarTest).value
  (test in Test).value

If one were to read this code out loud it could be spelled out as: "The test task in the Test scope, shall now: first get the value of the test task in the MultiReleaseJarTest scope, and then the result of the previous implementation of the test task in the Test scope." Which is exactly the problem statement I started out with. Of course we could use the actual values returned here, but in this case this is enough to just trigger them, as a failure would result in failure of the encompassing task. This works because .value basically transforms the operations into flatMap-ped sequences of task to perform, so if the previous one fails, the next one won't ever be invoked and the entire sequence fails (think of how Futures chained with flatMap work).

I thought that this nifty trick is something that often comes up in plugin development, and could be useful to a wider audience.