Skip to content

Commit c149250

Browse files
oschwaldclaude
andcommitted
Add API compatibility checking with japicmp
This adds a GitHub Actions workflow that runs on PRs to detect breaking changes in the public API. The check compares the current build against the baseline version on Maven Central. ENG-3367 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6aa0d3c commit c149250

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

.github/workflows/api-compat.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: API Compatibility Check
2+
on:
3+
pull_request:
4+
permissions:
5+
contents: read
6+
jobs:
7+
api-compat:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
11+
with:
12+
persist-credentials: false
13+
14+
- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0
15+
with:
16+
distribution: temurin
17+
java-version: '17'
18+
19+
- uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
20+
21+
- name: Check API Compatibility
22+
run: ./gradlew :device-sdk:japicmp

dev-bin/release.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ perl -pi -e "s/version = \"[^\"]+\"/version = \"$version\"/" build.gradle.kts
126126
# Update version in README.md
127127
perl -pi -e "s/com\.maxmind\.device:device-sdk:[0-9]+\.[0-9]+\.[0-9]+[a-zA-Z0-9\-]*/com.maxmind.device:device-sdk:$version/g" README.md
128128

129+
# Update baselineVersion in device-sdk/build.gradle.kts for API compatibility checking
130+
# This ensures the next PR compares against this newly released version
131+
perl -pi -e "s/val baselineVersion = \"[^\"]+\"/val baselineVersion = \"$version\"/" device-sdk/build.gradle.kts
132+
129133
git diff
130134

131135
read -r -n 1 -p "Commit changes? (y/n) " should_commit
@@ -135,7 +139,7 @@ if [ "$should_commit" != "y" ]; then
135139
exit 1
136140
fi
137141

138-
git add build.gradle.kts README.md
142+
git add build.gradle.kts README.md device-sdk/build.gradle.kts
139143
git commit -m "Preparing for $version"
140144

141145
# Build and publish to Maven Central

device-sdk/build.gradle.kts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import java.net.URI
2+
13
plugins {
24
alias(libs.plugins.android.library)
35
alias(libs.plugins.kotlin.android)
@@ -8,6 +10,7 @@ plugins {
810
alias(libs.plugins.maven.publish)
911
signing
1012
id("tech.apter.junit5.jupiter.robolectric-extension-gradle-plugin") version "0.9.0"
13+
id("me.champeau.gradle.japicmp") version "0.4.5"
1114
}
1215

1316
android {
@@ -168,3 +171,58 @@ mavenPublishing {
168171
signing {
169172
useGpgCmd()
170173
}
174+
175+
// API compatibility checking with japicmp
176+
// Compares the current build against the latest released version on Maven Central
177+
// Update this version after each release (the release script should do this automatically)
178+
val baselineVersion = "0.1.0"
179+
180+
// Download baseline AAR directly from Maven Central to avoid local project resolution
181+
val downloadBaselineAar by tasks.registering {
182+
val outputFile = layout.buildDirectory.file("japicmp/baseline.aar")
183+
outputs.file(outputFile)
184+
doLast {
185+
val url = "https://repo1.maven.org/maven2/com/maxmind/device/device-sdk/$baselineVersion/device-sdk-$baselineVersion.aar"
186+
val destFile = outputFile.get().asFile
187+
destFile.parentFile.mkdirs()
188+
URI(url).toURL().openStream().use { input ->
189+
destFile.outputStream().use { output ->
190+
input.copyTo(output)
191+
}
192+
}
193+
logger.lifecycle("Downloaded baseline AAR from $url")
194+
}
195+
}
196+
197+
// Extract classes.jar from baseline AAR for comparison
198+
val extractBaselineClasses by tasks.registering(Copy::class) {
199+
dependsOn(downloadBaselineAar)
200+
from(zipTree(layout.buildDirectory.file("japicmp/baseline.aar"))) {
201+
include("classes.jar")
202+
}
203+
into(layout.buildDirectory.dir("japicmp/baseline"))
204+
}
205+
206+
// Extract classes.jar from current AAR for comparison
207+
val extractCurrentClasses by tasks.registering(Copy::class) {
208+
dependsOn("bundleReleaseAar")
209+
from(zipTree(layout.buildDirectory.file("outputs/aar/device-sdk-release.aar"))) {
210+
include("classes.jar")
211+
}
212+
into(layout.buildDirectory.dir("japicmp/current"))
213+
}
214+
215+
tasks.register<me.champeau.gradle.japicmp.JapicmpTask>("japicmp") {
216+
dependsOn(extractBaselineClasses, extractCurrentClasses)
217+
oldClasspath.from(layout.buildDirectory.file("japicmp/baseline/classes.jar"))
218+
newClasspath.from(layout.buildDirectory.file("japicmp/current/classes.jar"))
219+
oldArchives.from(layout.buildDirectory.file("japicmp/baseline/classes.jar"))
220+
newArchives.from(layout.buildDirectory.file("japicmp/current/classes.jar"))
221+
accessModifier.set("public")
222+
onlyModified.set(true)
223+
failOnModification.set(true)
224+
includeSynthetic.set(false)
225+
ignoreMissingClasses.set(true)
226+
txtOutputFile.set(layout.buildDirectory.file("japicmp/report.txt"))
227+
htmlOutputFile.set(layout.buildDirectory.file("japicmp/report.html"))
228+
}

0 commit comments

Comments
 (0)