Gradle开发手册-高级篇之自定义任务
在之前的章节中我们了解了在开发过程中Gradle的所有核心应用,包括单module项目的配置、多模块项目的创建等,本章涉及一些和构建脚本编程相关的内容--Task。
Gradle的任务基本是和命令一对一的,但官方又提供了类似的java plugin、Intellij gradle plugin这些插件又额外提供了一些高级功能(命令打包)。同时开发者也可以根据需要自定义用户任务。
- 基础篇:从概念以及广度上介绍下gradle的核心内容,并构建一个简单的java项目;
- 进阶篇:详细讲解Gradle的配置相关内容;
- 高级篇:讲述一些高级内容,比如多项目构建、自定义task等;
- 实战篇:构建几类常见的java应用,这些代码可以做为模板方便日后使用;
一、概述
1、Gradle的生命周期
说到任务不得不提一下Gradle的生命周期,简单来讲。Gradle 构建的生命周期主要分为三个阶段,Initialization,Configuration,Execution。
- Initialization:Gradle支持单个或多个工程的构建。在Initialization阶段,Gradle决定哪些工程将参与到当前构建过程,并为每一个这样的工程创建一个Project实例。一般情况下,参与构建的工程信息将在settings.gradle中定义。
- Configuration:在这一阶段,配置project的实例。所有工程的构建脚本都将被执行。Task,configuration和许多其他的对象将被创建和配置。
- Execution:在之前的configuration阶段,task的一个子集被创建并配置。这些子集来自于作为参数传入gradle命令的task名字,在execution阶段,这一子集将被依次执行。
2、Gradle任务设计原理
Gradle任务本质是依赖编程的,可以定义任务和任务之间的依赖。Gradle 保证这些任务按照它们的依赖顺序执行。构建脚本和插件配置此依赖关系图,此图显示了两个示例任务图:一个是抽象的,另一个是具体的。该图将任务之间的依赖关系表示为箭头。在每个项目中,任务形成一个有向无环图(DAG)。原理图如下,

