根项目 subprojects { version = '1.0' } 不生效,因 gradle 的 project.version 是只读属性,插件在 afterevaluate 前已读取并锁定;必须在各模块 build.gradle 中显式设置或通过 versioncatalog/libs.versions.toml 统一管理。

模块版本不能靠 version 属性全局覆盖 —— 每个模块的 version 必须在各自 build.gradle 里显式声明,或通过 settings.gradle + versionCatalog 精确控制。
为什么根项目的 subprojects { version = '1.0' } 不生效
Gradle 的 version 是 Project 对象的一个只读属性(final),一旦被首次访问(比如被插件读取、生成 jar 名称时),再赋值就无效。很多插件(如 java-library、org.springframework.boot)会在配置阶段早期读取它,此时 subprojects 块还没执行完,导致子模块实际用的是默认 unspecified 或空字符串,最终打包出的 jar 名为 module-name-.jar。
- 现象:
./gradlew :core:build生成的 jar 是core-.jar,不是core-1.0.jar - 根本原因:插件在
afterEvaluate之前已锁定project.version - 替代方案:必须在子模块自己的
build.gradle中直接写死或从外部读取,不能依赖父级统一赋值
推荐做法:用 libs.versions.toml 统一管理 + 各模块按需引用
Gradle 7.0+ 官方推荐的 versionCatalog 是目前最健壮的方式,它不修改 project.version,而是把版本号作为命名空间变量提供,由各模块自行决定是否用于 version 属性。
- 在
gradle/libs.versions.toml中定义模块版本:[versions] core = "1.2.0" web = "2.5.1" service = "3.0.0" [libraries] core-module = { module = "com.example:core", version.ref = "core" } web-module = { module = "com.example:web", version.ref = "web" } - 在
core/build.gradle中显式设置:version = libs.versions.core.get()
- 在
web/build.gradle中:version = libs.versions.web.get()
- 这样既保证了集中管理,又避免了
subprojects赋值时机问题
如果不用 versionCatalog,buildSrc 是唯一靠谱的替代
用 buildSrc 写 Kotlin/Java 类暴露静态常量,比 Groovy 的 ext 变量更安全、有类型检查、支持 IDE 跳转,且不会因加载顺序出错。
- 路径:
buildSrc/src/main/kotlin/Versions.kt - 内容:
object Versions { const val core = "1.2.0" const val web = "2.5.1" } - 子模块中使用:
version = Versions.core
- 注意:
buildSrc会被 Gradle 自动编译并加入 classpath,无需apply from,也不受 Groovy 脚本解析顺序影响
千万别用 ext 全局变量设 version
虽然网上很多教程这么写,但在多模块中极易失效:
-
root/build.gradle里写ext.coreVersion = '1.2.0' -
core/build.gradle里写version = rootProject.ext.coreVersion - 问题:如果某个子模块没声明
apply plugin: 'java'或插件加载顺序异常,rootProject.ext可能还不可见 - 更糟的是:IDE(如 Android Studio)无法识别这种跨文件引用,改了
ext值后不提示重载,容易误以为生效了
真正要分别设置模块版本,关键不在“怎么写”,而在“什么时候被读取”——所有方案都必须确保 version 在插件初始化前就确定,否则打包名、Maven 发布坐标、依赖解析都会出错。











