【问题标题】:In a multi-module project can Gradle build a plugin as one module and then use that plugin in the same build?在多模块项目中,Gradle 可以将插件构建为一个模块,然后在同一个构建中使用该插件吗?
【发布时间】:2019-12-10 16:17:57
【问题描述】:

我们有一个包含大量模块的 Gradle 项目。其中一个模块是自定义代码生成器,编写为 Gradle 插件。我们希望稍后在同一个整体多模块构建中的另一个模块中运行该代码生成器插件,以测试代码生成器。

我们知道如何动态创建一个单独的项目并在其中运行代码生成器,但我们需要在主项目中运行代码生成器,而不是在临时测试项目中。

我们尝试过的任何方法都不起作用,Gradle 文档似乎也没有解决这个问题。这似乎是 Gradle 设计的基础,因为在构建中使用的整个插件集基本上是一个程序,在开始时组装。在事实似乎不受支持之后尝试添加刚刚构建的插件,或者我们遗漏了一些东西。

到目前为止,我们能想到的最好的方法是用 Java 实现插件(Kotlin 也可以),所以 Gradle 插件只是实现的薄 Gradle 皮肤,并调用 Java 实现直接在另一个模块中运行代码生成器时。这可行,但这意味着我们实际上并未测试代码生成器的 Gradle 部分。

Maven 原生支持这一点(maven multi-module project with one plugin modulehttps://maven.apache.org/guides/mini/guide-multiple-modules.html),这并不奇怪,因为 Maven 中的每个插件都在单独的类加载器中运行。如果在 Gradle 中不可能,那将是 Gradle 没有功能奇偶校验的少数情况之一。

【问题讨论】:

    标签: gradle plugins multi-module


    【解决方案1】:

    一个 hacky 方法是通过 Gradle 的测试套件运行程序运行新编译的插件。

    一种更简洁的方法是将插件编写为编写到 Gradle 的 API 的代码薄壳,将实际工作委托给普通的旧 Java(或 Kotlin)实用程序方法。这有很多优点:

    1. 您可以对实用程序方法进行单元测试。
    2. 您可以将实用程序方法用于与插件无关的其他目的。
    3. 您可以直接从项目中的其他模块调用实用程序方法,从而完成如果您可以构建插件然后在同一个构建中调用它,插件会完成的工作。

    【讨论】:

      【解决方案2】:

      扩展上述答案。

      不要像插件一样调用插件,而是添加一个 main 方法,该方法接受 Gradle 插件配置传递给插件的相同参数。

      然后使用 Gradle 的 Java exec 任务调用插件的 main:

      task(generateFoo, type: JavaExec) {
          main = 'com.bar.Foo'
          classpath = configurations.runtimeClasspath
          args = ["arg1", "${projectDir}/src/generated/java"]
      }
      

      注意参数:这些信息与过去通过 Gradle 配置传递的信息相同:

      apply plugin: 'foo-plugin'
      generateFoo {
          theArg "arg1"
          outputDir "${projectDir}/src/generated/java"
      }
      

      由于 Java exec 使用的运行时类路径是调用模块的路径,因此您可能会遇到运行时类加载器问题。

      如果发生这种情况,很容易解决。把重写的插件改成fat jar就行了:

      task fatJar(type: Jar) {
          manifest {
              attributes 'Implementation-Title': 'Foo Fat JAR', 'Main-Class': 'com.bar.Foo'
          }
          baseName = project.name + '-exec'
          from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
          with jar
      }
      artifacts {
          archives fatJar
      }
      

      然后用Java exec 执行fat jar:

        def fooGenerate = task(generateFoo, type: JavaExec) {
            main = 'com.bar.Foo'
            classpath = files("${projectDir}/../foo-plugin-module/build/libs/foo-plugin-module-exec.jar")
            args = ["arg1", "${projectDir}/src/generated/java"]
        }
      

      最后,使依赖模块的编译任务依赖于代码生成:

      compileJava.mustRunAfter fooGenerate
      

      如果使用 fatJar 方式,甚至不需要在依赖模块中声明实现 project(":foo")。

      也可以为此使用 Gradle 的复合构建 (https://docs.gradle.org/current/userguide/composite_builds.html)。

      【讨论】:

        猜你喜欢
        • 2021-11-20
        • 1970-01-01
        • 2014-03-03
        • 1970-01-01
        • 2013-06-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-03
        相关资源
        最近更新 更多