gradle扩展可以处理属性的懒惰评估吗?

问题描述:

我正在写一个自定义gradle插件来处理一些模糊复杂的工作,并且在使用属性来配置插件应用的一些任务时遇到了令人沮丧的问题。是gradle扩展可以处理属性的懒惰评估吗?

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}" 
    } 
} 

使用此文件中的结果如下:

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

BUILD SUCCESSFUL 

我认为这是非常意外。在我看来,从插件中应用任务并直接写入一个任务应该在给定相同输入时产生相同的输出。在这种情况下,两个任务都被赋予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 } 
     } 
    } 
} 

这相当漂亮解决了贪婪的评价问题,只有现在自定义任务必须修改以期望和处理闭包。这样做并不难,但我认为解决封闭问题不应该是任务的责任,因为插件是“责备”的。

我在这里使用扩展名不正确吗?还是他们不够?官方的立场似乎是we should use extensions,但我还没有找到任何扩展可以做我需要的例子。我可以继续使用闭包和编写一堆模板getter,它可以处理闭包和正常类型的封闭eval和setter,但它似乎非常违背groovy和gradle的哲学。如果有一种方法可以使用扩展并自动获得懒惰评估,我将非常高兴。

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

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 - 如果直接引用该字段,则约定映射不会启动。

+0

约定映射和约定之间有区别吗?你的答案看起来很漂亮,所以我几乎肯定会使用它,但其中一位核心开发人员说:“总之,只使用扩展,不要使用约定。”也许我从字面上理解了这一点。 – 2013-04-25 17:24:51

+2

Peter提到的[约定](http://www.gradle.org/docs/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:convention)是旧机制用于扩展。不同之处在于,你可以通过扩展免费获得dsl('myPluginProps {message =“Hello”}''),并且你没有按照惯例得到它们。约定和约定映射是两种不同的事情,约定映射在[gradle code]中内部使用(https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/groovy/org/gradle/ api/plugins/JavaPlugin.java#L131)很多。 – erdi 2013-04-25 17:36:02

+0

非常好。在我的研究中,我隐瞒了提到约定这个词的所有内容,但这很仓促。感谢您的建议! – 2013-04-25 18:43:29

Peter在我的问题here的答案表明conventionMapping功能一定会消失。最好避免它。

使用afterEvaluate解决延迟配置问题使我的代码比conventionMapping方法更加干净。

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 
      } 
     } 
    } 
}