Skip to content

Commit 7b2161c

Browse files
authored
Configure japicmp classpath to avoid false positives (#7945)
1 parent bfbb54c commit 7b2161c

File tree

1 file changed

+67
-40
lines changed

1 file changed

+67
-40
lines changed

buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import com.google.auto.value.AutoValue
2+
import com.google.common.io.Files
23
import japicmp.model.*
34
import me.champeau.gradle.japicmp.JapicmpTask
45
import me.champeau.gradle.japicmp.report.Violation
@@ -22,23 +23,29 @@ val latestReleasedVersion: String by lazy {
2223
}
2324
// pick the api, since it's always there.
2425
dependencies.add(temp.name, "io.opentelemetry:opentelemetry-api:latest.release")
25-
val moduleVersion = configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion
26+
val moduleVersion =
27+
configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion
2628
configurations.remove(temp)
2729
logger.debug("Discovered latest release version: " + moduleVersion)
2830
moduleVersion
2931
}
3032

3133
class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers() {
3234
override fun maybeAddViolation(member: JApiCompatibility): Violation? {
33-
val allowableAutovalueChanges = setOf(JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS,
34-
JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS, JApiCompatibilityChangeType.ANNOTATION_ADDED)
35-
if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) }.isEmpty() &&
36-
member is JApiMethod && isAutoValueClass(member.getjApiClass()))
37-
{
35+
val allowableAutovalueChanges = setOf(
36+
JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS,
37+
JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS,
38+
JApiCompatibilityChangeType.ANNOTATION_ADDED
39+
)
40+
if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) }
41+
.isEmpty() &&
42+
member is JApiMethod && isAutoValueClass(member.getjApiClass())
43+
) {
3844
return Violation.accept(member, "Autovalue will automatically add implementation")
3945
}
4046
if (member.compatibilityChanges.isEmpty() &&
41-
member is JApiClass && isAutoValueClass(member)) {
47+
member is JApiClass && isAutoValueClass(member)
48+
) {
4249
return Violation.accept(member, "Autovalue class modification is allowed")
4350
}
4451
return null
@@ -59,57 +66,77 @@ class SourceIncompatibleRule : AbstractRecordingSeenMembers() {
5966
}
6067
}
6168

62-
/**
63-
* Locate the project's artifact of a particular version.
64-
*/
65-
fun findArtifact(version: String): File {
69+
fun getAllPublishedModules(): List<Project> {
70+
return project.rootProject.allprojects.filter {
71+
it.plugins.hasPlugin("otel.publish-conventions") && !it.hasProperty("otel.release") && it.tasks.findByName(
72+
"jar"
73+
) != null
74+
}.toList()
75+
}
76+
77+
fun getOldClassPath(version: String): List<File> {
78+
// Temporarily change the group name because we want to fetch an artifact with the same
79+
// Maven coordinates as the project, which Gradle would not allow otherwise.
6680
val existingGroup = group
81+
group = "virtual_group"
6782
try {
68-
// Temporarily change the group name because we want to fetch an artifact with the same
69-
// Maven coordinates as the project, which Gradle would not allow otherwise.
70-
group = "virtual_group"
71-
val depModule = "io.opentelemetry:${base.archivesName.get()}:$version@jar"
72-
val depJar = "${base.archivesName.get()}-$version.jar"
73-
val configuration: Configuration = configurations.detachedConfiguration(
74-
dependencies.create(depModule),
75-
)
76-
return files(configuration.files).filter {
77-
it.name.equals(depJar)
78-
}.singleFile
83+
return getAllPublishedModules().map {
84+
val depModule = "io.opentelemetry:${it.base.archivesName.get()}:$version@jar"
85+
val depJar = "${it.base.archivesName.get()}-$version.jar"
86+
val configuration: Configuration = configurations.detachedConfiguration(
87+
dependencies.create(depModule),
88+
)
89+
files(configuration.files).filter { file ->
90+
file.name.equals(depJar)
91+
}.singleFile
92+
}.toList()
7993
} finally {
8094
group = existingGroup
8195
}
8296
}
8397

98+
fun getNewClassPath(): List<File> {
99+
return getAllPublishedModules().map {
100+
val archiveFile = it.tasks.getByName<Jar>("jar").archiveFile
101+
archiveFile.get().asFile
102+
}.toList()
103+
}
104+
84105
// generate the api diff report for any module that is stable and publishes a jar.
85106
if (!project.hasProperty("otel.release") && !project.name.startsWith("bom")) {
86107
afterEvaluate {
87108
tasks {
88109
val jApiCmp by registering(JapicmpTask::class) {
89-
dependsOn("jar")
110+
// Depends on jar task for all published modules. See notes below.
111+
getAllPublishedModules().forEach {
112+
dependsOn(it.tasks.getByName("jar"))
113+
}
90114

91115
// the japicmp "new" version is either the user-specified one, or the locally built jar.
92116
val apiNewVersion: String? by project
93-
val newArtifact = apiNewVersion?.let { findArtifact(it) }
94-
?: file(getByName<Jar>("jar").archiveFile)
95-
newClasspath.from(files(newArtifact))
96-
97-
// only output changes, not everything
98-
onlyModified.set(true)
99-
100117
// the japicmp "old" version is either the user-specified one, or the latest release.
101118
val apiBaseVersion: String? by project
102119
val baselineVersion = apiBaseVersion ?: latestReleasedVersion
103-
oldClasspath.from(
104-
try {
105-
files(findArtifact(baselineVersion))
106-
} catch (e: Exception) {
107-
// if we can't find the baseline artifact, this is probably one that's never been published before,
108-
// so publish the whole API. We do that by flipping this flag, and comparing the current against nothing.
109-
onlyModified.set(false)
110-
files()
111-
},
112-
)
120+
121+
// Setup new and old classpath, new and old archives.
122+
// New and old classpaths are set to the set of all artifacts published by this project, at
123+
// the appropriate version. Without this, japicmp is unable to resolve inheritance across module
124+
// boundaries (i.e. Span from opentelemetry-api extends ImplicitContextKeyed from opentelemetry-context)
125+
// and generates false positive compatibility errors when methods are lifted into superclasses,
126+
// superinterfaces.
127+
val archiveName = base.archivesName.get()
128+
val newClassPath = getNewClassPath()
129+
val oldClassPath = getOldClassPath(baselineVersion)
130+
val pattern = (archiveName + "-([0-9\\.]*)(-SNAPSHOT)?.jar").toRegex()
131+
val newArchive = newClassPath.singleOrNull { it.name.matches(pattern) }
132+
val oldArchive = oldClassPath.singleOrNull { it.name.matches(pattern) }
133+
newClasspath.from(newClassPath)
134+
oldClasspath.from(oldClassPath)
135+
newArchives.from(newArchive)
136+
oldArchives.from(oldArchive)
137+
138+
// Only generate API diff for changes.
139+
onlyModified.set(true)
113140

114141
// Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130
115142
// with some changes.

0 commit comments

Comments
 (0)