Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to configure gradle plugin extensions with groups of dynamic objects

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 {
    }
  }
}
like image 514
Shorn Avatar asked Mar 15 '23 04:03

Shorn


1 Answers

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.

like image 112
Opal Avatar answered Apr 07 '23 07:04

Opal