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 Configuration
s but whose values are logically Task
s.
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!