Gradle教程——(四)构建脚本概要

——《实战Gradle》中文版笔记

本篇将探索Gradle构建块,也就是project和task,以及它们是如何映射到Gradle的API中类上面的。这些类通过方法暴露了很多属性,从而帮助控制构建。你也会学到如何通过属性来控制构建的行为,以及结构化构建逻辑的好处等。

1、构建块

每个Gradle构建都包含三个基本构建块:project、task和property。每个构建块包含至少一个project,进而又包含一个或多个task。project和task暴露的属性可以用来控制构建。
在这里插入图片描述
Gradle使用领域驱动设计(DDD)的原理为其自己的领域构建软件建模。因此,在Gradle API中有相应的类来表示project和task。

1.1 项目

在Gradle术语中,一个项目(project)代表一个正在构建的组件(比如,一个JAR文件),或一个想要完成的目标,如部署应用程序。Gradle的build.gradle文件相当于Maven的pom.xml。当构建进程启动后,Gradle基于build.gradle中的配置实例化org.gradle.api.Project类,并且能够通过project变量使其隐式可用。
在这里插入图片描述
一个project可以创建新的task,添加依赖关系和配置,并应用插件和其他的构建脚本。它的许多属性,如name和description,可通过getter和setter方法访问。

API是最有效地使用Gradle的关键。

在构建中Project实例,让你可以通过代码来访问Gradle的所有特性,比如task的创建和依赖管理。当访问属性和方法时,不需要使用project变量——它会假设你是指Project实例。

1.2 任务

task的一些重要功能:任务动作(task action)和任务依赖(task dependency)。任务动作定义了一个任务执行时最小的工作单元。很多时候,运行一个task之前需要运行另一个task,尤其是当task的运行需要另一个task的输出作为输入来完成自己的行动时更是如此。Gradle task的API表示,org.gradle.api.Task接口,如下:
在这里插入图片描述

1.3 属性

每个Project和Task实例都提供了可以通过getter和setter方法访问的属性。一个属性可能是一个任务的描述或项目的版本。通常,你需要定义自己的属性。比如,你可能想要声明一个变量,该变量引用了在同一个构建脚本中多次使用的一个文件。Gradle允许用户通过扩展属性自定义一些变量。

1. 扩展属性
Gradle的很多领域模型类提供了特别的属性支持。在内部,这些属性以键值对的形式存储。为了添加属性,你需要使用ext命名空间。下面的代码片段演示了以不同的方式添加、读取和修改一个属性:
在这里插入图片描述
2. Gradle属性
Gradle属性可以通过gradle.properties文件中声明直接添加到项目中,这个文件位于<USER_HOME>/.gradle目录或项目的根目录下。这些属性可以通过项目实例访问。即使你有多个项目,每个用户也只能有一个Gradle属性文件在<USER_HOME>/.gradle目录下。这是目前Gradle对它的限制。在这个属性文件中生命的属性对所有的项目可用。例如,在gradle.properties文件中声明:
在这里插入图片描述
3. 声明属性的其他方式
在这里插入图片描述

2、使用task

默认情况下,每个新创建的task都是org.gradle.api.DefaultTask类型的,标准的org.gradle.api.Task实现。DefaultTask里的所有属性都是private的。这意味着它们只能通过public的getter和setter方法来访问。幸运的是,Gradle提供了一些语法糖,可以直接通过属性名来使用属性。在底层,Groovy会为你调用这些方法。

2.1 项目版本管理

通常,一些特性会被分组发布。为了识别每一次发布,应该为可交付软件添加一个唯一的版本号。

你可以轻松地使用类的属性来设置、检索和修改编号方案的特定部分。甚至,通过外部化版本信息来持久化数据存储,比如一个文件或数据库,可以避免通过修改构建脚本本身来改变项目的版本。下图展示了构建脚本之间的交互,属性文件中包含了版本信息和数据表示类:
在这里插入图片描述
如果你想要自动化项目生命周期,那么以编程方式控制版本管理方案将变得很有必要。举一个例子:你的代码已经通过了所有的测试并且准备组装发布。项目的当前版本是1.2-SNAPSHOT。在构建最后的WAR文件之前,你要把它编程一个发布版本1.2,并且自动部署到生成服务器上。这些步骤都可以通过创建task的方式进行建模:一个用于修改项目版本,一个用于部署WAR文件。

