二进制插件是指以 JAR 文件形式编译和分发的插件。这些插件通常用 Java 或 Kotlin 编写,并为 Gradle 构建提供自定义功能或任务。

使用插件开发插件

Gradle Plugin Development 插件可用于协助开发 Gradle 插件。

该插件将自动应用Java Plugin,将gradleApi()依赖项添加到api配置中,在生成的 JAR 文件中生成所需的插件描述符,并配置发布时要使用的插件标记工件。

要应用和配置该插件,请将以下代码添加到您的构建文件中:

build.gradle.kts
plugins {
    `java-gradle-plugin`
}

gradlePlugin {
    plugins {
        create("simplePlugin") {
            id = "org.example.greeting"
            implementationClass = "org.example.GreetingPlugin"
        }
    }
}
build.gradle
plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        simplePlugin {
            id = 'org.example.greeting'
            implementationClass = 'org.example.GreetingPlugin'
        }
    }
}

开发插件时建议编写和使用自定义任务类型,因为它会自动受益于增量构建。将插件应用到项目的另一个好处是,该任务validatePlugins会自动检查自定义任务类型实现中定义的每个公共属性的现有输入/输出注释。

创建插件 ID

插件 ID 是全局唯一的,类似于 Java 包名称(即反向域名)。这种格式有助于防止命名冲突,并允许将具有相似所有权的插件分组。

显式插件标识符简化了将插件应用到项目的过程。您的插件 ID 应结合反映命名空间(指向您或您的组织的合理指针)及其提供的插件名称的组件。例如,如果您的 Github 帐户名为foo且您的插件名为bar,则合适的插件 ID 可能是com.github.foo.bar。同样,如果插件是在baz组织中开发的,则插件 ID 可能是org.baz.bar

插件 ID 应遵循以下准则:

  • 可以包含任何字母数字字符、“.”和“-”。

  • 必须至少包含一个“.”将命名空间与插件名称分隔开的字符。

  • 通常,命名空间使用小写反向域名约定。

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

  • org.gradle不能使用、com.gradle、 和命名空间。com.gradleware

  • 不能以“.”开头或结尾特点。

  • 不能包含连续的“.”字符(即“..”)。

标识所有权和名称的命名空间对于插件 ID 来说就足够了。

将多个插件捆绑在单个 JAR 工件中时,建议遵循相同的命名约定。这种做法有助于对相关插件进行逻辑分组。

单个项目中可以定义和注册(通过不同标识符)的插件数量没有限制。

作为类编写的插件的标识符应在包含插件类的项目构建脚本中定义。为此,java-gradle-plugin需要应用:

buildSrc/build.gradle.kts
plugins {
    id("java-gradle-plugin")
}

gradlePlugin {
    plugins {
        create("androidApplicationPlugin") {
            id = "com.android.application"
            implementationClass = "com.android.AndroidApplicationPlugin"
        }
        create("androidLibraryPlugin") {
            id = "com.android.library"
            implementationClass = "com.android.AndroidLibraryPlugin"
        }
    }
}
buildSrc/build.gradle
plugins {
    id 'java-gradle-plugin'
}

gradlePlugin {
    plugins {
        androidApplicationPlugin {
            id = 'com.android.application'
            implementationClass = 'com.android.AndroidApplicationPlugin'
        }
        androidLibraryPlugin {
            id = 'com.android.library'
            implementationClass = 'com.android.AndroidLibraryPlugin'
        }
    }
}

处理文件

开发插件时,在接受文件位置的输入配置时保持灵活性是个好主意。

建议使用 Gradle 的托管属性project.layout选择文件或目录位置。这将启用延迟配置,以便仅在需要文件时才会解析实际位置,并且可以在构建配置期间随时重新配置。

此 Gradle 构建文件定义了一个GreetingToFileTask将问候语写入文件的任务。它还注册了两个任务:greet,它创建带有问候语的文件,以及sayGreeting,它打印文件的内容。该greetingFile属性用于指定问候语的文件路径:

build.gradle.kts
abstract class GreetingToFileTask : DefaultTask() {

