5、构建脚本基础:Project和Task对象、任务的创建与配置、任务的依赖关系

好,咱们今天聊聊 Gradle 构建脚本里最核心的两个东西——ProjectTask

说白了,你写的每一个 build.gradle 文件,本质上就是一个 Project 对象的配置脚本。而 Project 里干活的,全是 Task。我刚开始接触 Gradle 时,总觉得这俩概念有点虚,后来踩了几次坑才明白——Project 是容器,Task 是执行单元

Project 对象:脚本的“宿主”

每个 build.gradle 被加载时,Gradle 会自动创建一个 Project 对象。你在脚本里写的所有配置、插件、依赖、任务,最终都挂在这个对象上。

Project 提供了几个关键能力:

  • 管理任务:通过 task 方法创建任务
  • 管理依赖:通过 dependencies 块声明库
  • 管理插件:通过 pluginsapply 引入功能
  • 提供属性:比如 project.nameproject.version

核心要点:Project 是脚本的上下文。你写的每一行代码,其实都是在调用 Project 的方法或配置它的属性。

Task 对象:真正干活的“工人”

Task 是 Gradle 里最小的执行单元。一个构建过程,就是一组 Task 按顺序执行的过程。

每个 Task 都有:

  • 名称:唯一标识,比如 cleanbuild
  • 动作:用 doFirstdoLast 定义具体执行逻辑
  • 配置:可以在声明时或之后设置属性
  • 依赖:通过 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 对象,只有真正需要时才创建。能提升配置阶段的性能。嗯,这里要注意:如果你的脚本里有很多条件分支,用 registercreate 更高效。

避坑指南:我曾经在循环里用 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 依赖 taskBtaskB 依赖 taskA,那么执行 taskC 时,taskAtaskB 都会被执行。这就是传递性依赖。

但要注意:Gradle 不会重复执行同一个任务。如果 taskCtaskD 都依赖 taskAtaskA 只会执行一次。

知识体系图

下面这张图帮你理清本章的核心逻辑:

Gradle 构建脚本 Project 对象 Task 对象 管理任务 管理依赖 管理插件 提供属性 创建方式:声明 / create / register 配置 vs 动作(doFirst / doLast) 依赖关系:dependsOn 依赖传递与执行顺序

实战示例:一个完整的构建脚本

把上面这些知识点串起来,写一个完整的例子:

// 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 插件自带的 compileJavatest,再执行 greet,最后执行 buildAll 本身的动作。

小技巧:你可以用 gradle tasks --group=MyTasks 查看自定义任务组里的所有任务。这在项目大了以后特别有用。

总结

这一章我们聊了三个核心点:

  • Project 对象:脚本的宿主,管理一切
  • Task 的创建与配置:三种创建方式,配置与动作分离
  • 依赖关系:用 dependsOn 声明,Gradle 自动排序

说白了,Gradle 的构建脚本就是“在 Project 里定义一堆 Task,然后告诉它们谁先谁后”。你想想看,是不是这个理?

下一章我们会深入 Task 的输入输出和增量构建,到时候再聊。


公众号:蓝海资料掘金营,微信deep3321