读书笔记《gradle-effective-implementations-guide-second-edition》编写自定义任务和插件
在 Gradle 中,我们可以在构建文件中编写一个简单的任务,在其中添加带有闭包的操作,或者我们可以配置包含在 Gradle 中的现有任务。编写我们自己的任务的过程很容易。有多种方法可以创建自定义任务,我们将在本章中介绍:
我们将看到如何在我们的构建文件中创建一个新的任务类并在我们的项目中使用它。
我们将讨论如何在单独的源文件中创建自定义任务。我们还将讨论如何使我们的任务在其他项目中可重用。
我们将讨论如何为 Gradle 编写插件。与编写自定义任务类似,我们将介绍编写插件的不同方法。我们还将看到如何发布我们的插件并讨论如何在新项目中使用它。
我们可以在 Groovy 中编写任务和插件,这与 Gradle API 配合得很好,但我们也可以使用其他语言,例如 Java 和 Scala。只要将代码编译成字节码就可以了。
当我们在构建中创建一个新任务并使用 type
属性指定一个任务时,我们实际上配置了一个现有任务。现有任务在 Gradle 中称为 增强任务。例如,Copy
任务类型是增强任务。我们将在构建文件中配置任务,但 Copy
任务的实现在单独的类文件中。将任务使用与任务实现分开是一种很好的做法。它提高了任务的可维护性和可重用性。在本节中,我们将创建自己的增强任务。
首先,让我们看看如何通过简单的操作添加一个新任务来创建一个任务以在我们的构建中显示当前的 Gradle 版本。我们之前在其他示例构建文件中已经看到了这些类型的任务。在以下示例构建中,我们将创建一个新的 info
任务:
当我们从命令行调用 info
任务时,我们将看到以下输出:
现在,我们将在我们的构建文件中创建一个新的任务定义,并使其成为一个增强的任务。我们将在构建文件中创建一个新类,该类扩展 org.gradle.api.DefaultTask
。我们将通过添加一个新方法来为该类编写一个实现。为了表明该方法是类的动作,我们将使用 @TaskAction
注解。
在我们定义了我们的任务类之后,我们可以在我们的构建文件中使用它。我们将向 tasks
项目容器添加一个任务,并使用 type
属性来引用我们的新任务类。
在以下示例构建文件中,我们有一个新的 InfoTask
任务类和使用这个新任务类的info
任务:
接下来,我们将使用 info
任务运行我们的构建文件。在以下输出中,我们可以看到我们当前的 Gradle 版本:
要自定义我们的简单任务,我们可以向我们的任务添加属性。当我们在构建文件中配置任务时,我们可以为这些属性分配值。
对于我们的示例任务,我们将首先添加一个 prefix
属性。当我们打印 Gradle 版本而不是 'Current Gradle version'
文本时使用此属性。我们给它一个默认值,所以当我们使用任务并且不设置属性值时,我们仍然会得到一个有意义的前缀。由于默认值,我们可以使用 @Optional
注释将我们的属性标记为可选。通过这种方式,我们记录了在使用任务时不需要配置我们的属性。
如果我们想在输出中添加另一个前缀,我们可以在构建文件中配置 info
任务。我们将 'Running Gradle'
值分配给我们的 InfoTask
的前缀属性:
现在,如果我们运行我们的构建文件,我们可以在输出中看到我们的新前缀值:
我们知道 Gradle 支持增量构建。这意味着 Gradle 可以检查任务是否对文件、目录和属性的输入或输出有任何依赖关系。如果自上次构建以来这些都没有更改,则不会执行任务。我们将讨论如何将注释与我们的任务属性一起使用,以确保我们的任务支持 Gradle 的增量构建功能。
我们已经了解了如何使用我们迄今为止创建的任务的 inputs
和 outputs
属性。为了指示我们新的增强任务的属性,即输入和输出属性,Gradle 的增量支持使用的属性,我们必须在类定义中添加某些注释。我们可以将注解分配给 field
属性或属性的 getter
方法。
在前一章中,我们创建了一个读取 XML 源文件并将内容转换为文本文件的任务。让我们为此功能创建一个新的增强任务。我们将对保存源 XML 文件值的属性使用 @InputFile
注释。 @OutputFile
注解分配给保存输出文件的属性,如下所示:
让我们在当前目录中创建一个名为 people.xml
的 XML 文件,代码如下:
现在,我们可以在构建文件中调用 convert
任务。我们可以在输出中看到文件已转换:
如果我们查看 convert-output.txt
文件的内容,我们将从源文件中看到以下值:
当我们第二次调用convert
任务时,可以看到Gradle的增量构建支持已经注意到输入输出文件没有变化,所以我们的任务到了日期:
下表显示了我们可以用来指示我们增强任务的输入和输出属性的注释:
注解名称 |
说明 |
|
指示该属性指定一个输入值。当此属性的值更改时,任务不再是最新的。 |
|
指示该属性是输入文件。将此用于引用 |
|
将属性标记为包含 |
|
指示该属性是输入目录。将此用于引用目录结构的 |
|
指示该属性是一个输出文件。将此用于引用 |
|
将属性标记为包含 |
|
指示该属性是一个输出目录。将此用于引用目录结构的 |
|
将属性标记为输出目录 将此属性用于引用 |
|
如果应用于前面的任何注释,我们会将其标记为可选。该值不必应用于此属性。 |
|
我们可以将此注解应用于 JavaBean 属性。检查 bean 对象是否有任何前面的注释。这样,我们可以使用任意对象作为输入或输出属性。 |
在上一节中,我们在同一个构建文件中定义并使用了我们自己的增强任务。现在我们将从构建文件中提取类定义并将其放在一个单独的文件中。我们将把文件放在 buildSrc
项目源目录中。
让我们将 InfoTask
移动到项目的 buildSrc
目录。我们将首先创建 buildSrc/src/main/groovy/sample
目录。我们将在这个目录下创建一个InfoTask.groovy
文件,代码如下:
请注意,我们必须为 Gradle API 的类添加 import
语句。这些导入由 Gradle 隐式添加到构建脚本中;但如果我们在构建脚本之外定义任务,我们必须自己添加import
语句。
在我们的项目构建文件中,我们只需要创建一个新的 info
任务 InfoTask
类型。请注意,我们必须使用包名来标识我们的 InfoTask
类或添加 import sample.InfoTask
语句:
如果我们运行构建,我们可以看到Gradle首先编译了InfoTask.groovy
源文件,如下:
事实上,执行buildSrc
目录下的build
任务。我们可以通过添加一个 build.gradle
文件来自定义 buildSrc
目录的构建。在这个文件中,我们可以配置任务、添加新任务,并且实际上可以在普通项目构建文件中做任何我们能做的事情。 buildSrc
目录甚至可以是多项目构建。
让我们在buildSrc
目录中添加一个新的build.gradle
文件。我们将向 build
任务添加一个简单的操作,它会打印 'Done building buildSrc'
值:
如果我们运行我们的项目构建,我们可以看到以下输出:
由于 buildSrc
目录与任何其他 Java/Groovy 项目相似,我们也可以为我们的任务创建测试。我们的目录结构与 Java/Groovy 项目的目录结构相同,我们还可以在 build.gradle
文件中定义额外的依赖项。
如果我们想在我们的测试类中访问 Project
对象,我们可以使用 org.gradle.testfixtures.ProjectBuilder
类.使用这个类,我们可以配置一个Project
对象并在我们的测试用例中使用它。在使用 build()
方法创建新的 项目
之前,我们可以选择配置名称、父级和项目目录目的。我们可以使用 Project
对象,例如,添加一个新的增强任务类型的新任务,看看有没有错误。 ProjectBuilder
用于低级测试。不执行实际任务。
在下面的JUnit测试中,我们将测试是否可以设置属性值。我们有第二个测试来检查 InfoTask
类型的任务是否被添加到项目的任务容器中:
在我们的 build.gradle
文件中 buildSrc
目录中,我们必须添加一个 Maven 存储库和对 JUnit 库的依赖,使用以下代码行:
我们的测试会自动执行,因为 test
任务是 buildSrc
目录构建过程的一部分。
为了使一个任务可被其他项目重用,我们必须有一种分配任务的方法。另外,其他想要使用该任务的项目必须能够找到我们的任务。我们将看到如何在存储库中发布我们的任务,以及其他项目如何在他们的项目中使用该任务。
我们已经看到了如何将构建文件中的任务实现放在 buildSrc
目录中。 buildSrc
目录类似于普通的 Gradle 构建项目,因此很容易为我们的任务创建一个独立的项目。我们只需要将 buildSrc
目录的内容复制到我们新创建的项目目录中。
让我们创建一个新的项目目录并复制 buildSrc
目录的内容。我们必须编辑我们独立项目的 build.gradle
文件。当 build.gradle
文件在 buildSrc
目录。现在我们有一个独立的项目,我们必须自己添加这些依赖项。
以下 build.gradle
文件包含构建工件并将其部署到本地分发目录所需的所有定义。我们还可以定义一个公司内部网存储库,以便其他项目可以在他们的项目中重用我们的 InfoTask
:
当我们调用uploadArchives
任务发布我们打包的 InfoTask
到 ../ lib
目录,我们将看到以下输出:
我们已经发布了我们的任务,其他项目可以在他们的构建中使用它。请记住,项目的 buildSrc
目录中的任何内容都会自动添加到构建的类路径中。但是,如果我们有一个已发布的带有任务的工件,这将不会自动发生。我们必须配置我们的构建并将工件添加为构建脚本的依赖项。
我们将在构建中使用 buildscript{}
脚本块来配置 Gradle 项目的类路径。要将我们发布的 InfoTask
包含在新项目中,我们必须将工件添加为我们构建的 classpath
配置依赖项。
我们将创建一个新目录并将以下 build.gradle
文件添加到该目录:
接下来,我们可以运行构建并在输出中看到 InfoTask
已执行:
Gradle 的一大特色是对插件的支持。插件可以包含任务、配置、属性、方法、概念等,以便为我们的项目添加额外的功能。例如,如果我们将 Java 插件应用到我们的项目中,我们可以立即调用编译、测试和构建任务。我们还有可以使用的新依赖配置和可以配置的额外属性。 Java 插件本身应用 Java 基础插件。 Java 基础插件没有引入任务,但它引入了源集的概念。这是创建我们自己的插件的好模式,其中一个基本插件引入了新概念,另一个插件从基本插件派生并添加了显式的构建逻辑类任务。
因此,插件是分发我们希望在项目之间共享的构建逻辑的好方法。我们可以编写自己的插件,给它一个明确的版本,然后发布它;例如,存储库。然后,其他项目可以通过简单地将插件应用到项目来重用该功能。我们可以创建自己的插件并在我们的项目中使用它们。我们将从在构建文件中定义插件开始。
我们可以在项目构建文件中创建自定义插件。与自定义任务类似,我们可以使用插件的逻辑添加新的类定义。我们必须实现 org.gradle.api.Plugin<T>
接口。该接口有一个 apply()
方法。当我们编写自己的插件时,我们必须重写这个方法。该方法接受一个对象作为参数。对象的类型与泛型的 T
类型相同。当我们为项目创建插件时,使用Project
类型。我们还可以为其他 Gradle 类型编写插件,例如任务。那么我们必须使用 Task
类型。
我们将创建一个简单的插件来打印 Gradle 版本。该插件向项目添加了一个新的 info
任务。以下示例构建文件定义了一个新的 InfoPlugin
插件。我们将覆盖 apply()
方法并向项目添加一个名为 info
的新任务。此任务打印 Gradle 版本。在构建文件的顶部,我们将使用 apply()
方法并通过名称引用插件 InfoPlugin
,这是插件的类名:
在命令行中,我们可以在运行 Gradle 时调用 info
任务。我们可以在以下输出中看到 Gradle 版本:
info
任务总是在 Gradle 版本之前打印相同的文本。我们可以重写任务并使文本可配置。 Gradle 项目有一个关联的 ExtensionContainer
对象。这个对象可以保存我们想要传递给插件的所有设置和属性。我们可以将 JavaBean 添加到 ExtensionContainer
,以便我们可以从构建文件中配置 bean 的属性。 JavaBean 是一个所谓的扩展对象。
在我们的示例构建文件中,我们将首先添加一个具有String
属性前缀的新 InfoPluginExtension
类。这是我们添加到 ExtensionContainer
的符合 JavaBean 的类。在 apply()
方法中,我们将使用 create()
方法>ExtensionContainer 添加 InfoPluginExtension
名称为 info
到项目中。在构建文件中,我们将使用 info
配置闭包配置 prefix
属性。我们也可以通过 info
扩展对象简单地引用 prefix
属性:
如果我们运行 info
任务,我们将在输出中看到我们配置的 prefix
:
我们已经定义了插件并在同一个构建文件中使用它。我们将看到如何从构建文件中提取插件代码并将其放在项目源目录中的单独源文件中。此外,我们将讨论如何测试插件。
当我们在构建文件中定义插件时,我们不能在其他项目中重用它。我们现在在同一个文件中有了插件的定义和使用。为了分离定义和使用,我们可以在 Gradle 项目的 buildSrc
目录下创建插件类。在 Gradle 多项目中,我们必须使用根项目的 buildSrc
目录。这意味着对于多项目构建,我们可以在多项目构建的其他项目中重用插件。
我们在编写自定义任务时已经讨论过 buildSrc
目录中的任何源代码都会自动编译并添加到项目的类路径中。首先,我们将创建 buildSrc/src/main/groovy/sample
目录。在此目录中,我们将创建一个 InfoPlugin.groovy
文件,其代码如下:
接下来,我们将在目录中创建 InfoPluginExtension.groovy
文件:
在项目根目录的构建文件中,我们将使用包和类名引用我们的插件:
当我们运行 info
任务时,我们将在输出中看到首先编译插件代码,然后是 info
任务执行:
buildSrc
目录中为项目执行的任务之一是测试任务。我们可以编写测试用例来测试插件代码,就像在任何其他项目中一样。我们在buildSrc
中添加一个build.gradle
文件,并定义JUnit 测试框架的依赖关系。在以下示例构建文件中,我们将为 JUnit 添加一个依赖项:
接下来,我们可以在 buildSrc/src/test/groovy/sample
目录下添加一个InfoPluginTest.groovy
测试用例:
我们使用 ProjectBuilder
类为Project
对象创建一个fixture。我们可以将插件应用到项目中,然后测试一下info
任务是否可用。 Project
对象无法执行项目中的任务;它仅适用于像这样的简单检查。
当我们从命令行调用 info
任务时,我们的测试类被编译并执行。如果测试失败,项目将中止;但如果所有测试都通过,项目将继续。
我们已经在项目源目录中定义了我们的插件,但是我们不能在另一个项目中重用它。我们将讨论如何使用独立项目分发我们的插件逻辑。此外,我们将了解如何在其他项目中使用该插件。
通过将插件代码放在 buildSrc
目录中,我们将插件的定义和使用分开。该插件仍然不能被其他项目使用。为了使插件可重用,我们将创建一个独立项目并使用插件代码创建一个工件并将工件发布到存储库。然后其他项目可以从存储库中获取插件,并使用项目中插件的构建逻辑。
我们已经在 buildSrc
目录中获得了插件的代码和测试代码(来自上一节)。我们可以将此代码复制到包含插件项目的新目录中。在这个新目录中,我们还必须创建一个 build.gradle
文件。在 buildSrc
目录中添加到项目的隐式依赖项和插件必须在独立项目中显式。
让我们在 plugin
目录中创建一个新的 Gradle 项目,同时创建具有以下内容的build.gradle
文件:
接下来,我们将创建 plugin/src/main/groovy/sample
和 plugin/src/test/groovy/sample
目录。我们将 InfoPlugin.groovy
和 InfoPluginExtension.groovy
文件复制到 src/ main/groovy/sample
目录和 InfoPluginTest.groovy
文件到 plugin/src/test/groovy/sample< /代码>目录。
到目前为止,我们拥有使用插件代码创建工件 JAR 文件的所有要素。工件被部署到本地 ../lib
目录。当然,我们可以定义任何 Maven 或 Ivy 存储库来部署插件工件。
为了确保 Gradle 可以找到插件,我们必须在 plugin/src/main/resources/META-INF 中提供一个
目录与我们的插件名称。属性文件有一个 properties
文件/gradle-pluginsimplementation-class
属性键和 Plugin
类的完整类名。
我们想将我们的插件命名为info
,所以在 plugin/src/main/resources/META-INF/gradle-plugins
目录,我们将使用以下代码创建
info.properties
文件:
我们已准备好使用插件创建工件并将其上传到我们的存储库。我们将调用 uploadArchives
任务并获得以下输出:
该插件现在位于存储库中。要使用该插件,我们必须创建一个新的 Gradle 项目。我们必须扩展这个新项目的类路径并将插件作为依赖项包含在内。我们使用 buildscript{}
脚本块,我们可以在其中配置存储库位置和 classpath
依赖项。对于我们的示例,我们将引用本地 ../lib
目录。在依赖项部分,我们将 classpath
配置设置为 InfoPlugin
工件。
以下示例构建文件包含定义:
我们的项目现在有来自插件的 info
任务。我们可以通过info
对象或配置闭包来配置插件扩展。
如果我们运行 info
任务,我们将得到以下输出:
在本章中,我们讨论了如何创建自己的增强任务。我们还看到了如何在构建文件中添加类定义并直接在构建中使用它。
如果我们将任务定义放在 Gradle 项目或多项目构建的 buildSrc
目录中,我们可以在 Gradle 构建的上下文中重用任务。此外,我们现在已经很好地分离了任务的定义和配置。
然后,我们讨论了如何将任务作为工件发布到存储库。其他项目可以使用 buildscript{}
脚本块将任务包含在其类路径中。然后,我们就可以在项目中配置和使用任务了。
在本章中,我们还讨论了如何编写自己的 Gradle 插件。我们已经看到了如何将插件类添加到我们的 Gradle 构建文件中。然后你学会了使用 buildSrc
目录并将插件的源代码放在那里。
最后,为了使插件在其他项目中真正可重用,我们将插件代码放在一个单独的项目中。然后将插件代码打包到 JAR 文件中并发布到存储库。然后其他项目可以在插件中定义依赖项并使用插件中的构建逻辑。
在下一章中,我们将看到如何在持续集成工具中使用 Gradle。