Gradle 进阶学习 之 Task

1、项目的生命周期

Gradle 项目的生命周期分为三大阶段: Initialization -> Configuration -> Execution. 每个阶段都有自己的职责。

想象一下,你正在制作一个大型的乐高城堡。在这个过程中,你需要做三件事:

  1. 初始化阶段:这就像是准备搭建乐高城堡的工具和材料。在Gradle中,这个阶段包括两个小步骤:
    • 执行Init Script:这就像是设置乐高城堡的搭建规则,比如决定使用哪些颜色的乐高块,或者设定一些通用的搭建技巧。例如: 配置内部的仓库信息(如公司的 maven 仓库信息);一些全局属性;用户名及密码信息
    • 执行Setting Script:这一步更加重要,它决定了哪些乐高块(项目模块)会参与到城堡的搭建中。
  2. 配置阶段:这个阶段就像是开始搭建乐高城堡。你会按照图纸(build.gradle文件)上的指示,一块一块地搭建乐高块。在Gradle中,这个阶段会根据你的搭建图纸(build.gradle脚本)来创建任务(task),然后这些任务会形成一个有序的任务网络,确保每个任务都能按照正确的顺序完成。
  3. 执行阶段:最后,当你的乐高城堡搭建图纸和所有乐高块都准备好了,这个阶段就是开始动手搭建的时候了。Gradle会按照之前创建的任务网络,一步步地完成每个任务,直到整个项目构建完成。

2、settings 文件

首先对 settings 文件的几点说明:

  1. 作用:这个文件就像是项目构建的“入门手册”,它告诉我们哪些部分(工程)需要被包含进来一起构建。

  2. 工程树:在Gradle中,工程树就像是一棵大树,有主干(主工程)和分支(子工程),这与Maven中的项目和模块概念相似。

  3. 内容:settings文件里主要记录了项目的名字,以及它包含的所有子项目的名字。

  4. 位置:这个文件必须放在项目的最顶层目录,也就是根工程目录下。

  5. 命名:它的名字是固定的,叫做settings.gradle,不能随意更改。

  6. 实例对应:settings文件对应着Gradle中的一个特定实例(org.gradle.api.initialization.Settings),每个项目只有一个这样的文件。

  7. 关注点:作为开发者,我们主要关注文件中的include方法,它用来添加子项目。通过使用相对路径(例如:子项目名)来引入这些子项目。

  8. 识别与包含:只有当子项目在settings文件中被配置后,Gradle才能识别它们,进而在构建过程中将它们包含进来。下面是一个简单的例子:

// 设置根项目的名称
rootProject.name = 'root' 
// 添加子项目到构建中
include 'subject01' 
include 'subject02' 
include 'subject03' 
// 如果子项目下还有更细分的项目,也可以这样添加
include 'subject01:subproject011' 
include 'subject01:subproject012'

好的,让我们用更通俗的语言来解释一下什么是Gradle中的Task。

3、Task

  1. 项目组成:在Gradle中,项目是由一系列的Task(任务)组成的。每个Task代表一个具体的工作单元。

  2. 独立执行过程:每个Task都是一个独立的执行过程,意味着它有特定的开始和结束。这些过程可以是:

    • 编译Java源代码
    • 拷贝文件到指定位置
    • 打包应用程序成Jar文件
    • 执行系统命令,比如运行测试或者部署应用
  3. 读取和设置属性:Task不仅可以执行操作,还可以读取和修改Project(项目)的属性(Property)。这允许Task根据配置的不同来改变其行为,从而更加灵活。

简而言之,Task就像是Gradle项目中的小小机器人,每个机器人都有特定的工作要做,而且它们还可以根据项目的需要调整自己的工作方式。通过组合不同的Task,你可以构建起复杂的构建流程。

3.1 任务入门

可参考官方文档:docs.gradle.org/current/use…

在Gradle中,任务(Task)是构建过程中执行工作的单元。每个任务可以包含特定的行为,比如执行某些命令或脚本。理解任务的配置和执行阶段对于掌握Gradle的使用至关重要。

  1. 任务配置:任务的配置是在配置阶段完成的,这意味着你会在这个阶段定义任务的行为,例如添加动作(actions)到任务中。

  2. 执行命令:一旦配置好,你可以通过Gradle命令行工具执行任务。例如,如果你有一个名为A的任务,你可以在任务所在的目录下运行gradle A来执行它。

  3. 配置段和行为

    • 配置段:这是在配置阶段执行的,主要用于设置任务的属性和依赖关系。
    • 行为:任务的行为,如doFirstdoLast,是在执行阶段执行的。doFirst中定义的动作会在doLast之前执行。

