What is the best way to write a configurable plugin?


(Gordon Child) #1

I’m trying to create a couple plugins that have a small amount of configuration. However, when I try to override the default configuration in the gradle build script, then I still get the default configuration. Currently, I’m using extensions, but I don’t know if this is the best approach. I would like to learn how to do things “the right way” convention over configuration. But currently, I’m having difficulty getting it to work at all.

class ReflectionsPlugin implements Plugin<Project> {
  void apply(Project project) {
    project.extensions.create('reflections', ReflectionsPluginExtension)
    project.task([type:DefaultTask, dependsOn:[project.tasks.classes]], 'buildReflectionsXml') {
      project.reflections.scanners.split(',').each {
 // This takes the default, but does not get overridden in the build script
      }
    }
  }
}
class ReflectionsPluginExtension {
  def String scanners = 'SubTypesScanner, TypeAnnotationsScanner'
}

Then in my actual build script, I’m expecting to do the following…

reflections {
  scanners = 'ResourcesScanner, SubTypesScanner, TypeAnnotationsScanner'
}

But in my plugin, when I try to access project.reflections.scanners or project.extensions.reflections.scanners it has the default value of… ‘SubTypeScanner, TypeAnnotationsScanner’


(Gary Hale) #2

Well, you’re doing things mostly the “right way”, but my guess is that your issue is in how you are defining your task. The scanners.split is in the configuration of the task, so it’s being executed when you define your task. You’re setting the value later, but it’s after it has already been split with the default values, so you never see the change.

I’d suggest that you define a custom task instead and make buildReflectionsXml an instance of that task. Then do your split (or whatever “uses” the reflections extension) in the execution phase (whatever your @TaskAction is). Depending on what makes sense, you might also move the reflections extension to the task instead of the project.


(Gordon Child) #3

I see what you mean by moving the split to the execution phase. I missed that while trying to build the task. I assumed that the closure that is passed to the project.tasks.create method is run during the some sort of execution phase.

I was able to get it to work by using the following…

class ReflectionsPlugin implements Plugin<Project> {
  void apply(Project project) {
    project.extensions.create('reflections', ReflectionsPluginExtension)
    Task rTask = project.tasks.create('buildReflectionsXml')
    rTask.dependsOn << project.tasks.classes
    rTask.actions << new Action<Task>() {
      void execute(Task t) {
        project.reflections.scanners.split(',').each { // correctly has the overridden value
        }
      }
    }
  }
}

However, creating an anonymous instance of Action<Task>() seems a little clunky. Is there a better way to do this?

I tried doing the following, but gradle doesn’t appear to accept it…

rTask.actions << {Task t->
}

java.lang.ClassCastException: ReflectionsPlugin$_apply_closure1 cannot be cast to org.gradle.api.Action

It seems to me like this should work. Or at least it makes the most sense to me.


(Gary Hale) #4

Definitely.

project.tasks.create('buildReflectionsXml').doLast {
  project.reflections.scanners.split(',').each { ... }
}

doFirst and doLast both add actions to the list of actions for the task. They add them at the head or tail of the list, respectively. But for a new task, the actions list is empty so it doesn’t matter which you use, they’ll both result in a single action in the list.


(Gordon Child) #5

Ahh, ok. I should be using doLast. And now, if I understand right, the closure that is passed to project.tasks.create is part of the configuration of the task. So I could do the same thing using

project.tasks.create('buildReflectionsXml') {
  dependsOn << project.tasks.classes
  doLast {
    project.reflections.scanners.split(',').each { ... }
  }
}

Thanks Gary! I appreciate your help!


(Gordon Child) #6

And now, I thought I’d go back and look at the documentation to see if there was an error there. But the documentation is correct, but I was missing a very important detail… http://www.gradle.org/docs/current/userguide/custom_plugins.html

I was using this…

project.task([dependsOn:project.tasks.classes], 'buildReflectionXml') {
  project.reflections.scanners.split(',').each { ... }
}

When I SHOULD have been doing this…

project.task([dependsOn:project.tasks.classes], 'buildReflectionXml') << {
  project.reflections.scanners.split(',').each { ... }
}

The first example closure is run during the configuration of the task. Whereas the second example closure is run during the execution of the task.


(Gary Hale) #7

Yeah, the “<<” syntax has kind of fallen out of favor for exactly this reason - that it’s so similar to the configure syntax that it’s really easy to just omit it or look past it. Always doing a configure with a doLast, though, tends to be a lot less confusing.