Skip to content

Commit 492af6c

Browse files
authored
Add japicmp (open-telemetry#2218)
1 parent f3a54dd commit 492af6c

File tree

6 files changed

+219
-15
lines changed

6 files changed

+219
-15
lines changed

.github/workflows/build-common.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ jobs:
5656
- name: Build
5757
run: ./gradlew build -x spotlessCheck -x test ${{ inputs.no-build-cache && '--no-build-cache' || '' }}
5858

59+
- name: Check for jApiCmp diffs
60+
# The jApiCmp diff compares current to latest, which isn't appropriate for release branches
61+
if: ${{ !startsWith(github.ref_name, 'release/') && !startsWith(github.base_ref, 'release/') }}
62+
run: |
63+
# need to "git add" in case any generated files did not already exist
64+
git add docs/apidiffs
65+
if git diff --cached --quiet
66+
then
67+
echo "No diff detected."
68+
else
69+
echo "Diff detected - did you run './gradlew jApiCmp'?"
70+
git diff --cached --name-only
71+
git diff --cached
72+
exit 1
73+
fi
74+
5975
test:
6076
name: Test
6177
runs-on: ${{ matrix.os }}

.github/workflows/release.yml

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,15 @@ jobs:
173173
174174
echo "version=$VERSION" >> $GITHUB_OUTPUT
175175
176-
merge-change-log-to-main:
176+
post-release-updates:
177177
permissions:
178178
contents: write # for git push to PR branch
179179
runs-on: ubuntu-latest
180180
needs:
181181
- release
182182
steps:
183+
# add change log sync (if any) into this PR since the apidiff update
184+
# is required before any other PR can be merged anyway
183185
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
184186

185187
- name: Copy change log section from release branch
@@ -201,6 +203,37 @@ jobs:
201203
release_date=$(gh release view v$VERSION --json publishedAt --jq .publishedAt | sed 's/T.*//')
202204
RELEASE_DATE=$release_date .github/scripts/merge-change-log-after-release.sh
203205
206+
- name: Wait for release to be available in maven central
207+
env:
208+
VERSION: ${{ needs.release.outputs.version }}
209+
run: |
210+
until curl --silent \
211+
--show-error \
212+
--output /dev/null \
213+
--head \
214+
--fail \
215+
https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-aws-xray/$VERSION/opentelemetry-aws-xray-$VERSION.jar
216+
do
217+
sleep 60
218+
done
219+
220+
- name: Set up JDK for running Gradle
221+
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
222+
with:
223+
distribution: temurin
224+
java-version: 17
225+
226+
- name: Set up Gradle
227+
uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2
228+
229+
- name: Update apidiff baseline
230+
env:
231+
VERSION: ${{ needs.release.outputs.version }}
232+
PRIOR_VERSION: ${{ needs.release.outputs.prior-version }}
233+
run: |
234+
./gradlew japicmp -PapiBaseVersion=$PRIOR_VERSION -PapiNewVersion=$VERSION
235+
./gradlew --refresh-dependencies japicmp
236+
204237
- name: Use CLA approved bot
205238
run: .github/scripts/use-cla-approved-bot.sh
206239

@@ -216,22 +249,14 @@ jobs:
216249
# not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
217250
GH_TOKEN: ${{ steps.otelbot-token.outputs.token }}
218251
run: |
219-
if git diff --quiet; then
220-
if [[ $VERSION == *.0 ]]; then
221-
echo there are no updates to merge, not creating pull request
222-
exit 0 # success
223-
else
224-
echo patch release notes did not get applied for some reason
225-
exit 1 # failure
226-
fi
227-
fi
228-
229-
message="Merge change log updates from $GITHUB_REF_NAME"
230-
body="Merge log updates from \`$GITHUB_REF_NAME\`."
231-
branch="otelbot/merge-change-log-updates-from-${GITHUB_REF_NAME//\//-}"
252+
message="Post-release updates for $VERSION"
253+
body="Post-release updates for `$VERSION`."
254+
branch="otelbot/update-apidiff-baseline-to-released-version-${VERSION}"
232255
233256
git checkout -b $branch
234-
git commit -a -m "$message"
257+
git add CHANGELOG.md
258+
git add docs/apidiffs
259+
git commit -m "$message"
235260
git push --set-upstream origin $branch
236261
gh pr create --title "$message" \
237262
--body "$body" \

buildSrc/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dependencies {
1818
implementation("org.owasp:dependency-check-gradle:12.1.3")
1919
implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1")
2020
implementation("com.gradle:develocity-gradle-plugin:4.1.1")
21+
implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6")
22+
implementation("com.google.auto.value:auto-value-annotations:1.11.0")
2123
}
2224

2325
spotless {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import com.google.auto.value.AutoValue
2+
import japicmp.model.*
3+
import me.champeau.gradle.japicmp.JapicmpTask
4+
import me.champeau.gradle.japicmp.report.Violation
5+
import me.champeau.gradle.japicmp.report.stdrules.*
6+
7+
8+
plugins {
9+
base
10+
11+
id("me.champeau.gradle.japicmp")
12+
}
13+
14+
/**
15+
* The latest *released* version of the project. Evaluated lazily so the work is only done if necessary.
16+
*/
17+
val latestReleasedVersion: String by lazy {
18+
// hack to find the current released version of the project
19+
val temp: Configuration = configurations.create("tempConfig") {
20+
resolutionStrategy.cacheChangingModulesFor(0, "seconds")
21+
resolutionStrategy.cacheDynamicVersionsFor(0, "seconds")
22+
}
23+
// pick aws-xray, since it's a stable module that's always there.
24+
dependencies.add(temp.name, "io.opentelemetry.contrib:opentelemetry-aws-xray:latest.release")
25+
val moduleVersion = configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion
26+
configurations.remove(temp)
27+
logger.debug("Discovered latest release version: " + moduleVersion)
28+
moduleVersion
29+
}
30+
31+
class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers() {
32+
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+
{
38+
return Violation.accept(member, "Autovalue will automatically add implementation")
39+
}
40+
if (member.compatibilityChanges.isEmpty() &&
41+
member is JApiClass && isAutoValueClass(member)) {
42+
return Violation.accept(member, "Autovalue class modification is allowed")
43+
}
44+
return null
45+
}
46+
47+
fun isAutoValueClass(japiClass: JApiClass): Boolean {
48+
return japiClass.newClass.get().getAnnotation(AutoValue::class.java) != null ||
49+
japiClass.newClass.get().getAnnotation(AutoValue.Builder::class.java) != null
50+
}
51+
}
52+
53+
class SourceIncompatibleRule : AbstractRecordingSeenMembers() {
54+
override fun maybeAddViolation(member: JApiCompatibility): Violation? {
55+
if (!member.isSourceCompatible()) {
56+
return Violation.error(member, "Not source compatible: $member")
57+
}
58+
return null
59+
}
60+
}
61+
62+
/**
63+
* Locate the project's artifact of a particular version.
64+
*/
65+
fun findArtifact(version: String): File {
66+
val existingGroup = group
67+
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.contrib:${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
79+
} finally {
80+
group = existingGroup
81+
}
82+
}
83+
84+
// generate the api diff report for any module that is stable and publishes a jar.
85+
if (project.findProperty("otel.stable") == "true" && !project.name.startsWith("bom")) {
86+
afterEvaluate {
87+
tasks {
88+
val jApiCmp by registering(JapicmpTask::class) {
89+
dependsOn("jar")
90+
91+
// the japicmp "new" version is either the user-specified one, or the locally built jar.
92+
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+
100+
// the japicmp "old" version is either the user-specified one, or the latest release.
101+
val apiBaseVersion: String? by project
102+
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+
)
113+
114+
// Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130
115+
// with some changes.
116+
val exclusions = mutableListOf<String>()
117+
// Generics are not detected correctly
118+
exclusions.add("CLASS_GENERIC_TEMPLATE_CHANGED")
119+
// Allow new default methods on interfaces
120+
exclusions.add("METHOD_NEW_DEFAULT")
121+
// Allow adding default implementations for default methods
122+
exclusions.add("METHOD_ABSTRACT_NOW_DEFAULT")
123+
// Bug prevents recognizing default methods of superinterface.
124+
// Fixed in https://github.com/siom79/japicmp/pull/343 but not yet available in me.champeau.gradle.japicmp
125+
exclusions.add("METHOD_ABSTRACT_ADDED_IN_IMPLEMENTED_INTERFACE")
126+
compatibilityChangeExcludes.set(exclusions)
127+
richReport {
128+
addSetupRule(RecordSeenMembersSetup::class.java)
129+
addRule(JApiChangeStatus.NEW, SourceCompatibleRule::class.java)
130+
addRule(JApiChangeStatus.MODIFIED, SourceCompatibleRule::class.java)
131+
addRule(JApiChangeStatus.UNCHANGED, UnchangedMemberRule::class.java)
132+
// Allow new abstract methods on autovalue
133+
addRule(AllowNewAbstractMethodOnAutovalueClasses::class.java)
134+
addRule(BinaryIncompatibleRule::class.java)
135+
// Disallow source incompatible changes, which are allowed by default for some reason
136+
addRule(SourceIncompatibleRule::class.java)
137+
}
138+
139+
// this is needed so that we only consider the current artifact, and not dependencies
140+
ignoreMissingClasses.set(true)
141+
packageExcludes.addAll(
142+
"*.internal",
143+
"*.internal.*"
144+
)
145+
annotationExcludes.add("@kotlin.Metadata")
146+
val baseVersionString = if (apiBaseVersion == null) "latest" else baselineVersion
147+
txtOutputFile.set(
148+
apiNewVersion?.let { file("$rootDir/docs/apidiffs/${apiNewVersion}_vs_$baselineVersion/${base.archivesName.get()}.txt") }
149+
?: file("$rootDir/docs/apidiffs/current_vs_$baseVersionString/${base.archivesName.get()}.txt"),
150+
)
151+
}
152+
// have the check task depend on the api comparison task, to make it more likely it will get used.
153+
named("check") {
154+
dependsOn(jApiCmp)
155+
}
156+
}
157+
}
158+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77

88
id("otel.errorprone-conventions")
99
id("otel.spotless-conventions")
10+
id("otel.japicmp-conventions")
1011
id("org.owasp.dependencycheck")
1112
}
1213

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Comparing source compatibility of opentelemetry-aws-xray-1.50.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.49.0.jar
2+
No changes.

0 commit comments

Comments
 (0)