Introduce ${target}CheckAbiCompatibility task.

Since Kotlin/Native is moving tovards stable ABI it is important to check between releases that there are no unexpected breaking changes.
${target}CheckAbiCompatibility task allows to compare `klib signatures` dumps of stdlib and platforms libs from current distribution and the given one.
This commit is contained in:
Sergey Bogolepov
2020-10-27 19:20:28 +07:00
committed by Stanislav Erokhin
parent 86af222d9b
commit 8c5bb6420a
2 changed files with 139 additions and 0 deletions
@@ -0,0 +1,126 @@
package org.jetbrains.kotlin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.konan.target.HostManager
import java.io.File
/**
* Compares SignatureIds of the current distribution and the given older one.
* Can be used to validate that there are no unexpected breaking ABI changes.
*/
open class CompareDistributionSignatures : DefaultTask() {
@Input
lateinit var target: String
@Input
lateinit var oldDistribution: String
private val newDistribution: String =
project.kotlinNativeDist.absolutePath
enum class OnMismatchMode {
FAIL,
NOTIFY
}
private val messageBuilder = StringBuilder()
@Input
var onMismatchMode: OnMismatchMode = OnMismatchMode.NOTIFY
@TaskAction
fun run() {
val klibs = getKlibs(oldDistribution, newDistribution)
if (klibs.missingLibs.isNotEmpty()) {
messageBuilder.apply {
appendln("Following libraries are missing in the new distro:")
klibs.missingLibs.forEach { appendln(it) }
}
}
if (klibs.newLibs.isNotEmpty()) {
messageBuilder.apply {
appendln("Following libraries were added:")
klibs.newLibs.forEach { appendln(it) }
}
}
for ((new, old) in klibs.remainingLibs) {
val result = compareSignatures(new, old)
if (result.run { newKlibOnly.isNotEmpty() || oldKlibOnly.isNotEmpty() }) {
reportMismatch(result, new.name)
}
}
if (messageBuilder.isNotEmpty()) {
report(messageBuilder.toString())
}
}
private fun reportMismatch(compareDiff: CompareDiff, klibName: String) {
messageBuilder.apply {
appendln("Mismatch for $klibName:")
if (compareDiff.oldKlibOnly.isNotEmpty()) {
appendln("Following signatures are missing in the new version:")
compareDiff.oldKlibOnly.forEach { appendln(it) }
}
if (compareDiff.newKlibOnly.isNotEmpty()) {
appendln("Following signatures were added:")
compareDiff.newKlibOnly.forEach { appendln(it) }
}
}
}
private fun report(message: String) {
when (onMismatchMode) {
OnMismatchMode.FAIL -> error(message)
OnMismatchMode.NOTIFY -> println(message)
}
}
private data class RemainingLibrary(val new: File, val old: File)
private class KlibDiff(
val newLibs: Collection<File>,
val missingLibs: Collection<File>,
val remainingLibs: Collection<RemainingLibrary>
)
private fun String.stdlib(): File =
File("$this/klib/common/stdlib")
private fun String.platformLibs(target: String): File =
File("$this/klib/platform/$target")
private fun getKlibs(oldDistribution: String, newDistribution: String): KlibDiff {
val oldPlatformLibs = oldDistribution.platformLibs(target)
val oldPlatformLibsNames = oldPlatformLibs.list().toSet()
val newPlatformLibs = newDistribution.platformLibs(target)
val newPlatformLibsNames = newPlatformLibs.list().toSet()
return KlibDiff(
(newPlatformLibsNames - oldPlatformLibsNames).map(newPlatformLibs::resolve),
(oldPlatformLibsNames - newPlatformLibsNames).map(oldPlatformLibs::resolve),
oldPlatformLibsNames.intersect(newPlatformLibsNames).map {
RemainingLibrary(newPlatformLibs.resolve(it), oldPlatformLibs.resolve(it))
} + RemainingLibrary(newDistribution.stdlib(), oldDistribution.stdlib())
)
}
private fun getKlibSignatures(klib: File): List<String> {
val tool = if (HostManager.hostIsMingw) "klib.bat" else "klib"
val klibTool = File("$newDistribution/bin/$tool").absolutePath
val args = listOf("signatures", klib.absolutePath)
return runProcess(localExecutor(project), klibTool, args).stdOut.lines()
}
private class CompareDiff(val newKlibOnly: Set<String>, val oldKlibOnly: Set<String>)
private fun compareSignatures(new: File, old: File): CompareDiff {
val newKlibSignatures = getKlibSignatures(new).toSet()
val oldKlibSignatures = getKlibSignatures(old).toSet()
return CompareDiff(
newKlibSignatures - oldKlibSignatures,
oldKlibSignatures - newKlibSignatures,
)
}
}
+13
View File
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.KotlinBuildPusher
import org.jetbrains.kotlin.CollisionDetector
import org.jetbrains.kotlin.CollisionTransformer
import org.jetbrains.kotlin.CompilationDatabaseKt
import org.jetbrains.kotlin.CompareDistributionSignatures
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.apache.tools.ant.filters.ReplaceTokens
import org.jetbrains.kotlin.UtilsKt
@@ -778,3 +779,15 @@ task compdb(type: Copy) {
from "$buildDir/${hostName}/compile_commands.json"
into "$projectDir"
}
if (project.hasProperty("anotherDistro")) {
targetList.each { targetName ->
task "${targetName}CheckAbiCompatibility"(type: CompareDistributionSignatures) {
dependsOn "${targetName}PlatformLibs"
target = targetName
oldDistribution = project.findProperty("anotherDistro")
onMismatchMode = CompareDistributionSignatures.OnMismatchMode.FAIL
}
}
}