How to code a custom plugin to create DSL elements similar to "dependencies {}" or "repositories {}"


(Rob Black) #1

How can I code my custom plugin to support scripts like this:

apply plugin: ‘thingsPlugin’

configurations {

config1 }

things {

config1 name:“Thing 1”, version:“3.3”, weight:“10 pounds”

config1 name:“Another thing”

config2 name:“Something else”, colour:“black” }

Obviously a contrived example, but the intention is to create a new DSL block (“things”) that associates information with native Gradle configurations in a manner similar to the “dependencies” or “repositories” blocks do.

I made a small bit of progress by defining a method in my build script that delegates to a custom “handler” that I created in a manner similar of DefaultDependenciesHandler:

void things( Closure configureClosure ) {

ThingsHandler thingsHandler = new DefaultThingsHandler(configurations)

ConfigureUtil.configure(configureClosure, thingsHandler); }

That seemed to work (aside from me needing to move the thingsHandler instantiation out of the method). But I don’t know how to get my plugin to define the new “things” method at the project scope. This question could have been posed as “How do you code a plugin to add a method to the Project?”. Thanks for any pointers…/rob


(Rolf Suurd) #2

I think the way to do this is using gradle’s extension stuff, chapter 51.3 of the userguide: http://www.gradle.org/docs/current/userguide/custom_plugins.html

In your extension object, you can (for example) implement methodMissing to handle the configuration closure.


(Rob Black) #3

Thanks for the pointer. I got something working by creating an extension object from a custom “configuration” class (though I don’t know if that terminology is still current in gradle-1.0). My convention class contained a field for a “handler” object that implemented methodMissing as you suggested. I patterned it from the DefaultDependencyHandler in the gradle source. So I ended up with something like this:

class MyPlugin implements Plugin<Project>
 {
    void apply(Project project) {
        project.configure(project) {
            extensions.create("mystuff", MyConvention, project)
        }
    }
}
  class MyConvention {
    DefaultThingHandler things
      def MyConvention(Project project) {
        this.things = new DefaultThingHandler(project)
    }
      void things(Closure configureClosure) {
        ConfigureUtil.configure(configureClosure, this.things)
    }
}
  class DefaultThingHandler {..} // patterned off of Gradle's internal DefaultDependencyHandler

So although I dont have a first class “things” object at the same level as “repositories” or “dependencies”, I get the next best thing:

configurations {
    myconfig
}
  myStuff {
    things {
        myconfig name:'whatever', colour:'black', price:'59.95'
    }
}

Thanks for the help.


(Peter Niederwieser) #4

The ‘MyConvention’ stuff was only necessary before extensions came along. Instead, simply do ‘project.extensions.things = new DefaultThingHandler(project)’, and use it like this;

things {
  // configure DefaultThingHandler in here
}

‘DefaultThingHandler’ doesn’t need any special methods to make this work. Also see the “Writing Plugins” chapter in the Gradle user guide.


(Luke Daley) #5

Instead of:

project.extensions.things = new DefaultThingHandler(project)

You should do:

project.extensions.create(“things”, DefaultThingHandler, project)

This adds some extra behaviour to the eventual extension (this will become more important in the future).

For example, this makes the extension extensible.

project.extensions.create(“things”, DefaultThingHandler, project)

project.things.extensions.create(“nestedThings”, DefaultThingHandler, project)


(Peter Niederwieser) #6

Can these be made to mean the same? I prefer the former syntax, and it isn’t obvious that it isn’t as “good”.


(Rob Black) #7

I tried this in the plugin’s apply() method:

extensions.create("things", DefaultThingHandler, project)

to support this in my build script:

things {
    myconfig name:'whatever', colour:'black', price:'59.95'
}

but it didn’t work because “things” was not found to be a method taking a Closure argument:

> Could not find method things() for arguments [sample_4p96i3djuvadhncfl6iatdnskb$_run_closure5@1408325] on root project 'myPlugin'.

I tried this before and assumed it could not be done, which is why I went back to creating my “convention” class containing a field for the DefaultThingHandler object.


(Luke Daley) #8

Can you post your complete plugin please.