21、Gradle Composite Build:复合构建
说实话,我第一次接触复合构建这个概念时,脑子里冒出的第一个念头是:「这不就是多项目构建吗?」
后来踩了坑才明白——两者完全是两码事。今天我们就来好好掰扯掰扯。
什么是复合构建?
复合构建(Composite Build)是 Gradle 提供的一种构建组织方式。它允许你把多个独立的 Gradle 项目组合在一起,让它们像一个整体一样协同工作。
我个人的理解是:复合构建 = 多个独立项目的「联邦」。每个项目都有自己的 build.gradle,自己管自己的依赖,但合在一起时又能互相引用。
核心特点:
- 每个子项目都是独立的 Gradle 构建
- 子项目之间通过
includeBuild引入 - 支持跨项目任务依赖
- 子项目可以有自己的版本管理
includeBuild 的使用
用法其实很简单。你在根项目的 settings.gradle.kts 里写:
// settings.gradle.kts
rootProject.name = "my-app"
includeBuild("../my-library") {
name = "my-library"
dependencySubstitution {
substitute(module("com.example:my-library"))
.using(project(":"))
}
}
includeBuild("../shared-utils") {
name = "shared-utils"
}
嗯,这里要注意:dependencySubstitution 这块容易搞混。它的作用是告诉 Gradle——「当有人依赖 com.example:my-library 这个外部坐标时,直接用本地项目替换掉」。
我在项目中遇到过一个问题:团队里有人改了库的代码,但忘了发布新版本,结果主项目一直用的旧版。用复合构建后,本地改完立刻生效,再也不用等发布流程了。
复合构建 vs 多项目构建
很多人分不清这两者。我画了张图帮你理解:
说白了,多项目构建是「一家人住在一个大房子里」,复合构建是「几个独立的房子组成一个小区」。前者共享水电煤气(构建逻辑),后者各管各的但互相串门。
| 对比维度 | 多项目构建 | 复合构建 |
|---|---|---|
| settings.gradle | 只有一个根 settings.gradle | 每个项目都有自己的 settings.gradle |
| 构建逻辑 | 共享根项目的 build.gradle | 各自独立,互不干扰 |
| 版本管理 | 统一版本号 | 每个项目独立版本号 |
| 发布能力 | 子模块不能独立发布 | 每个项目可以独立发布 |
| 适用场景 | 单体应用内部模块拆分 | 跨项目、跨团队协作 |
避坑指南:我曾经在一个大项目里把复合构建和多项目构建混着用,结果 Gradle 的依赖解析直接炸了。记住一条原则:复合构建里的子项目不要再用 include 引入其他模块,否则会出现循环依赖。
模块化开发的最佳实践
复合构建在模块化开发中特别有用。我总结了几条经验:
1. 按团队边界拆分
每个团队维护一个独立的 Gradle 项目。比如:
user-module/— 用户团队维护payment-module/— 支付团队维护core-module/— 基础架构团队维护
每个团队可以自由选择自己的构建策略、版本号、甚至 Gradle 版本。你想想看,这比把所有代码塞到一个仓库里灵活多了。
2. 用版本目录统一外部依赖
虽然每个项目独立,但外部依赖(比如 Retrofit、OkHttp)最好统一版本。我习惯在每个项目里都放一个 libs.versions.toml:
// my-library/gradle/libs.versions.toml
[versions]
retrofit = "2.9.0"
okhttp = "4.11.0"
[libraries]
retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
okhttp-core = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
这样每个项目引用同一份版本号,不会出现「你的 Retrofit 是 2.9.0,我的是 2.8.0」这种尴尬情况。
3. 利用依赖替换进行本地调试
这是复合构建最爽的地方。假设你在开发 payment-module,同时也在改 core-module 的代码。你只需要在 payment-module 的 settings.gradle 里加上:
// payment-module/settings.gradle.kts
includeBuild("../core-module") {
dependencySubstitution {
substitute(module("com.company:core"))
.using(project(":"))
}
}
改完 core-module 的代码,直接运行 payment-module 的测试,改动立即可见。不用发布、不用等 CI、不用改版本号。
小技巧:我一般会在根项目里建一个 composite/ 目录,里面放一个总的 settings.gradle,把所有子项目用 includeBuild 引进来。这样开发时用这个总的 settings,发布时每个项目用自己的 settings。两不耽误。
4. 注意构建缓存
复合构建的构建缓存默认是隔离的。每个子项目有自己的缓存目录。如果你想让它们共享缓存,可以在 gradle.properties 里配置:
# gradle.properties
org.gradle.caching=true
org.gradle.caching.local.enabled=true
不过要注意——共享缓存可能导致缓存冲突。我建议在 CI 上开启,本地开发时保持默认。
5. 渐进式迁移策略
如果你想把一个多项目构建迁移到复合构建,别一次性全改。我的做法是:
- 先把最独立的模块抽出来,比如
network、database这种底层模块 - 用 includeBuild 引入,验证构建是否正常
- 逐步把业务模块也抽出来
- 最后把根项目变成一个「空壳」,只负责组合
这样做的好处是——万一出问题,回滚范围很小。我曾经一次性把 20 多个模块全抽了,结果 Gradle 配置调了整整两天……
总结
复合构建不是银弹,但它解决了一个很实际的问题:如何让多个独立项目协同工作,同时保持各自的独立性。
如果你正在做一个大型项目,或者需要跨团队协作,我建议你试试复合构建。刚开始可能会觉得配置有点繁琐,但用顺手了就会发现——嗯,真香。
公众号:蓝海资料掘金营,微信deep3321