    @get:OutputFile
    abstract val destination: RegularFileProperty

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

val greetingFile = objects.fileProperty()

tasks.register<GreetingToFileTask>("greet") {
    destination = greetingFile
}

tasks.register("sayGreeting") {
    dependsOn("greet")
    val greetingFile = greetingFile
    doLast {
        val file = greetingFile.get().asFile
        println("${file.readText()} (file: ${file.name})")
    }
}

greetingFile = layout.buildDirectory.file("hello.txt")
build.gradle
abstract class GreetingToFileTask extends DefaultTask {

    @OutputFile
    abstract RegularFileProperty getDestination()

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

def greetingFile = objects.fileProperty()

tasks.register('greet', GreetingToFileTask) {
    destination = greetingFile
}

tasks.register('sayGreeting') {
    dependsOn greet
    doLast {
        def file = greetingFile.get().asFile
        println "${file.text} (file: ${file.name})"
    }
}

greetingFile = layout.buildDirectory.file('hello.txt')
$ gradle -q sayGreeting
Hello! (file: hello.txt)

在此示例中,我们将greet任务destination属性配置为闭包/提供程序,并使用Project.file(java.lang.Object)File方法对其进行评估,以在最后一刻将闭包/提供程序的返回值转换为对象。请注意,我们在任务配置之后greetingFile指定属性值。这种惰性评估是在设置文件属性时接受任何值然后在读取属性时解析该值的一个关键好处。

您可以在使用文件中了解有关延迟处理文件的更多信息。

使用扩展使插件可配置

大多数插件都提供构建脚本和其他插件的配置选项,以自定义插件的工作方式。插件使用扩展对象来完成此操作。

项目有一个关联的ExtensionContainer对象其中包含已应用于项目的插件的所有设置和属性。您可以通过向此容器添加扩展对象来为您的插件提供配置。

扩展对象只是一个具有表示配置的 Java Bean 属性的对象。

让我们greeting向项目添加一个扩展对象,它允许您配置问候语:

build.gradle.kts
interface GreetingPluginExtension {
    val message: Property<String>
}

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.get())
            }
        }
    }
}

apply<GreetingPlugin>()

// Configure the extension
the<GreetingPluginExtension>().message = "Hi from Gradle"
build.gradle
interface GreetingPluginExtension {
    Property<String> getMessage()
}

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.get()
            }
        }
    }
}

apply plugin: GreetingPlugin

// Configure the extension
greeting.message = 'Hi from Gradle'
$ gradle -q hello
Hi from Gradle

在此示例中,GreetingPluginExtension是一个具有名为 的属性的对象message。扩展对象将添加到名为 的项目中greeting。该对象可用作与扩展对象同名的项目属性。 the<GreetingPluginExtension>()相当于project.extensions.getByType(GreetingPluginExtension::class.java).

通常,您需要在单个插件上指定多个相关属性。 Gradle 为每个扩展对象添加了一个配置块,因此您可以对设置进行分组:

build.gradle.kts
interface GreetingPluginExtension {
    val message: Property<String>
    val greeter: Property<String>
}

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

apply<GreetingPlugin>()

// Configure the extension using a DSL block
configure<GreetingPluginExtension> {
    message = "Hi"
    greeter = "Gradle"
}
build.gradle
interface GreetingPluginExtension {
    Property<String> getMessage()
    Property<String> getGreeter()
}

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

apply plugin: GreetingPlugin

// Configure the extension using a DSL block
greeting {
    message = 'Hi'
    greeter = 'Gradle'
}
$ gradle -q hello
Hi from Gradle

在此示例中,可以将多个设置分组在configure<GreetingPluginExtension>块内。该configure函数用于配置扩展对象。它提供了一种设置属性或将配置应用于这些对象的便捷方法。构建脚本的configure函数 ( )中使用的类型GreetingPluginExtension必须与扩展类型匹配。然后,当块被执行时,块的接收者就是扩展。

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

声明 DSL 配置容器

使用扩展对象扩展Gradle DSL 来为插件添加项目属性和 DSL 块。由于扩展对象是常规对象,因此您可以通过向扩展对象添加属性和方法来提供嵌套在插件块内的自己的 DSL。

为了说明目的,我们考虑以下构建脚本。

build.gradle.kts
plugins {
    id("org.myorg.server-env")
}

