Copying values from conventions in plugins to tasks


(crotwell) #1

Hi

I am trying to write a plugin and make use of the plugin convention. The examples I have looked at (for example izpack, gradle’s application plugin) seem to involve a lot of copying of values from the convention to a task and then on to an object that implements them. For example the application plugin defines fields in the ApplicationPluginConvention, then in ApplicationPlugin copies them from the ApplicationPluginConvention to the CreateStartScripts task which then copies them to the StartScriptGenerator which actually does the work.

Is this considered the best way to do this? It feels like a lot of boilerplate copying to me, but I am new to groovy and to writing anything but the simplest of gradle tasks, so I suspect I am missing something.

Is there a reason to not just pass the entire convention to the task/object? Is there a reason to not access the convention outside of the plugin? It seems like that would reduce much of the code that looks like: startScripts.conventionMapping.mainClassName = { pluginConvention.mainClassName } or generator.mainClassName = getMainClassName()

BTW, this page helped me understand what was going on with conventions: http://hamletdarcy.blogspot.com/2010/03/gradle-plugin-conventions-groovy-magic.html

thanks, Philip


(Peter Niederwieser) #2

A central idea behind convention mapping is to separate the configuration of tasks by plugins from the tasks themselves. Tasks shouldn’t make assumptions about the world. They shouldn’t know about things like source sets, configurations and convention objects. All of that would make them less reusable and tie them to the plugin.

Additionally, convention mapping is one of the ways to tackle the evaluation order problem, where a value used to configure a task might not yet be available at the time the plugin executes.

Ideally, a convention/extension is on a higher abstraction level than the task and makes it easy to solve the common use cases. Whether the task delegates somewhere else is an implementation detail of the task.

By the way, in general you should favor extensions (introduced in 1.0-m3) over convention objects.


(Luke Daley) #3

As an addition to what Peter said, a feature of convention mappings is that the allow values to be specified as the result of computations, which facilitates flexible conventions.

Given the following:

class Generate extends DefaultTask {
  File output
      @TaskAction
  def generate() {
    new File(output, "generated").text = "some stuff"
  }
}
  task generate(type: Generate)

Consider the difference between:

generate.conventionMappings.output = project.buildDir
project.buildDir = file("non-standard-build-dir")

and:

generate.conventionMappings.output = { project.buildDir }
project.buildDir = file("non-standard-build-dir")

With the first example, the task is going to write to the standard build dir because it doesn’t pick up the change to the ‘buildDir’ after the convention mapping is wired in.

With the second example, we wire in a computation that is executed when the value is read (i.e. lazily) so the change to this value after the convention mapping is wired in is effective.


(crotwell) #4

Ah, I see now. I wasn’t paying attention to the extra { } in the task configuration. I am feeling my lack of groovy-ness, not noticing that these task fields were being pulled from the convention lazily. The way the ApplicationPlugin was written makes much more sense now.

I have also switched to the extension mechanism and it seems to be working well, so thanks for the pointer.

thanks Philip


(Zbyszek Sokolowski) #5

I’ve prepared plugin with task APMSonarAnalyze, I’m trying to set it as if above example

project.APMSonarAnalyze.conventionMappings.test = { "test" }

groovy.lang.MissingPropertyException: Could not find property ‘conventionMappings’ on task ‘:APMSonarAnalyze’.

at org.gradle.api.internal.AbstractDynamicObject.propertyMissingException(AbstractDynamicObject.java:43)

at org.gradle.api.internal.AbstractDynamicObject.getProperty(AbstractDynamicObject.java:35)

at org.gradle.api.internal.CompositeDynamicObject.getProperty(CompositeDynamicObject.java:94)

at com.compuware.ta.sonarutils.tasks.java.APMSonarPluginFacade_Decorated.getProperty(Unknown Source)

at org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:47)

at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:227)


(Zbyszek Sokolowski) #6

default task doesn’t have a conventionMappings method


(Marcel Overdijk) #7

@Zbysek I think you are looking for conventionMapping (in your code you have incorrect s appended)


(Zbyszek Sokolowski) #8

thx, Still it doesn’t change the fact that default task doesn’t have method conventionMapping, ConventionTask has. I do it in very simple way:

APMSonarAnalyze.doFirst {
        APMSonarAnalyze.test = "${project.sonarutils.test}"
      }

(Peter Niederwieser) #9

All tasks have a ‘conventionMapping’ property. The property is added dynamically at runtime.


(Marcel Overdijk) #10

You mean you can’t see in docs it has conventionMapping? Maybe it’s added dynamically? DefaultTask should be enough as discussed here: http://gradle.1045684.n5.nabble.com/Convention-mapping-for-plugins-td4885140.html

I was using the conventionMapping today myself and it works for me.


(Zbyszek Sokolowski) #11

this doesn’t work maybe I’m doing something incorrectly:

some snippets:

from extension (in separate file):

class SonarUtilsExtension {
      Project project
  String test = ""

from plugin:

void apply(Project project) {
    project.plugins.apply(BasePlugin)
    project.extensions.create(PLUGIN_NAME, SonarUtilsExtension, project)

then

later

/* APMSonarAnalyze.doFirst {
        APMSonarAnalyze.test = "${project.sonarutils.test}"
      }*/
    APMSonarAnalyze.conventionMapping.map("test", {project.sonarutils.test})

(Zbyszek Sokolowski) #12

wow I changed to getter in my test task class and it works, thx !