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