environments {
    create("dev") {
        url = "http://localhost:8080"
    }

    create("staging") {
        url = "http://staging.enterprise.com"
    }

    create("production") {
        url = "http://prod.enterprise.com"
    }
}
build.gradle
plugins {
    id 'org.myorg.server-env'
}

environments {
    dev {
        url = 'http://localhost:8080'
    }

    staging {
        url = 'http://staging.enterprise.com'
    }

    production {
        url = 'http://prod.enterprise.com'
    }
}

插件公开的 DSL 公开了一个用于定义一组环境的容器。用户配置的每个环境都有一个任意但声明性的名称,并用其自己的 DSL 配置块表示。上面的示例实例化了开发、暂存和生产环境,包括其各自的 URL。

每个环境都必须有代码中的数据表示形式来捕获值。环境的名称是不可变的,可以作为构造函数参数传入。目前,数据对象存储的唯一其他参数是 URL。

以下ServerEnvironment对象满足这些要求:

服务器环境.java
abstract public class ServerEnvironment {
    private final String name;

    @javax.inject.Inject
    public ServerEnvironment(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    abstract public Property<String> getUrl();
}

Gradle 公开工厂方法 ObjectFactory.domainObjectContainer(Class, NamedDomainObjectFactory) 来创建数据对象的容器。该方法采用的参数是表示数据的类。创建的NamedDomainObjectContainer类型实例可以通过将其添加到具有特定名称的扩展容器来向最终用户公开。

插件通常会在插件实现中对捕获的值进行后处理,例如配置任务:

服务器环境插件.java
public class ServerEnvironmentPlugin implements Plugin<Project> {
    @Override
    public void apply(final Project project) {
        ObjectFactory objects = project.getObjects();

        NamedDomainObjectContainer<ServerEnvironment> serverEnvironmentContainer =
            objects.domainObjectContainer(ServerEnvironment.class, name -> objects.newInstance(ServerEnvironment.class, name));
        project.getExtensions().add("environments", serverEnvironmentContainer);

        serverEnvironmentContainer.all(serverEnvironment -> {
            String env = serverEnvironment.getName();
            String capitalizedServerEnv = env.substring(0, 1).toUpperCase() + env.substring(1);
            String taskName = "deployTo" + capitalizedServerEnv;
            project.getTasks().register(taskName, Deploy.class, task -> task.getUrl().set(serverEnvironment.getUrl()));
        });
    }
}

在上面的示例中,为每个用户配置的环境动态创建部署任务。

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

对类似 DSL 的 API 进行建模

插件公开的 DSL 应该可读且易于理解。

例如,让我们考虑插件提供的以下扩展。在当前形式中,它提供了一个“平面”属性列表,用于配置网站的创建:

build-flat.gradle.kts
plugins {
    id("org.myorg.site")
}

site {
    outputDir = layout.buildDirectory.file("mysite")
    websiteUrl = "https://gradle.org"
    vcsUrl = "https://github.com/gradle/gradle-site-plugin"
}
build-flat.gradle
plugins {
    id 'org.myorg.site'
}

site {
    outputDir = layout.buildDirectory.file("mysite")
    websiteUrl = 'https://gradle.org'
    vcsUrl = 'https://github.com/gradle/gradle-site-plugin'
}

随着公开属性数量的增加,您应该引入一个嵌套的、更具表现力的结构。

以下代码片段添加了一个名为customData扩展的一部分的新配置块。这更有力地表明了这些属性的含义:

build.gradle.kts
plugins {
    id("org.myorg.site")
}

site {
    outputDir = layout.buildDirectory.file("mysite")

    customData {
        websiteUrl = "https://gradle.org"
        vcsUrl = "https://github.com/gradle/gradle-site-plugin"
    }
}
build.gradle
plugins {
    id 'org.myorg.site'
}

site {
    outputDir = layout.buildDirectory.file("mysite")

    customData {
        websiteUrl = 'https://gradle.org'
        vcsUrl = 'https://github.com/gradle/gradle-site-plugin'
    }
}

实现此类扩展的支持对象很简单。首先,引入一个新的数据对象来管理属性websiteUrlvcsUrl

自定义数据.java
abstract public class CustomData {

    abstract public Property<String> getWebsiteUrl();