2.2 声明task动作

动作(action)就是在task中合适的地方放置构建逻辑。Task接口提供了两个相关的方法来声明task动作:doFirst(Closure)和doLast(Closure)。当task被执行的时候,动作逻辑被定义为闭包参数被依次执行。
在这里插入图片描述

给现有task添加动作

在task创建后,你可以根据需要添加很多动作。在内部,每个task都把持了一个动作列表。在运行时,它们按顺序执行。
在这里插入图片描述
如上所示,我们可以给现有的task添加一些动作。这在你想要为不是自己编写的task执行自定义逻辑时非常有用。比如,为Java插件的compileJava task添加一个doFirst动作来检查项目中至少包含一个Java源文件。

2.3 访问DefaultTask属性

Gradle提供了一个基于SLF4J日志库的logger实现。除了实现常规范围的日志级别(DEBUG、ERROR、INFO、TRACE、WARN)之外,Gradle还增加了一些额外的日志级别。通过Task的方法可以直接访问logger实例。打印OUIET日志级别的版本号:

task printVersion {
	doLast{
		logger.quiet "Version: $version"
	}
}

还有两个属性:group和description,它们都是task文档的一部分。
description属性用于描述任务的作用,而group属性则用于定义task的逻辑。在创建task的时候,为这两个属性设置值作为参数:

task printVersion(group: 'versioning',description: 'Prints project version.'){
	doLast{
		logger.quiet "Version: $version"
	}
}

或者,也可以通过setter方法来设置属性:

task printVersion{
	group = 'versioning'
	description = 'Prints project version'
	doLast{
		logger.quiet "Version: $version"
	}
}

在这里插入图片描述
尽管设置task的描述和分组是可选的,但是为所有的task指定值是一个好主意。这会帮助最终用户比较容易地去识别task的功能。

2.4 定义task依赖

dependsOn方法允许声明依赖一个或多个task。你已经看到Java插件冲分利用了这个概念,通过创建task依赖关系图来建模完整的task声明周期,如build task。如下:
在这里插入图片描述
在这里插入图片描述

task依赖的执行顺序

理解gradle并不能保证task依赖的执行顺序是很重要的。dependsOn方法只是定义了所依赖的task需要先执行。Gradle的思想是声明在一个给定的task执行之前什么该被执行,而没有定义它该如何执行。

在Gradle中,执行顺序是由task的输入/输出规范自动确定的。这种构建设计有很多好处。一方面,你不需要知道整个task依赖链上的关系是否发生改变,这样可以提高代码的可维护性和避免潜在的破坏。另一方面,因为构建没有严格的执行顺序,也就是支持task的并发执行,这样可以极大地节约构建执行时间。

2.5 终结器 task

在实践中,你会发现所依赖的task执行后需要清理某种资源。一个典型的例子就是Web容器需要对已经部署的应用程序运行集成测试。针对这种情景Gradle提供了终结器task(finalizer task),即使终结器task失败了,Gradle的task也会按预期运行。如下:
在这里插入图片描述

2.6 添加任意代码

在实践中,你可以在Groovy脚本或类中以习惯的方式来编写类和方法了。在Java中,遵循bean惯例的类被称为POJO。根据定义,它们通过getter和setter方法来暴露属性。Groovy也有等效的POJO,即POGO,只是需要声明属性,而不需要设置访问权限修饰符。它们的getter和setter方法本质上是在生产自己码时自动添加的,因此在运行时它们可以直接被使用。如下:
在这里插入图片描述

2.7 理解task配置

在开始编代码之前,你需要创建一个名为version.properties的属性文件,并且为每一个版本的类别如主版本和此版本设置不同的属性。下面的键值对表示最初的版本0.1-SNAPSHOT:
在这里插入图片描述

添加task配置块

以下声明了一个名为loadVersion的task,用于从属性文件中读取版本类别,并将新创建的ProjectVersion实例赋值给项目的版本属性。
注:没有定义动作(doFirst等),则称为task配置。
在这里插入图片描述
在这里插入图片描述

