根项目自定义约定插件为何覆盖子项目插件的传递依赖?
构建环境设置
项目结构如下:
. ├── gradle │ ├── build-logic │ │ ├── buf-convention │ │ │ ├── build.gradle.kts │ │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── buf-check-convention.gradle.kts │ │ ├── jib-convention │ │ │ ├── build.gradle.kts │ │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ └── jib-configuration-convention.gradle.kts │ │ └── settings.gradle.kts ├── subproject-with-buf │ ├── build.gradle.kts │ └── src │ └── main │ └── proto │ └── com │ └── example │ └── constants.proto ├── build.gradle.kts └── settings.gradle.kts
build-logic包含两个Gradle约定插件:
- 适配Google Jib Gradle插件的约定
- 适配Buf Gradle插件的约定
Jib插件约定配置
// gradle/build-logic/jib-convention/build.gradle.kts plugins { `kotlin-dsl` } repositories { gradlePluginPortal() } dependencies { implementation("com.google.cloud.tools:jib-gradle-plugin:3.4.3") } // gradle/build-logic/jib-convention/src/main/kotlin/jib-configuration-convention.gradle.kts plugins { id("com.google.cloud.tools.jib") } jib { from { image = "gcr.io/distroless/java21-debian12:debug" } }
Buf插件约定配置
// gradle/build-logic/buf-convention/build.gradle.kts plugins { `kotlin-dsl` } repositories { gradlePluginPortal() } dependencies { implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.4") implementation("build.buf:buf-gradle-plugin:0.10.0") } // gradle/build-logic/buf-convention/src/main/kotlin/buf-check-convention.gradle.kts plugins { id("java") id("com.google.protobuf") id("build.buf") id("maven-publish") } buf { publishSchema = true previousVersion = "0.0.1-SNAPSHOT" imageArtifact { groupId = "com.buf.image" artifactId = "app" version = "0.0.1-SNAPSHOT" } } val repoUri = uri("file://${rootProject.projectDir}/gradle/buf-repo") repositories { mavenCentral() maven { url = repoUri } } publishing { repositories { maven { url = repoUri name = "LocalArtifacts" } } }
重要说明:Jib Gradle插件v3.4.3依赖Jackson v2.15.2,而Buf Gradle插件v0.10.0要求类路径中使用Jackson v2.17.2。
根项目settings.gradle.kts配置:
rootProject.name = "gradle-overrides-jackson-with-lower-version" include("subproject-with-buf") pluginManagement { includeBuild("gradle/build-logic") }
问题描述
根项目应用jib-configuration-convention,子项目subproject-with-buf应用buf-check-convention时,执行./gradlew :subproject-with-buf:writeWorkspaceYaml抛出java.lang.NoSuchMethodError,错误栈如下:
java.lang.NoSuchMethodError: 'void com.fasterxml.jackson.core.base.GeneratorBase.<init>(int, com.fasterxml.jackson.core.ObjectCodec, com.fasterxml.jackson.core.io.IOContext)' at com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.<init>(YAMLGenerator.java:299) at com.fasterxml.jackson.dataformat.yaml.YAMLFactory._createGenerator(YAMLFactory.java:532) at com.fasterxml.jackson.dataformat.yaml.YAMLFactory.createGenerator(YAMLFactory.java:481) at com.fasterxml.jackson.dataformat.yaml.YAMLFactory.createGenerator(YAMLFactory.java:15) at com.fasterxml.jackson.databind.ObjectMapper.createGenerator(ObjectMapper.java:1215) at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3964) at build.buf.gradle.BufYamlGenerator.generate(BufYamlGenerator.kt:43)
插件应用方式:
// 根项目build.gradle.kts plugins { id("java") id("jib-configuration-convention") } // subproject-with-buf/build.gradle.kts plugins { id("buf-check-convention") } sourceSets { main { proto { srcDirs("src/main/proto") } } }
测试用proto文件:
// subproject-with-buf/src/main/proto/com/example/constants.proto syntax = "proto3"; package com.example; enum Boolean { FALSE = 0; TRUE = 1; }
复现步骤
- 按上述结构创建项目
- 将Buf插件版本改为v0.9.1,执行
./gradlew :subproject-with-buf:publishBufImagePublicationPublicationToLocalArtifactsRepository填充本地Maven仓库 - 切换回Buf插件v0.10.0,执行
./gradlew :subproject-with-buf:writeWorkspaceYaml --stacktrace即可触发错误
调试尝试
- 自定义任务查看类路径,显示子项目buildscript类路径中的Jackson为v2.17.2,符合预期:
allprojects { tasks.register("printClasspath") { doLast { buildscript.configurations .named("classpath").get() .asFileTree .filter { it.name.contains("jackson")} .forEach { println(it) } } } }
- 直接在项目中应用Jib插件而非通过约定插件时,问题消失
原因分析
Gradle的构建脚本类路径采用父项目优先的加载机制:
- 根项目应用
jib-configuration-convention时,约定插件依赖的Jib插件会将Jackson v2.15.2引入根项目的buildscript类路径 - 子项目的buildscript类路径继承自根项目,类加载器会优先加载根项目类路径中的低版本Jackson
- 虽然Buf约定插件依赖高版本Jackson,但类加载时已经加载了低版本,导致Buf插件调用不存在的方法
直接应用Jib插件时,Gradle会在同一个项目的buildscript依赖解析中自动处理版本冲突,选择更高版本的Jackson;但通过约定插件时,根项目先加载低版本,子项目无法覆盖。
解决方法
方法1:规范约定插件的依赖声明(推荐)
在jib-convention的build.gradle.kts中,将implementation改为compileOnly依赖Jib插件:
// gradle/build-logic/jib-convention/build.gradle.kts dependencies { compileOnly("com.google.cloud.tools:jib-gradle-plugin:3.4.3") }
这样约定插件只会引入Jib插件的API,不会将其传递依赖(如Jackson)打包到约定插件的类路径中。当项目应用约定插件时,Gradle会自动解析Jib插件的依赖,并通过自身的版本冲突解决机制选择合适的Jackson版本。
方法2:强制统一Jackson版本
在根项目的build.gradle.kts中添加buildscript依赖约束,强制所有Jackson依赖使用v2.17.2:
buildscript { dependencies { constraints { implementation("com.fasterxml.jackson.core:jackson-core:2.17.2") implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2") implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2") } } }
此方法直接强制替换所有Jackson版本,确保Buf插件能使用兼容的版本,但需要同步维护版本号。
方法3:避免全局应用Jib约定插件
如果只有部分子项目需要Jib插件,仅在这些子项目中应用jib-configuration-convention,不要在根项目全局应用,这样根项目的buildscript类路径不会引入低版本Jackson,子项目的Buf插件可正常使用高版本依赖。
内容的提问来源于stack exchange,提问作者Kirill

