Skip to content

Commit 82dc21b

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 82dc21b

18 files changed

+89
-301
lines changed

api/binary-compatibility-validator.api

Lines changed: 0 additions & 208 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/functionalTest/kotlin/kotlinx/validation/api/BaseKotlinGradleTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.api
77

8-
import kotlinx.validation.API_DIR
98
import org.junit.Rule
109
import org.junit.rules.TemporaryFolder
1110
import java.io.File

src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
package kotlinx.validation.api
77

88
import java.io.*
9-
import kotlinx.validation.API_DIR
109
import org.gradle.testkit.runner.GradleRunner
1110
import org.intellij.lang.annotations.Language
1211

12+
public const val API_DIR: String = "api"
13+
1314
internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner {
1415
val baseKotlinScope = BaseKotlinScope()
1516
fn(baseKotlinScope)

src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.test
77

8-
import kotlinx.validation.API_DIR
98
import kotlinx.validation.api.*
109
import org.assertj.core.api.*
1110
import org.junit.Test

src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.test
77

8-
import kotlinx.validation.API_DIR
98
import kotlinx.validation.api.*
109
import org.assertj.core.api.Assertions.assertThat
1110
import org.junit.Test

src/functionalTest/kotlin/kotlinx/validation/test/MultipleJvmTargetsTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.test
77

8-
import kotlinx.validation.API_DIR
98
import kotlinx.validation.api.*
109
import org.assertj.core.api.Assertions.assertThat
1110
import org.gradle.testkit.runner.GradleRunner

src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnRootTests.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.test
77

8-
import kotlinx.validation.API_DIR
98
import kotlinx.validation.api.*
109
import kotlinx.validation.api.BaseKotlinGradleTest
1110
import kotlinx.validation.api.assertTaskSuccess

src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnSubTests.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
package kotlinx.validation.test
77

8-
import kotlinx.validation.API_DIR
98
import kotlinx.validation.api.*
109
import kotlinx.validation.api.BaseKotlinGradleTest
1110
import kotlinx.validation.api.assertTaskSuccess

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" // Mirrored in functional tests
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+
public var inputClassesDirs: FileCollection? = null
2525

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

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

3535
@OutputDirectory
36-
lateinit var outputApiDir: File
36+
public 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+
public 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+
public 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+
public lateinit var apiBuildDir: File
4949

5050
@OutputFile
5151
@Optional
5252
@Suppress("unused")
53-
val dummyOutputFile: File? = null
53+
public 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)

0 commit comments

Comments
 (0)