依赖扩展对象的任务的Gradle插件最佳实践 [英] Gradle plugin best practices for tasks that depend on extension objects

查看:88
本文介绍了依赖扩展对象的任务的Gradle插件最佳实践的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想获得关于定义依赖于外部状态的插件任务的最佳实践的反馈(即在引用插件的build.gradle中定义的插件任务)。我使用扩展对象和闭包来延迟访问这些设置,直到它们需要和可用。我也有兴趣分享任务之间的状态,例如配置一个任务的输出作为另一个任务的输入。

代码使用project.afterEvaluate定义通过扩展配置所需设置时的任务目的。这似乎比应该需要的更复杂。如果我将代码从afterEvaluate移出,它会得到compileFlag == null,这不是外部设置。如果代码再次改变以使用<<或doLast语法,那么它会得到外部标志...但它不能与type:Exec和其他类似有用的类型一起工作。



我觉得我'在某些方面与Gradle作战,这意味着我不了解如何更好地使用Gradle。以下是我正在使用的简化伪代码。这有效,但我期待看看这可以简化,或者确实是最佳实践。

  apply plugin:MyPlugin 
$ b这个例外不应该被抛出。 $ b class MyPluginExtension {
String compileFlag = null
}

class MyPlugin implements Plugin< Project> {

void apply(Project project){

project.extensions.create(myPluginConfig,MyPluginExtension)

project.afterEvaluate {

//关闭延迟获取和检查标志直到严格需要
def compileFlag = {
if(project.myPluginConfig.compileFlag == null){
throw new InvalidUserDataException(
必须设置compileFlag:myPluginConfig {compileFlag ='-flag'})
}
return project.myPluginConfig.compileFlag
}

//输入对于translateTask
def javaInputs = {
project.files(project.fileTree(
dir:project.projectDir,include:['** / *。java']))
}

//这是第一个任务的输出并输入到第二个
def翻译的输入中输出= {
project.files(javaInputs()。collect {file - >
return file.path.replace('src /','build / dir /')
})
}

//将所有java文件转换为' ()
project.tasks.create(name:'translateTask',type:Exec){
inputs.files javaInputs()
outputs.files translatedOutputs()

可执行文件'/ bin / echo'
inputs.files.each {file - >
args file.path


$ b $ //将'compiledOutputs'编译为二进制
project.tasks.create(name:'compileTask',类型:Exec,dependsOn:'translateTask'){
inputs.files translatedOutputs()
outputs.file project.file(project.buildDir.path +'/ compiledBinary')

可执行文件'/ bin / echo'
args compileFlag()
translatedOutputs()。each {file - >
args file.path
}
}
}
}
}


解决方案

我会用另一种方式来看待这个问题。看起来你想要放在你的扩展中的东西真的是你的每个任务所拥有的。如果你有一些是全局插件配置选项,它会被视为一个输入吗?



另一种做法是使用你自己的SourceSets并将它们连接到您的自定义任务中。这还不够简单,国际海事组织。我们仍然将JVM和源代码的本地代表结合在一起。



我建议使用@TaskAction作为自定义任务来提取您的Exec任务,该任务负担繁重(即使它只是调用project.exec {})。然后,您可以使用@Input,@InputFiles等注释您的输入,并使用@OutputFiles,@OutputDirectory等输出您的输出。这些注释将帮助自动连接您的依赖项和输入/输出(我认为这是一些战斗即将到来的地方从)。 你缺少的另一件事是,如果compileFlag影响最终输出,你想要检测它的变化并强制重建(但不是重新编译)翻译)。

我使用 Groovy .with方法。



我对此并不满意(我认为convertedFiles可能是做得不一样),但我希望它能向你展示一些最佳实践。我通过将翻译作为复制/重命名以及编译为只创建可执行文件的内容(只要您有src / something.java),就可以创建一个工作示例(内容就是输入列表)。我也离开了你的扩展类来演示全局插件配置。还要看看compileFlag没有设置(我希望这个错误会更好一些)。



translateTask不会是增量式的(虽然,我认为你可能想出一个办法这样做)。所以你可能需要每次删除输出目录。如果你想保持简单,我不会将其他输出混入该目录。



HTH



<$ p $应用插件:'base'
apply插件:MyPlugin

class MyTranslateTask extends DefaultTask {
@InputFiles FileCollection srcFiles
@OutputDirectory File translatedDir

@TaskAction
public void translate(){
// printlntoolhome is $ {project.myPluginConfig.toolHome}
//翻译java文件重命名它们
project.copy {
includeEmptyDirs = false $ b $ from(srcFiles)
into(translatedDir)
rename'(。+)。java','$ 1。 m'
}
}
}

class MyCompileTask extends DefaultTask {
@Input String compileFlag
@InputFiles FileCollection translatedFiles
@OutputDirectory File outputDir

@TaskAction
public void compile(){
//将输入写入可执行文件
project.file($ outputDir / executable)<< $ {project.myPluginConfig.toolHome} $ compileFlag $ {convertedFiles.collect {it.path}}
}
}

class MyPluginExtension {
文件工具主页= new File(/ some / sane / default)
}

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

tasks.create(name:'translateTask ',输入:MyTranslateTask){
description =将所有java文件转换为compiledDir
srcFiles = fileTree(dir:projectDir,包含:['** / *。java'])
compiledDir = file($ {buildDir} / dir)
}

tasks.create(名称:'compileTask',类型:MyCompileTask){
description =编译翻译文件转换为outputDir
convertedFiles = fileTree(tasks.translateTask.outputs.files.singleFile){
包含['** / *。m']
builtBy tasks.translateTask
}
outputDir = file($ {buildDir} / compiledBinary)
}
}
}
}

