Skip to content

Commit 7f3a71d

Browse files
committed
Introduce 'nonPublicMarkers' configuration to exclude API that is effectively non-public from API dump
Co-authored-by: ilya-g <ilya.gorbunov@jetbrains.com> Fixes #17
1 parent 54c1af1 commit 7f3a71d

File tree

7 files changed

+80
-23
lines changed

7 files changed

+80
-23
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ apiValidation {
6363
* Sub-projects that are excluded from API validation
6464
*/
6565
ignoredProjects += ["benchmarks", "examples"]
66+
67+
/**
68+
* Set of annotations that exclude API from being public.
69+
* Typically, it is all kinds of `@InternalApi` annotations that mark
70+
* effectively private API that cannot be actually private for technical reasons.
71+
*/
72+
nonPublicMarkers += ["my.package.MyInternalApiAnnotation"]
73+
74+
/**
75+
* Flag to programmatically disable compatibility validator
76+
*/
77+
validationDisabled = true
6678
}
6779
```
6880

src/main/kotlin/ApiValidationExtension.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ open class ApiValidationExtension {
2222
* Projects that are ignored by the API check.
2323
*/
2424
public var ignoredProjects: MutableSet<String> = HashSet()
25+
26+
/**
27+
* Fully qualified names of annotations that effectively exclude declarations from being public.
28+
* Example of such annotation could be `kotlinx.coroutines.InternalCoroutinesApi`.
29+
*/
30+
public var nonPublicMarkers: MutableSet<String> = HashSet()
2531
}

src/main/kotlin/ExternalApi.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package kotlinx.validation
77

88
/**
9-
* API that is used externally and programmatically by Kotlin standard library
9+
* API that is used externally and programmatically by binary-compatibility-validator tool in Kotlin standard library
1010
*/
1111
@Retention(AnnotationRetention.SOURCE)
12-
annotation class ExternalApi
12+
annotation class ExternalApi

src/main/kotlin/KotlinApiBuildTask.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ open class KotlinApiBuildTask : DefaultTask() {
4040
.map { it.inputStream() }
4141
.loadApiFromJvmClasses()
4242
.filterOutNonPublic(extension.ignoredPackages)
43+
.filterOutAnnotated(extension.nonPublicMarkers.map { it.replace(".", "/") }.toSet())
4344

4445
outputApiDir.resolve("${project.name}.api").bufferedWriter().use { writer ->
4546
signatures

src/main/kotlin/api/AsmMetadataLoading.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package kotlinx.validation.api
77

88
import kotlinx.metadata.jvm.*
9-
import org.objectweb.asm.Opcodes
9+
import org.objectweb.asm.*
1010
import org.objectweb.asm.tree.*
1111

1212
val ACCESS_NAMES = mapOf(
@@ -70,8 +70,14 @@ private fun List<Any>.annotationValue(key: String): Any? {
7070
return null
7171
}
7272

73-
private fun findAnnotation(annotationName: String, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, includeInvisible: Boolean): AnnotationNode? =
73+
private fun findAnnotation(
74+
annotationName: String,
75+
visibleAnnotations: List<AnnotationNode>?,
76+
invisibleAnnotations: List<AnnotationNode>?,
77+
includeInvisible: Boolean
78+
): AnnotationNode? =
7479
visibleAnnotations?.firstOrNull { it.refersToName(annotationName) }
75-
?: if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null
80+
?: if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null
7681

77-
fun AnnotationNode.refersToName(name: String) = desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length)
82+
fun AnnotationNode.refersToName(name: String) =
83+
desc.startsWith('L') && desc.endsWith(';') && desc.regionMatches(1, name, 0, name.length)

src/main/kotlin/api/KotlinMetadataSignature.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ package kotlinx.validation.api
77

88
import kotlinx.metadata.jvm.*
99
import kotlinx.validation.*
10-
import org.objectweb.asm.tree.FieldNode
11-
import org.objectweb.asm.tree.MethodNode
10+
import org.objectweb.asm.tree.*
1211

1312
@ExternalApi // Only name is part of the API, nothing else is used by stdlib
1413
data class ClassBinarySignature(
@@ -19,7 +18,8 @@ data class ClassBinarySignature(
1918
val memberSignatures: List<MemberBinarySignature>,
2019
val access: AccessFlags,
2120
val isEffectivelyPublic: Boolean,
22-
val isNotUsedWhenEmpty: Boolean
21+
val isNotUsedWhenEmpty: Boolean,
22+
val annotations: List<AnnotationNode>
2323
) {
2424
val signature: String
2525
get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}"
@@ -31,6 +31,7 @@ interface MemberBinarySignature {
3131
val desc: String get() = jvmMember.desc
3232
val access: AccessFlags
3333
val isPublishedApi: Boolean
34+
val annotations: List<AnnotationNode>
3435

3536
fun isEffectivelyPublic(classAccess: AccessFlags, classVisibility: ClassVisibility?) =
3637
access.isPublic && !(access.isProtected && classAccess.isFinal)
@@ -46,7 +47,8 @@ interface MemberBinarySignature {
4647
data class MethodBinarySignature(
4748
override val jvmMember: JvmMethodSignature,
4849
override val isPublishedApi: Boolean,
49-
override val access: AccessFlags
50+
override val access: AccessFlags,
51+
override val annotations: List<AnnotationNode>
5052
) : MemberBinarySignature {
5153
override val signature: String
5254
get() = "${access.getModifierString()} fun $name $desc"
@@ -87,12 +89,17 @@ data class MethodBinarySignature(
8789
}
8890

8991
fun MethodNode.toMethodBinarySignature() =
90-
MethodBinarySignature(JvmMethodSignature(name, desc), isPublishedApi(), AccessFlags(access))
92+
MethodBinarySignature(
93+
JvmMethodSignature(name, desc),
94+
isPublishedApi(),
95+
AccessFlags(access),
96+
annotations(visibleAnnotations, invisibleAnnotations))
9197

9298
data class FieldBinarySignature(
9399
override val jvmMember: JvmFieldSignature,
94100
override val isPublishedApi: Boolean,
95-
override val access: AccessFlags
101+
override val access: AccessFlags,
102+
override val annotations: List<AnnotationNode>
96103
) : MemberBinarySignature {
97104
override val signature: String
98105
get() = "${access.getModifierString()} field $name $desc"
@@ -104,7 +111,11 @@ data class FieldBinarySignature(
104111
}
105112

106113
fun FieldNode.toFieldBinarySignature() =
107-
FieldBinarySignature(JvmFieldSignature(name, desc), isPublishedApi(), AccessFlags(access))
114+
FieldBinarySignature(
115+
JvmFieldSignature(name, desc),
116+
isPublishedApi(),
117+
AccessFlags(access),
118+
annotations(visibleAnnotations, invisibleAnnotations))
108119

109120
private val MemberBinarySignature.kind: Int
110121
get() = when (this) {

src/main/kotlin/api/KotlinSignaturesLoading.kt

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ package kotlinx.validation.api
88
import kotlinx.validation.*
99
import org.objectweb.asm.*
1010
import org.objectweb.asm.tree.*
11-
import java.io.InputStream
12-
import java.util.jar.JarFile
13-
11+
import java.io.*
12+
import java.util.jar.*
1413

1514
@ExternalApi
1615
@Suppress("unused")
17-
fun JarFile.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> =
16+
public fun JarFile.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> =
1817
classEntries().map { entry -> getInputStream(entry) }.loadApiFromJvmClasses(visibilityFilter)
1918

2019
@ExternalApi
21-
fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> {
20+
public fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String) -> Boolean = { true }): List<ClassBinarySignature> {
2221
val classNodes = map {
2322
it.use { stream ->
2423
val classNode = ClassNode()
@@ -46,16 +45,35 @@ fun Sequence<InputStream>.loadApiFromJvmClasses(visibilityFilter: (String) -> Bo
4645
ClassBinarySignature(
4746
name, superName, outerClassName, supertypes, memberSignatures, classAccess,
4847
isEffectivelyPublic(mVisibility),
49-
metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata)
48+
metadata.isFileOrMultipartFacade() || isDefaultImpls(metadata),
49+
annotations(visibleAnnotations, invisibleAnnotations)
5050
)
5151
}}
5252
.asIterable()
5353
.sortedBy { it.name }
5454
}
5555

56+
internal fun List<ClassBinarySignature>.filterOutAnnotated(targetAnnotations: Set<String>): List<ClassBinarySignature> {
57+
if (targetAnnotations.isEmpty()) return this
58+
return filter {
59+
it.annotations.all { ann -> !targetAnnotations.any { ann.refersToName(it) } }
60+
}.map {
61+
ClassBinarySignature(
62+
it.name,
63+
it.superName,
64+
it.outerName,
65+
it.supertypes,
66+
it.memberSignatures.filter { it.annotations.all { ann -> !targetAnnotations.any { ann.refersToName(it) } } },
67+
it.access,
68+
it.isEffectivelyPublic,
69+
it.isNotUsedWhenEmpty,
70+
it.annotations
71+
)
72+
}
73+
}
5674

5775
@ExternalApi
58-
fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: Collection<String> = emptyList()): List<ClassBinarySignature> {
76+
public fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: Collection<String> = emptyList()): List<ClassBinarySignature> {
5977
val nonPublicPaths = nonPublicPackages.map { it.replace('.', '/') + '/' }
6078
val classByName = associateBy { it.name }
6179

@@ -93,10 +111,10 @@ fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: Collection<
93111
}
94112

95113
@ExternalApi
96-
fun List<ClassBinarySignature>.dump() = dump(to = System.out)
114+
public fun List<ClassBinarySignature>.dump() = dump(to = System.out)
97115

98116
@ExternalApi
99-
fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T {
117+
public fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T {
100118
forEach { classApi ->
101119
with(to) {
102120
append(classApi.signature).appendln(" {")
@@ -111,4 +129,7 @@ fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T {
111129

112130
private fun JarFile.classEntries() = Sequence { entries().iterator() }.filter {
113131
!it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/")
114-
}
132+
}
133+
134+
internal fun annotations(l1: List<AnnotationNode>?, l2: List<AnnotationNode>?): List<AnnotationNode> =
135+
((l1 ?: emptyList()) + (l2 ?: emptyList()))

0 commit comments

Comments
 (0)