vlambda博客
学习文章列表

读书笔记《gradle-essentials》组织构建逻辑和插件

Chapter 8. Organizing Build Logic and Plugins

插件是 Gradle 的主要构建块之一,直到现在我们还没有进行太多讨论。您已经看到了不同的标准插件,例如 Java、Eclipse、Scala 等,它们带有一组已定义的任务。开发人员只需包含插件、配置所需任务并利用这些功能。在本章中,我们将概述什么是插件,如何将任务分组到插件中,如何将插件逻辑从构建文件中提取到 buildSrc,以及如何创建一个独立的插件。

Extracting build logic to buildSrc


插件不过是一组具有特定顺序和默认配置的任务,它们被创建以提供特定的功能。例如,java 插件包含提供构建 Java 项目功能的任务,scala 插件包含构建 Scala 项目的任务,等等。虽然 Gradle 提供了许多标准插件,但您也可以找到不同的第三方插件来满足项目的需要。当您无法使用现有插件找到所需的功能并希望为您的自定义需求创建一个新功能时,可能总会出现这种情况。我们将看到开发人员创建插件并使用它的不同方式。

用户可以创建的第一个插件位于构建文件本身中。下面是一个插件的示例代码,开发者可以在 build.gradle 中编写并使用它:

apply plugin: CustomPlugin

class CustomPlugin implements Plugin<Project> {
  void apply(Project project) {
    project.task('task1') << {
      println "Sample task1 in custom plugin"
      
    }
    project.task('task2') << {
      println "Sample task2 in custom plugin"
      
    }    
  }
}
task2.dependsOn task1

在这里,我们 在构建文件本身中创建了一个插件。这就是 Gradle 脚本的美妙之处。您还可以在 Gradle 文件中编写一个类。要创建自定义插件,您需要创建一个实现 Plugin 接口的 Groovy 类。您甚至可以使用 Java 或任何其他 JVN 语言编写插件。由于 Gradle 构建脚本是用 Groovy 编写的,因此我们使用 Groovy 来编写插件实现。您想要实现的所有任务,您需要在 apply 方法中定义。我们定义了两个任务,task1task2。此外,我们将生命周期定义为两个任务之间的关系。如果开发者调用task1,则只会执行task1。如果你执行 task2task1task2 都会被执行。尝试执行以下命令:

> gradle task2
:task1
Sample task1 in customer plugin
:task2
Sample task2 in custom plugin

BUILD SUCCESSFUL
Total time: 2.206 secs

Tip

要在构建文件中使用插件,您始终需要使用 apply plugin:<plugin name/plugin 类(如果插件在同一脚本中或在 < code class="literal">buildSrc 目录)。

这是开发人员可以定义自定义插件的简单方法之一。但是,如果我们遵循设计原则,将构建逻辑和自定义逻辑混合到同一个文件中并不是一个好习惯。维护代码会很困难,也可能会增加维护工作量。我们将始终建议您编写与构建逻辑分开的插件代码。为了实现这一点,Gradle 提供了两种不同的方法,如下所示:

  • 提取插件代码到 buildSrc

  • 独立插件

要将插件代码提取到 buildSrc,Gradle 建议您在项目目录中创建一个 buildSrc 目录,并将插件代码保存在那里。以下是相同的文件夹层次结构:

C:./Gradle/Chapter8/CustomPlugin1
│   build.gradle
│
└───buildSrc
    └───src
        └───main
            └───groovy
                └───ch8
                        CustomPlugin.groovy

在这里,我们创建了一个单独的buildSrc目录;在其中,我们将插件代码保存在 CustomPlugin.groovy 文件中。将前面的 Groovy 类从 build.gradle 文件移动到此文件中。在顶部包含包装声明。您还需要导入 org.gradle.api.*。您的 CustomPlugin.groovy 文件将如下所示:

package ch8
import org.gradle.api.*
class CustomPlugin implements Plugin<Project> {
// Plugin functionality here
}

build.gradle 文件内容如下:

import ch8.CustomPlugin
apply plugin: CustomPlugin

