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

您可以使用任何喜欢的语言来实现Gradle插件,前提是该实现最终被编译为JVM字节码. 在我们的示例中,我们将使用Java作为独立插件项目的实现语言,并在buildscript插件示例中使用Groovy或Kotlin. 通常,使用Java或Kotlin(静态类型)实现的插件比使用Groovy实施的插件性能更好.

Packaging a plugin

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

Build script

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

buildSrc project

您可以将插件的源代码放在rootProjectDir /buildSrc/src/main/java目录中(或rootProjectDir /buildSrc/src/main/groovyrootProjectDir /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)方法尽可能晚地将值解析为文件.

例子4.懒惰地评估文件属性
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

现在,我们将插件移至独立项目,以便我们可以发布它并与他人共享. 该项目只是一个Java项目,它产生包含插件类的JAR. 打包和发布插件的最简单且推荐的方法是使用Java Gradle插件开发插件 . 该插件将自动应用Java插件 ,将gradleApi()依赖项添加到api配置中,在生成的JAR文件中生成所需的插件描述符,并配置要在发布时使用的插件标记工件 . 这是该项目的简单构建脚本.

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

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

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

Creating a plugin id

Plugin ids are fully qualified in a manner similar to Java packages (i.e. a reverse domain name). This helps to avoid collisions and provides a way to group plugins with similar ownership.

您的插件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

要在构建脚本中使用插件,您需要在项目设置文件的pluginManagement {}块中配置存储库. 以下示例显示了将插件发布到本地存储库后如何执行此操作:

例子6.在另一个项目中使用一个自定义插件
settings.gradle
pluginManagement {
    repositories {
        maven {
            url = uri(repoLocation)
        }
    }
}
build.gradle
plugins {
    id 'org.samples.greeting' version '1.0-SNAPSHOT'
}
settings.gradle.kts
pluginManagement {
    repositories {
        maven {
            url = uri(repoLocation)
        }
    }
}
build.gradle.kts
plugins {
    id("org.samples.greeting") version "1.0-SNAPSHOT"
}

Note for plugins published without java-gradle-plugin

如果您的插件是在未使用Java Gradle插件开发插件的情况下发布的,则该出版物将缺少Plugin Marker Artifact ,这是插件DSL查找插件所需的. 在这种情况下,建议在另一个项目中解析该插件的方法是在该项目的设置文件的pluginManagement {}块中添加一个resolutionStrategy部分,如下所示.

例子7.没有插件标记工件的插件的解析策略
settings.gradle
    resolutionStrategy {
        eachPlugin {
            if (requested.id.namespace == 'org.samples') {
                useModule("org.gradle:customPlugin:${requested.version}")
            }
        }
    }
settings.gradle.kts
    resolutionStrategy {
        eachPlugin {
            if (requested.id.namespace == "org.samples") {
                useModule("org.gradle:customPlugin:${requested.version}")
            }
        }
    }

Precompiled script plugins

除了作为独立项目编写的插件之外,Gradle还允许您提供以Groovy或Kotlin DSL编写的构建逻辑作为预编译脚本插件. 你写这些为*.gradle在文件中src/main/groovy目录或*.gradle.kts在文件中src/main/kotlin目录.

预编译的脚本插件被编译成类文件,然后打包到jar中. 出于所有目的和目的,它们是二进制插件,可以通过插件ID进行应用,经过测试并作为二进制插件发布. 实际上,它们的插件元数据是使用Gradle插件开发插件生成的.

Gradle 6.0内置的Kotlin DSL预编译脚本插件不能与Gradle的早期版本一起使用. 在Gradle的未来版本中将取消此限制.

Groovy DSL预编译脚本插件可从Gradle 6.4开始使用.

要应用预编译的脚本插件,您需要知道其ID,该ID是从插件脚本的文件名(减去.gradle扩展名)派生而来的.

要应用预编译的脚本插件,您需要知道其ID,该ID是从插件脚本的文件名(减去.gradle.kts扩展名)及其(可选)程序包声明.gradle.kts .

例如,脚本src/main/groovy/java-library-convention.gradle的插件ID为java-library-convention . 同样地, src/main/groovy/my.java-library-convention.gradle将导致一个插件ID my.java-library-convention .

