On 6/08/10 1:33 AM, Luke Daley wrote:
Hi,

I am having trouble understanding the “task” and “convention” concepts as it 
relates to plugin authoring (e.g classes like ConventionTask and 
ConventionMapping).

Are there any guides or articles on developing more advanced plugins? 
Particularly on how to support conventional style configuration using these 
classes?

Not really. At this stage, these classes are considered a bit of an experiment and internal to Gradle. You can use them if you like, but keep in mind that these internal classes might change at any time. At some point, when we're happy with the results of the experiment, we'll move them (or whatever they happen to evolve into) across to the public API.

You don't have to use any of these classes to implement a plugin. Here's a bit more detail about them, so you can decide if you want to use them or not:

One problem we're trying to solve is that we want to provide dynamic default values for domain object properties. By 'domain object' I mean the objects which make up the Gradle model and which you configure in your build script. This includes things like tasks, projects, configurations, dependencies, source sets and so on. And by 'dynamic default values', I mean the values need to reflect changes in other property values. For example, if you change the value for 'buildDir', then the default value for, say, compileJava.classesDir should change to reflect the new value of 'buildDir'. In this way, you can think of the default values as rules.

We implement theses rules as pieces of code which are lazily evaluated when the property value is queried. It works best if you're coding in Groovy, but also works from Java. Here's an example:

class MyCustomTask extends DefaultTask {
    def File destinationDir
    ....
}

def task = project.task('mytask', type: MyCustomTask)
assert task.destinationDir == null

// Attach a rule to the task's destinationDir property
task.conventionMapping.destinationDir = { new File(project.buildDir, 'mytask-out') }
assert task.destinationDir == new File(project.buildDir, 'mytask-out')

// Explicitly set the value
task.destinationDir = new File('some-dir')
assert task.destinationDir = new File('some-dir')

So, we attach a rule to the destinationDir property, as a closure. Gradle evaluates the closure when the property value is queried, and the property setter has not been called to set an explicit value. All tasks happen to implement IConventionAware, which provides a 'conventionMapping' property of type ConventionMapping. This is what you use to manage the rules for a given object.

We use this in Gradle to keep the rules out of the tasks. This is because we want the tasks to depend on as little as possible on the environment they are running in, to keep them as reusable as possible. You don't need to use any of this stuff if you don't want to. Here's the above example without using ConventionMapping:

class MyCustomTask extends DefaultTask {
    def File destinationDir

    def File getDesintationDir() {
        destinationDir ?: new File(project.buildDir, 'mytask-out')
    }
}

def task = project.task('mytask', type: MyCustomTask)
assert task.destinationDir == new File(project.buildDir, 'mytask-out')

task.destinationDir = new File('some-dir')
assert task.destinationDir = new File('some-dir')

That's pretty much it for ConventionMapping.

The other problem we want to solve is that we want to be able to add plugin-specific properties and methods to existing domain objects. For example, the Java plugin adds a bunch of Java-specific properties to the project: http://gradle.org/0.9-rc-1/docs/userguide/java_plugin.html#N11C70

To do this, we attach a POJO (or POGO) to the project's 'convention' property:

class MyPluginConvention {
    def File reportDir
    def getReportDir() { reportDir ?: new File(buildDir, 'my-reports') }
}

project.convention.plugins.myplugin = new MyPluginConvention()
assert project.reportDir == new File(buildDir, 'my-reports')

project.reportDir = new File('some-dir')
assert project.reportDir == new File('some-dir')

Project happens to implement DynamicObjectAware, which provides the 'convention' property and the dynamic behaviour. There's a bunch of other types which happen to implement DynamicObjectAware - tasks, configurations, dependencies, source sets, and you can extend them in the same way.

There's more about this topic in the user guide: http://gradle.org/0.9-rc-1/docs/userguide/custom_plugins.html#N148A7

Finally, ConventionTask is simply a convenience class which mixes in DynamicObjectAware and IConventionAware. Everything that extends DefaultTask happens to implement these interfaces - it just happens dynamically rather than statically.

Again, remember that all this stuff is optional. You can implement the same stuff in plain Groovy or Java without needing to use these internal classes.


--
Adam Murdoch
Gradle Developer
http://www.gradle.org
CTO, Gradle Inc. - Gradle Training, Support, Consulting
http://www.gradle.biz


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

   http://xircles.codehaus.org/manage_email


Reply via email to