    abstract public Property<String> getVcsUrl();
}

在扩展中,创建CustomData类的实例和方法以将捕获的值委托给数据实例。

Action以下示例演示了在扩展定义中的使用:

站点扩展.java
abstract public class SiteExtension {

    abstract public RegularFileProperty getOutputDir();

    @Nested
    abstract public CustomData getCustomData();

    public void customData(Action<? super CustomData> action) {
        action.execute(getCustomData());
    }
}

将扩展属性映射到任务属性

插件通常使用扩展来捕获来自构建脚本的用户输入并将其映射到自定义任务的输入/输出属性。构建脚本作者与扩展的 DSL 交互,而插件实现则处理底层逻辑:

app/build.gradle.kts
// Extension class to capture user input
class MyExtension {
    @Input
    var inputParameter: String? = null
}

// Custom task that uses the input from the extension
class MyCustomTask : org.gradle.api.DefaultTask() {
    @Input
    var inputParameter: String? = null

    @TaskAction
    fun executeTask() {
        println("Input parameter: $inputParameter")
    }
}

// Plugin class that configures the extension and task
class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        // Create and configure the extension
        val extension = project.extensions.create("myExtension", MyExtension::class.java)
        // Create and configure the custom task
        project.tasks.register("myTask", MyCustomTask::class.java) {
            group = "custom"
            inputParameter = extension.inputParameter
        }
    }
}
app/build.gradle
// Extension class to capture user input
class MyExtension {
    @Input
    String inputParameter = null
}

// Custom task that uses the input from the extension
class MyCustomTask extends DefaultTask {
    @Input
    String inputParameter = null

    @TaskAction
    def executeTask() {
        println("Input parameter: $inputParameter")
    }
}

// Plugin class that configures the extension and task
class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        // Create and configure the extension
        def extension = project.extensions.create("myExtension", MyExtension)
        // Create and configure the custom task
        project.tasks.register("myTask", MyCustomTask) {
            group = "custom"
            inputParameter = extension.inputParameter
        }
    }
}

在此示例中,MyExtension该类定义了一个inputParameter可以在构建脚本中设置的属性。该类MyPlugin配置此扩展并使用其inputParameter值来配置MyCustomTask任务。然后,任务MyCustomTask在其逻辑中使用此输入参数。

您可以在延迟配置中了解有关可在任务实现和扩展中使用的类型的更多信息。

添加带有约定的默认配置

插件应该在特定上下文中提供合理的默认值和标准,从而减少用户需要做出的决策数量。使用该project对象,您可以定义默认值。这些被称为约定

约定是使用默认值初始化的属性,并且可以由用户在其构建脚本中覆盖。例如:

build.gradle.kts
interface GreetingPluginExtension {
    val message: Property<String>
}

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

apply<GreetingPlugin>()
build.gradle
interface GreetingPluginExtension {
    Property<String> getMessage()
}

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

apply plugin: GreetingPlugin
$ gradle -q hello
Hello from GreetingPlugin

在此示例中,GreetingPluginExtension是一个表示约定的类。 message 属性是约定属性,默认值为“Hello from GreetingPlugin”。

用户可以在其构建脚本中覆盖此值:

build.gradle.kts
GreetingPluginExtension {
    message = "Custom message"
}
build.gradle
GreetingPluginExtension {
    message = 'Custom message'
}
$ gradle -q hello
Custom message

将功能与约定分开

将插件中的功能与约定分开允许用户选择要应用的任务和约定。

例如,Java Base 插件提供了非固定(即通用)功能,如SourceSets,而 Java 插件添加了 Java 开发人员熟悉的任务和约定,如classesjarjavadoc

在设计自己的插件时,请考虑开发两个插件 - 一个用于功能,另一个用于约定 - 为用户提供灵活性。

在下面的示例中,MyPlugin包含约定并MyBasePlugin定义功能。然后,MyPlugin应用MyBasePlugin,这就是所谓的插件组合。要应用另一个插件的插件:

MyBasePlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyBasePlugin implements Plugin<Project> {
    public void apply(Project project) {
        // define capabilities
    }
}
MyPlugin.java
import org.gradle.api.Plugin;
import org.gradle.api.Project;