例如,脚本src/main/kotlin/java-library-convention.gradle.kts的插件ID为java-library-convention (假设它没有包声明). 同样, src/main/kotlin/my/java-library-convention.gradle.kts的软件包声明为my ,其插件ID为my.java-library-convention .

为了演示如何实现和使用预编译的脚本插件,让我们buildSrc一个基于buildSrc项目的示例.

首先,您需要一个buildSrc/build.gradle groovy-gradle-plugin插件的buildSrc/build.gradle文件:

首先,您需要一个应用kotlin-dsl插件的buildSrc/build.gradle.kts文件:

例子8.启用预编译脚本插件
buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin'
}
buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}

我们建议您还创建一个buildSrc/settings.gradle文件,您可以将其保留为空.

我们建议您还创建一个buildSrc/settings.gradle.kts文件,您可以将其保留为空.

接下来,在buildSrc/src/main/groovy目录中创建一个新的java-library-convention.gradle文件,并将其内容设置为以下内容:

接下来,在buildSrc/src/main/kotlin目录中创建一个新的java-library-convention.gradle.kts文件,并将其内容设置为以下内容:

例子9.创建一个简单的脚本插件
buildSrc/src/main/groovy/java-library-convention.gradle
plugins {
    id 'java-library'
    id 'checkstyle'
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

checkstyle {
    maxWarnings = 0
    // ...
}

tasks.withType(JavaCompile) {
    options.warnings = true
    // ...
}

dependencies {
    testImplementation("junit:junit:4.13")
    // ...
}
buildSrc/src/main/kotlin/java-library-convention.gradle.kts
plugins {
    `java-library`
    checkstyle
}

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

checkstyle {
    maxWarnings = 0
    // ...
}

tasks.withType<JavaCompile> {
    options.isWarnings = true
    // ...
}

dependencies {
    testImplementation("junit:junit:4.13")
    // ...
}

该脚本插件仅应用Java库和Checkstyle插件并对其进行配置. 请注意,这实际上会将插件应用到主项目,即应用预编译脚本插件的插件.

最后,将脚本插件应用于根项目,如下所示:

例子10.将预编译的脚本插件应用到主项目
build.gradle
plugins {
    id 'java-library-convention'
}
build.gradle.kts
plugins {
    `java-library-convention`
}

Applying external plugins in precompiled script plugins

为了将外部插件应用到预编译的脚本插件中,必须将其添加到插件的构建文件中的插件项目的实现类路径中.

buildSrc/build.gradle
plugins {
    id 'groovy-gradle-plugin'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'com.bmuschko:gradle-docker-plugin:6.4.0'
}
buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    jcenter()
}

dependencies {
    implementation("com.bmuschko:gradle-docker-plugin:6.4.0")
}

It can then be applied in the precompiled script plugin.

buildSrc/src/main/groovy/my-plugin.gradle
plugins {
    id 'com.bmuschko.docker-remote-api'
}
buildSrc/src/main/kotlin/my-plugin.gradle.kts
plugins {
    id("com.bmuschko.docker-remote-api")
}

The plugin version in this case is defined in the dependency declaration.

在将来的Gradle版本中将删除此限制.

Writing tests for your plugin

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

Example: Testing a custom plugin

src/test/java/org/gradle/GreetingPluginTest.java
public class GreetingPluginTest {
    @Test
    public void greeterPluginAddsGreetingTaskToProject() {
        Project project = ProjectBuilder.builder().build();
        project.getPluginManager().apply("org.samples.greeting");

        assertTrue(project.getTasks().getByName("hello") instanceof GreetingTask);
    }
}

More details

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

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

在开发Gradle插件时,将信息记录到构建日志中时请务必小心. 记录敏感信息(例如凭据,令牌,某些环境变量)被视为安全漏洞 . 公共持续集成服务的构建日志在世界范围内可见,并且可以公开此敏感信息.

Behind the scenes

那么Gradle如何找到插件实现? 答案是-您需要在JAR的META-INF/gradle-plugins目录中提供一个与您的插件ID相匹配的属性文件,该文件由Java Gradle插件开发插件处理 .

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属性标识插件实现类.