示例

假设我们有一个名为A的任务,它包含doFirstdoLast块:

task A {
    println "root taskA"
    doFirst {
        println "root taskA doFirst"
    }
    doLast {
        println "root taskA doLast"
    }
}

image-20240420144533126

  • 当你运行gradle A时,Gradle首先进入配置阶段,解析并配置任务A
  • 然后,Gradle进入执行阶段,依次执行任务A中的doFirstdoLast块。

提示说明

  • 提示 1:强调任务的配置是在配置阶段完成的,这是Gradle构建生命周期中的一个阶段,用于确定需要执行哪些任务以及它们的配置。
  • 提示 2:说明doFirstdoLast是在执行阶段执行的动作,且doFirst中的动作会在doLast之前执行。
  • 提示 3:提醒我们区分任务的配置和行为,配置在配置阶段执行,而行为在执行阶段执行。

3.2 任务的行为

案例如下:doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义。

image-20240420144939525

image-20240420150156298

底层原理分析

在Gradle中,任务的行为是通过一系列动作(actions)来定义的。这些动作按照特定的顺序执行,确保了任务的逻辑可以按照预期的方式运行。以下是对底层原理的分析,以及对doFirstdoLast方法的说明。

动作列表(Action List)

  1. 动作列表初始化:每个任务都有一个动作列表,最初这个列表是空的。

  2. 添加动作:当你为任务定义动作(通过闭包传递给任务的构造函数或者使用doLast方法)时,这个动作被添加到动作列表的末尾。

  3. doFirst方法:使用doFirst添加的动作会被放置在动作列表的开头。这意味着doFirst动作将最先执行。

  4. doLast方法:使用doLast添加的动作会被追加到动作列表的末尾。这意味着doLast动作将在其他动作之后执行。

  5. 执行顺序:最终,动作列表按照doFirst -> 任务自身定义的动作(通常称为doSelf)-> doLast的顺序执行。

提示说明

  • 提示 1:在Gradle 5.x版本之后,使用左移操作符(<<)来添加任务动作的方式已经被废弃。这意味着你不能再使用如下语法来添加任务动作:
ask hello << { println 'Hello world!' } 

取而代之的是,你应该使用doLastdoFirst方法来添加动作。

动作顺序的重要性

理解任务动作的顺序对于控制任务的执行流程至关重要。例如,你可能需要在编译之前先进行代码检查,或者在打包之后执行测试。通过doFirstdoLast,你可以灵活地控制这些依赖关系,确保任务按照正确的顺序执行。

总结

Gradle的任务系统通过动作列表提供了强大的灵活性,允许开发者精确控制任务的执行逻辑。随着Gradle版本的更新,一些旧的语法可能会被新的、更明确的方法所取代,因此,保持对Gradle最佳实践和最新版本的了解是非常重要的。

3.3 任务的依赖方式

Task 之间的依赖关系可以在以下几部分设置:

方式一:参数方式依赖

image-20240420150717922

方式二:内部依赖

image-20240420150904089

方式三:外部依赖

当然:task也支持跨项目依赖

image-20240420151711128

拓展1:无依赖关系的任务执行顺序

当一个任务依赖多个任务时,如果这些被依赖的任务之间没有指定依赖关系,Gradle会根据任务的有向无环图(DAG)来决定执行顺序。在这种情况下:

  • 执行顺序:Gradle会尝试以一种效率最高的方式执行这些任务,但如果没有特定的依赖关系,任务的执行顺序可能不是完全确定的。也就是说,两个没有直接依赖关系的任务可能会以任意顺序执行,因为它们是独立的。

  • 无影响:尽管执行顺序可能是随机的,但这通常不会影响最终的构建结果,因为这些任务是相互独立的。

拓展2:重复依赖的任务执行

在Gradle中,如果一个任务被重复依赖,Gradle会确保该任务只执行一次,即使它被多个任务依赖:

  • 执行一次:无论一个任务被依赖多少次,Gradle都会保证这个任务在当前的构建过程中只执行一次。这是通过内部的缓存机制来实现的,确保了构建的效率。

  • 依赖传递:在依赖图中,如果任务A依赖任务B,而任务B又依赖任务C,那么执行任务A时,任务C也会被执行,但任务C只会执行一次,即使它被B和A间接或直接依赖。

依赖关系的重要性

理解任务依赖关系对于构建复杂的项目至关重要。依赖关系不仅决定了任务的执行顺序,还影响了构建的效率和结果。通过合理地设置依赖关系,可以确保项目按照正确的顺序构建,同时避免不必要的重复工作。

