Gradle插件打包了可重用的构建逻辑,可在许多不同的项目和构建中使用. Gradle允许您实现自己的插件,因此您可以重复使用构建逻辑,并与他人共享.

您可以使用任何喜欢的语言来实现Gradle插件,前提是该实现最终被编译为JVM字节码. 在我们的示例中,我们将使用Groovy作为实现语言. Groovy,Java或Kotlin都是用于实现插件的语言,都是不错的选择,因为Gradle API旨在与这些语言配合使用. 通常,使用Java或Kotlin(静态类型)实现的插件比使用Groovy实施的插件性能更好.

Packaging a plugin

您可以在几个地方放置插件的源代码.

Build script

您可以将插件的源代码直接包含在构建脚本中. 这样做的好处是,无需执行任何操作即可自动编译插件并将其包含在构建脚本的类路径中. 但是,该插件在构建脚本之外不可见,因此您不能在定义该构建脚本的外部重用该插件.

buildSrc project

您可以将插件的源代码放在rootProjectDir /buildSrc/src/main/groovy目录中(或rootProjectDir /buildSrc/src/main/javarootProjectDir /buildSrc/src/main/kotlin具体取决于您喜欢的语言). Gradle将负责编译和测试插件,并使其在构建脚本的类路径中可用. 该插件对构建使用的每个构建脚本都是可见的. 但是,它在构建外部不可见,因此您不能在定义该构建的外部重用该插件.

有关buildSrc项目的更多详细信息,请参见组织Gradle项目.

Standalone project

您可以为插件创建一个单独的项目. 这个项目产生并发布了一个JAR,您可以在多个版本中使用它并与他人共享. 通常,此JAR可能包含一些插件,或将几个相关的任务类捆绑到一个库中. 或两者的某种组合.

在我们的示例中,我们将从构建脚本中的插件开始,以使事情变得简单. 然后,我们将考虑创建一个独立的项目.

Writing a simple plugin

要创建Gradle插件,您需要编写一个实现Plugin接口的类. 将插件应用于项目时,Gradle将创建插件类的实例,并调用该实例的Plugin.apply()方法. 项目对象作为参数传递,插件可以使用它来配置项目,但需要这样做. 下面的示例包含一个Greeting插件,该插件向项目添加了hello任务.

例子1.一个自定义插件
build.gradle
class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('hello') {
            doLast {
                println 'Hello from the GreetingPlugin'
            }
        }
    }
}

// Apply the plugin
apply plugin: GreetingPlugin
build.gradle.kts
class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.task("hello") {
            doLast {
                println("Hello from the GreetingPlugin")
            }
        }
    }
}

// Apply the plugin
apply<GreetingPlugin>()
gradle -q hello输出
> gradle -q hello
Hello from the GreetingPlugin

需要注意的一件事是,将为每个应用插件创建一个新的插件实例. 另请注意, Plugin类是泛型类型. 此示例接收项目类型作为类型参数. 插件可以改为接收类型为Settings的参数,在这种情况下,可以将其应用在设置脚本中,或者可以将类型为Gradle的参数应用在初始化脚本中.

Making the plugin configurable

大多数插件为构建脚本提供了一些配置选项,其他插件则用于自定义插件的工作方式. 插件使用扩展对象执行此操作. Gradle 项目具有关联的ExtensionContainer对象,该对象包含已应用于项目的插件的所有设置和属性. 您可以通过向该容器添加扩展对象来为您的插件提供配置. 扩展对象只是具有表示配置的Java Bean属性的对象.

让我们向项目添加一个简单的扩展对象. 在这里,我们将greeting扩展对象添加到项目中,该对象使您可以配置问候语.

例子2.一个自定义插件扩展
build.gradle
class GreetingPluginExtension {
    String message = 'Hello from GreetingPlugin'
}

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        // Add the 'greeting' extension object
        def extension = project.extensions.create('greeting', GreetingPluginExtension)
        // Add a task that uses configuration from the extension object
        project.task('hello') {
            doLast {
                println extension.message
            }
        }
    }
}

apply plugin: GreetingPlugin

// Configure the extension
greeting.message = 'Hi from Gradle'
build.gradle.kts
open class GreetingPluginExtension {
    var message = "Hello from GreetingPlugin"
}

class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        // Add the 'greeting' extension object
        val extension = project.extensions.create<GreetingPluginExtension>("greeting")
        // Add a task that uses configuration from the extension object
        project.task("hello") {
            doLast {
                println(extension.message)
            }
        }
    }
}

apply<GreetingPlugin>()

// Configure the extension
the<GreetingPluginExtension>().message = "Hi from Gradle"
gradle -q hello输出
> gradle -q hello
Hi from Gradle

在此示例中, GreetingPluginExtension是具有名为message的属性的对象. 该扩展对象将以名称greeting添加到项目中. 然后,该对象可用作与扩展对象同名的项目属性.

通常,您需要在单个插件上指定几个相关属性. Gradle为每个扩展对象添加一个配置块,因此您可以将设置分组在一起. 以下示例向您展示了它是如何工作的.

例子3.一个带有配置块的自定义插件
build.gradle
class GreetingPluginExtension {
    String message
    String greeter
}

