Skip to content

Commit f3730f3

Browse files
committed
Enable strict explicit API mode
Rationale: We know have quite a lot of accidentally public entities while both promoting and using BCV as a standalone JAR dependency. Apart from that, we have quite an unfortunate package name 'api' that might imply all these methods are part of public API (when, in fact, it's all related to API validation). It would be nice to explicitly confine our visibilities and be more deliberate about that
1 parent 041d70b commit f3730f3

11 files changed

+87
-311
lines changed

api/binary-compatibility-validator.api

Lines changed: 0 additions & 225 deletions
Large diffs are not rendered by default.

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dependencies {
7676

7777
tasks.compileKotlin {
7878
compilerOptions {
79+
freeCompilerArgs.add("-Xexplicit-api=strict")
7980
allWarningsAsErrors.set(true)
8081
@Suppress("DEPRECATION") // Compatibility with Gradle 7 requires Kotlin 1.4
8182
languageVersion.set(KotlinVersion.KOTLIN_1_4)

src/main/kotlin/ApiValidationExtension.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
package kotlinx.validation
77

8-
open class ApiValidationExtension {
8+
public open class ApiValidationExtension {
99

1010
/**
1111
* Disables API validation checks completely.
1212
*/
13-
public var validationDisabled = false
13+
public var validationDisabled: Boolean = false
1414

1515
/**
1616
* Fully qualified package names that are not consider public API.

src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 JetBrains s.r.o.
2+
* Copyright 2016-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -13,9 +13,9 @@ import org.jetbrains.kotlin.gradle.dsl.*
1313
import org.jetbrains.kotlin.gradle.plugin.*
1414
import java.io.*
1515

16-
const val API_DIR = "api"
16+
private const val API_DIR = "api"
1717

18-
class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
18+
public class BinaryCompatibilityValidatorPlugin : Plugin<Project> {
1919

2020
override fun apply(target: Project): Unit = with(target) {
2121
val extension = extensions.create("apiValidation", ApiValidationExtension::class.java)
@@ -216,7 +216,7 @@ internal val Project.apiValidationExtensionOrNull: ApiValidationExtension?
216216
.map { it.extensions.findByType(ApiValidationExtension::class.java) }
217217
.firstOrNull { it != null }
218218

219-
fun apiCheckEnabled(projectName: String, extension: ApiValidationExtension): Boolean =
219+
private fun apiCheckEnabled(projectName: String, extension: ApiValidationExtension): Boolean =
220220
projectName !in extension.ignoredProjects && !extension.validationDisabled
221221

222222
private fun Project.configureApiTasks(
@@ -283,7 +283,7 @@ private fun Project.configureCheckTasks(
283283
}
284284
}
285285

286-
inline fun <reified T : Task> Project.task(
286+
private inline fun <reified T : Task> Project.task(
287287
name: String,
288288
noinline configuration: T.() -> Unit,
289289
): TaskProvider<T> = tasks.register(name, T::class.java, Action(configuration))

src/main/kotlin/ExternalApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ package kotlinx.validation
99
* 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+
public annotation class ExternalApi

src/main/kotlin/KotlinApiBuildTask.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,69 +13,69 @@ import java.io.*
1313
import java.util.jar.JarFile
1414
import javax.inject.Inject
1515

16-
open class KotlinApiBuildTask @Inject constructor(
16+
public open class KotlinApiBuildTask @Inject constructor(
1717
) : DefaultTask() {
1818

1919
private val extension = project.apiValidationExtensionOrNull
2020

2121
@InputFiles
2222
@Optional
2323
@PathSensitive(PathSensitivity.RELATIVE)
24-
var inputClassesDirs: FileCollection? = null
24+
internal var inputClassesDirs: FileCollection? = null
2525

2626
@InputFile
2727
@Optional
2828
@PathSensitive(PathSensitivity.RELATIVE)
29-
val inputJar: RegularFileProperty = this.project.objects.fileProperty()
29+
internal val inputJar: RegularFileProperty = this.project.objects.fileProperty()
3030

3131
@InputFiles
3232
@PathSensitive(PathSensitivity.RELATIVE)
33-
lateinit var inputDependencies: FileCollection
33+
internal lateinit var inputDependencies: FileCollection
3434

3535
@OutputDirectory
36-
lateinit var outputApiDir: File
36+
internal lateinit var outputApiDir: File
3737

3838
private var _ignoredPackages: Set<String>? = null
3939
@get:Input
40-
var ignoredPackages : Set<String>
40+
internal var ignoredPackages : Set<String>
4141
get() = _ignoredPackages ?: extension?.ignoredPackages ?: emptySet()
4242
set(value) { _ignoredPackages = value }
4343

4444
private var _nonPublicMarkes: Set<String>? = null
4545
@get:Input
46-
var nonPublicMarkers : Set<String>
46+
internal var nonPublicMarkers : Set<String>
4747
get() = _nonPublicMarkes ?: extension?.nonPublicMarkers ?: emptySet()
4848
set(value) { _nonPublicMarkes = value }
4949

5050
private var _ignoredClasses: Set<String>? = null
5151
@get:Input
52-
var ignoredClasses : Set<String>
52+
internal var ignoredClasses : Set<String>
5353
get() = _ignoredClasses ?: extension?.ignoredClasses ?: emptySet()
5454
set(value) { _ignoredClasses = value }
5555

5656
private var _publicPackages: Set<String>? = null
5757
@get:Input
58-
var publicPackages: Set<String>
58+
internal var publicPackages: Set<String>
5959
get() = _publicPackages ?: extension?.publicPackages ?: emptySet()
6060
set(value) { _publicPackages = value }
6161

6262
private var _publicMarkers: Set<String>? = null
6363
@get:Input
64-
var publicMarkers: Set<String>
64+
internal var publicMarkers: Set<String>
6565
get() = _publicMarkers ?: extension?.publicMarkers ?: emptySet()
6666
set(value) { _publicMarkers = value}
6767

6868
private var _publicClasses: Set<String>? = null
6969
@get:Input
70-
var publicClasses: Set<String>
70+
internal var publicClasses: Set<String>
7171
get() = _publicClasses ?: extension?.publicClasses ?: emptySet()
7272
set(value) { _publicClasses = value }
7373

7474
@get:Internal
7575
internal val projectName = project.name
7676

7777
@TaskAction
78-
fun generate() {
78+
internal fun generate() {
7979
cleanup(outputApiDir)
8080
outputApiDir.mkdirs()
8181

src/main/kotlin/KotlinApiCompareTask.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import org.gradle.api.file.*
1515
import org.gradle.api.model.ObjectFactory
1616
import org.gradle.api.tasks.*
1717

18-
open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() {
18+
public open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() {
1919

2020
/*
2121
* Nullability and optionality is a workaround for
@@ -26,14 +26,14 @@ open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectF
2626
@Optional
2727
@InputDirectory
2828
@PathSensitive(PathSensitivity.RELATIVE)
29-
var projectApiDir: File? = null
29+
internal var projectApiDir: File? = null
3030

3131
// Used for diagnostic error message when projectApiDir doesn't exist
3232
@Input
3333
@Optional
34-
var nonExistingProjectApiDir: String? = null
34+
internal var nonExistingProjectApiDir: String? = null
3535

36-
fun compareApiDumps(apiReferenceDir: File, apiBuildDir: File) {
36+
internal fun compareApiDumps(apiReferenceDir: File, apiBuildDir: File) {
3737
if (apiReferenceDir.exists()) {
3838
projectApiDir = apiReferenceDir
3939
} else {
@@ -45,19 +45,19 @@ open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectF
4545

4646
@InputDirectory
4747
@PathSensitive(PathSensitivity.RELATIVE)
48-
lateinit var apiBuildDir: File
48+
internal lateinit var apiBuildDir: File
4949

5050
@OutputFile
5151
@Optional
5252
@Suppress("unused")
53-
val dummyOutputFile: File? = null
53+
internal val dummyOutputFile: File? = null
5454

5555
private val projectName = project.name
5656

5757
private val rootDir = project.rootProject.rootDir
5858

5959
@TaskAction
60-
fun verify() {
60+
internal fun verify() {
6161
val projectApiDir = projectApiDir
6262
?: error("Expected folder with API declarations '$nonExistingProjectApiDir' does not exist.\n" +
6363
"Please ensure that ':apiDump' was executed in order to get API dump to compare the build against")
Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 JetBrains s.r.o.
2+
* Copyright 2016-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -9,7 +9,7 @@ import kotlinx.metadata.jvm.*
99
import org.objectweb.asm.*
1010
import org.objectweb.asm.tree.*
1111

12-
val ACCESS_NAMES = mapOf(
12+
internal val ACCESS_NAMES = mapOf(
1313
Opcodes.ACC_PUBLIC to "public",
1414
Opcodes.ACC_PROTECTED to "protected",
1515
Opcodes.ACC_PRIVATE to "private",
@@ -21,13 +21,13 @@ val ACCESS_NAMES = mapOf(
2121
Opcodes.ACC_ANNOTATION to "annotation"
2222
)
2323

24-
fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0
25-
fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0
26-
fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0
27-
fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0
28-
fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0
24+
internal fun isPublic(access: Int) = access and Opcodes.ACC_PUBLIC != 0 || access and Opcodes.ACC_PROTECTED != 0
25+
internal fun isProtected(access: Int) = access and Opcodes.ACC_PROTECTED != 0
26+
internal fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0
27+
internal fun isFinal(access: Int) = access and Opcodes.ACC_FINAL != 0
28+
internal fun isSynthetic(access: Int) = access and Opcodes.ACC_SYNTHETIC != 0
2929

30-
fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) =
30+
internal fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) =
3131
isPublic(access)
3232
&& !isLocal()
3333
&& !isWhenMappings()
@@ -36,27 +36,26 @@ fun ClassNode.isEffectivelyPublic(classVisibility: ClassVisibility?) =
3636
&& (classVisibility?.isPublic(isPublishedApi()) ?: true)
3737

3838

39-
val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name }
40-
fun ClassNode.isLocal() = outerMethod != null
41-
fun ClassNode.isInner() = innerClassNode != null
42-
fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings")
43-
fun ClassNode.isSyntheticAnnotationClass() = isSynthetic(access) && name.contains("\$annotationImpl\$")
44-
fun ClassNode.isEnumEntriesMappings() = isSynthetic(access) && name.endsWith("\$EntriesMappings")
39+
private val ClassNode.innerClassNode: InnerClassNode? get() = innerClasses.singleOrNull { it.name == name }
40+
private fun ClassNode.isLocal() = outerMethod != null
41+
private fun ClassNode.isInner() = innerClassNode != null
42+
private fun ClassNode.isWhenMappings() = isSynthetic(access) && name.endsWith("\$WhenMappings")
43+
private fun ClassNode.isSyntheticAnnotationClass() = isSynthetic(access) && name.contains("\$annotationImpl\$")
44+
private fun ClassNode.isEnumEntriesMappings() = isSynthetic(access) && name.endsWith("\$EntriesMappings")
4545

46-
val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access
47-
val ClassNode.outerClassName: String? get() = innerClassNode?.outerName
46+
internal val ClassNode.effectiveAccess: Int get() = innerClassNode?.access ?: access
47+
internal val ClassNode.outerClassName: String? get() = innerClassNode?.outerName
4848

4949

50-
const val publishedApiAnnotationName = "kotlin/PublishedApi"
51-
fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
52-
fun List<AnnotationNode>.isPublishedApi() = firstOrNull { it.refersToName(publishedApiAnnotationName) } != null
50+
private const val publishedApiAnnotationName = "kotlin/PublishedApi"
51+
private fun ClassNode.isPublishedApi() = findAnnotation(publishedApiAnnotationName, includeInvisible = true) != null
52+
internal fun List<AnnotationNode>.isPublishedApi() = firstOrNull { it.refersToName(publishedApiAnnotationName) } != null
5353

54-
fun ClassNode.isDefaultImpls(metadata: KotlinClassMetadata?) = isInner() && name.endsWith("\$DefaultImpls") && metadata.isSyntheticClass()
54+
internal fun ClassNode.isDefaultImpls(metadata: KotlinClassMetadata?) = isInner() && name.endsWith("\$DefaultImpls") && metadata.isSyntheticClass()
5555

56-
57-
fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) =
56+
internal fun ClassNode.findAnnotation(annotationName: String, includeInvisible: Boolean = false) =
5857
findAnnotation(annotationName, visibleAnnotations, invisibleAnnotations, includeInvisible)
59-
operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key)
58+
internal operator fun AnnotationNode.get(key: String): Any? = values.annotationValue(key)
6059

6160
private fun List<Any>.annotationValue(key: String): Any? {
6261
for (index in (0 until size / 2)) {
@@ -75,5 +74,5 @@ private fun findAnnotation(
7574
visibleAnnotations?.firstOrNull { it.refersToName(annotationName) }
7675
?: if (includeInvisible) invisibleAnnotations?.firstOrNull { it.refersToName(annotationName) } else null
7776

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

src/main/kotlin/api/KotlinMetadataSignature.kt

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 JetBrains s.r.o.
2+
* Copyright 2016-2023 JetBrains s.r.o.
33
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
44
*/
55

@@ -11,22 +11,23 @@ import org.objectweb.asm.*
1111
import org.objectweb.asm.tree.*
1212

1313
@ExternalApi // Only name is part of the API, nothing else is used by stdlib
14-
data class ClassBinarySignature(
15-
val name: String,
16-
val superName: String,
17-
val outerName: String?,
18-
val supertypes: List<String>,
19-
val memberSignatures: List<MemberBinarySignature>,
20-
val access: AccessFlags,
21-
val isEffectivelyPublic: Boolean,
22-
val isNotUsedWhenEmpty: Boolean,
23-
val annotations: List<AnnotationNode>
14+
public data class ClassBinarySignature internal constructor(
15+
internal val name: String,
16+
internal val superName: String,
17+
internal val outerName: String?,
18+
internal val supertypes: List<String>,
19+
internal val memberSignatures: List<MemberBinarySignature>,
20+
internal val access: AccessFlags,
21+
internal val isEffectivelyPublic: Boolean,
22+
internal val isNotUsedWhenEmpty: Boolean,
23+
internal val annotations: List<AnnotationNode>
2424
) {
25-
val signature: String
25+
internal val signature: String
2626
get() = "${access.getModifierString()} class $name" + if (supertypes.isEmpty()) "" else " : ${supertypes.joinToString()}"
27+
2728
}
2829

29-
interface MemberBinarySignature {
30+
internal interface MemberBinarySignature {
3031
val jvmMember: JvmMemberSignature
3132
val name: String get() = jvmMember.name
3233
val desc: String get() = jvmMember.desc
@@ -45,7 +46,7 @@ interface MemberBinarySignature {
4546
val signature: String
4647
}
4748

48-
data class MethodBinarySignature(
49+
internal data class MethodBinarySignature(
4950
override val jvmMember: JvmMethodSignature,
5051
override val isPublishedApi: Boolean,
5152
override val access: AccessFlags,
@@ -93,7 +94,7 @@ internal fun MethodNode.alternateDefaultSignature(className: String): JvmMethodS
9394
}
9495
}
9596

96-
fun MethodNode.toMethodBinarySignature(
97+
internal fun MethodNode.toMethodBinarySignature(
9798
/*
9899
* Extra annotations are:
99100
* * Annotations from the original method for synthetic `$default` method
@@ -113,7 +114,7 @@ fun MethodNode.toMethodBinarySignature(
113114
)
114115
}
115116

116-
data class FieldBinarySignature(
117+
internal data class FieldBinarySignature(
117118
override val jvmMember: JvmFieldSignature,
118119
override val isPublishedApi: Boolean,
119120
override val access: AccessFlags,
@@ -128,7 +129,7 @@ data class FieldBinarySignature(
128129
}
129130
}
130131

131-
fun FieldNode.toFieldBinarySignature(extraAnnotations: List<AnnotationNode>): FieldBinarySignature {
132+
internal fun FieldNode.toFieldBinarySignature(extraAnnotations: List<AnnotationNode>): FieldBinarySignature {
132133
val allAnnotations = visibleAnnotations.orEmpty() + invisibleAnnotations.orEmpty() + extraAnnotations
133134
return FieldBinarySignature(
134135
JvmFieldSignature(name, desc),
@@ -144,26 +145,26 @@ private val MemberBinarySignature.kind: Int
144145
else -> error("Unsupported $this")
145146
}
146147

147-
val MEMBER_SORT_ORDER = compareBy<MemberBinarySignature>(
148+
internal val MEMBER_SORT_ORDER = compareBy<MemberBinarySignature>(
148149
{ it.kind },
149150
{ it.name },
150151
{ it.desc }
151152
)
152153

153-
data class AccessFlags(val access: Int) {
154+
internal data class AccessFlags(val access: Int) {
154155
val isPublic: Boolean get() = isPublic(access)
155156
val isProtected: Boolean get() = isProtected(access)
156157
val isStatic: Boolean get() = isStatic(access)
157158
val isFinal: Boolean get() = isFinal(access)
158159
val isSynthetic: Boolean get() = isSynthetic(access)
159160

160-
fun getModifiers(): List<String> =
161+
private fun getModifiers(): List<String> =
161162
ACCESS_NAMES.entries.mapNotNull { if (access and it.key != 0) it.value else null }
162163

163164
fun getModifierString(): String = getModifiers().joinToString(" ")
164165
}
165166

166-
fun FieldBinarySignature.isCompanionField(outerClassMetadata: KotlinClassMetadata?): Boolean {
167+
internal fun FieldBinarySignature.isCompanionField(outerClassMetadata: KotlinClassMetadata?): Boolean {
167168
if (!access.isFinal || !access.isStatic) return false
168169
val metadata = outerClassMetadata ?: return false
169170
// Non-classes are not affected by the problem

0 commit comments

Comments
 (0)