您只需要导入包并添加 apply plugin 语句。在运行时编译类和将类包含到类路径中的所有后台工作都将由 Gradle 执行。现在,尝试执行以下命令:

> gradle task1
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
:task1
Sample task1 in custom plugin

BUILD SUCCESSFUL

Total time: 3.374 secs

在这里,您 可以看到 Gradle 为您的自定义插件代码执行了编译和构建任务,现在您只需执行属于您的一部分的任务自定义插件。 Gradle 还允许您在构建文件中配置自定义插件。您可以设置任务之间的依赖关系或在构建文件本身中为任务添加更多功能,而不是一次又一次地更新插件代码。如果您想为 task1 添加更多功能,可以按如下方式进行:

task1.doLast {
println "Added more functionality to task1"
}
task2.dependsOn task1

现在,如果您尝试执行 task1,它将附加前面的语句。

这样就可以将build.gradle文件中的构建逻辑分离到buildSrc目录下的一个单独的类文件中。如果您有一个多项目构建,则根项目 buildSrc 中定义的插件可以被所有子项目的构建文件重用。您不需要为每个子项目定义单独的插件。这个过程仍然有一个限制。它不允许您将此插件用于其他项目。由于它与当前项目紧密耦合,因此您只能将此插件用于同一个项目或根项目中定义的子项目。为了克服这个问题,您可以将插件代码插入到独立插件中,并将其打包到 JAR 文件中,您可以将其发布到存储库,以便任何项目都可以重用它。在下一节中,我们将讨论独立插件。

The first plugin


为了使 插件可重用于所有其他项目,Gradle 允许您分离插件代码并将其打包到 JAR 文件中。您可以在要重用此功能的任何项目中包含此 JAR 文件。您可以使用 Java 或 Groovy 创建独立项目。我们将继续使用 Groovy。您可以使用任何编辑器(Eclipse、NetBeans 或 Idea)来创建插件。由于我们的主要目的是向您展示如何创建一个独立的插件,因此我们不会详细介绍编辑器。我们将使用一个简单的文本编辑器。要继续使用独立插件,请将上述 buildSrc 代码分离到一个独立目录中。您可以将其命名为 CustomPlugin。所以,目录结构如下:

C:/Gradle/Chapter8/CustomPlugin.
│   build.gradle
│
└───src
    └───main
        └───groovy
            └───ch8
                    CustomPlugin.groovy

您可能 惊讶地知道我们为什么要在此处创建 build.gradle 文件。有了这个build.gradle,我们将插件代码打包成一个jar文件。现在,问题出现了,您将如何将此插件包含到其他构建文件中。您需要此插件的 插件 ID 。要将插件 ID 添加到您的插件,您需要在 src/main/resources/META-INF/gradle-plugins 目录中创建一个属性文件。属性文件的名称将是您的插件 ID。在这里,我们将在上述目录中添加 customplugin.properties 文件。该文件的内容如下:

implementation-class=ch8.CustomPlugin

Your build file content would be.

apply plugin: 'groovy'
version = '1.0'
dependencies {
  compile gradleApi()
  compile localGroovy()
}

要编译 Groovy 代码,您需要在编译配置中包含上述两条语句。由于我们在这里使用的是普通的 Groovy 类,因此我们没有添加任何其他依赖 JAR。如果您的插件代码依赖于任何其他第三方 JAR,您可以将它们包含在依赖项中并配置相应的存储库。

现在,我们将按如下方式构建插件:

> gradle clean build
:clean
:compileJava UP-TO-DATE
:compileGroovy
:processResources
:classes
:jar
:assemble
:compileTestJava UP-TO-DATE
:compileTestGroovy UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:test UP-TO-DATE
:check UP-TO-DATE
:build

BUILD SUCCESSFUL
Total time: 4.671 secs

您可以在 <project>/build/libs/CustomPlugin-1.0.jar 中找到 JAR 文件。

您可以将此插件 JAR 发布到您组织的内部存储库,以便任何其他项目可以直接从那里下载并使用它。现在,我们将创建另一个项目并将这个插件 JAR 引用到该项目中。