class GreetingPlugin implements Plugin<Project> {
    void apply(Project project) {
        def extension = project.extensions.create('greeting', GreetingPluginExtension)
        project.task('hello') {
            doLast {
                println "${extension.message} from ${extension.greeter}"
            }
        }
    }
}

apply plugin: GreetingPlugin

// Configure the extension using a DSL block
greeting {
    message = 'Hi'
    greeter = 'Gradle'
}
build.gradle.kts
open class GreetingPluginExtension {
    var message: String? = null
    var greeter: String? = null
}

class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val extension = project.extensions.create<GreetingPluginExtension>("greeting")
        project.task("hello") {
            doLast {
                println("${extension.message} from ${extension.greeter}")
            }
        }
    }
}

apply<GreetingPlugin>()

// Configure the extension using a DSL block
configure<GreetingPluginExtension> {
    message = "Hi"
    greeter = "Gradle"
}
gradle -q hello输出
> gradle -q hello
Hi from Gradle

在此示例中,几个设置可以在greeting闭包中分组在一起. 构建脚本中的闭包块名称( greeting )需要与扩展对象名称匹配. 然后,当执行闭包时,扩展对象上的字段将基于标准Groovy闭包委托功能映射到闭包内的变量.

在此示例中,几个设置可以在configure<GreetingPluginExtension>块中分组在一起. 在构建脚本( GreetingPluginExtension )中的configure函数上使用的类型需要与扩展名类型匹配. 然后,当执行该块时,该块的接收者就是扩展.

这样,使用扩展对象可以扩展 Gradle DSL,从而为插件添加项目属性和DSL块. 并且由于扩展对象只是一个常规对象,因此可以通过向扩展对象添加属性和方法来提供嵌套在插件块中的自己的DSL.

Developing project extensions

您可以在开发自定义Gradle类型中找到有关实现项目扩展的更多信息.

Working with files in custom tasks and plugins

在开发自定义任务和插件时,最好在接受文件位置的输入配置时非常灵活. 为此,您可以利用Project.file(java.lang.Object)方法尽可能晚地将值解析为文件.

Example 4. Evaluating file properties lazily
build.gradle
class GreetingToFileTask extends DefaultTask {

    def destination

    @OutputFile
    File getDestination() {
        project.file(destination)
    }

    @TaskAction
    def greet() {
        def file = getDestination()
        file.parentFile.mkdirs()
        file.write 'Hello!'
    }
}

task greet(type: GreetingToFileTask) {
    destination = { project.greetingFile }
}

task sayGreeting(dependsOn: greet) {
    doLast {
        println file(greetingFile).text
    }
}

ext.greetingFile = "$buildDir/hello.txt"
build.gradle.kts
open class GreetingToFileTask : DefaultTask() {

    var destination: Any? = null

    @OutputFile
    fun getDestination(): File {
        return project.file(destination!!)
    }

    @TaskAction
    fun greet() {
        val file = getDestination()
        file.parentFile.mkdirs()
        file.writeText("Hello!")
    }
}

tasks.register<GreetingToFileTask>("greet") {
    destination = { project.extra["greetingFile"]!! }
}

tasks.register("sayGreeting") {
    dependsOn("greet")
    doLast {
        println(file(project.extra["greetingFile"]!!).readText())
    }
}

extra["greetingFile"] = "$buildDir/hello.txt"
gradle -q sayGreeting输出
> gradle -q sayGreeting
Hello!

在此示例中,我们将greet任务的destination属性配置为闭包/提供者,使用Project.file(java.lang.Object)方法对其进行评估,以将闭包/提供者的返回值最后转换为File对象.分钟. 您会注意到,在上面的示例中,在配置为任务使用它之后,我们指定了greetingFile属性值. 这种惰性评估的主要好处是在设置文件属性时接受任何值,然后在读取属性时解析该值.

Mapping extension properties to task properties

从构建脚本通过扩展捕获用户输入并将其映射到自定义任务的输入/输出属性是一种有用的模式. 构建脚本作者仅与扩展定义的DSL交互. 命令式逻辑隐藏在插件实现中.

Gradle提供了一些类型,您可以在任务实现和扩展中使用这些类型来帮助您. 有关更多信息,请参考惰性配置 .

A standalone project

现在,我们将插件移至独立项目,以便我们可以发布它并与他人共享. 这个项目只是一个Groovy项目,它产生一个包含插件类的JAR. 这是该项目的简单构建脚本. 它应用了Groovy插件,并将Gradle API添加为编译时依赖项.

例子5.一个自定义插件的构建
build.gradle
plugins {
    id 'groovy'
}

dependencies {
    implementation gradleApi()
    implementation localGroovy()
}
build.gradle.kts
plugins {
    groovy
}

dependencies {
    implementation(gradleApi())
    implementation(localGroovy())
}
该示例的代码可以在Gradle的'-all'发行版的samples/customPlugin中找到.

那么Gradle如何找到插件实现? 答案是您需要在jar的META-INF/gradle-plugins目录中提供一个META-INF/gradle-plugins ID相匹配的属性文件.