myPluginConfig {
toolHome = file(/ some / custom / path)
}

compileTask {
compileFlag =' -flag'
}


I would like feedback on the best practices for defining plugin tasks that depend on external state (i.e. defined in the build.gradle that referenced the plugin). I'm using extension objects and closures to defer accessing those settings until they're needed and available. I'm also interested in sharing state between tasks, e.g. configuring the outputs of one task to be the inputs of another.

The code uses "project.afterEvaluate" to define the tasks when the required settings have been configured through the extension object. This seems more complex than should be needed. If I move the code out of the "afterEvaluate", it gets compileFlag == null which isn't the external setting. If the code is changed again to use the << or doLast syntax, then it will get the external flag... but then it fails to work with type:Exec and other similarly helpful types.

I feel that I'm fighting Gradle in some ways, which means I don't understand better how to work well with it. The following is a simplified pseudo-code of what I'm using. This works but I'm looking to see if this can be simplified, or indeed what the best practices are. Also, the exception shouldn't be thrown unless the tasks are being executed.

apply plugin: MyPlugin

class MyPluginExtension {
    String compileFlag = null
}

class MyPlugin implements Plugin<Project> {

    void apply(Project project) {

        project.extensions.create("myPluginConfig", MyPluginExtension)

        project.afterEvaluate {

            // Closure delays getting and checking flag until strictly needed
            def compileFlag = {
                if (project.myPluginConfig.compileFlag == null) {
                    throw new InvalidUserDataException(
                            "Must set compileFlag:  myPluginConfig { compileFlag = '-flag' }")
                }
                return project.myPluginConfig.compileFlag
            }

            // Inputs for translateTask
            def javaInputs = {
                project.files(project.fileTree(
                        dir: project.projectDir, includes: ['**/*.java']))
            }

            // This is the output of the first task and input to the second
            def translatedOutputs = {
                project.files(javaInputs().collect { file ->
                    return file.path.replace('src/', 'build/dir/')
                })
            }

            // Translates all java files into 'translatedOutputs'
            project.tasks.create(name: 'translateTask', type:Exec) {
                inputs.files javaInputs()
                outputs.files translatedOutputs()

                executable '/bin/echo'
                inputs.files.each { file ->
                    args file.path
                }
            }

            // Compiles 'translatedOutputs' to binary
            project.tasks.create(name: 'compileTask', type:Exec, dependsOn: 'translateTask') {
                inputs.files translatedOutputs()
                outputs.file project.file(project.buildDir.path + '/compiledBinary')

                executable '/bin/echo'
                args compileFlag()
                translatedOutputs().each { file ->
                    args file.path
                }
            }
        }
    }
}