总结

Gradle的任务依赖系统提供了灵活性和效率,允许开发者定义复杂的构建流程。了解任务依赖的执行顺序和重复依赖的处理方式,可以帮助开发者更好地控制构建过程,提高构建的可预测性和性能。

3.4 任务执行

任务执行语法:gradle [taskName...] [--option-name...]。

3.4.1 常见的任务 *

命令 描述
gradle build 构建项目,执行编译、测试、打包等操作。
gradle run 运行一个服务。需要application插件支持,并且指定了主启动类。
gradle clean 清除当前项目的build目录。
gradle init 初始化Gradle项目。
gradle wrapper 生成wrapper文件夹。
gradle wrapper --gradle-version=<version> 升级wrapper到指定的Gradle版本号。
gradle wrapper --gradle-version <version> --distribution-type all 升级wrapper到指定的Gradle版本号,并关联源码。

请根据需要替换<version>为实际的Gradle版本号。例如,使用gradle wrapper --gradle-version=4.4将wrapper升级到Gradle 4.4版本。

3.4.2 项目报告相关任务

命令 描述
gradle projects 列出所选项目及其子项目列表,以层次结构形式显示。
gradle tasks 列出所选项目(当前project)的已分配给任务组的任务。
gradle tasks --all 列出所选项目的所有任务。
gradle tasks --group="build setup" 列出所选项目中指定分组(如"build setup")中的任务。
gradle help --task someTask 显示某个任务(如someTask)的详细信息。
gradle dependencies 查看整个项目的依赖信息,以依赖树的方式显示。
gradle properties 列出所选项目的属性列表。

请注意,someTask应替换为您想要获取帮助信息的实际任务名称。这些命令可以帮助您更好地管理和理解Gradle项目的结构、任务和依赖。

3.4.3 调试相关选项

选项 描述
-h--help 查看帮助信息。
-v--version 打印Gradle、Groovy、Ant、JVM和操作系统的版本信息。
-S--full-stacktrace 打印所有异常的完整(非常详细)堆栈跟踪信息。
-s--stacktrace 打印用户异常的堆栈跟踪(例如编译错误)。
-Dorg.gradle.daemon.debug=true 调试Gradle守护进程。
-Dorg.gradle.debug=true 调试Gradle客户端(非守护进程)进程。
-Dorg.gradle.debug.port=(port number) 指定启用调试时要侦听的端口号,默认值为5005。

在使用这些选项时,您需要将(port number)替换为实际想要指定的端口号。这些选项对于调试和获取Gradle运行时的详细信息非常有用。

3.4.4 性能选项

在gradle.properties 中指定这些选项中的许多选项,因此不需要命令行标志.

选项 描述
--build-cache--no-build-cache 启用或禁用尝试重用先前版本的输出。默认关闭(off)。
--max-workers 设置Gradle可以使用的工作线程数。默认值是处理器的数量。
--parallel--no-parallel 启用或禁用并行执行项目。有关此选项的限制,请参阅并行项目执行的文档。默认关闭(off)。

这些选项可以帮助您优化Gradle构建的性能,通过调整构建缓存的使用、工作线程的数量以及是否并行执行项目来提升构建效率。

3.4.5 守护进程选项

选项 描述
--daemon--no-daemon 启用或禁用使用Gradle守护进程运行构建。默认是启用(on)。
--foreground 在前台进程中启动Gradle守护进程。
-Dorg.gradle.daemon.idletimeout=(毫秒数) 设置Gradle守护进程在空闲指定毫秒数后自动停止。默认值为10800000毫秒(3小时)。

在使用这些选项时,您需要将(毫秒数)替换为实际想要指定的毫秒数值。这些选项对于控制Gradle守护进程的行为和性能优化非常有用。

3.4.6 日志选项

选项 描述
-Dorg.gradle.logging.level=(quiet,warn,lifecycle,info,debug) 通过Gradle属性设置日志记录级别,可选级别包括quiet, warn, lifecycle, info, debug。
-q--quiet 将日志级别设置为quiet,只能记录错误信息。
-w--warn 将日志级别设置为warn。
-i--info 将日志级别设置为info。
-d--debug 将日志级别设置为debug,包括正常的堆栈跟踪信息。

在使用这些选项时,您需要将(quiet,warn,lifecycle,info,debug)替换为实际想要指定的日志级别。这些选项可以帮助您根据需要调整Gradle的输出信息量,以便更好地进行问题诊断或减少输出干扰。

3.4.7 其它 *