创建一个新目录 SampleProject,并将 build.gradle 添加到项目中。现在,问题出现了,您的 build.gradle 将如何引用 SamplePlugin。为此,您需要在 buildscript 闭包 中提及 SamplePlugin JAR 的位置,并在 中添加对该 JAR 的依赖class="literal">依赖 闭包。

build.gradle 的内容如下:

buildscript {
repositories {
  flatDir {dirs "../CustomPlugin/build/libs/"}
}
dependencies {
classpath group: 'ch8', name: 'CustomPlugin',version: '1.0'
}
}
apply plugin: 'customplugin'

在这里,我们使用 平面文件存储库,因此,使用 flatDir 配置引用自定义插件 JAR。我们建议您使用组织的本地存储库;因此,组织的任何项目都可以集中访问它。在依赖项闭包中,我们指的是 CustomPlugin JAR 文件。这是使用任何插件的先决条件。最后,我们添加 apply plugin 语句并在单引号中提及插件名称。

Tip

插件名称是您在 src/main/resources/META-INF/gradle-plugins 目录中创建的属性文件的名称。

现在,您可以使用以下命令执行构建文件:

> gradle task1

:task1
Sample task1 in custom plugin

BUILD SUCCESSFUL

Total time: 2.497 secs

Configuring plugins


到目前为止,我们已经了解了如何创建一个独立的自定义插件并将其包含在另一个项目构建文件中。 Gradle 还允许您配置插件属性并根据项目的需要自定义它们。您已经了解了如何在 java 插件中自定义源代码位置和测试代码位置。我们将看到一个示例,说明如何在自定义插件中复制相同的行为。要定义插件属性,您需要创建一个额外的 extension 类并将该类注册到您的 plugin 类中。假设我们想将 location 属性添加到插件中。创建 CustomPluginExtension.groovy 类,如下所示:

package ch8
class CustomPluginExtension {
def location = "/plugin/defaultlocation"
}

现在,将这个类注册到你的 plugin 类:

class CustomPlugin implements Plugin<Project> {
  void apply(Project project) {
    def extension = project.extensions.create("customExt",CustomPluginExtension)

project.task('task1') << {
      println "Sample task1 in custom plugin"
      println "location is "+project.customExt.location
  }
}
}

现在,再次构建插件,使您的更改成为最新插件 JAR 文件的一部分,然后尝试执行 SampleProject build.gradle代码>:

> gradle task1
:task1
Sample task1 in custom plugin
location is /plugin/defaultlocation

BUILD SUCCESSFUL

Total time: 2.79 secs

在这里,您可以查看命令行输出的默认值。如果您想将此字段更改为其他值,请将 customExt 闭包 添加到您的 SampleProject build.gradle 文件,为该位置配置了不同的值:

buildscript {
repositories {
  flatDir {dirs "../CustomPlugin/build/libs/"}
}
dependencies {
  classpath group: 'ch8', name: 'CustomPlugin',version: '1.0'
}
}
apply plugin: 'customplugin'

customExt {
  location="/plugin/newlocation"
}

现在尝试再次执行 task1

> gradle task1
:task1
Sample task1 in custom plugin
location is /plugin/newlocation

BUILD SUCCESSFUL
Total time: 5.794 secs

在这里,您可以观察位置属性的更新值。

Summary


在本章中,我们讨论了 Gradle 的主要构建块之一,插件。插件有助于组织和模块化功能,还有助于打包一组相关的任务和配置。我们还讨论了创建自定义插件的不同方法,从在构建文件本身中编写插件代码到创建独立的插件 JAR 文件并在不同的项目中重用它。在上一节中,我们还介绍了如何配置插件的现有属性并根据项目要求对其进行自定义。

在下一章结束本书之前,我们将讨论如何在 Gradle 的帮助下构建 Groovy 和 Scala 项目。此外,由于这是一个移动时代,所有传统软件或 Web 应用程序现在都转向应用程序,我们还将讨论构建 Android 项目。