Skip to content

Commit 6ce041e

Browse files
committed
Detect breaking changes on pull requests
Adds a gradle task and github action to check for breaking changes made to the APIs in server by running comparison against most resent snapshot build on sonotype maven repository. Only classes with @publicapi are checked for breaking changes. Uses japicmp to perform the comparison against the jar files, learn more https://siom79.github.io/japicmp/ Signed-off-by: Peter Nied <petern@amazon.com>
1 parent eba3b57 commit 6ce041e

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: "Detect Breaking Changes"
2+
on: [push, pull_request]
3+
jobs:
4+
detect-breaking-change:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v4
8+
- uses: actions/setup-java@v4
9+
with:
10+
distribution: temurin # Temurin is a distribution of adoptium
11+
java-version: 21
12+
- uses: gradle/gradle-build-action@v3
13+
with:
14+
arguments: japicmp
15+
gradle-version: 8.7
16+
build-root-directory: server
17+
- if: failure()
18+
run: cat server/build/reports/java-compatibility/report.txt
19+
- if: failure()
20+
uses: actions/upload-artifact@v4
21+
with:
22+
name: java-compatibility-report.html
23+
path: ${{ github.workspace }}/server/build/reports/java-compatibility/report.html
24+

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
108108
- Allow setting KEYSTORE_PASSWORD through env variable ([#12865](https://github.com/opensearch-project/OpenSearch/pull/12865))
109109
- [Concurrent Segment Search] Perform buildAggregation concurrently and support Composite Aggregations ([#12697](https://github.com/opensearch-project/OpenSearch/pull/12697))
110110
- [Concurrent Segment Search] Disable concurrent segment search for system indices and throttled requests ([#12954](https://github.com/opensearch-project/OpenSearch/pull/12954))
111+
- Detect breaking changes on pull requests ([#9044](https://github.com/opensearch-project/OpenSearch/pull/9044))
111112

112113
### Dependencies
113114
- Bump `org.apache.commons:commons-configuration2` from 2.10.0 to 2.10.1 ([#12896](https://github.com/opensearch-project/OpenSearch/pull/12896))

server/build.gradle

+79
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ plugins {
3636
id('opensearch.publish')
3737
id('opensearch.internal-cluster-test')
3838
id('opensearch.optional-dependencies')
39+
id('me.champeau.gradle.japicmp') version '0.4.2'
3940
}
4041

4142
publishing {
@@ -378,3 +379,81 @@ tasks.named("sourcesJar").configure {
378379
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
379380
}
380381
}
382+
383+
/** Compares the current build against a snapshot build */
384+
tasks.register("japicmp", me.champeau.gradle.japicmp.JapicmpTask) {
385+
oldClasspath.from(files("${buildDir}/snapshot/opensearch-${version}.jar"))
386+
newClasspath.from(tasks.named('jar'))
387+
onlyModified = true
388+
failOnModification = true
389+
ignoreMissingClasses = true
390+
annotationIncludes = ['@org.opensearch.common.annotation.PublicApi']
391+
txtOutputFile = layout.buildDirectory.file("reports/java-compatibility/report.txt")
392+
htmlOutputFile = layout.buildDirectory.file("reports/java-compatibility/report.html")
393+
dependsOn downloadSnapshot
394+
}
395+
396+
/** If the Java API Comparison task failed, print a hint if the change should be merged from its target branch */
397+
gradle.taskGraph.afterTask { Task task, TaskState state ->
398+
if (task.name == 'japicmp' && state.failure != null) {
399+
def sha = getGitShaFromJar("${buildDir}/snapshot/opensearch-${version}.jar")
400+
logger.info("Incompatiable java api from snapshot jar built off of commit ${sha}")
401+
402+
if (!inHistory(sha)) {
403+
logger.warn('\u001B[33mPlease merge from the target branch and run this task again.\u001B[0m')
404+
}
405+
}
406+
}
407+
408+
/** Downloads latest snapshot from maven repository */
409+
tasks.register("downloadSnapshot", Copy) {
410+
def mavenSnapshotRepoUrl = "https://aws.oss.sonatype.org/content/repositories/snapshots/"
411+
def groupId = "org.opensearch"
412+
def artifactId = "opensearch"
413+
414+
repositories {
415+
maven {
416+
url mavenSnapshotRepoUrl
417+
}
418+
}
419+
420+
configurations {
421+
snapshotArtifact
422+
}
423+
424+
dependencies {
425+
snapshotArtifact("${groupId}:${artifactId}:${version}:")
426+
}
427+
428+
from configurations.snapshotArtifact
429+
into "$buildDir/snapshot"
430+
}
431+
432+
/** Check if the sha is in the current history */
433+
def inHistory(String sha) {
434+
try {
435+
def commandCheckSha = "git merge-base --is-ancestor ${sha} HEAD"
436+
commandCheckSha.execute()
437+
return true
438+
} catch (Exception) {
439+
return false
440+
}
441+
}
442+
443+
/** Extracts the Git SHA used to build a jar from its manifest */
444+
def getGitShaFromJar(String jarPath) {
445+
def sha = ''
446+
try {
447+
// Open the JAR file
448+
def jarFile = new java.util.jar.JarFile(jarPath)
449+
// Get the manifest from the JAR file
450+
def manifest = jarFile.manifest
451+
def attributes = manifest.mainAttributes
452+
// Assuming the Git SHA is stored under an attribute named 'Git-SHA'
453+
sha = attributes.getValue('Change')
454+
jarFile.close()
455+
} catch (IOException e) {
456+
println "Failed to read the JAR file: $e.message"
457+
}
458+
return sha
459+
}

0 commit comments

Comments
 (0)