命令/选项 描述
-x--exclude-task 排除指定的任务,不执行该任务。
gradle -x test clean build 构建项目时排除test任务,然后执行cleanbuild
--rerun-tasks 强制执行任务,忽略up-to-date检查。
gradle build --rerun-tasks 强制重新构建项目,忽略缓存。
--continue 忽略前面失败的任务,继续执行构建。
gradle build --continue 即使某些任务失败,也继续构建过程。
gradle init --type pom 将Maven项目转换为Gradle项目(在根目录执行)。
gradle [taskName] 执行指定的自定义任务。

这些命令和选项能够提供构建过程中的灵活性和控制力,允许用户根据需要排除任务、强制重新执行任务、在遇到错误时继续构建,或者将现有Maven项目迁移到Gradle。

更详细请参考官方文档: docs.gradle.org/current/use…

拓展:任务名缩写

  1. 驼峰式命名风格:在编程中,驼峰式命名风格是一种常见的命名约定,其中每个单词的首字母大写,不使用下划线或连字符。例如,connectTask 是一个遵循驼峰命名法的变量名。

  2. 任务名缩写:Gradle 允许你使用任务名的缩写来执行任务,这使得执行长任务名更加方便。缩写应该是唯一的,以便 Gradle 能够正确地识别要执行的任务。

    • 例如,如果有一个名为 connectTask 的任务,你可以使用缩写 cT 来执行它,通过命令 gradle cT
  3. 执行任务:在命令行中,你可以使用缩写的名称来告诉 Gradle 你想要运行哪个任务。

拓展 1:Gradle 指令本质

  1. 基于任务的操作:Gradle 的所有指令本质上都是对任务的操作。无论是编译代码、测试、打包还是部署,这些操作都是通过执行定义好的任务来完成的。

  2. 任务的依赖性:任务可以相互依赖,这意味着某些任务只有在其他任务完成后才能执行。这种依赖性定义了项目的构建顺序。

  3. 灵活性和自动化:通过任务和它们的依赖关系,Gradle 能够自动化复杂的构建过程,提供了一种灵活的方式来管理和执行项目中的各种构建步骤。

拓展 2:gradle 默认各指令之间相互的依赖关系:

image-20240420154243499

相关解释:

image-20240420154257630

3.5 任务定义方式

在Gradle中,定义任务是构建自动化流程的基础。你可以通过几种不同的方式定义任务,每种方式都适用于不同的场景。以下是对提到的任务定义方式的详细解释:

通过Projecttask()方法

  1. 基本用法:使用Project对象的task()方法可以定义一个新任务。你可以传递一个字符串作为任务名称,以及一个闭包来定义任务的行为。

    task('A') {
        println "taskA..."
    }
    
  2. 闭包作为参数:闭包可以作为task()方法的最后一个参数,用来指定任务的行动。

    task('B') {
        println "taskB..."
    }
    
  3. 省略方法括号:在Groovy语言中,当方法的最后一个参数是一个闭包,你可以省略该方法的括号,使得代码更加简洁。

    task 'C' {
        println "taskC..."
    }
    

通过tasks对象的createregister方法

  1. 使用create方法tasks对象的create方法可以用来创建一个新的任务,它接受一个任务名称和一个闭包作为参数。

    tasks.create('E') {
        println "taskE..."
    }
    
  2. 使用register方法register方法与create方法类似,但它执行的是延迟创建。这意味着任务只有在实际需要执行时才会被创建。

    tasks.register('F') {
        println "taskF..."
    }
    
  3. 注解register方法通常用于那些可能不会总是执行的任务,比如某些仅在特定条件下需要运行的配置任务。

使用Map来定义任务

  1. Map属性:你还可以使用一个Map来定义任务,其中Map中的action键可以设置为一个闭包,用来指定任务的行为。

    def map = new HashMap<String, Object>()
    map.put("action", { println "taskD.." })
    task(map, "D")
    

Gradle提供了多种定义任务的方法,每种方法都有其使用场景。task()方法是最直观的,适合简单任务的定义。而tasks.createtasks.register方法提供了更多的灵活性,特别是当任务的创建需要一些条件判断或者延迟初始化时。使用Map定义任务则是一种更高级的技巧,可以在某些复杂的构建脚本中见到。了解这些不同的方法可以帮助你更好地编写和维护Gradle构建脚本。