Example: Wiring for a custom plugin

src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties
implementation-class=org.gradle.GreetingPlugin

请注意,属性文件名与插件ID匹配,并放置在资源文件夹中,并且implementation-class属性标识插件实现类.

Creating a plugin id

插件ID以类似于Java包的方式(即反向域名)完全合格. 这有助于避免冲突,并提供了一种将具有相似所有权的插件分组的方法.

您的插件ID应该是反映名称空间(指向您或您的组织的合理指针)的组件及其提供的插件名称的组合. 例如,如果您有一个名为" foo"的Github帐户,而您的插件名为" bar",则合适的插件ID可能是com.github.foo.bar . 同样,如果插件是由baz组织开发的,则插件ID可能是org.baz.bar .

插件ID应符合以下条件:

  • 可以包含任何字母数字字符"."和"-".

  • 必须至少包含一个".". 分隔命名空间和插件名称的字符.

  • 按照惯例,对名称空间使用小写的反向域名约定.

  • 通常,名称中仅使用小写字符.

  • 不能使用org.gradlecom.gradleware命名空间.

  • 不能以"."开头或结尾. 字符.

  • 不能包含连续的"." 字符(即" ..").

尽管插件ID与程序包名称之间存在常规的相似之处,但通常包名称比插件ID所需的名称更为详细. 例如,在您的插件ID中添加" gradle"作为组件似乎很合理,但是由于插件ID仅用于Gradle插件,因此这是多余的. 通常,一个良好的插件ID仅需要一个用于标识所有权和名称的名称空间.

Publishing your plugin

如果要在内部发布插件供组织内部使用,则可以像其他任何代码工件一样发布. 请参阅有关发布工件的IvyMaven章节.

如果您有兴趣发布供更广泛的Gradle社区使用的插件,则可以将其发布到Gradle插件门户 . 该站点提供了搜索和收集有关Gradle社区贡献的插件的信息的功能. 请参阅相应的指南,以了解如何在此站点上使用您的插件.

Using your plugin in another project

要在构建脚本中使用插件,您需要将插件类添加到构建脚本的类路径中. 为此,请使用" buildscript {}"块,如参见使用buildscript块应用插件中所述 . 以下示例显示了包含插件的JAR已发布到本地存储库时如何执行此操作:

Example 6. Using a custom plugin in another project
build.gradle
buildscript {
    repositories {
        maven {
            url = uri(repoLocation)
        }
    }
    dependencies {
        classpath 'org.gradle:customPlugin:1.0-SNAPSHOT'
    }
}
apply plugin: 'org.samples.greeting'
build.gradle.kts
buildscript {
    repositories {
        maven {
            url = uri(repoLocation)
        }
    }
    dependencies {
        classpath("org.gradle:customPlugin:1.0-SNAPSHOT")
    }
}
apply(plugin = "org.samples.greeting")

或者,您可以使用插件DSL(请参阅使用插件DSL 应用插件 )来应用插件:

例子7.通过插件DSL应用社区插件
build.gradle
plugins {
    id 'com.jfrog.bintray' version '0.4.1'
}
build.gradle.kts
plugins {
    id("com.jfrog.bintray") version "0.4.1"
}

Writing tests for your plugin

您可以使用ProjectBuilder类创建在测试插件实现时要使用的Project实例.

Example: Testing a custom plugin

src/test/groovy/org/gradle/GreetingPluginTest.groovy
class GreetingPluginTest {
    @Test
    public void greeterPluginAddsGreetingTaskToProject() {
        Project project = ProjectBuilder.builder().build()
        project.pluginManager.apply 'org.samples.greeting'

        assertTrue(project.tasks.hello instanceof GreetingTask)
    }
}

Using the Java Gradle Plugin Development Plugin

您可以使用Java Gradle插件开发插件来消除构建脚本中的一些样板声明,并提供插件元数据的一些基本验证. 该插件将自动应用Java插件 ,将gradleApi()依赖项添加到编译配置中,并在jar任务执行过程中执行插件元数据验证,并在生成的JAR的META-INF目录中生成插件描述符.

例子8.使用Java Gradle插件开发插件
build.gradle
plugins {
    id 'java-gradle-plugin'
    id 'groovy'
}

gradlePlugin {
    plugins {
        simplePlugin {
            id = 'org.samples.greeting'
            implementationClass = 'org.gradle.GreetingPlugin'
        }
    }
}
build.gradle.kts
plugins {
    `java-gradle-plugin`
    groovy
}

gradlePlugin {
    plugins {
        create("simplePlugin") {
            id = "org.samples.greeting"
            implementationClass = "org.gradle.GreetingPlugin"
        }
    }
}

当使用IvyMaven发布插件将插件发布到自定义插件存储库时, Java Gradle插件开发插件还将生成基于插件ID命名的插件标记工件,插件标识工件取决于插件的实现工件.

More details

插件通常还提供自定义任务类型. 有关更多详细信息,请参见开发自定义Gradle任务类型 .

Gradle提供了许多功能,这些功能在开发Gradle类型(包括插件)时很有用. 有关更多详细信息,请参见开发自定义Gradle类型 .