I’m trying to write a plugin for Android, but my Gradle knowledge isn’t quite good enough yet. Specifically, I’d like to allow dynamic “blocks” (closures?) nested under some plugin extension. Case of point, in Android you can define dynamic “product flavors” like this:
…where brandA, brandB etc. are definitions being picked up and used by the plugin.
I know how handle “hardwired” flavors:
android.productFlavors.extensions.create(“brandA”,MyCustomPluginExtention)
…but this obviously won’t cut it. So my question is, how can I handle such a dynamic situation - clearly it’s possible because I see plugins use it.
EDIT: I get the impression, that methodMissing(…) is the way forward, although that seems to require a construct on the form:
The ‘productFlavors’ block here is actually an instance of NamedDomainObjectContainer. You can create one of these by calling Project.container() and passing a container type. The type is generally a POJO, with the requirement that it must define a property called “name” and that it must have a constructor that takes the name as an argument.
class Foo {
String name
String bar
Foo(String name) {
this.name = name
}
}
project.extensions.add('foos', project.container(Foo))
Yeah it’s something like that I ended up doing… now I just have to figure out how to use the DSL to define dynamic build tasks inside the apply(…) method:
class DistributionExtension{
final NamedDomainObjectContainer<ProductFlavor> productFlavors
DistributionExtension(productFlavors){
this.productFlavors = productFlavors
}
def productFlavors(Closure closure){
this.productFlavors.configure(closure)
}
}
class ProductFlavor {
final String name
String uploadUUID
ProductFlavor(String name) {
this.name = name
}
}
class DistributionPlugin implements Plugin<Project> {
def void apply(Project project) {
def productFlavors = project.container(ProductFlavor)
project.configure(project){
extensions.create("distributions", DistributionsExtension, productFlavors)
}
productFlavors.each { distribution ->
project.task("distribute$distribution.name") <<{
println "Test"
}
}
}
}
…with this DSL:
distributions {
productFlavors{
A {
uploadUUID = 'bff3750c-efeb-430b-b523-0781a6b700a3'
}
B {
uploadUUID = '45771f0d-202c-47e9-9904-8aae8f4115bf'
}
C {
uploadUUID = '991e998a-99f2-4c08-b086-c7ca3f934308'
}
}
}
…but to no avail; productFlavors is empty, so no tasks (deployA, deployB…) gets created. It makes me wonder, just where can I create new dynamic tasks based on a DSL - it doesn’t look like it’s in the apply(…) section.
“each” iterates on the collection “right now”, so when inside the “apply” method of your plug-in, the collection is empty.
You want to use “productFlavors.all{}” in your case
Your constructor should take multiple arguments. Also, when calling extensions.create() the last argument is actually a multiple argument list of constructor args.
What about checking for dynamic properties when navigating such a DSL? I thought I could just write if(distriutions.productFlavors[‘debug’] == null){ … }
…but this fails:
ProductFlavor with name ‘debug’ not found.
I also tried with .hasProperty(‘debug’) but this is always false. I get the feeling I need to check for an item in a collection instead… somehow?!
Right. NamedDomainObjectContainer extends Collection, and therefore you can call contains
but in your case, you want to find an element from this collection by name, so you’d better use