当然:我们也可以在定义任务的同时指定任务的属性,具体属性有:

  1. type

    • 描述:这个配置项允许你指定任务的类型,即基于一个已存在的任务类型来创建新任务,类似于在面向对象编程中的类继承。当你想要创建一个具有特定行为的任务时,你可以指定一个已有的任务类作为其类型。
    • 默认值DefaultTask,这是Gradle中所有任务的基类。
  2. overwrite

    • 描述:这个配置项决定当你使用type配置项创建一个同名的新任务时,是否覆盖已存在的任务。如果设置为true,则新任务会替换掉同名的旧任务。
    • 默认值false,意味着同名任务不会覆盖,除非明确设置。
  3. dependsOn

    • 描述:这个配置项用来指定任务的依赖关系。一个任务可以依赖一个或多个其他任务,确保在执行当前任务之前,所依赖的任务已经完成。
    • 默认值[],即空数组,表示没有依赖。
  4. action

    • 描述:这个配置项允许你为任务添加一个动作(Action),可以是一个实现了Action<T>接口的实例,或者是一个闭包。这个动作定义了任务执行时的具体行为。
    • 默认值null,表示没有指定动作。
  5. description

    • 描述:这个配置项用来为任务提供一个描述性的文本,这个文本可以用于帮助理解任务的作用,尤其是在列出所有可用任务时。
    • 默认值null,表示没有描述。
  6. group

    • 描述:这个配置项用于将任务分组。通过分组,可以在列出所有任务时更好地组织和展示任务,也可以通过组名来执行一组任务。
    • 默认值null,表示没有分组。

使用示例

/**
 * 定义一个名为B的任务。
 * 该任务具有以下特点:
 * 1. 依赖于根工程下名为gradle001的项目的任务A,实现了跨项目依赖。
 * 2. 在执行任务时,首先会打印'Executing myTask'。
 * 3. 任务具有自定义的描述和所属组信息。
 */
task B {
    // 依赖根工程下的任务A,实现跨项目依赖
    dependsOn(":gradle001:A") 

    // 定义任务执行前的动作,这里打印一条信息
    doFirst { 
        println 'Executing myTask'
    }

    // 设置任务的描述信息
    description = 'This is a custom task' 

    // 设置任务所属的组
    group = 'jie' 
}

image-20240420155801691

3.6 任务类型

Gradle 提供了一系列预定义的任务类型,这些任务类型封装了常见的构建操作,使得用户不需要从头开始编写复杂的脚本。通过指定任务的类型,你可以利用 Gradle 提供的内置功能来执行特定的构建任务。以下是一些常见的内置任务类型及其作用:

任务类型 作用
Delete 删除文件或目录。
Copy 将文件复制到目标目录中,支持重命名和筛选文件。
CreateStartScripts 创建启动脚本,以便可以在不同操作系统上运行项目。
Exec 执行命令行进程。
GenerateMavenPom 生成 Maven 模块描述符(POM)文件。
GradleBuild 执行 Gradle 构建。
Jar 组装 JAR 归档文件。
JavaCompile 编译 Java 源文件。
Javadoc 为 Java 类生成 HTML API 文档。
PublishToMavenRepository 将 MavenPublication 发布到 Maven 仓库。
Tar 组装 TAR 存档文件。
Test 执行 JUnit (3.8.x、4.x 或 5.x) 或 TestNG 测试。
Upload 将配置的构件上传到一组仓库。
War 组装 WAR 档案。
Zip 组装 ZIP 归档文件,默认情况下会压缩 ZIP 的内容。

使用内置任务类型

要使用这些内置任务类型,你只需在定义任务时指定 type 参数。例如,如果你想要创建一个删除特定目录的任务,可以这样做:

task clean(type: Delete) {
    delete 'path/to/directory'
}

这将创建一个名为 clean 的任务,使用 Delete 任务类型来删除指定的目录。

Gradle 的内置任务类型提供了一种快速、简便的方式来执行常见的构建任务,无需编写大量的自定义脚本。通过使用这些类型,你可以减少构建脚本的复杂性,同时提高构建过程的可读性和可维护性。

更详细的 gradle 自带 Task 类型,请参考官方文档: docs.gradle.org/current/dsl…

官方文档在给出这些任务类型的时候,同时给出了案例代码,可以点进去上述官网地址中的某个类型中观看

image-20240420160305645

3.7 任务的执行顺序

在Gradle中,确实有几种方法可以指定任务(Task)的执行顺序。以下是这三种方法的详细说明:

1. dependsOn 强依赖方式

dependsOn 是一种显式指定任务依赖关系的方法。当你设置一个任务的 dependsOn 属性时,Gradle 会在执行当前任务之前确保所依赖的任务已经完成。

task taskOne {
    doLast {
        println 'Executing taskOne'
    }
}

task taskTwo {
    dependsOn taskOne
    doLast {
        println 'Executing taskTwo'
    }
}