public class MyPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getPlugins().apply(MyBasePlugin.class);

        // define conventions
    }
}

对插件做出反应

Gradle 插件实现中的一个常见模式是配置构建中现有插件和任务的运行时行为。

例如,插件可以假设它应用于基于 Java 的项目并自动重新配置标准源目录:

InhouseStrongOpinionConventionJavaPlugin.java
public class InhouseStrongOpinionConventionJavaPlugin implements Plugin<Project> {
    public void apply(Project project) {
        // Careful! Eagerly appyling plugins has downsides, and is not always recommended.
        project.getPlugins().apply(JavaPlugin.class);
        SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
        SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
        main.getJava().setSrcDirs(Arrays.asList("src"));
    }
}

这种方法的缺点是它会自动强制项目应用Java插件,对其施加强烈的意见(即降低灵活性和通用性)。实际上,应用该插件的项目甚至可能不处理 Java 代码。

该插件可以对使用项目应用 Java 插件的事实做出反应,而不是自动应用 Java 插件。只有在这种情况下,才会应用特定的配置:

InhouseConventionJavaPlugin.java
public class InhouseConventionJavaPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getPlugins().withType(JavaPlugin.class, javaPlugin -> {
            SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
            SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
            main.getJava().setSrcDirs(Arrays.asList("src"));
        });
    }
}

如果没有充分的理由假设使用的项目具有预期的设置,则对插件做出反应优于应用插件。

同样的概念也适用于任务类型:

InhouseConventionWarPlugin.java
public class InhouseConventionWarPlugin implements Plugin<Project> {
    public void apply(Project project) {
        project.getTasks().withType(War.class).configureEach(war ->
            war.setWebXml(project.file("src/someWeb.xml")));
    }
}

响应构建功能

插件可以访问构建中构建功能的状态。构建功能 API允许检查用户是否请求特定的 Gradle 功能以及该功能在当前构建中是否处于活动状态。构建功能的一个示例是配置缓存

有两个主要用例:

  • 在报告或统计数据中使用构建功能的状态。

  • 通过禁用不兼容的插件功能来逐步采用实验性 Gradle 功能。

下面是一个使用这两种情况的插件示例。

响应构建功能
public abstract class MyPlugin implements Plugin<Project> {

    @Inject
    protected abstract BuildFeatures getBuildFeatures(); (1)

    @Override
    public void apply(Project p) {
        BuildFeatures buildFeatures = getBuildFeatures();

        Boolean configCacheRequested = buildFeatures.getConfigurationCache().getRequested() (2)
            .getOrNull(); // could be null if user did not opt in nor opt out
        String configCacheUsage = describeFeatureUsage(configCacheRequested);
        MyReport myReport = new MyReport();
        myReport.setConfigurationCacheUsage(configCacheUsage);

        boolean isolatedProjectsActive = buildFeatures.getIsolatedProjects().getActive() (3)
            .get(); // the active state is always defined
        if (!isolatedProjectsActive) {
            myOptionalPluginLogicIncompatibleWithIsolatedProjects();
        }
    }

    private String describeFeatureUsage(Boolean requested) {
        return requested == null ? "no preference" : requested ? "opt-in" : "opt-out";
    }

    private void myOptionalPluginLogicIncompatibleWithIsolatedProjects() {
    }
}
1 BuildFeatures服务可以注入插件、任务和其他托管类型。
2 访问requested功能的状态以进行报告。
3 使用active功能的状态来禁用不兼容的功能。

构建特征属性

状态BuildFeature属性用类型表示Provider<Boolean>

构建功能的状态BuildFeature.getRequested()决定用户是否请求启用或禁用该功能。

requested提供者值为:

  • true — 用户选择使用该功能

  • false — 用户选择不使用该功能

  • undefined — 用户既没有选择加入也没有选择退出使用该功能

BuildFeature.getActive()构建功能的状态始终是已定义的。它代表构建中功能的有效状态。

active提供者值为:

  • true - 该功能可能会以特定于该功能的方式影响构建行为

  • false — 该功能不会影响构建行为

请注意,active状态不依赖于requested状态。即使用户请求某个功能,由于构建中使用了其他构建选项,该功能仍然可能未激活。即使用户没有指定首选项,Gradle 也可以默认激活某项功能。

