2014-11-22 54 views
4

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

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

我觉得我在某些方面与Gradle作斗争,这意味着我不了解如何更好地使用它。以下是我正在使用的简化伪代码。这有效,但我期待看看这可以简化,或者确实是最佳实践。此外,除非任务正在执行,否则不应抛出异常。

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

回答

4

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

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

我建议使用@TaskAction作为自定义任务来提取您的Exec任务,它会完成繁重的任务(即使它只是调用project.exec {})。然后,您可以使用@Input,@InputFiles等注释您的输入,并使用@OutputFiles,@OutputDirectory等输出您的输出。这些注释将帮助自动连接您的依赖项和输入/输出(我认为这是一些战斗即将到来的地方从)。

你缺少的另一件事是如果compileFlag影响最终输出,你想检测它的变化并强制重建(但不是重新翻译)。

我使用Groovy .with method.

我不是这个完全满意(我觉得translatedFiles可以做不同的)简化了插件类的身体,但我希望它会显示一些最佳做法。我通过将翻译作为复制/重命名以及编译为只创建“可执行”文件的内容(只要您有src/something.java),就可以创建一个工作示例(内容就是输入列表)。我也离开了你的扩展类来演示“全局”插件配置。还要看看compileFlag没有设置会发生什么(我希望错误会更好一点)。

translateTask不会是增量式的(尽管我认为you could probably figure out a way to do that)。所以你可能需要每次删除输出目录。如果你想保持简单,我不会将其他输出混合到该目录中。

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' 
} 
+1

感谢bigguy,这是非常有用的。什么是“project.with”,因为我看不到任何文档?另外,当它们本身依赖于扩展对象的值时,是否有办法处理InputFile(s)OutputFile(s)依赖项?你只是使用InputDir OutputDir而不是? 'singleFile'技巧很好。最后是否有类似于我可以从execute调用的project.sync(){}?我想在目标文件夹上添加一些理智测试,以便它只删除生成的代码而不是其他东西。也许只是过滤意外的文件扩展名? – brunobowden 2014-11-23 19:16:22

+0

我在答案中增加了更多信息。一般来说,我会尽量远离包含任务配置的扩展类。这没什么不妥,但你必须自己做更多的连线。 – bigguy 2014-11-23 19:44:53

+0

感谢您的帮助bigguy – brunobowden 2014-11-24 16:46:25