Search code examples
sbt

try/finally with SBT tasks


I have an existing task called myTask, whose implementation I don't control.

I want to redefine it in this way: myTask := { val x = prepare() try myTask.value finally cleanup(x) }

As you probably know, this code wouldn't work, as we don't control when myTask.value is executed.

prepare can be called with Def.sequential(), and cleanup with the andFinally construct. The only problem is how cleanup can get the return value of prepare().
Def.sequential{ Def.task{ prepare() }, myTask }.andFinally(cleanup(???))

One workaround is to use global variables, but this is a dirty hack.

Any ideas?

Related doc


Solution

  • I've come to the following solution that doesn't require to use any kind of the global state.

    val initializer = taskKey[Int]("an initializer that makes something and returns some data")
    val tryFinallyTask = taskKey[Unit]("a task that requires initializing and finalizer based on init results")
    val myTask = taskKey[Unit]("your main task that can fail")
    
    myTask := {
      if(scala.util.Random.nextBoolean()) throw new Exception()
    }
    
    initializer := { scala.util.Random.nextInt() }
    
    // the task you would like to execute in a try-finally fashion
    myTask := {
      if(scala.util.Random.nextBoolean) throw new Exception()
    }
    
    initializer := {
      // some logic here
      scala.util.Random.nextInt()
    }
    
    def finalizer(initValue: Int): Unit = {
      println(s"init result is $initValue")
    }
    
    tryFinallyTask := Def.taskDyn {
      Def.sequential(
        initializer,
        myTask.andFinally {
          val initRes = initializer.value
          finalizer(initRes)
        }
      )
    }.value
    

    tryFinallyTask executes initializer and then myTask. In case if initializer succeeds, i.e. myTask starts execution, finalizer logic will be executed in any case.

    Some notes to the code:

    • Def.taskDyn expects a Def.Initializer of SBT task. This is the returning type of Def.sequential as well as andFinally.
    • You have to get initializer.value inside of Def.taskDyn and pass it into the finalizer function. Accessing the value in finalizer will not work, since you can't use .value outside of SBT macros.
    • You are free to use values of other tasks in andFinally clause, although the execution order of these tasks is not guaranteed.