From 0d390d43e24004f9a6708b68cb19a9d04b199c18 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 31 Dec 2025 10:49:54 -0600 Subject: [PATCH 1/3] Configure japicmp classpath to avoid false positives --- .../java/io/opentelemetry/api/trace/Span.java | 11 +- .../opentelemetry/api/trace/SpanParent.java | 15 +++ .../otel.japicmp-conventions.gradle.kts | 107 +++++++++++------- .../current_vs_latest/opentelemetry-api.txt | 9 +- .../opentelemetry-sdk-trace.txt | 4 + 5 files changed, 95 insertions(+), 51 deletions(-) create mode 100644 api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java index 708bb1de077..b0a2efbb509 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java @@ -26,7 +26,7 @@ *

{@code Span} must be ended by calling {@link #end()}. */ @ThreadSafe -public interface Span extends ImplicitContextKeyed { +public interface Span extends ImplicitContextKeyed, SpanParent { /** * Returns the {@link Span} from the current {@link Context}, falling back to a default, no-op @@ -339,15 +339,6 @@ default Span recordException(Throwable exception) { return recordException(exception, Attributes.empty()); } - /** - * Records information about the {@link Throwable} to the {@link Span}. - * - * @param exception the {@link Throwable} to record. - * @param additionalAttributes the additional {@link Attributes} to record. - * @return this. - */ - Span recordException(Throwable exception, Attributes additionalAttributes); - /** * Updates the {@code Span} name. * diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java new file mode 100644 index 00000000000..220315d2027 --- /dev/null +++ b/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java @@ -0,0 +1,15 @@ +package io.opentelemetry.api.trace; + +import io.opentelemetry.api.common.Attributes; + +public interface SpanParent { + + /** + * Records information about the {@link Throwable} to the {@link Span}. + * + * @param exception the {@link Throwable} to record. + * @param additionalAttributes the additional {@link Attributes} to record. + * @return this. + */ + Span recordException(Throwable exception, Attributes additionalAttributes); +} diff --git a/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts index e44372bd6bc..4dc37286f3f 100644 --- a/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts @@ -1,4 +1,5 @@ import com.google.auto.value.AutoValue +import com.google.common.io.Files import japicmp.model.* import me.champeau.gradle.japicmp.JapicmpTask import me.champeau.gradle.japicmp.report.Violation @@ -22,7 +23,8 @@ val latestReleasedVersion: String by lazy { } // pick the api, since it's always there. dependencies.add(temp.name, "io.opentelemetry:opentelemetry-api:latest.release") - val moduleVersion = configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion + val moduleVersion = + configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion configurations.remove(temp) logger.debug("Discovered latest release version: " + moduleVersion) moduleVersion @@ -30,15 +32,20 @@ val latestReleasedVersion: String by lazy { class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers() { override fun maybeAddViolation(member: JApiCompatibility): Violation? { - val allowableAutovalueChanges = setOf(JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS, - JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS, JApiCompatibilityChangeType.ANNOTATION_ADDED) - if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) }.isEmpty() && - member is JApiMethod && isAutoValueClass(member.getjApiClass())) - { + val allowableAutovalueChanges = setOf( + JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS, + JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS, + JApiCompatibilityChangeType.ANNOTATION_ADDED + ) + if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) } + .isEmpty() && + member is JApiMethod && isAutoValueClass(member.getjApiClass()) + ) { return Violation.accept(member, "Autovalue will automatically add implementation") } if (member.compatibilityChanges.isEmpty() && - member is JApiClass && isAutoValueClass(member)) { + member is JApiClass && isAutoValueClass(member) + ) { return Violation.accept(member, "Autovalue class modification is allowed") } return null @@ -59,57 +66,77 @@ class SourceIncompatibleRule : AbstractRecordingSeenMembers() { } } -/** - * Locate the project's artifact of a particular version. - */ -fun findArtifact(version: String): File { +fun getAllPublishedModules(): List { + return project.rootProject.allprojects.filter { + it.plugins.hasPlugin("otel.publish-conventions") && !it.hasProperty("otel.release") && it.tasks.findByName( + "jar" + ) != null + }.toList() +} + +fun getOldClassPath(version: String): List { + // Temporarily change the group name because we want to fetch an artifact with the same + // Maven coordinates as the project, which Gradle would not allow otherwise. val existingGroup = group + group = "virtual_group" try { - // Temporarily change the group name because we want to fetch an artifact with the same - // Maven coordinates as the project, which Gradle would not allow otherwise. - group = "virtual_group" - val depModule = "io.opentelemetry:${base.archivesName.get()}:$version@jar" - val depJar = "${base.archivesName.get()}-$version.jar" - val configuration: Configuration = configurations.detachedConfiguration( - dependencies.create(depModule), - ) - return files(configuration.files).filter { - it.name.equals(depJar) - }.singleFile + return getAllPublishedModules().map { + val depModule = "io.opentelemetry:${it.base.archivesName.get()}:$version@jar" + val depJar = "${it.base.archivesName.get()}-$version.jar" + val configuration: Configuration = configurations.detachedConfiguration( + dependencies.create(depModule), + ) + files(configuration.files).filter { file -> + file.name.equals(depJar) + }.singleFile + }.toList() } finally { group = existingGroup } } +fun getNewClassPath(): List { + return getAllPublishedModules().map { + val archiveFile = it.tasks.getByName("jar").archiveFile + archiveFile.get().asFile + }.toList() +} + // generate the api diff report for any module that is stable and publishes a jar. if (!project.hasProperty("otel.release") && !project.name.startsWith("bom")) { afterEvaluate { tasks { val jApiCmp by registering(JapicmpTask::class) { - dependsOn("jar") + // Depends on jar task for all published modules. See notes below. + getAllPublishedModules().forEach { + dependsOn(it.tasks.getByName("jar")) + } // the japicmp "new" version is either the user-specified one, or the locally built jar. val apiNewVersion: String? by project - val newArtifact = apiNewVersion?.let { findArtifact(it) } - ?: file(getByName("jar").archiveFile) - newClasspath.from(files(newArtifact)) - - // only output changes, not everything - onlyModified.set(true) - // the japicmp "old" version is either the user-specified one, or the latest release. val apiBaseVersion: String? by project val baselineVersion = apiBaseVersion ?: latestReleasedVersion - oldClasspath.from( - try { - files(findArtifact(baselineVersion)) - } catch (e: Exception) { - // if we can't find the baseline artifact, this is probably one that's never been published before, - // so publish the whole API. We do that by flipping this flag, and comparing the current against nothing. - onlyModified.set(false) - files() - }, - ) + + // Setup new and old classpath, new and old archives. + // New and old classpaths are set to the set of all artifacts published by this project, at + // the appropriate version. Without this, japicmp is unable to resolve inheritance across module + // boundaries (i.e. Span from opentelemetry-api extends ImplicitContextKeyed from opentelemetry-context) + // and generates false positive compatibility errors when methods are lifted into superclasses, + // superinterfaces. + val archiveName = base.archivesName.get() + val newClassPath = getNewClassPath() + val oldClassPath = getOldClassPath(baselineVersion) + val pattern = (archiveName + "-([0-9\\.]*)(-SNAPSHOT)?.jar").toRegex() + val newArchive = newClassPath.singleOrNull { it.name.matches(pattern) } + val oldArchive = oldClassPath.singleOrNull { it.name.matches(pattern) } + newClasspath.from(newClassPath) + oldClasspath.from(oldClassPath) + newArchives.from(newArchive) + oldArchives.from(oldArchive) + + // Only generate API diff for changes. + onlyModified.set(true) // Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130 // with some changes. diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index f8ee836f8fe..d3be78b5976 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,2 +1,9 @@ Comparing source compatibility of opentelemetry-api-1.58.0-SNAPSHOT.jar against opentelemetry-api-1.57.0.jar -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.Span (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW INTERFACE: io.opentelemetry.api.trace.SpanParent + --- REMOVED METHOD: PUBLIC(-) ABSTRACT(-) io.opentelemetry.api.trace.Span recordException(java.lang.Throwable, io.opentelemetry.api.common.Attributes) ++++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.SpanParent (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.Span recordException(java.lang.Throwable, io.opentelemetry.api.common.Attributes) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index ab607b7bb6d..f3a7800bf54 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -6,6 +6,10 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder setMeterProvider(java.util.function.Supplier) +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.ReadWriteSpan (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + ===! UNCHANGED INTERFACE: io.opentelemetry.api.trace.Span + +++ NEW INTERFACE: io.opentelemetry.api.trace.SpanParent *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(java.util.function.Supplier) From 4b63276d80b854a42fa74d375094e4d740369809 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 31 Dec 2025 11:04:23 -0600 Subject: [PATCH 2/3] Fix build --- .../main/java/io/opentelemetry/api/trace/SpanParent.java | 5 +++++ .../io/opentelemetry/opencensusshim/DelegatingSpanTest.java | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java index 220315d2027..9b2952a82ca 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.api.trace; import io.opentelemetry.api.common.Attributes; diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java index 2843ed89061..01c8a1ce6bb 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java @@ -63,9 +63,9 @@ void verifyAllMethodsAreUnderTest() { assertThat(methods) .describedAs("all interface methods are being tested") .containsAll(allInterfaceMethods(Span.class)); - assertThat(allInterfaceMethods(Span.class)) - .describedAs("all tested methods are on the Span interface") - .containsAll(methods); + // assertThat(allInterfaceMethods(Span.class)) + // .describedAs("all tested methods are on the Span interface") + // .containsAll(methods); } @ParameterizedTest From 973e2174f2050bfe3fb8f48dfc459f3b27badc05 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Wed, 31 Dec 2025 14:58:26 -0600 Subject: [PATCH 3/3] Revert demonstration bits --- .../java/io/opentelemetry/api/trace/Span.java | 11 +++++++++- .../opentelemetry/api/trace/SpanParent.java | 20 ------------------- .../current_vs_latest/opentelemetry-api.txt | 9 +-------- .../opentelemetry-sdk-trace.txt | 4 ---- .../opencensusshim/DelegatingSpanTest.java | 6 +++--- 5 files changed, 14 insertions(+), 36 deletions(-) delete mode 100644 api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java index b0a2efbb509..708bb1de077 100644 --- a/api/all/src/main/java/io/opentelemetry/api/trace/Span.java +++ b/api/all/src/main/java/io/opentelemetry/api/trace/Span.java @@ -26,7 +26,7 @@ *

{@code Span} must be ended by calling {@link #end()}. */ @ThreadSafe -public interface Span extends ImplicitContextKeyed, SpanParent { +public interface Span extends ImplicitContextKeyed { /** * Returns the {@link Span} from the current {@link Context}, falling back to a default, no-op @@ -339,6 +339,15 @@ default Span recordException(Throwable exception) { return recordException(exception, Attributes.empty()); } + /** + * Records information about the {@link Throwable} to the {@link Span}. + * + * @param exception the {@link Throwable} to record. + * @param additionalAttributes the additional {@link Attributes} to record. + * @return this. + */ + Span recordException(Throwable exception, Attributes additionalAttributes); + /** * Updates the {@code Span} name. * diff --git a/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java b/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java deleted file mode 100644 index 9b2952a82ca..00000000000 --- a/api/all/src/main/java/io/opentelemetry/api/trace/SpanParent.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.api.trace; - -import io.opentelemetry.api.common.Attributes; - -public interface SpanParent { - - /** - * Records information about the {@link Throwable} to the {@link Span}. - * - * @param exception the {@link Throwable} to record. - * @param additionalAttributes the additional {@link Attributes} to record. - * @return this. - */ - Span recordException(Throwable exception, Attributes additionalAttributes); -} diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt index d3be78b5976..f8ee836f8fe 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-api.txt @@ -1,9 +1,2 @@ Comparing source compatibility of opentelemetry-api-1.58.0-SNAPSHOT.jar against opentelemetry-api-1.57.0.jar -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.api.trace.Span (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - +++ NEW INTERFACE: io.opentelemetry.api.trace.SpanParent - --- REMOVED METHOD: PUBLIC(-) ABSTRACT(-) io.opentelemetry.api.trace.Span recordException(java.lang.Throwable, io.opentelemetry.api.common.Attributes) -+++ NEW INTERFACE: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.SpanParent (not serializable) - +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. - +++ NEW SUPERCLASS: java.lang.Object - +++ NEW METHOD: PUBLIC(+) ABSTRACT(+) io.opentelemetry.api.trace.Span recordException(java.lang.Throwable, io.opentelemetry.api.common.Attributes) +No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt index f3a7800bf54..ab607b7bb6d 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-trace.txt @@ -6,10 +6,6 @@ Comparing source compatibility of opentelemetry-sdk-trace-1.58.0-SNAPSHOT.jar ag *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.export.SimpleSpanProcessorBuilder setMeterProvider(java.util.function.Supplier) -*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.trace.ReadWriteSpan (not serializable) - === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 - ===! UNCHANGED INTERFACE: io.opentelemetry.api.trace.Span - +++ NEW INTERFACE: io.opentelemetry.api.trace.SpanParent *** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.sdk.trace.SdkTracerProviderBuilder (not serializable) === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 +++ NEW METHOD: PUBLIC(+) io.opentelemetry.sdk.trace.SdkTracerProviderBuilder setMeterProvider(java.util.function.Supplier) diff --git a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java index 01c8a1ce6bb..2843ed89061 100644 --- a/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java +++ b/opencensus-shim/src/test/java/io/opentelemetry/opencensusshim/DelegatingSpanTest.java @@ -63,9 +63,9 @@ void verifyAllMethodsAreUnderTest() { assertThat(methods) .describedAs("all interface methods are being tested") .containsAll(allInterfaceMethods(Span.class)); - // assertThat(allInterfaceMethods(Span.class)) - // .describedAs("all tested methods are on the Span interface") - // .containsAll(methods); + assertThat(allInterfaceMethods(Span.class)) + .describedAs("all tested methods are on the Span interface") + .containsAll(methods); } @ParameterizedTest