3、任务执行结果
比如我们运行一个build任务,一般在控制台会输出如下内容,注意后第一行和第二行,其任务输出日志格式为【模块名:任务名 任务执行结果】,在最后会给出一个执行结果的统计信息。
> Task :lib:check UP-TO-DATE > Task :lib:build UP-TO-DATE BUILD SUCCESSFUL in 213ms 7 actionable tasks: 5 executed, 2 up-to-date- EXECUTED:任务执行了它的动作。
- UP-TO-DATE:任务的输出没有改变。
- FROM-CACHE:任务的输出可以从以前的执行中找到。
- SKIPPED:任务未执行其操作。
- NO-SOURCE:任务不需要执行它的动作。
二、任务详解
1、自定义任务
简单任务
任务的定义有两种方式,其中第二种是可以通过继承Gradle提供的API来扩展的,比如tasks.register<Copy>("copy")意思就是自定义了一个名为copy的任务,引用Copy-API提供的方法,任务体中的from和into都是Copy-API提供的工具方法。
- java-API:
- Kotlin-API:
tasks.register("hello") { description = "Copies the resource directory to the target directory." doLast { println("hello") } } tasks.register<Copy>("copy") { from(file("srcDir")) into(buildDir) }val classesDir = file("build/classes") classesDir.mkdirs() tasks.register<Delete>("clean") { delete("build") } tasks.register("compile") { dependsOn("clean") val classesDir = classesDir doLast { if (!classesDir.isDirectory) { println("The class directory does not exist. I can not operate") // do something } // do something } } > gradle -q compile The class directory does not exist. I can not operate添加任务参数
与配置创建后的可变属性相反Task,您可以将参数值传递给Task类的构造函数。为了将值传递给Task构造函数,您必须使用注释相关的构造函数@javax.inject.Inject。
abstract class CustomTask @Inject constructor( private val message: String, private val number: Int ) : DefaultTask() tasks.register<CustomTask>("myTask", "hello", 42)添加依赖任务
project("project-a") { tasks.register("taskX") { dependsOn(":project-b:taskY") doLast { println("taskX") } } }调用三方依赖jar包
这个功能需要用到buildscript标签。这个标签中的依赖是和项目的依赖相分离的。
import org.apache.commons.codec.binary.Base64 buildscript { repositories { mavenCentral() } dependencies { "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2") } } tasks.register("encode") { doLast { val encodedString = Base64().encode("hello world\n".toByteArray()) println(String(encodedString)) } }定义任务执行顺序
val taskX by tasks.registering { doLast { println("taskX") } } val taskY by tasks.registering { doLast { println("taskY") } } taskY { mustRunAfter(taskX) }跳过特定任务
val hello by tasks.registering { doLast { println("hello world") } } hello { val skipProvider = providers.gradleProperty("skipHello") onlyIf("there is no property skipHello") { !skipProvider.isPresent() } }2、定位任务
如果子模块大多,有时需要查看此任务属于哪个模块,例如,需要配置它们或将它们用于依赖项时。
println(tasks.named("hello").get().description) println(tasks.named<Copy>("copy").get().destinationDir) println(tasks.getByPath("hello").path) > Configure project :gradleDemo Copies the resource directory to the target directory. /Users/liudong/ideaWS/gradleLearn/gradleDemo/build :gradleDemo:hello3、 任务控制
tasks.withType()
也可以使用该tasks.withType()方法访问特定类型的任务。这使得能够轻松避免代码重复并减少冗余。
tasks.withType<Tar>().configureEach { enabled = false } tasks.register("test") { dependsOn(tasks.withType<Copy>()) }启用和禁用任务
val disableMe by tasks.registering { doLast { println("This should not be printed if the task is disabled.") } } disableMe { enabled = false }结束任务
val taskX by tasks.registering { doLast { println("taskX") } } val taskY by tasks.registering { doLast { println("taskY") } } taskX { finalizedBy(taskY) }4、执行任务
有两种方式可执行任务,第一种:命令行执行,下面代码是依赖的gradle-wrapper执行的,如果是原生其命令格式是 gradle :myTask。
//任务执行 $ ./gradlew :myTask //执行单个任务 $ ./gradlew myTask --exampleOption=exampleValue //执行任务同时传递exampleOption参数 $ ./gradlew my-subproject:myTask //在多项目中执行任务,执行my-subproject模块中的myTask任务 $ ./gradlew test //执行所有子项目中的test任务 //高级用法 $ ./gradlew test deploy //执行多个任务,一般用于执行依赖任务,比如build前要先clean $ ./gradlew dist --exclude-task test //执行dist任务,并跳过test任务 $ ./gradlew test --rerun-tasks //强制执行 $ ./gradlew test --continue //遇到错误时继续执行,这用于比如批量执行测试任务时,不至于一个任务有问题就全部中断如果在父目录执行子模块的命令,则需要指定模块名,如下:
./gradlew gradleDemo:hello第二种:在插件窗口执行,一般新定义任务时,会生成在others/目录下,双击执行即可:

三、API编程文件和目录操作
1、文件操作
复制单个文件
此示例模拟将生成的报告复制到一个目录中,该目录将被打包到一个存档中,例如 ZIP 或 TAR。
tasks.register<Copy>("copyReport") { from(layout.buildDirectory.file("reports/my-report.pdf")) into(layout.buildDirectory.dir("toArchive")) } tasks.register<Copy>("copyReport2") { from("$buildDir/reports/my-report.pdf") into("$buildDir/toArchive") } tasks.register<Copy>("copyReport3") { from(myReportTask.get().outputFile) into(archiveReportsTask.get().dirToArchive) }复制多个文件
tasks.register<Copy>("copyReportsForArchiving") { from(layout.buildDirectory.file("reports/my-report.pdf"), layout.projectDirectory.file("src/docs/manual.pdf")) into(layout.buildDirectory.dir("toArchive")) } tasks.register<Copy>("copyPdfReportsForArchiving") { from(layout.buildDirectory.dir("reports")) include("*.pdf") into(layout.buildDirectory.dir("toArchive")) }压缩文件(zip、tar 等)
tasks.register<Zip>("packageDistribution") { archiveFileName.set("my-distribution.zip") destinationDirectory.set(layout.buildDirectory.dir("dist")) from(layout.buildDirectory.dir("toArchive")) }解压文件(zip、tar 等)
tasks.register<Copy>("unpackFiles") { from(zipTree("src/resources/thirdPartyResources.zip")) into(layout.buildDirectory.dir("resources")) }过滤文件
tasks.register<Copy>("copyTaskWithPatterns") { from("src/main/webapp") into(layout.buildDirectory.dir("explodedWar")) include("**/*.html") include("**/*.jsp") exclude { details: FileTreeElement -> details.file.name.endsWith(".html") && details.file.readText().contains("DRAFT") } }2、目录操作
创建目录
tasks.register("ensureDirectory") { // Store target directory into a variable to avoid project reference in the configuration cache val directory = file("images") doLast { Files.createDirectories(directory.toPath()) } }重命名
tasks.register<Copy>("copyFromStaging") { from("src/main/webapp") into(layout.buildDirectory.dir("explodedWar")) rename("(.+)-staging(.+)", "$1$2") }删除文件和目录
tasks.register<Delete>("myClean") { delete(buildDir) } tasks.register<Delete>("cleanTempFiles") { delete(fileTree("src").matching { include("**/*.tmp") }) }