Consider the following project setup:
.
├── 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
The build-logic
project basically contains 2 Gradle "conventions":
Here is the convention for Jib:
// build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
dependencies {
implementation("com.google.cloud.tools:jib-gradle-plugin:3.4.3")
}
// jib-configuration-convention.gradle.kts
plugins {
id("com.google.cloud.tools.jib")
}
jib {
from {
image = "gcr.io/distroless/java21-debian12:debug"
}
}
and the convention for Buf:
// 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.9.1") // <-- use this version for :subproject-with-buf:publishBufImagePublicationPublicationToLocalArtifactsRepository task
implementation("build.buf:buf-gradle-plugin:0.10.0")
}
// 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"
}
}
}
Important note: the Jib Gradle plugin v3.4.3 relies on Jackson v2.15.2, while Buf Gradle plugin v0.10.0 expects Jackson v2.17.2 in classpath.
The software project itself consists of the root Gradle project + subproject-with-buf
Gradle subporoject.
// settings.gradle.kts
rootProject.name = "gradle-overrides-jackson-with-lower-version"
include("subproject-with-buf")
pluginManagement {
includeBuild("gradle/build-logic")
}
The problem is: if jib-configuration-convention
gets applied to the root project and the buf-check-convention
is applied to subproject-with-buf
, then there is a java.lang.NoSuchMethodError
on attempt to execute ./gradlew :subproject-with-buf:writeWorkspaceYaml
(as if Buf Gradle plugin encountered Jackson 2.15.2 in classpath while executing writeWorkspaceYaml
task).
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)
Why does the Jib Gradle plugin coming from my custom conventional plugin applied to the root project overrides transitive dependency of Buf plugin in subproject and how to avoid that?
Here is how plugins get applied to Gradle projects:
// 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")
}
}
}
There is also a minimal .proto
source required to reproduce the issue at subproject-with-buf/src/main/proto/com/example/constants.proto
:
syntax = "proto3";
package com.example;
enum Boolean {
FALSE = 0;
TRUE = 1;
}
./gradlew :subproject-with-buf:publishBufImagePublicationPublicationToLocalArtifactsRepository
to populate local Maven repo with the reference snapshot (unfortunately Buf plugin v0.10.0 is too eager at resolving dependencies, so this step is needed)./gradlew :subproject-with-buf:writeWorkspaceYaml --stacktrace
, you will see the java.lang.NoSuchMethodError
./gradlew :subproject-with-buf:printClasspath
:allprojects {
tasks.register("printClasspath") {
doLast {
buildscript.configurations
.named("classpath").get()
.asFileTree
.filter { it.name.contains("jackson")}
.forEach { println(it) }
}
}
}
The classpath of the root project's build script is put to a classloader.
This classloader is the parent of the classloader for the sub project's build script.
And due to usual classloader logic, a classloader first asks its parent classloader for a class before resolving it self.
So you cannot avoid this overwriting, except by applying the Jib plugin not to the root project, but maybe to a sibling project of the other subproject.
Or if Jib could also cope with the newer Jackson version, then you could add the Buf plugin with apply false
to the root project, as then conflict resolution which is done within one classpath kicks in and selects the newer Jackson version that then both can use.