Gradle开发手册-高级篇之自定义任务

Gradle开发手册-高级篇之自定义任务

在之前的章节中我们了解了在开发过程中Gradle的所有核心应用,包括单module项目的配置、多模块项目的创建等,本章涉及一些和构建脚本编程相关的内容--Task。

Gradle的任务基本是和命令一对一的,但官方又提供了类似的java plugin、Intellij gradle plugin这些插件又额外提供了一些高级功能(命令打包)。同时开发者也可以根据需要自定义用户任务。

  1. 基础篇:从概念以及广度上介绍下gradle的核心内容,并构建一个简单的java项目;
  2. 进阶篇:详细讲解Gradle的配置相关内容;
  3. 高级篇:讲述一些高级内容,比如多项目构建、自定义task等;
  4. 实战篇:构建几类常见的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)。原理图如下,

www.zeeklog.com - Gradle开发手册-高级篇之自定义任务

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:hello

3、 任务控制

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/目录下,双击执行即可:

www.zeeklog.com - Gradle开发手册-高级篇之自定义任务

三、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") }) }