./gradlew build,知道它在背后跑了哪些步骤。
Gradle(构建工具 build tool):把「从源码到产物」要做的事拆成一个个 任务 task,再按任务之间的依赖排成一张 依赖图 dependency graph 依次执行, 顺手替你解析、下载要用到的外部库。它跟包管理器的分工是: 一个管「装什么」,一个管「做出什么」。 像厨房里那张流程单:从原料到出餐要洗、切、炒,谁先谁后、切过的就不必再切; 它管的就是这条线 —— 你只说一句「出餐」,它照着单子把每道工序派下去。
没有构建工具的时候,一个项目从源码走到能交付,中间全是没人愿意手管的琐事:
这四件事有个共同点:它们属于流程,不属于程序本身。构建工具专门接管这一段。
构建脚本 你写下的规则 -> 任务 task 一件可执行的事 -> 依赖图 dependency graph 谁必须排在谁之前 -> 增量构建 incremental build 输入没变就沿用上次 -> 产物 build 可交付的那份东西
build.gradle
或 build.gradle.kts;多模块项目再加一个 settings.gradle 列出有哪些模块。gradlew 加几个配置文件),别人克隆下来用的是同一个 Gradle 版本,而不是他本机装的那份。场景一:本地跑构建与测试。
./gradlew build # 按依赖跑完编译、测试、打包,产物进 build/
./gradlew test # 只跑到测试;已经算过的步骤自动跳过
./gradlew bootRun # Spring Boot 项目:直接起服务,不必先手动打包
命令前那个 ./gradlew 就是项目自带的 Wrapper,所以第一次跑它会先把对应版本的 Gradle 拉下来。
场景二:CI/CD 流水线里构建。
./gradlew --no-daemon build # 一次性环境:跑完就退出,不留常驻进程
./gradlew --offline build # 依赖已在缓存里时,不联网也能构建
流水线用的是用完即弃的容器,常驻进程留不下来,反而成了纯开销,所以显式关掉;要让结果可复现, 还得把 Gradle 版本与依赖来源一起锁死。
场景三:多模块与 Android 项目。
一个仓库拆成 app/、core/ 等多个模块时,settings.gradle 列出成员,
./gradlew :app:build 这种带路径的任务名可以只构建其中一块。Android 项目也是同一套配置,
只是插件换成了 Android Gradle Plugin —— 这也是 Gradle 最常见的一张门面。
先问一句:你要解决的是「怎么编」,还是「编的流程谁来管」? 再问一句:这句话说的是一步,还是整条路?
| 名称 | 它是什么 | 和 Gradle 什么关系 |
|---|---|---|
| Maven | 更早的 Java 构建工具:流程写在一份 XML 里,目录结构与生命周期由它规定 | 同类替代品:约定更强、写起来更啰嗦;要自定义流程时 Gradle 更灵活,代价是你要会写脚本 |
| Ant | 纯脚本式构建工具:目标与步骤全靠你一条条写,没有默认流程 | 上一代:它只执行你写的步骤,不替你做依赖解析与增量判断 |
| Make | 最朴素的构建工具:写明「目标依赖哪些文件」,再按文件时间戳重建 | 思路相通(依赖图 + 增量),但它没有依赖解析与远程仓库,跨平台也得自己想办法 |
| npm scripts / pnpm | 包管理器的附带能力:在配置里写几条命名命令,拼起来当流程用 | 一半重叠:它擅长「装什么」,流程靠一串命令硬拼,任务依赖、增量、缓存都要你自己补 |
| Bazel | 为超大仓库设计的构建系统:跨语言,强调可复现与远程缓存 | 同一位置、不同量级:仓库大到需要分布式缓存时,才值得付它那份学习成本 |
| 编译器 | 把源码翻译成机器能执行的指令的那个程序 | 上下两层:编译只是构建里的一步;取依赖、跑测试、打包、记住哪步能跳过,都不归它管 |
一句话分工:包管理器管「装」,编译器管「翻」,构建工具管「编排整条路」,部署工具管「送出去」。
三题自测,每题只有一个正确选项;选完立刻看到解释。
1. 只改了一个源文件后重新构建,没变的任务不再重做 —— 这个机制叫什么?
对。Gradle 给每个任务记下输入与输出,输入没变就直接沿用上次结果;这会跳过没必要的重做,但不会替你跳过你没让它跳的测试。
2. 项目里带一个 gradlew,替你保证了什么?
对。Wrapper 钉住的是构建工具本身的版本;依赖版本要靠依赖声明与锁定,产物是否可复现还牵涉编译参数与环境,三件事别混成一件。
3. 把各个任务的前后顺序算清楚、决定谁先跑的那张图,叫什么?
对。你只声明任务与依赖,图由 Gradle 算出来;顺序不是它硬编的,而是从你的声明里推出来的。