If you consider something like ExecSpec, if you were building a DSL to run a particular tool, you could expose all of the ExecSpec API or you could provide something more domain-specific.
So if you had a tool that’s command line looked like:
./tool -h
-o outputDir
-i inputDir
-v verbose mode
-f cool flag
You could have an Exec task that your users had to setup (easy to get wrong):
task execTool(type: Exec) {
executable 'tool'
args '-o', file("output"), '-i', file("input")
}
And you could make it “more convenient” with a method, which expands to about the same thing (complexity multiplies as you add options):
plugin.execTool(file("output"), file("input"), false, false)
Or you could make a custom Exec-like task (usual go-to choice):
task execTool(type: CustomTask) {
output = file("output")
input = file("input")
}
And to wrap it up, if you just wanted to make it easier to add exec
actions to an existing task, you might have a builder DSL (this makes sense if you’re composing lots of little things that don’t make sense as separate tasks, but you have to be careful that you’re not missing inputs/outputs for the overall task):
task someTask {
doLast plugin.execTool().output("output").input("input").build()
}
The central idea is to expose something that you can document and test vs giving unlimited freedom (or at least putting the unlimited freedom behind an escape hatch). So, if you say “ExecSpec” is my API and DSL, you’re stuck with whatever that means, even if you decide later that you need to inspect/restrict the things someone can do with that interface. And your users are stuck with having to understand how to map the generic interface to a specific domain.