Relation between extension and task parameters is unclear


(Cédric Champeau) #1

Hi guys,

This might look like a noob question, but the documentation is unclear about the topic and I can’t find an example in the Gradle source base… What I want is somehow simple: getting the task configuration from extension, with output/input specified.

Basically, I want something like this:

jbake {
    input=file(...)
    output=file(...)
}

and I have a ‘bake’ task, so ‘jbake’ is an extension (‘JBakePluginExtension’) and ‘bake’ is a task of type ‘JBakeTask’. So far so good, I tried several versions but I miss the relation between the extension and the task parameters.

For example if I do:

class JBakePluginExtension {
    File input
    File output
    Map<String, Object> configuration = [:]
    boolean clearCache = false
      JBakePluginExtension(Project project) {
        input = project.file('src/jbake')
        output = project.file("${project.buildDir}/jbake")
    }
}
class JBakeTask extends AbstractTask {
    @TaskAction
    void bake() {
        new Oven(project.jbake.input, project.jbake.output, project.jbake.clearCache).with {
            config = new CompositeConfiguration([new MapConfiguration(project.jbake.configuration), config])
            setupPaths()
            bake()
        }
    }
}

then I get the configuration from the extension (yeah!) but it doesn’t handle input/outputs (incremental builds). I tried this:

class JBakePluginExtension {
    @InputDirectory File input
    @OututDirectory File output
    @Input Map<String, Object> configuration = [:]
    boolean clearCache = false
      JBakePluginExtension(Project project) {
        input = project.file('src/jbake')
        output = project.file("${project.buildDir}/jbake")
    }
}

but it doesn’t work. If I change it to:

class JBakeTask extends AbstractTask {
    @InputDirectory File input
    @OutputDirectory File output
    @TaskAction
    void bake() {
        prepareTask()
        new Oven(input, output, clearCache).with {
            config = new CompositeConfiguration([new MapConfiguration(configuration), config])
            setupPaths()
            bake()
        }
    }
      private void prepareTask() {
        if (input==null) {
            input = project.jbake.input
        }
        if (output==null) {
            output = project.jbake.output
        }
    }
}

which has a lot of boilerplate code (and that doesn’t seem right because I’m defining twice the same fields) then running the task fails with: “No value has been specified for property ‘input’.”

So the question is quite simple: what is the idiomatic way to create a task which takes configuration from an extension, defines inputs/outputs and allows overriding some configuration keys?

Thank you!


(Peter Niederwieser) #2

The idea is that an extension is a higher-level abstraction that gets mapped to a lower-level task abstraction by a plugin. For common use cases, users can then apply the plugin and configure the extension, which is more convenient because they benefit from the higher-level abstraction. For the remaining 20%, they can declare and configure standalone task(s), without using the plugin or extension.

To make this work, the task class needs to be self-contained, shouldn’t reach out into the project model, and shouldn’t know anything about the extension. In cases where a higher-level abstraction isn’t desirable/beneficial, an extension/plugin isn’t needed.

Configuration-heavy tasks sometimes accept configuration classes (e.g. ‘JavaCompile’ accepts ‘CompileOptions’). In such a case, the configuration class will carry the input/output annotations, and the corresponding property on the task class will be marked as ‘@Nested’.


(Cédric Champeau) #3

Ok thanks for the clarification. So I better not use extensions at all in my case.


(Peter Niederwieser) #4

That’s always a good way to start, and you can always add a plugin and extension later if the need arises.