解决方案

I'd look at this problem another way. It seems like what you want to put in your extension is really owned by each of your tasks. If you had something that was a "global" plugin configuration option, would it be treated as an input necessarily?

Another way of doing this would have been to use your own SourceSets and wire those into your custom tasks. That's not quite easy enough yet, IMO. We're still pulling together the JVM and native representations of sources.

I'd recommend extracting your Exec tasks as custom tasks with a @TaskAction that does the heavy lifting (even if it just calls project.exec {}). You can then annotate your inputs with @Input, @InputFiles, etc and your outputs with @OutputFiles, @OutputDirectory, etc. Those annotations will help auto-wire your dependencies and inputs/outputs (I think that's where some of the fighting is coming from).

Another thing that you're missing is if the compileFlag effects the final output, you'd want to detect changes to it and force a rebuild (but not a re-translate).

I simplified the body of the plugin class by using the Groovy .with method.

I'm not completely happy with this (I think the translatedFiles could be done differently), but I hope it shows you some of the best practices. I made this a working example (as long as you have a src/something.java) by implementing the translate as a copy/rename and the compile as something that just creates an 'executable' file (contents is just the list of the inputs). I've also left your extension class in place to demonstrate the "global" plug-in config. Also take a look at what happens with compileFlag is not set (I wish the error was a little better).

The translateTask isn't going to be incremental (although, I think you could probably figure out a way to do that). So you'd probably need to delete the output directory each time. I wouldn't mix other output into that directory if you want to keep that simple.

HTH

apply plugin: 'base'
apply plugin: MyPlugin

class MyTranslateTask extends DefaultTask {
    @InputFiles FileCollection srcFiles
    @OutputDirectory File translatedDir

    @TaskAction
    public void translate() {
        // println "toolhome is ${project.myPluginConfig.toolHome}"
        // translate java files by renaming them
        project.copy {
            includeEmptyDirs = false
            from(srcFiles)
            into(translatedDir)
            rename '(.+).java', '$1.m'
        }
    }
}

class MyCompileTask extends DefaultTask {
    @Input String compileFlag
    @InputFiles FileCollection translatedFiles
    @OutputDirectory File outputDir

    @TaskAction
    public void compile() {
        // write inputs to the executable file
        project.file("$outputDir/executable") << "${project.myPluginConfig.toolHome} $compileFlag ${translatedFiles.collect { it.path }}"  
    }
}

class MyPluginExtension {
    File toolHome = new File("/some/sane/default")
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.with { 
            extensions.create("myPluginConfig", MyPluginExtension)

            tasks.create(name: 'translateTask', type: MyTranslateTask) {
                description = "Translates all java files into translatedDir"
                srcFiles = fileTree(dir: projectDir, includes: [ '**/*.java' ])
                translatedDir = file("${buildDir}/dir")
            }

            tasks.create(name: 'compileTask', type: MyCompileTask) {
                description = "Compiles translated files into outputDir"                
                translatedFiles = fileTree(tasks.translateTask.outputs.files.singleFile) { 
                   includes [ '**/*.m' ]
                   builtBy tasks.translateTask 
                }
                outputDir = file("${buildDir}/compiledBinary")
            }
        }
    }
}

myPluginConfig {
    toolHome = file("/some/custom/path")
}

compileTask { 
  compileFlag = '-flag'
}

这篇关于依赖扩展对象的任务的Gradle插件最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