提供默认依赖项

插件的实现有时需要使用外部依赖项。

您可能希望使用 Gradle 的依赖管理机制自动下载工件,然后在插件中声明的任务类型的操作中使用它。理想情况下,插件实现不需要询问用户该依赖项的坐标 - 它可以简单地预定义一个合理的默认版本。

让我们看一个插件示例,该插件下载包含数据以供进一步处理的文件。插件实现声明了一个自定义配置,允许使用依赖坐标分配这些外部依赖项

数据处理插件.java
public class DataProcessingPlugin implements Plugin<Project> {
    public void apply(Project project) {
        Configuration dataFiles = project.getConfigurations().create("dataFiles", c -> {
            c.setVisible(false);
            c.setCanBeConsumed(false);
            c.setCanBeResolved(true);
            c.setDescription("The data artifacts to be processed for this plugin.");
            c.defaultDependencies(d -> d.add(project.getDependencies().create("org.myorg:data:1.4.6")));
        });

        project.getTasks().withType(DataProcessing.class).configureEach(
            dataProcessing -> dataProcessing.getDataFiles().from(dataFiles));
    }
}
数据处理.java
abstract public class DataProcessing extends DefaultTask {

    @InputFiles
    abstract public ConfigurableFileCollection getDataFiles();

    @TaskAction
    public void process() {
        System.out.println(getDataFiles().getFiles());
    }
}

这种方法对于最终用户来说很方便,因为不需要主动声明依赖项。该插件已经提供了有关此实现的所有详细信息。

但是如果用户想要重新定义默认依赖项怎么办?

没问题。该插件还公开了可用于分配不同依赖项的自定义配置。实际上,默认依赖项被覆盖:

build.gradle.kts
plugins {
    id("org.myorg.data-processing")
}

dependencies {
    dataFiles("org.myorg:more-data:2.6")
}
build.gradle
plugins {
    id 'org.myorg.data-processing'
}

dependencies {
    dataFiles 'org.myorg:more-data:2.6'
}

您会发现此模式非常适合执行任务操作时需要外部依赖项的任务。您可以通过公开扩展属性(例如toolVersionJaCoCo 插件中)进一步抽象用于外部依赖项的版本 。

尽量减少外部库的使用

在 Gradle 项目中使用外部库可以带来很大的便利,但请注意它们可能会引入复杂的依赖关系图。 Gradle 的buildEnvironment任务可以帮助您可视化这些依赖关系,包括插件的依赖关系。请记住,插件共享相同的类加载器,因此同一库的不同版本可能会出现冲突。

为了演示,我们假设以下构建脚本:

build.gradle.kts
plugins {
    id("org.asciidoctor.jvm.convert") version "4.0.2"
}
build.gradle
plugins {
    id 'org.asciidoctor.jvm.convert' version '4.0.2'
}

任务的输出清楚地指示了classpath配置的类路径:

$ gradle buildEnvironment

> Task :buildEnvironment

------------------------------------------------------------
Root project 'external-libraries'
------------------------------------------------------------

