I am trying to write my own gradle plugin and it needs to be able to configure a set of objects - how many of these objects and what they're called is up to the user.
The doco for creating custom gradle plugins with advanced customisability is quite poor. It mentions project.container()
method to do this kind of thing, but I couldn't figure out how to make it work in my usecase.
This is an example of my plugin's configuration DSL as it stands:
teregrin {
terraformVersion = '0.6.6'
root("dev"){
accessKey = "flobble"
}
root("prd"){
}
}
And this is my plugin extension object that allows me to configure it:
class TeregrinPluginExtension {
boolean debug = false
boolean forceUnzip = false
String terraformVersion = null
Set<TeregrinRoot> roots = []
def root(String name, Closure c){
def newRoot = new TeregrinRoot(name)
c.setDelegate(newRoot)
c()
roots << newRoot
}
}
The extensions wired up in my plugin in the standard way:
project.extensions.create("teregrin", TeregrinPluginExtension)
This works ok, but it's a pretty ugly configuration style, not really in the style of the typical gradle DSL.
How can I change my plugin configuration DSL to be something like this:
teregrin {
terraformVersion = '0.6.6'
roots {
dev {
accessKey = "flobble"
}
prd {
}
}
}
The gradle way of implementing such DSL is by using extensions and containers:
apply plugin: SamplePlugin
whatever {
whateverVersion = '0.6.6'
conf {
dev {}
qa {}
prod {
accessKey = 'prod'
}
}
}
task printWhatever << {
println whatever.whateverVersion
whatever.conf.each { c ->
println "$c.name -> $c.accessKey"
}
}
class SamplePlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create('whatever', SampleWhatever)
project.whatever.extensions.conf = project.container(SampleConf)
project.whatever.conf.all {
accessKey = 'dev'
}
}
}
class SampleWhatever {
String whateverVersion
}
class SampleConf {
final String name
String accessKey
SampleConf(String name) {
this.name = name
}
}
while groovy way of doing implementing such DSL is meta programming - you need to implement methodMissing
in this particular case. Below is a very simple example that demonstrates how it works:
class SomeExtension {
def devConf = new SomeExtensionConf()
void methodMissing(String name, args) {
if ('dev'.equals(name)) {
def c = args[0]
c.resolveStrategy = Closure.DELEGATE_FIRST
c.delegate = devConf
c()
} else {
throw new MissingMethodException("Could not find $name method")
}
}
def getDev() {
devConf
}
}
class SomeExtensionConf {
def accessKey
}
project.extensions.create('some', SomeExtension)
some {
dev {
accessKey = 'lol'
}
}
assert 'lol'.equals(some.dev.accessKey)
Of course it has no error checking - so the args
size and type of each argument need to be validated - it's omitted for the sake of brevity.
Of course there's no need to create a separate class for each configuration (I mean dev
, prod
, etc.). Create a single class that holds configuration and store them all in a Map
where key is configuration name.
You can find a demo here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With