Skip to content

Commit 72b34aa

Browse files
committed
Validate list of accepted breaking changes
(cherry picked from commit 79e290a)
1 parent bc62d27 commit 72b34aa

10 files changed

+139
-122
lines changed

gradle/plugins/japicmp/src/main/kotlin/junitbuild.japicmp.gradle.kts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import de.undercouch.gradle.tasks.download.Download
22
import junitbuild.extensions.javaModuleName
3+
import junitbuild.japicmp.AcceptedViolationSuppressor
4+
import junitbuild.japicmp.AcceptedViolationsPostProcessRule
5+
import junitbuild.japicmp.BreakingSuperClassChangeRule
36
import junitbuild.japicmp.InternalApiFilter
47
import junitbuild.japicmp.JApiCmpExtension
5-
import junitbuild.japicmp.UnacceptedIncompatibilityRule
6-
import junitbuild.japicmp.UnacceptedSuperClassChangeRule
8+
import junitbuild.japicmp.SourceIncompatibleRule
79
import me.champeau.gradle.japicmp.JapicmpTask
10+
import me.champeau.gradle.japicmp.report.stdrules.BinaryIncompatibleRule
811
import me.champeau.gradle.japicmp.report.stdrules.RecordSeenMembersSetup
912

1013
plugins {
@@ -19,7 +22,7 @@ val extension = extensions.create<JApiCmpExtension>("japicmp").apply {
1922
val acceptedBreakingChangesFile = rootProject.layout.projectDirectory.file("gradle/config/japicmp/accepted-breaking-changes.txt")
2023
if (acceptedBreakingChangesFile.asFile.exists()) {
2124
convention(providers.fileContents(acceptedBreakingChangesFile).asText
22-
.map { it.lineSequence().filter { it.startsWith(project.javaModuleName) }.toList() })
25+
.map { it.lineSequence().filter { line -> line.startsWith(project.javaModuleName) }.toList() })
2326
} else {
2427
empty()
2528
}
@@ -50,15 +53,18 @@ val checkBackwardCompatibility by tasks.registering(JapicmpTask::class) {
5053
newClasspath.from(tasks.jar)
5154
onlyModified = true
5255
ignoreMissingClasses = true
53-
txtOutputFile = layout.buildDirectory.file("reports/japicmp/plain-report.txt")
54-
mdOutputFile = layout.buildDirectory.file("reports/japicmp/plain-report.md")
5556
htmlOutputFile = layout.buildDirectory.file("reports/japicmp/plain-report.html")
5657
addExcludeFilter(InternalApiFilter::class.java)
5758
packageExcludes.add("*.shadow.*")
5859
inputs.property("acceptedIncompatibilities", extension.acceptedIncompatibilities)
5960
richReport {
61+
title = "Compatibility report"
62+
description = extension.previousVersion.map { "and source compatibility compared against $it" }
6063
destinationDir = layout.buildDirectory.dir("reports/japicmp")
6164
addSetupRule(RecordSeenMembersSetup::class.java)
65+
addRule(BreakingSuperClassChangeRule::class.java)
66+
addRule(BinaryIncompatibleRule::class.java)
67+
addRule(SourceIncompatibleRule::class.java)
6268
}
6369
}
6470

@@ -72,8 +78,8 @@ afterEvaluate {
7278
)
7379
checkBackwardCompatibility {
7480
richReport {
75-
addRule(UnacceptedIncompatibilityRule::class.java, params)
76-
addRule(UnacceptedSuperClassChangeRule::class.java, params)
81+
addViolationTransformer(AcceptedViolationSuppressor::class.java, params)
82+
addPostProcessRule(AcceptedViolationsPostProcessRule::class.java, params)
7783
}
7884
}
7985
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package junitbuild.japicmp
2+
3+
import japicmp.model.JApiClass
4+
import japicmp.model.JApiImplementedInterface
5+
import me.champeau.gradle.japicmp.report.Severity.accepted
6+
import me.champeau.gradle.japicmp.report.Violation
7+
import me.champeau.gradle.japicmp.report.ViolationTransformer
8+
import java.util.*
9+
10+
class AcceptedViolationSuppressor(params: Map<String, String>) : ViolationTransformer {
11+
12+
val acceptedIncompatibilities = params["acceptedIncompatibilities"]!!.split(',').filter { !it.isEmpty() }.toSet()
13+
14+
override fun transform(type: String?, violation: Violation): Optional<Violation> = when {
15+
violation.isRedundant() -> Optional.empty()
16+
violation.isAccepted() -> Optional.of(violation.withSeverity(accepted))
17+
else -> Optional.of(violation)
18+
}
19+
20+
private fun Violation.isRedundant(): Boolean =
21+
// The changes about the interface's methods will be reported already
22+
member is JApiImplementedInterface
23+
// A member of the class breaks binary or source compatibility and will be reported
24+
|| (member is JApiClass && member.compatibilityChanges.none { !it.isBinaryCompatible || !it.isSourceCompatible } && !member.isBreakingSuperClassChange())
25+
26+
private fun Violation.isAccepted(): Boolean =
27+
acceptedIncompatibilities.contains(member.fullyQualifiedName)
28+
|| acceptedIncompatibilities.contains(member.fullyQualifiedClassName)
29+
30+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package junitbuild.japicmp
2+
3+
import me.champeau.gradle.japicmp.report.PostProcessViolationsRule
4+
import me.champeau.gradle.japicmp.report.Severity.accepted
5+
import me.champeau.gradle.japicmp.report.ViolationCheckContextWithViolations
6+
7+
class AcceptedViolationsPostProcessRule(params: Map<String, String>) : PostProcessViolationsRule {
8+
9+
val acceptedViolations = params["acceptedIncompatibilities"]!!.split(',').filter { !it.isEmpty() }.toSet()
10+
11+
override fun execute(context: ViolationCheckContextWithViolations) {
12+
13+
val actualViolations = context.violations.asSequence()
14+
.flatMap { it.value.asSequence() }
15+
.filter { it.severity == accepted }
16+
.map { it.member }
17+
.flatMap {
18+
sequenceOf(
19+
it.fullyQualifiedClassName,
20+
it.fullyQualifiedName
21+
)
22+
}
23+
.toSet()
24+
25+
val diff = acceptedViolations - actualViolations
26+
27+
require(diff.isEmpty()) {
28+
"The following elements are listed as 'accepted' but are not actually violations:\n- ${diff.joinToString("\n- ")}"
29+
}
30+
}
31+
32+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package junitbuild.japicmp
2+
3+
import japicmp.model.JApiCompatibility
4+
import me.champeau.gradle.japicmp.report.Violation
5+
import me.champeau.gradle.japicmp.report.stdrules.AbstractRecordingSeenMembers
6+
7+
// Required due to https://github.com/melix/japicmp-gradle-plugin/issues/56
8+
class BreakingSuperClassChangeRule : AbstractRecordingSeenMembers() {
9+
10+
override fun maybeAddViolation(element: JApiCompatibility): Violation? {
11+
return when {
12+
element.isBreakingSuperClassChange() -> Violation.error(element, "Is not binary compatible due to super class change")
13+
else -> null
14+
}
15+
}
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package junitbuild.japicmp
2+
3+
import japicmp.model.JApiBehavior
4+
import japicmp.model.JApiClass
5+
import japicmp.model.JApiCompatibility
6+
import japicmp.model.JApiField
7+
8+
internal val JApiCompatibility.fullyQualifiedName: String
9+
get() = when (this) {
10+
is JApiClass -> fullyQualifiedClassName
11+
is JApiBehavior -> "${fullyQualifiedClassName}#${name}"
12+
is JApiField -> "${fullyQualifiedClassName}#${name}"
13+
else -> throw IllegalArgumentException("Could not determine fully-qualified name for $this")
14+
}
15+
16+
internal val JApiCompatibility.fullyQualifiedClassName: String
17+
get() = when (this) {
18+
is JApiClass -> fullyQualifiedName
19+
is JApiBehavior -> getjApiClass().fullyQualifiedName
20+
is JApiField -> getjApiClass().fullyQualifiedName
21+
else -> throw IllegalArgumentException("Could not determine fully-qualified class name for $this")
22+
}
23+
24+
internal fun JApiCompatibility.isBreakingSuperClassChange(): Boolean {
25+
if (this !is JApiClass || superclass.isBinaryCompatible || superclass.compatibilityChanges.isEmpty()) {
26+
return false
27+
}
28+
// breaking change would otherwise be reported
29+
return oldClass.isPresent && newClass.isPresent
30+
31+
}

gradle/plugins/japicmp/src/main/kotlin/junitbuild/japicmp/SeveritySource.kt

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package junitbuild.japicmp
2+
3+
import japicmp.model.JApiCompatibility
4+
import me.champeau.gradle.japicmp.report.Violation
5+
import me.champeau.gradle.japicmp.report.stdrules.AbstractRecordingSeenMembers
6+
7+
class SourceIncompatibleRule : AbstractRecordingSeenMembers() {
8+
9+
override fun maybeAddViolation(member: JApiCompatibility): Violation? = when {
10+
!member.isSourceCompatible -> Violation.error(member, "Is not source compatible")
11+
else -> null
12+
}
13+
}

gradle/plugins/japicmp/src/main/kotlin/junitbuild/japicmp/UnacceptedIncompatibilityRule.kt

Lines changed: 0 additions & 43 deletions
This file was deleted.

gradle/plugins/japicmp/src/main/kotlin/junitbuild/japicmp/UnacceptedSuperClassChangeRule.kt

Lines changed: 0 additions & 26 deletions
This file was deleted.

junit-platform-suite/junit-platform-suite.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ dependencies {
1212
osgiVerification(projects.junitJupiterEngine)
1313
osgiVerification(projects.junitPlatformLauncher)
1414
}
15+
16+
japicmp {
17+
acceptedIncompatibilities.empty()
18+
}

0 commit comments

Comments
 (0)