上一章当中的makeReleaseVersion中的动作逻辑相当简单。当前代码的可维护性显然不是问题。然而,随着项目的增长,原本简单的task需要增加更多的逻辑,此时就有了对类和方法中的代码的结构化需求。应该使用像在常规的产品源代码中习惯使用的编码管理。Gradle不建议使用某种特殊方法编写task。完全可以根据熟悉程度,选择熟悉的编程语法,比如Java、Groovy等基于JVM的语言。
自定义task包含两个组件:
- 自定义的task类,封装了逻辑思维,也被称为任务类型,
- 真实的task,提供了用于配置行为的task类所暴露的属性值。Gradle把这些task称为增强的task.
可维护性和可重用性是自定义task类的优势
编写自定义的task类
Gradle为构建脚本中的每个简单的task都创建了一个DefaultTask类型的实例,当创建一个自定义的task时,需要做的是,创建一个继承DefaultTask的类。比如以下为使用Groovy编写的自定义类ReleaseVersionTask
。
class ReleaseVersionTask extends DefaultTask {
@Input Boolean release
@OutputFile File destFile
ReleaseVersionTask() {
group = 'versioning'
description = 'Makes project a release version.'
}
@TaskAction
void start() {
project.version.release = true
ant.propertyfile(file: destFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
在这个类当中,没有使用DefaultTask的属性来声明它的输入和输出,而是使用org.gradle.api.tasks包下的注解。它们不仅与TaskInputs和TaskOutputs方法由相同的效果。在自定义的task当中,使用@Input注解声明输入属性release,使用@outputFile注解定义输出文件。为属性添加输入和输出注解并不是唯一的选择,你也可以为getter方法添加注解。
task输入验证:@Input注解会在配置期间验证属性值,如果值为null,Gradle会抛出TaskValidationException异常。为了允许输入null值,可以添加@Optional注解。
使用自定义task
通过创建一个动作方法暴露它的配置属性,实现了一个自定义的task类。那么该如何使用它呢?在构建脚本当中,需要创建一个ReleaseVersionTask类型的task,并且通过为它的属性赋值来设置输入和输出,如下所示
task makeReleaseVersion(type: ReleaseVersionTask) {
release = version.release
destFile = versionFile
}
构建脚本build.gradle中其他代码如下所示,在上一章都已经讲解过
ext.versionFile = file('version.properties')
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
logger.quiet 'Reading the version file.'
if (!versionFile.exists()) {
throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
new ProjectVersion(versionProps.major.toInteger(), versionProps.minor.toInteger(), versionProps.release.toBoolean())
}
class ProjectVersion {
Integer major
Integer minor
Boolean release
ProjectVersion(Integer major, Integer minor) {
this.major = major
this.minor = minor
this.release = Boolean.FALSE
}
ProjectVersion(Integer major, Integer minor, Boolean release) {
this(major, minor)
this.release = release
}
@Override
String toString() {
"$major.$minor${release ? '' : '-SNAPSHOT'}"
}
}
以及同目录下的version.properties文件
major=0
minor=1
release=false
在控制台执行任务
而version.properties文件被修改了
任务起效了。
自定义task的可重用性
假设你想要在另一个项目当中使用自定义task,在那个项目中,需求是不同的。其版本POJO通过暴露不同的属性来表示版本管理方案,如下所示
class ProjectVersion {
Integer min
Integer maj
Boolean prodReady
@Override
String toString() {
"$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
}
}
而且此时项目所有者决定命名版本文件为project-version.properties
而不是version.properties
。如何让增强的task满足这些需求呢?其实你只需要给所暴露的属性赋予不同的值就可以了。自定义的task类可以灵活得处理需求的变化。
task makeReleaseVersion(type: ReleaseVersionTask) {
release = version.prodReady
destFile = new File('project-version.properties')
}
结果如下
以下为完整的代码
ext.versionFile = file('project-version.properties')
task loadVersion {
project.version = readVersion()
}
ProjectVersion readVersion() {
logger.quiet 'Reading the version file.'
if (!versionFile.exists()) {
throw new GradleException("Required version file does not exit: $versionFile.canonicalPath")
}
Properties versionProps = new Properties()
versionFile.withInputStream { stream ->
versionProps.load(stream)
}
new ProjectVersion(min: versionProps.major.toInteger(), maj: versionProps.minor.toInteger(), prodReady: versionProps.release.toBoolean())
}
task makeReleaseVersion(type: ReleaseVersionTask) {
release = version.prodReady
destFile = new File('project-version.properties')
}
class ReleaseVersionTask extends DefaultTask {
@Input Boolean release
@OutputFile File destFile
ReleaseVersionTask() {
group = 'versioning'
description = 'Makes project a release version.'
}
@TaskAction
void start() {
project.version.prodReady = true
ant.propertyfile(file: destFile) {
entry(key: 'release', type: 'string', operation: '=', value: 'true')
}
}
}
class ProjectVersion {
Integer min
Integer maj
Boolean prodReady
@Override
String toString() {
"$maj.$min${prodReady ? '' : '-SNAPSHOT'}"
}
}