Gradle构建生命周期阶段

无论什么时候执行Gradle构建,都会运行三个不同的生命周期阶段:初始化、配置和执行。如下,可视化了构建阶段的运行顺序和它们执行的代码:
在这里插入图片描述

  • 初始化阶段:Gradle为项目创建了一个Project实例,在给定的构建脚本中只定义了一个项目。
    在多项目构建中,这个构建阶段变得更加重要。根据你正在执行的项目,Gradle找出哪些项目依赖需要参与构建中。注意,在这个构建阶段当前已有的构建脚本代码都不会被执行。

  • 配置阶段:Gradle构造了一个模型来表示任务,并参与到构建中来。增量式构建特性决定了模型中的task是否需要被运行。这个阶段非常适合于为项目或指定task设置所需的配置。
    在这里插入图片描述

  • 执行阶段:所有的task都应该以正确的顺序执行。执行顺序是由它们依赖决定的。如果任务被认为没有修改过,将被跳过。比如,如果task B依赖于task A,那么当在命令行运行gradle B时执行顺序将是A->B

Gradle的增量式构建特性紧紧地与生命周期相结合。

2.8 声明task的inputs和outputs

Gradle通过比较两个构建task的inputs和outputs来决定task是否是最新的,如图。自从最后一个task执行以来,如果inputs和outpus没有发生变化,则认为task是最新的。。因此,只有当inputs和outpus不同时,task才运行;否则将跳过。
在这里插入图片描述
输入可以是一个目录、一个或多个文件,或者是一个任意属性。一个task的输出是通过一个目录或1—n个文件来定义的。inputs和outpus在DefaultTask类中被定义为属性或者有一个直接类表示,如图:
在这里插入图片描述
若你想创建一个task,为产品发布准备项目的可交付版本。为此,你想将项目版本从SNAPSHOT改变为release。下面的清单定义了一个新的task,将Boolean值true赋值给版本属性release。这个task也将版本变化传递到属性文件中。
在这里插入图片描述
在这里插入图片描述
makeReleaseVersion task可能是另一个生命周期task的一部分,用来将WAR文件部署到生成服务器上。注意,虽然我们将项目版本标记为产品发布版本,但Gradle并不知道。为了解决这一问题,需要声明它的inputs和outpus,如下所述。
在这里插入图片描述
在这里插入图片描述

2.9 编写和使用自定义task

自定义task包含两个组件:自定义的task类,封装了逻辑行为,也被称作任务类型,以及真实的task,提供了用于配置行为的task类所暴露的属性值。Gradle把这些task称为增强的task。

可维护性是编写自定义task类的优势之一。与简单的task相比,增强的task的另一个优势是可重用性。自定义task所暴露的属性可以在构建脚本中进行单独设置。

编写自定义的task类

Gradle为构建脚本中的每个简单的task都创建了一个DefaultTask类型的实例。当创建一个自定义task时,需要创建一个继承DefaultTask的类。
在这里插入图片描述

通过注解表示输入和输出

task的输入和输出注解为你的实现添加了语义糖。它们不仅与TaskInputs和TaskOutpus方法有相同的效果,而且它们还能够充当自动文档。

在自定义的task类中,你可以使用@Input注解声明输入属性release,使用@OutputFile注解定义输出文件。为属性添加输入和输出注解并不是唯一的选择,你也可以为getter方法添加注解。
在这里插入图片描述

使用自定义task

通过创建一个动作方法和暴露它的配置属性,你实现了一个自定义的task类。在构建脚本中,需要创建一个ReleaseVersionTask类型的task,并且通过为它的属性赋值来设置输入和输出。如下所示,认为这是创建一个特定类的新实例,并且在构造器中为它的属性设置值。
在这里插入图片描述
增强的makeReleaseVersion task与简单的task的运行结果表现完全一致。与简单的task实现相比,增强的task的一个巨大优势在于所暴露的属性可以被单独赋值。

应用自定义task的可重用性

在这里插入图片描述
在这里插入图片描述

2.10 Gradle的内置task类型