在这个例子中,taskTwo 明确依赖于 taskOne,因此 taskOne 将先于 taskTwo 执行。

2. 通过任务输入输出

Gradle 也允许你通过定义任务的输入和输出文件来指定执行顺序。Gradle 会根据这些文件的存在与否来决定是否需要执行任务。如果任务的输出文件不存在于文件系统中,或者输入文件自上次任务执行后发生了变化,Gradle 将重新执行该任务。

task compileJava {
    // 定义任务的输出目录
    outputs.dir 'build/classes'
    doLast {
        println 'Compiling Java code'
    }
}

task packageApp {
    // 定义任务的输入目录,即 compileJava 任务的输出目录
    inputs.dir 'build/classes'
    doLast {
        println 'Packaging the application'
    }
}

在这个例子中,packageApp 任务依赖于 compileJava 任务的输出目录,因此 Gradle 会先执行 compileJava,然后再执行 packageApp

3. 通过 API 指定执行顺序

Gradle 提供了 Task Ordering API,允许你更细致地控制任务的执行顺序。你可以使用 mustRunAfter 或者 finalizedBy 方法来指定任务的相对顺序。

task taskA {
    doLast {
        println 'Executing taskA'
    }
}

task taskB {
    mustRunAfter taskA // taskB 必须在 taskA 之后执行
    doLast {
        println 'Executing taskB'
    }
}

task taskC {
    finalizedBy taskB // taskC 完成后,taskB 将被执行
    doLast {
        println 'Executing taskC'
    }
}

在这个例子中,taskB 使用 mustRunAfter 确保它在 taskA 之后执行,而 taskC 使用 finalizedBy 确保在 taskC 完成后执行 taskB

总结

这三种方法提供了不同的层面来控制任务的执行顺序,从简单的显式依赖到基于文件的依赖,再到更复杂的 API 控制,Gradle 为用户提供了灵活的方式来定义和管理构建过程中的任务顺序。

3.8 动态分配任务

Gradle 的灵活性和强大功能确实不仅限于定义单一任务,它还允许进行更高级的操作,比如在构建脚本中动态生成任务。以下是对您提供的示例和概念的解释:

3.8.1 在循环中注册多个任务

通过使用 Groovy 语言的闭包和循环结构,Gradle 允许你在构建脚本中动态地注册多个任务。这种方式非常适合当你需要根据某些条件或循环生成的任务集合时使用。

4.times { counter ->
    tasks.register("task$counter") {
        doLast {
            println "I'm task number $counter"
        }
    }
}

在这个例子中,使用 4.times 循环四次,每次迭代都注册一个新任务,任务名根据循环的计数器动态生成。

3.8.2 动态添加依赖项

一旦任务被注册,你可以通过 Gradle 提供的 Task API 在运行时动态地修改任务的行为,包括添加依赖项。这与像 Ant 这样的传统构建工具不同,后者不提供这种运行时修改的能力。

tasks.named('task0') { it.dependsOn('task2', 'task3') } 

在这个例子中,我们通过 tasks.named 方法获取对任务 task0 的引用,并使用 dependsOn 方法动态地给 task0 添加了依赖,指定 task2 和 task3 必须在 task0 执行之前完成。

3.8.3 构建顺序的确保

通过上述动态添加依赖的方式,Gradle 确保了 task2 和 task3 会在 task0 之前执行,因为 task0 显式地声明了对它们依赖。这种依赖关系的声明是 Gradle 构建系统正确执行任务顺序的关键。

3.8.4 总结

Gradle 的动态任务注册和 API 使得构建脚本更加灵活和强大。通过循环和 API 动态地创建任务以及添加依赖,Gradle 允许开发者根据构建逻辑的需要,以编程方式生成复杂的任务网络。这种灵活性是 Gradle 成为现代自动化构建首选工具之一的原因之一。

3.9 任务的关闭与开启

在Gradle中,每个任务都有一个enabled属性,用来控制任务是否应该被执行。这个属性默认值为true,意味着任务是启用的,将按照正常的流程执行其动作。如果将enabled设置为false,则任务将被禁用,不会执行任何动作,并且在构建过程中会标记为“跳过”。

以下是如何设置任务的enabled属性的示例:

task disableMe {
    doLast {
        println 'This task is Executing...'
    }
    // 直接设置任务为开启(启用),这是默认行为,所以实际上下面的enabled(true)是可选的
    enabled true
    
    // 可以通过闭包的方式动态设置任务的启用状态
    enabled = { false } // 这样设置任务将被禁用,不会执行
}

// 另一种设置任务禁用的方式
disableMe.enabled = false // 这样也可以设置任务关闭(禁用)

