Wonay
Wonay

Reputation: 1250

Sbt plugin run tasks before / after an other task

I know, I saw Run custom task automatically before/after standard task but it seems outdated. I also found SBT before/after hooks for a task but it does not have any code example.

I am on SBT 0.13.17.

So I want to run my task MyBeforeTask and MyAfterTask automatically after an other tasks, says Compile.

So when you do sbt compile I would like to see:

...log...
This is my before test text
...compile log...
This is my after test text

So I would need to have:

object MyPlugin extends AutoPlugin {
  object autoImport {
     val MyBeforeTask = taskKey[Unit]("desc...")
     val MyAfterTask = taskKey[Unit]("desc...")
  }

  import autoImport._

  override def projectSettings: Seq[Def.Setting[_]] = {
     MyBeforeTask := {
       println("This is my before test text")
     },
     MyAfterTask := {
       println("This is my after test text")
     }
  }
}

So I think I need things like dependsOn and in but I am not sure how to set them up.

Upvotes: 1

Views: 1787

Answers (1)

Vladimir Matveev
Vladimir Matveev

Reputation: 127771

It is not possible to configure for a particular task to run after the given task, because that's not how the task dependencies model works - when you specify the task, its dependencies and itself will be executed, but there is no way to define an "after" dependency. However, you can simulate that with dynamic tasks.

To run some task before another, you can use dependsOn:

compile in Compile := (compile in Compile).dependsOn(myBeforeTask).value

This establishes a dependency between two tasks, which ensures that myBeforeTask will be run before compile in Compile.


Note that there is a more generic way to make multiple tasks run one after another:

aggregateTask := Def.sequential(task1, task2, task3, task4).value

Def.sequential relies on the dynamic tasks machinery, which sets up dependencies between tasks at runtime. However, there are some limitations to this mechanism, in particular, you cannot reference the task being defined in the list of tasks to execute, so you can't use Def.sequential to augment existing tasks:

compile in Compile := Def.sequential(myBeforeTask, compile in Compile).value

This definition will fail at runtime with a strange error message which basically means that you have a loop in your task dependencies graph. However, for some use cases it is extremely useful.


To run some task after another, however, you have to resort to defining a dynamic task dependency using Def.taskDyn:

compile in Compile := Def.taskDyn {
  val result = (compile in Compile).value
  Def.task {
    val _ = myAfterTask.value
    result
  }
}.value

Def.taskDyn accepts a block which must return a Def.Initialize[Task[T]], which will be used to instantiate a task to be run later, after the main body of Def.taskDyn completes. This allows one to compute tasks dynamically, and establish dependencies between tasks at runtime. As I said above, however, this can result in very strange errors happening at runtime, which are usually caused by loops in the dependency graph.

Therefore, the full example, with both "before" and "after" tasks, would look like this:

compile in Compile := Def.taskDyn {
  val result = (compile in Compile).value
  Def.task {
    val _ = myAfterTask.value
    result
  }
}.dependsOn(myBeforeTask).value

Upvotes: 6

Related Questions