在将代码部署到产品环境中之前,你需要创建一个发布包。把它作为将来项目部署失败时回退的可交付版本。发布包是一个ZIP文件,包括了Web应用程序存档、所有的源文件和版本属性文件。创建发布包后,文件被赋值到备份服务器上。备份服务器可以通过一个挂载的共享驱动器来访问或者通过FTP传输文件。为了不让这个实例太过复杂,所以只需要将其复制到build/backup子目录中就行。如下,展示了你想要执行的task的顺序:
在这里插入图片描述

使用task类型

Gradle的内置task类型都是DefaultTask的派生类。因此,它们可以被构建脚本中的增强的task使用。Gradle提供了广泛的task类型,但在这个例子中只是用了两个。下面展示了在产品发布过程中用到的task类型Zip和Copy。可以再DSL指南中找到完整的task参考。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
你使用的task类型所提供的配置选项远多于这个实例中所显示的。同样的,想了解全部可用的配置选项,请参考DSL参考手册或Javadocs文档。

task依赖推断

清单中的两个task之间的依赖关系是通过dependsOn方法显式声明的。然而,一些task并不直接依赖其他task(比如,createDistribution对于war)。通过使用一个task的输出作为另一个task的输入,Gradle就可以在事先执行前就推断出依赖关系了。因此,所依赖的task会自动运行。如下为完整的task执行图:
在这里插入图片描述
运行构建后,可以在build/distributions目录下找到所生成的ZIP文件,这个目录是用于归档task的默认输出目录。通过设置destinationDir属性可以很容易地指定一个不同的发布包输出目录。下面的目录树显示了构建所生成的相关工件:
在这里插入图片描述
增量式构建支持内置的task类型。在不改变任何源文件的情况下,连续多次运行的task会被标记为最新的。

2.11 task规则

task规则命名模式

Gradle引入了task规则的概念,根据task名称模式执行特定的逻辑。该模式由两部分组成:task名称的静态部分和一个占位符。它们联合起来就组成了一个动态的task名称。如果你想将task规则应用于前面的例子中,那么命名模式看起来应该是这样的:incrementVersion。这个命令行上执行这个task,将以“驼峰命名法”(camel-case)指定类别占位符(比如,incrementMajorVersion或incrementMinorVersion)
在这里插入图片描述

声明task规则

为了给项目添加task规则,首先需要获得对TaskContainer的引用。一旦有了这个引用,就可以调用addRule(String,Closure)方法了。第一个参数提供了描述信息(比如,task命名模式),第二个参数声明了要执行的闭包来应用规则。
在这里插入图片描述
task规则不能像处理任何其他的简单的task或增强的task一样被独立分组。task规则即使通过插件声明了,它也将永远显示在Rules组下。

2.12 在buildSrc目录下构建代码

Gradle在buildSrc目录下使源文件结构标准化。Java代码需要放在src/main/java目录下,Groovy代码则放在src/mian/groovy目录下。位于这些目录下的代码都会被自动编译,并且加入到Gradle构建脚本的classpath中。buildSrc目录是组织代码的最佳方式。因为要处理这些类,所以也可以把它们放到指定的报下。可以让它们成为com.manning.gia包的一部分。下面的目录结构显示了Groovy类的存放位置:
在这里插入图片描述
提取类放到源文件中需要一些额外的工作。在构建脚本中定义一个类和一个独立的源文件的区别在于你需要从Gradle API导入类。

相应地,构建脚本需要从buildSrc导入编译过的类(比如,com.manning.gia.ReleaseVersionTask)。下面的控制台输出显示了在命令行调用这个task之前运行编译task:
在这里插入图片描述

buildSrc目录被视为Gradle项目的指定路径。由于没有编写任何单元测试,所以跳过了编译和执行task。

3、挂接到构建生命周期

有时候当一个特定的生命周期事件发生时你可能想要执行代码。一个生命周期事件可能发生在某个构建阶段之前、期间或者之后。在执行阶段之后发生的生命周期事件是构建的完成。

