16

I'm writing a custom gradle plugin to handle some vaguely complicated work and I have run into a frustrating problem while using properties to configure some of the tasks that the plugin applies.

apply plugin: myPlugin

//Provide properties for the applied plugin
myPluginProps {
    message = "Hello"
}

//Define a task that uses my custom task directly
task thisTaskWorksFine(type: MyTask) {
    input = myPluginProps.message
}

//Define a plugin that will apply a task of my custom type
class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('myPluginProps', MyPluginExtension)

        project.task(type: MyTask, 'thisTaskWorksIncorrectly') {
            input = project.myPluginProps.message
        }
    }
}

//The extension used by my custom plugin to get input
class MyPluginExtension {
    def String message
}

//The task used by both the standard build section and the plugin
class MyTask extends DefaultTask {
    def String input

    @TaskAction
    def action() {
        println "You gave me this: ${input}"
    }
}

The results from using this file are as follows:

$ gradle thisTaskWorksFine thisTaskWorksIncorrectly
:thisTaskWorksFine
You gave me this: Hello
:thisTaskWorksIncorrectly
You gave me this: null

BUILD SUCCESSFUL

I consider this to be very unexpected. To my mind, applying a task from the plugin and writing one directly should result in the same output when given the same input. In this case, both tasks are given myPluginProps.message as input, but the task applied by the plugin is greedy and evaluates to null early on. (During the apply phase?)

The only solution I have found is to use closures in the plugin task's configuration block like so:

//Define a plugin that will apply a task of my custom type
class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('myPluginProps', MyPluginExtension)

        project.task(type: MyTask, 'thisTaskWorksIncorrectly') {
            input = { project.myPluginProps.message }
        }
    }
}

That solves the greedy evaluation problem pretty nicely except that now the custom task has to be modified to expect and deal with a closure. It's not terrifically hard to do, but I don't think it should be the task's responsibility to deal with the closure, since the plugin is "to blame".

Am I using extensions incorrectly here? Or are they just not adequate? The official stance appears to be that we should use extensions but I've yet to find any examples where extensions could do what I need. I can move forward with my use of closures and writing a bunch of boilerplate getters that do closure eval and setters that can handle closures and normal types, but it seems very against the philosophy of groovy and therefore gradle. I would be very happy if there is a way that I can use extensions and get the lazy evaluation automatically.

4

2 回答 2

13

编辑

下面的答案现在已经过时了。由于我为它提供了比约定映射更好的机制,因此引入了一种称为惰性属性的机制。


这个问题的通常解决方案是使用约定映射:

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
       project.extensions.create('myPluginProps', MyPluginExtension)

        project.task(type: MyTask, 'thisTaskWorksIncorrectly') {
            conventionMapping.input = { project.myPluginProps.message }
        }
    }
}

然后在任务中:

class MyTask extends DefaultTask {
    def String input

    @TaskAction
    def action() {
        println "You gave me this: ${getInput()}"
    }

}

请注意,我明确使用了 getter input- 如果您直接引用该字段,则约定映射不会启动。

于 2013-04-25T17:15:36.760 回答
13

Peter 在我的问题中的回答表明,conventionMapping 功能肯定会消失。最好避免它。

afterEvaluate用于解决延迟配置问题使我的代码比约定映射方法更干净。

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.extensions.create('myPluginProps', MyPluginExtension)

        project.afterEvaluate {
            project.task(type: MyTask, 'thisTaskWorksIncorrectly') {
                input = project.myPluginProps.message
            }
        }
    }
}
于 2014-08-20T08:46:56.957 回答