1

By trial and probably error, as a plugin author I've fallen into using the following style, which seems to work:

object AmazingPlugin extends AutoPlugin {
  object autoImport {
    val amaze = TaskKey[Amazement]("Do something totally effing amazing")
  }
  import autoImport._
  lazy val ethDefaults : Seq[sbt.Def.Setting[_]] = Seq(
    amaze in Compile := { amazeTask( Compile ).value },
    amaze in Test    := { amazeTask( Test ).value }
  )
  def amazeTask( config : Configuration ) : Initialize[Task[Amazement]] = Def.task {
    ???
  }
}

Unfortunately, I do not actually understand how all of these constructs work, why it is an Initialize[Task[T]] I generate rather than a Task[T], for example. I assume that this idiom "does the right thing", which I take to mean that the amazeTask function generates some wrapper or seed or generator of an immutable Task for each Configuration and binds it just once to the appropriate task key. But it is entirely opaque to me how this might work. For example, when I look up Initialize, I see a value method that requires an argument, which I do not supply in the idiom above. I assume in designing the SBT configuration DSL tricks with implicits and/or macros were used, and shrug it off.

However, recently I've wanted to factor out some logic from my tasks into what are logically private functions, but which also require access to the value of tasks and settings. If just used private functions, gathering all the arguments would become repetitive boilerplate of the form

val setting1 = (setting1 in config).value
val setting2 = (setting2 in config).value
...
val settingN = (settingN in config).value

val derivedValue = somePrivateFunction( setting1, setting2 ... settingN )

everywhere I need to use get the value derived from settings. So it's better to factor all this into a derivedValue task, and I can replace all of the above with

derivedValue.value

Kewl.

But I do want derivedValue to be private, so I don't bind it to a task key. I just do this:

private def findDerivedValueTask( config : Configuration ) : Initialize[Task[DerivedValue]] = Def.task {
  val setting1 = (setting1 in config).value
  val setting2 = (setting2 in config).value
  ...
  val settingN = (settingN in config).value

  somePrivateFunction( setting1, setting2 ... settingN )
}

Now it works fine to implement my real, public task as...

def amazeTask( config : Configuration ) : Initialize[Task[Amazement]] = Def.task {
  val derivedValue = findDerivedValueTask( config ).value      
  // do stuff with derivedValue that computes and Amazement object
  ???
}

Great! It really does work just fine.

But I have a hard time presuming that there is some magic by which this idiom does the right thing, that is generate an immutable Task object just once per config and reuse it. So, I think to myself, I should memoize the findDerivedValueTask function, so that after it generates a task, the task gets stored in a Map, whose keys are Configurations but whose values are logically Tasks.

But now my nonunderstanding of what happens behind the scenes bites. Should I store Initialize[Task[DerivedValue]] or just Task[DerivedValue], or what? Do I have to bother, does sbt have some clever magic that is already taking care of this for me? I really just don't know.

If you have read this far, I am very grateful. If you can clear this up, or point me to documentation that explains how this stuff works, I'll be even more grateful. Thank you!

Steve Waldman
  • 13,689
  • 1
  • 35
  • 45
  • Hi! I read it all and I cannot answer, but I'll just leave here my opinion. I think you're actually doing well, both in how you implement things and in your intuition about how things work. I think you shouldn't overcomplicate things and implement any manual memoization. I don't know for sure, but think that sbt will handle this with some "clever magic". – laughedelic Mar 19 '18 at 23:54
  • thanks! so far i've just left it alone, but i do wish i understood what was going on better. – Steve Waldman Mar 20 '18 at 06:24

0 Answers0