有两种方式可以编写回调生命周期事件:在闭包中,或者是通过Gradle API所提供的监听器接口实现。Gradle不会引导你采用哪种方式去监听生命周期事件,这完全取决于你的选择。采用监听器实现最大的优势在于你处理的类通过编写单元测试是完全可测试的。下面为你提供一个有用的生命周期狗子(hook)的想法,如下:
在这里插入图片描述
许多生命周期回调方法被定义在Project和Gradle几口中。Gradle的javadocs为你的用例提供了合适的回调方法。
在这里插入图片描述

Task执行图的内部展示

在配置时,Gradle决定了在执行阶段要运行的task的顺序。你最有可能通过声明dependsOn关系或者通过利用隐式task依赖干预机制来创建这些节点之间的连接。下图展示了早起发布过程建模的DAG表示:
在这里插入图片描述

3.1 挂接到task执行图

在这里插入图片描述
在这里插入图片描述

3.2 实现task执行图监听器

通过监听器挂接到构建生命周期只需要两个简单的步骤。首先,通过在构建脚本中编写一个类来实现特定的监听接口。其次,注册监听器实现。

用于监听task执行图时间的接口由TaskExecutionGraphListener接口所提供。
在这里插入图片描述
如果将监听器添加到构建脚本中,那么久不能直接访问Project实例了。但你可以冲分地使用Gradle的API。以下展示了如何通过调用release task上的getProject()方法来访问project。
在这里插入图片描述
并不局限于在构建脚本中注册生命周期监听器。在任何task执行之前,都可以应用生命周期逻辑来监听Gradle事件。

3.3 初始化构建环境

假设你想要在构建完成后获得构建结果。当构建完成后,你想知道构建成功了还是失败了。你也希望能够确定执行了多少task。Gradle的核心插件之一,build-announcements插件,提供了一张将通告发送到本地通知系统如Snarl(windows)或Growl(Mac OS X)的方式。这个插件根据你的操作系统自动选择正确的通知系统。

你可以把这个插件独立地运用到每个项目上,但为什么不使用Gradle提供的强大机制呢?初始化脚本会在任何构建脚本逻辑解析执行之前运行。编写一个初始化脚本把插件应用到项目中,不需要人工干预。在<USER_HOME>/.gradle/init.d下创建初始化脚本,如下面的目录树所示:
在这里插入图片描述
Gradle会执行在init.d下以.gradle为扩展名的所有初始化脚本。因为想在任何构建脚本代码执行前使用插件,所以我们选择使用最合适的生命周期回调方法来处理这种情况,Gradle#projectsLoaded(Closure)。以下显示了如何将build-announcements插件应用到构建的根项目中:
在这里插入图片描述
请注意,一些生命周期事件只有在适当的位置声明才会发生。比如,如果将生命周期挂接闭包Gradle#projectsLoaded(Closure)声明在build.gradle文件中,那么将不会发生这个事件,因为项目创建发生在初始化阶段。

4、总结

每个Gradle构建脚本都包含两个基本的构建块:一个或多个项目(project)和任务(task)。这两个元素都与Gradle的API密切相关,并且它都有一个直接类表示。在运行时,Gradle会通过构建定义创建一个模型,将它存储在内存中,并且可以通过方法访问。你了解到属性是控制构建动作的一种方式。项目暴露了标准的属性。初次之外,你也可以再Gradle的领域模型对象上定义额外的属性(比如,在项目和任务级别)来声明任意的用户数据。

对于初学者而言,理解构建生命周期及其阶段执行顺序是至关重要的。Gradle明确区分了task动作和task配置。task动作,通过闭包doFirst和doLast或者定义,在执行阶段运行。那些不在task动作中定义的代码被认为是配置,因此在配置阶段提前执行。

对于实现非功能性需求:构建执行性能、代码可维护性和可重用性上。通过声明输入和输出数据,对已有的task实现添加增量式构建支持。在初始的和后续的构建task之间如果输入数据没有发生变化,那么执行将被跳过。实现增量式构建支持其实非常简单和容易。如果做的正确,它可以极大地减少构建的执行时间。复制的构建逻辑最好在自定义的task类中实现结构化,这可以给你带来面向对象编程的所有好处。

访问Gradle的内部并不局限于模型。你可以注册构建生命周期钩子(hook),当触发目标事件时来执行代码。

参考书籍:《实战Gradle》中文版,李建等人译

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

辰阳星宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值