在这个例子中,disableMe任务被创建并定义了要执行的动作。然后通过两种不同的方法展示了如何禁用任务:

  1. 使用闭包和enabled属性,将任务设置为禁用。
  2. 直接在任务对象上使用enabled属性,将其设置为false

禁用的任务在构建过程中不会被执行,这可以用于条件性地跳过某些任务,例如在某些环境或配置下不需要执行测试任务时。

3.9.1 总结

Gradle的任务enabled属性提供了一种简单而直接的方式来控制任务的执行。通过这个属性,可以根据构建的不同需求动态地启用或禁用任务,增加了构建流程的灵活性。

3.10 任务的超时

在Gradle中,timeout属性允许你为任务设置一个执行时间限制。如果任务的执行时间超过了这个限制,Gradle会中断该任务的执行,并将任务标记为失败。这个过程称为超时。

3.10.1 超时行为

  • 任务中断:当任务达到超时时间时,Gradle会尝试中断执行该任务的线程。
  • 失败标记:超时的任务会被标记为失败。
  • 终结器任务:即使某个任务因为超时而失败,Gradle的终结器(finalizer)任务仍然会执行。终结器任务通常用于清理工作,比如删除临时文件。
  • 继续执行:如果使用了--continue命令行选项,即使前面的任务失败,Gradle也会继续执行后续的任务。

3.10.2 示例解析

task a {
    doLast {
        Thread.sleep(1000) // 使线程休眠1秒
        println "当前任务a执行了"
    }
    timeout = Duration.ofMillis(500) // 设置超时为500毫秒
}

task b {
    doLast {
        println "当前任务b执行了"
    }
}
  • 任务a:设置了超时时间为500毫秒,但它实际休眠了1000毫秒,因此会超时。

3.10.3 控制台命令测试

  • 命令1gradle a b,当你执行这个命令时,任务a会因为超时而失败,并且由于默认行为,Gradle会停止执行后续的任务b。

  • 命令2gradle a b --continue,使用--continue选项后,即使任务a超时失败,Gradle也会继续执行任务b。

3.10.4 不响应中断的任务

需要注意的是,如果任务在执行时不响应中断(例如,它在执行一个阻塞操作但没有检查中断信号),那么超时机制可能无法正常工作。

3.10.5 总结

Gradle的timeout属性提供了一种机制,可以避免任务执行时间过长。通过合理设置超时,你可以确保构建过程不会因为某个任务的长时间挂起而停滞不前。同时,--continue选项提供了在面对失败时继续执行的能力,这对于执行多个任务的构建流程特别有用。

3.11 任务的查找

在Gradle中,查找任务是一项常见的操作,特别是在复杂的构建脚本中,你可能需要对特定的任务进行操作或者配置。以下是几种常用的任务查找方法:

3.11.1 根据任务名查找

  1. tasks.findByName(String name):根据指定的任务名查找任务。如果找到了任务,返回对应的任务对象;如果没有找到,返回null

  2. tasks.getByName(String name):同样根据指定的任务名获取任务。但是,如果任务不存在,这个方法会抛出一个UnknownTaskException异常。

示例

image-20240420162639566

3.11.2 根据任务路径查找

  1. tasks.findByPath(String path):根据任务的路径查找任务。任务路径通常是相对于项目根目录的路径,例如:atguigu表示当前项目的atguigu任务。

  2. tasks.getByPath(String path):根据任务路径获取任务对象。如果任务不存在,会抛出UnknownTaskException异常。

示例

image-20240420162802043

3.11.3 执行结果

当你执行gradle atguigu命令时,Gradle会按照你添加动作的顺序执行它们。在这个例子中,由于getByPath方法添加的动作会被优先执行,其次是findByPath,然后是getByNamefindByName

  • 使用findByNamefindByPath方法时,由于它们返回null而不是抛出异常,你可以安全地使用?.操作符来为可能为null的任务添加动作。
  • 动作添加的顺序很重要,Gradle会按照你添加动作的顺序来执行它们。

通过这些查找方法,你可以灵活地在构建脚本中引用和操作任务,从而实现更复杂的构建逻辑。

3.12 任务的规则

Gradle 提供了一个非常有用的功能,称为“任务规则(Task Rules)”,它允许你在运行一个不存在的任务时执行自定义逻辑,而不是直接报错。通过添加任务规则,你可以改进 Gradle 的行为,使其在找不到指定任务时提供一个更友好的错误消息,或者动态创建任务。

3.12.1 添加任务规则

以下是如何使用 tasks.addRule 方法添加一个任务规则的示例:

task hello {
    doLast {
        println 'hello 阿杰的粉丝们'
    }
}

tasks.addRule("对该规则的一个描述,便于调试、查看等") {
    String taskName ->
    if (!tasks.findByName(taskName)) {
        task(taskName) {
            doLast {
                println "该${taskName}任务不存在,请查证后再执行"
            }
        }
    }
}

在这个例子中,我们定义了一个规则,它会检查尝试执行的任务是否存在。如果不存在,它不会报错,而是创建一个新的任务并提供一条提示信息。

3.12 .2 测试任务规则

使用以下命令进行测试:

radle abc hello 

在这个测试中,abc 任务不存在,但由于我们添加了上述规则,Gradle 不会报错,而是会打印出我们定义的提示信息。之后,Gradle 会继续执行存在的 hello 任务。

3.12.3 动态创建任务

Gradle 的任务规则不仅可以用于提供更好的错误消息,还可以用于动态创建任务。例如,你可以编写一个规则,根据某些条件动态创建任务,或者为一组相似的任务提供一个模板。

3.12.4 总结

任务规则是 Gradle 提供的一个强大功能,它允许你以更灵活和用户友好的方式处理未知任务的执行。通过合理利用任务规则,你可以改进构建脚本的健壮性,提供更清晰的错误消息,或者实现复杂的动态任务创建逻辑。

3.13 任务的 onlyIf 断言

在 Gradle 中,断言(assertion)是一种确保某些条件为真的方法。在构建自动化中,这可以用于确保在执行任务之前满足特定的先决条件。

Task 对象的 onlyIf 方法允许你提供一个闭包,这个闭包将作为决定任务是否执行的条件。如果闭包返回 true,则任务将执行;如果返回 false,则任务将被跳过。

3.13.1 使用 onlyIf 的示例

以下是如何使用 onlyIf 方法的示例:

task hello {
    doLast {
        println 'hello 阿杰的粉丝们'
    }
}

// 使用 onlyIf 来决定是否执行 hello 任务
hello.onlyIf { !project.hasProperty('fensi') }

在这个例子中,hello 任务将检查是否存在一个名为 fensi 的属性。如果不存在,hello 任务将执行;如果存在,则跳过。

3.13.2 测试 onlyIf 行为

要测试这个行为,你可以使用 -P 命令行选项来为项目添加属性:

gradle hello -Pfensi 

在这个测试中,由于我们使用 -Pfensi 提供了 fensi 属性,hello 任务将不会执行。

3.13.3 使用场景

onlyIf 方法可以用于多种场景,例如:

  • 仅在某些环境变量设置时执行特定任务。
  • 根据项目属性决定是否运行测试。
  • 条件性地打包或部署应用程序。

3.13.4 总结

onlyIf 方法为 Gradle 任务的执行提供了条件控制,允许你灵活地根据构建时的上下文决定任务的执行。这种方法可以减少不必要的构建步骤,提高构建的效率和灵活性。

3.14 默认任务

在 Gradle 中,当你运行 gradle 命令而没有指定具体的任务时,Gradle 会寻找并执行默认任务。默认任务是一组在没有明确指定要执行的任务时被自动执行的任务。

3.14.1 设置默认任务

你可以通过两种方式设置默认任务:

  1. 全局默认任务:在 settings.gradle 文件中,你可以为整个项目或特定的项目设置默认任务。

  2. 项目特定默认任务:在 build.gradle 文件中,你可以为单个项目设置默认任务。

以下是如何设置默认任务的示例:

// 在 build.gradle 文件中设置项目特定的默认任务
defaultTasks 'myClean', 'myRun'

tasks.register('myClean') {
    doLast {
        println 'Default Cleaning!'
    }
}

tasks.register('myRun') {
    doLast {
        println 'Default Running!'
    }
}

tasks.register('other') {
    doLast {
        println "I'm not a default task!"
    }
}

在这个例子中,myClean 和 myRun 被设置为默认任务。

3.14.2 测试默认任务

当你在命令行运行 gradle 而不指定任务时,Gradle 会执行上面设置的默认任务:

> gradle -q 

执行上述命令,输出结果将是:

Default Cleaning!
Default Running!

这里使用了 -q 参数来减少 Gradle 的日志输出,以便更清晰地展示默认任务的执行结果。

3.14.3 总结

默认任务的概念使得 Gradle 构建更加灵活和方便。通过设置默认任务,你可以为常用的构建步骤定义快捷方式,从而提高日常构建的效率。这对于快速启动常规的构建流程或为新团队成员提供简单的入门步骤特别有用。

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/140146043