classpath
\--- org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:4.0.2
     \--- org.asciidoctor:asciidoctor-gradle-jvm:4.0.2
          +--- org.ysb33r.gradle:grolifant-rawhide:3.0.0
          |    \--- org.tukaani:xz:1.6
          +--- org.ysb33r.gradle:grolifant-herd:3.0.0
          |    +--- org.tukaani:xz:1.6
          |    +--- org.ysb33r.gradle:grolifant40:3.0.0
          |    |    +--- org.tukaani:xz:1.6
          |    |    +--- org.apache.commons:commons-collections4:4.4
          |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0
          |    |    |    +--- org.tukaani:xz:1.6
          |    |    |    +--- org.apache.commons:commons-collections4:4.4
          |    |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
          |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
          |    +--- org.ysb33r.gradle:grolifant50:3.0.0
          |    |    +--- org.tukaani:xz:1.6
          |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    |    \--- org.ysb33r.gradle:grolifant40-legacy-api:3.0.0
          |    |         +--- org.tukaani:xz:1.6
          |    |         +--- org.apache.commons:commons-collections4:4.4
          |    |         +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    |         \--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
          |    +--- org.ysb33r.gradle:grolifant60:3.0.0
          |    |    +--- org.tukaani:xz:1.6
          |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
          |    +--- org.ysb33r.gradle:grolifant70:3.0.0
          |    |    +--- org.tukaani:xz:1.6
          |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant60:3.0.0 (*)
          |    |    \--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    +--- org.ysb33r.gradle:grolifant80:3.0.0
          |    |    +--- org.tukaani:xz:1.6
          |    |    +--- org.ysb33r.gradle:grolifant40:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant50:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant60:3.0.0 (*)
          |    |    +--- org.ysb33r.gradle:grolifant70:3.0.0 (*)
          |    |    \--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    +--- org.ysb33r.gradle:grolifant-core:3.0.0 (*)
          |    \--- org.ysb33r.gradle:grolifant-rawhide:3.0.0 (*)
          +--- org.asciidoctor:asciidoctor-gradle-base:4.0.2
          |    \--- org.ysb33r.gradle:grolifant-herd:3.0.0 (*)
          \--- org.asciidoctor:asciidoctorj-api:2.5.7

(*) - Indicates repeated occurrences of a transitive dependency subtree. Gradle expands transitive dependency subtrees only once per project; repeat occurrences only display the root of the subtree, followed by this annotation.

A web-based, searchable dependency report is available by adding the --scan option.

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

Gradle 插件不会在其自己的独立类加载器中运行,因此您必须考虑是否确实需要一个库,或者一个更简单的解决方案是否足够。

对于作为任务执行的一部分执行的逻辑,请使用允许您隔离库的Worker API 。

提供插件的多个变体

配置其他插件变体的最便捷方法是使用功能变体,这是应用 Java 插件之一的所有 Gradle 项目中都可用的概念:

dependencies {
    implementation 'com.google.guava:guava:30.1-jre'        // Regular dependency
    featureVariant 'com.google.guava:guava-gwt:30.1-jre'    // Feature variant dependency
}

在以下示例中,每个插件变体都是单独开发的。为每个变体编译单独的源集并将其打包在单独的 jar 中。

以下示例演示了如何添加与 Gradle 7.0+ 兼容的变体,同时“主”变体与旧版本兼容:

build.gradle.kts
val gradle7 = sourceSets.create("gradle7")

java {
    registerFeature(gradle7.name) {
        usingSourceSet(gradle7)
        capability(project.group.toString(), project.name, project.version.toString()) (1)
    }
}

configurations.configureEach {
    if (isCanBeConsumed && name.startsWith(gradle7.name))  {
        attributes {
            attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, (2)
                objects.named("7.0"))
        }
    }
}

tasks.named<Copy>(gradle7.processResourcesTaskName) { (3)
    val copyPluginDescriptors = rootSpec.addChild()
    copyPluginDescriptors.into("META-INF/gradle-plugins")
    copyPluginDescriptors.from(tasks.pluginDescriptors)
}

dependencies {
    "gradle7CompileOnly"(gradleApi()) (4)
}
build.gradle
def gradle7 = sourceSets.create('gradle7')

java {
    registerFeature(gradle7.name) {
        usingSourceSet(gradle7)
        capability(project.group.toString(), project.name, project.version.toString()) (1)
    }
}

configurations.configureEach {
    if (canBeConsumed && name.startsWith(gradle7.name))  {
        attributes {
            attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, (2)
                      objects.named(GradlePluginApiVersion, '7.0'))
        }
    }
}

tasks.named(gradle7.processResourcesTaskName) { (3)
    def copyPluginDescriptors = rootSpec.addChild()
    copyPluginDescriptors.into('META-INF/gradle-plugins')
    copyPluginDescriptors.from(tasks.pluginDescriptors)
}

dependencies {
    gradle7CompileOnly(gradleApi()) (4)
}
只有 Gradle 版本 7 或更高版本可以明确作为变体的目标,因为仅在 Gradle 7 中添加了对此的支持。

首先,我们为 Gradle7 插件变体声明一个单独的源集和一个功能变体。然后,我们进行一些特定的连接,将该功能转换为适当的 Gradle 插件变体:

