5、构建脚本基础:Project和Task对象、任务的创建与配置、任务的依赖关系
好,咱们今天聊聊 Gradle 构建脚本里最核心的两个东西——Project 和 Task。
说白了,你写的每一个 build.gradle 文件,本质上就是一个 Project 对象的配置脚本。而 Project 里干活的,全是 Task。我刚开始接触 Gradle 时,总觉得这俩概念有点虚,后来踩了几次坑才明白——Project 是容器,Task 是执行单元。
Project 对象:脚本的“宿主”
每个 build.gradle 被加载时,Gradle 会自动创建一个 Project 对象。你在脚本里写的所有配置、插件、依赖、任务,最终都挂在这个对象上。
Project 提供了几个关键能力:
- 管理任务:通过
task方法创建任务 - 管理依赖:通过
dependencies块声明库 - 管理插件:通过
plugins或apply引入功能 - 提供属性:比如
project.name、project.version
核心要点:Project 是脚本的上下文。你写的每一行代码,其实都是在调用 Project 的方法或配置它的属性。
Task 对象:真正干活的“工人”
Task 是 Gradle 里最小的执行单元。一个构建过程,就是一组 Task 按顺序执行的过程。
每个 Task 都有:
- 名称:唯一标识,比如
clean、build - 动作:用
doFirst或doLast定义具体执行逻辑 - 配置:可以在声明时或之后设置属性
- 依赖:通过
dependsOn指定前置任务
个人经验:我习惯把 Task 想象成一个“函数”。只不过 Gradle 帮你管理了它的执行顺序、输入输出和缓存。你只需要关心它做什么,不用操心什么时候做。
任务的创建方式
Gradle 提供了三种创建 Task 的方式。我一个个说。
方式一:直接声明
task hello {
doLast {
println 'Hello, Gradle!'
}
}
这是最常用的方式。直接在脚本里写 task 名称 { ... },花括号里是配置代码。
方式二:通过 TaskContainer 创建
tasks.create('hello') {
doLast {
println 'Hello from TaskContainer!'
}
}
这种方式更灵活,适合在循环或条件判断中动态创建任务。我在做多模块项目时经常用这个。
方式三:注册延迟创建(Gradle 8.x 推荐)
tasks.register('hello') {
doLast {
println 'Hello from register!'
}
}
这种方式不会立即创建 Task 对象,只有真正需要时才创建。能提升配置阶段的性能。嗯,这里要注意:如果你的脚本里有很多条件分支,用 register 比 create 更高效。
避坑指南:我曾经在循环里用 create 创建了 200 多个 Task,结果配置阶段直接卡了 3 秒。换成 register 后,降到 0.5 秒。所以,能用 register 就别用 create。
任务的配置与动作
Task 的配置和动作是分开的。配置在配置阶段执行,动作在执行阶段执行。
task myTask {
// 配置代码:在配置阶段执行
description = '这是一个示例任务'
group = 'MyGroup'
// 动作代码:在执行阶段执行
doFirst {
println '动作一:最先执行'
}
doLast {
println '动作二:最后执行'
}
}
为什么会这样?因为 Gradle 的构建分三个阶段:初始化 → 配置 → 执行。配置代码只跑一次,动作代码按需执行。
我个人的习惯是:把属性设置放在 Task 块里,把业务逻辑放在 doLast 里。这样代码清晰,也方便调试。
任务的依赖关系
实际项目中,任务很少是孤立的。比如你要打包,得先编译;要编译,得先检查代码。这就用到了依赖。
声明依赖:dependsOn
task taskA {
doLast {
println '执行 Task A'
}
}
task taskB {
dependsOn taskA
doLast {
println '执行 Task B'
}
}
执行 gradle taskB 时,Gradle 会自动先执行 taskA,再执行 taskB。
多依赖与链式依赖
task compile {
doLast { println '编译...' }
}
task test {
dependsOn compile
doLast { println '测试...' }
}
task packageApp {
dependsOn test
doLast { println '打包...' }
}
执行 gradle packageApp,Gradle 会按依赖链依次执行:compile → test → packageApp。
核心逻辑:Gradle 会自动解析依赖图,保证每个任务只执行一次,且按正确的顺序执行。你不需要手动排序,只需要声明“谁依赖谁”。
依赖的传递性
如果 taskC 依赖 taskB,taskB 依赖 taskA,那么执行 taskC 时,taskA 和 taskB 都会被执行。这就是传递性依赖。
但要注意:Gradle 不会重复执行同一个任务。如果 taskC 和 taskD 都依赖 taskA,taskA 只会执行一次。
知识体系图
下面这张图帮你理清本章的核心逻辑:
实战示例:一个完整的构建脚本
把上面这些知识点串起来,写一个完整的例子:
// build.gradle
plugins {
id 'java'
}
// 配置 Project 属性
group = 'com.example'
version = '1.0.0'
// 声明依赖
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.guava:guava:32.1.3-jre'
}
// 创建自定义任务
tasks.register('greet') {
description = '打印欢迎信息'
group = 'MyTasks'
doLast {
println "欢迎使用 ${project.name} v${project.version}"
}
}
tasks.register('prepare') {
description = '准备构建环境'
group = 'MyTasks'
doLast {
println '环境准备完成'
}
}
tasks.register('buildAll') {
description = '执行完整构建'
group = 'MyTasks'
dependsOn 'prepare', 'compileJava', 'test', 'greet'
doLast {
println '构建完成!'
}
}
执行 gradle buildAll,Gradle 会先执行 prepare,然后执行 Java 插件自带的 compileJava 和 test,再执行 greet,最后执行 buildAll 本身的动作。
小技巧:你可以用 gradle tasks --group=MyTasks 查看自定义任务组里的所有任务。这在项目大了以后特别有用。
总结
这一章我们聊了三个核心点:
- Project 对象:脚本的宿主,管理一切
- Task 的创建与配置:三种创建方式,配置与动作分离
- 依赖关系:用
dependsOn声明,Gradle 自动排序
说白了,Gradle 的构建脚本就是“在 Project 里定义一堆 Task,然后告诉它们谁先谁后”。你想想看,是不是这个理?
下一章我们会深入 Task 的输入输出和增量构建,到时候再聊。