How to write gradle plugin task with input/output coming from the plugin extension object?


(Mike Caldwell) #1

Hi,
I am trying to write a gradle plugin that has a task which uses the configuration values coming from the extension object as input and outputs of the task. It seems the extension object values are not available at “configuration” phase which is when the inputs and outputs of the task are defined. SO I am lost as to how to define the inputs/outputs of the task. Below is the code I am testing with

class MyPluginExtension {
    def String input
    def String output
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create("myplugin", MyPluginExtension)
        project.task('test') {
            inputs.dir project.myplugin.input
            outputs.dir project.myplugin.output
            doLast {
                println "----------------------------  DO LAST: ${project.myplugin.input}"
                println "----------------------------  DO LAST: ${project.myplugin.output}"
            }
        }
    }
}

Here is the build code
myplugin {
input = “${buildDir}”
putput = “${outDir}”
}

gradle test

Any help on how to get these extension properties to be used as task input and output would be greatly appreciated

– Mike


(Chris Doré) #2

There’s a section on this topic in the Implementing Gradle Plugins guide (section 1.5).


(Mike Caldwell) #3

Yes I saw that section but that is a feature of Gradle 4.0 and I am forced to use 2.7.


(Chris Doré) #4

You can use a task’s convention mapping. However, note that it is an internal component of Gradle which could be modified or removed from Gradle sometime in the future.

Note, I also changed your implementation from using Strings, to any object. Doing so allows users of your plugin to use a wider range of types without you needing to do any additional work. In this case, everything supported by Project.file() will work (for example, I defaulted the output using a closure that generates a string).

class MyPluginExtension {
    def input = "default input"
    def output = {
        ["default", "output"].join(" ")
    }
}

class MyTask extends DefaultTask {
    @InputDirectory
    def input
    @OutputDirectory
    def output

    @TaskAction
    void doSomething() {
        // In order for convention mapping to work, it is
        // critical that you call the getters explicitly.
        // Accessing the fields directly will not work.
        println "  input = ${input} (${input ? input.class.name : 'N/A'})"
        println "  getInput = ${getInput()} (${getInput() ? getInput().class.name : 'N/A'})"
        println "    input file = ${project.file(getInput())}"
        println "  output = ${output} (${output ? output.class.name : 'N/A'})"
        println "  getOutput = ${getOutput()} (${getOutput() ? getOutput().class.name : 'N/A'})"
        println "    output file = ${project.file(getOutput())}"
    }
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        MyPluginExtension e = project.extensions.create("myplugin", MyPluginExtension)
        MyTask t = project.tasks.create('test', MyTask)

        // Using closures here is key.
        t.conventionMapping.input = {e.input}
        t.conventionMapping.output = {e.output}
    }
}

apply plugin: MyPlugin

// Experiment with un/commenting these values to see the effects.
myplugin {
    input = "src1"
}
test {
    //input = file('src2')
}

Example:

$ mkdir src1
$ gradle test
> Task :test
  input = null
  getInput = src1 (java.lang.String)
    input file = /home/cdore/projects/conventionMapping/src1
  output = null
  getOutput =  (MyPluginExtension$_closure1)
    output file = /home/cdore/projects/conventionMapping/default output