1 将与组件 GAV 相对应的隐式功能分配给变体。
2 Gradle API 版本属性分配给Gradle7 变体的所有可用配置。 Gradle 使用此信息来确定在插件解析期间选择哪个变体。
3 配置processGradle7Resources任务以确保插件描述符文件添加到 Gradle7 变体 Jar 中。
4 为我们的新变体添加依赖项gradleApi(),以便 API 在编译期间可见。

请注意,目前没有方便的方法来访问其他 Gradle 版本的 API,就像您用来构建插件的 API 一样。理想情况下,每个变体都应该能够声明对其支持的最小 Gradle 版本的 API 的依赖关系。今后这一点将会得到改善。

上面的代码片段假设插件的所有变体都在同一位置具有插件类。也就是说,如果您的插件类是org.example.GreetingPlugin,您需要在 中创建该类的第二个变体src/gradle7/java/org/example

使用多变体插件的版本特定变体

鉴于对多变体插件的依赖,Gradle 在解决以下任一问题时将自动选择与当前 Gradle 版本最匹配的变体:

最佳匹配变体是针对最高 Gradle API 版本且不超过当前构建的 Gradle 版本的变体。

在所有其他情况下,如果存在未指定支持的 Gradle API 版本的插件变体,则首选该变体。

在使用插件作为依赖项的项目中,可以请求支持不同 Gradle 版本的插件依赖项的变体。这允许依赖其他插件的多变体插件访问其 API,这些 API 专门在其特定于版本的变体中提供。

此代码片段使上面定义的插件变体gradle7消耗其对其他多变体插件的依赖项的匹配变体:

build.gradle.kts
configurations.configureEach {
    if (isCanBeResolved && name.startsWith(gradle7.name))  {
        attributes {
            attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE,
                objects.named("7.0"))
        }
    }
}
build.gradle
configurations.configureEach {
    if (canBeResolved && name.startsWith(gradle7.name))  {
        attributes {
            attribute(GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE,
                objects.named(GradlePluginApiVersion, '7.0'))
        }
    }
}

报告问题

插件可以通过 Gradle 的问题报告 API 报告问题。 API 报告有关构建过程中发生的问题的丰富的结构化信息。不同的用户界面(例如 Gradle 的控制台输出、构建扫描或 IDE)可以使用此信息以最合适的方式向用户传达问题。

以下示例显示了插件报告的问题:

ProblemReportingPlugin.java
public class ProblemReportingPlugin implements Plugin<Project> {

    private final ProblemReporter problemReporter;

    @Inject
    public ProblemReportingPlugin(Problems problems) { (1)
        this.problemReporter = problems.forNamespace("org.myorg"); (2)
    }

    public void apply(Project project) {
        this.problemReporter.reporting(builder -> builder (3)
            .label("Plugin 'x' is deprecated")
            .details("The plugin 'x' is deprecated since version 2.5")
            .solution("Please use plugin 'y'")
            .severity(Severity.WARNING)
        );
    }
}
1 Problem服务被注入到插件中。
2 为插件创建了一个问题报告器。虽然命名空间由插件作者决定,但建议使用插件 ID。
3 报告有问题。此问题是可以恢复的,因此构建将继续。

有关完整示例,请参阅我们的端到端示例

问题构建

报告问题时,可以提供多种信息。 ProblemSpec描述可以提供的所有信息。

报告问题

在报告问题时,我们支持三种不同的模式:

  • 报告问题用于报告可恢复的问题,并且构建应该继续。

  • 抛出问题用于报告不可恢复的问题,并且构建应该失败。

  • 重新抛出问题用于包装已经抛出的异常。否则,行为与 相同Throwing

有关更多详细信息,请参阅ProblemReporter文档。

问题聚合

报告问题时,Gradle 会根据问题的类别标签,通过 Tooling API 发送类似的问题来进行聚合。

  • 当报告问题时,第一次出现的问题将被报告为ProblemDescriptor,其中包含有关问题的完整信息。

  • 任何后续出现的相同问题都将报告为ProblemAggregationDescriptor。该描述符将在构建结束时到达并包含问题发生的次数。

  • 如果对于任何存储桶(即类别和标签配对),收集的出现次数大于 10.000,则将立即发送,而不是在构建结束时发送。