Skip to content

Commit ce7c919

Browse files
committed
- introduce new configuration option to ignore by pattern
- introduce tests to verify excluding by pattern works
1 parent 18cd0df commit ce7c919

File tree

9 files changed

+151
-6
lines changed

9 files changed

+151
-6
lines changed

api/binary-compatibility-validator.api

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public class kotlinx/validation/ApiValidationExtension {
44
public final fun getApiDumpDirectory ()Ljava/lang/String;
55
public final fun getIgnoredClasses ()Ljava/util/Set;
66
public final fun getIgnoredPackages ()Ljava/util/Set;
7+
public final fun getIgnoredPatterns ()Ljava/util/Set;
78
public final fun getIgnoredProjects ()Ljava/util/Set;
89
public final fun getKlib ()Lkotlinx/validation/KlibValidationSettings;
910
public final fun getNonPublicMarkers ()Ljava/util/Set;
@@ -16,6 +17,7 @@ public class kotlinx/validation/ApiValidationExtension {
1617
public final fun setApiDumpDirectory (Ljava/lang/String;)V
1718
public final fun setIgnoredClasses (Ljava/util/Set;)V
1819
public final fun setIgnoredPackages (Ljava/util/Set;)V
20+
public final fun setIgnoredPatterns (Ljava/util/Set;)V
1921
public final fun setIgnoredProjects (Ljava/util/Set;)V
2022
public final fun setNonPublicMarkers (Ljava/util/Set;)V
2123
public final fun setPublicClasses (Ljava/util/Set;)V
@@ -34,6 +36,7 @@ public abstract class kotlinx/validation/BuildTaskBase : kotlinx/validation/Work
3436
public fun <init> ()V
3537
public final fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty;
3638
public final fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty;
39+
public final fun getIgnoredPatterns ()Lorg/gradle/api/provider/SetProperty;
3740
public final fun getNonPublicMarkers ()Lorg/gradle/api/provider/SetProperty;
3841
public final fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty;
3942
public final fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty;
@@ -133,8 +136,8 @@ public final class kotlinx/validation/api/KotlinSignaturesLoadingKt {
133136
public static final fun dump (Ljava/util/List;Ljava/lang/Appendable;)Ljava/lang/Appendable;
134137
public static final fun extractAnnotatedPackages (Ljava/util/List;Ljava/util/Set;)Ljava/util/List;
135138
public static final fun filterOutAnnotated (Ljava/util/List;Ljava/util/Set;)Ljava/util/List;
136-
public static final fun filterOutNonPublic (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;)Ljava/util/List;
137-
public static synthetic fun filterOutNonPublic$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List;
139+
public static final fun filterOutNonPublic (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;)Ljava/util/List;
140+
public static synthetic fun filterOutNonPublic$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List;
138141
public static final fun loadApiFromJvmClasses (Ljava/util/jar/JarFile;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
139142
public static final fun loadApiFromJvmClasses (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
140143
public static synthetic fun loadApiFromJvmClasses$default (Ljava/util/jar/JarFile;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/List;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
package kotlinx.validation.test
7+
8+
import kotlinx.validation.api.*
9+
import kotlinx.validation.api.BaseKotlinGradleTest
10+
import kotlinx.validation.api.assertTaskSuccess
11+
import kotlinx.validation.api.buildGradleKts
12+
import kotlinx.validation.api.kotlin
13+
import kotlinx.validation.api.readFileList
14+
import kotlinx.validation.api.resolve
15+
import kotlinx.validation.api.runner
16+
import kotlinx.validation.api.test
17+
import org.assertj.core.api.Assertions
18+
import org.junit.Test
19+
import kotlin.test.assertTrue
20+
21+
internal class IgnoredTests : BaseKotlinGradleTest() {
22+
23+
@Test
24+
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern`() {
25+
val runner = test {
26+
buildGradleKts {
27+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
28+
resolve("/examples/gradle/configuration/ignored/oneValidPattern.gradle.kts")
29+
}
30+
31+
kotlin("BuildConfig.kt") {
32+
resolve("/examples/classes/BuildConfig.kt")
33+
}
34+
35+
emptyApiFile(projectName = rootProjectDir.name)
36+
37+
runner {
38+
arguments.add(":apiCheck")
39+
}
40+
}
41+
42+
runner.build().apply {
43+
assertTaskSuccess(":apiCheck")
44+
}
45+
}
46+
47+
@Test
48+
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern (based on package)`() {
49+
val runner = test {
50+
buildGradleKts {
51+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
52+
resolve("/examples/gradle/configuration/ignored/oneValidPackagePattern.gradle.kts")
53+
}
54+
55+
kotlin("BuildConfig.kt") {
56+
resolve("/examples/classes/BuildConfig.kt")
57+
}
58+
59+
emptyApiFile(projectName = rootProjectDir.name)
60+
61+
runner {
62+
arguments.add(":apiCheck")
63+
}
64+
}
65+
66+
runner.build().apply {
67+
assertTaskSuccess(":apiCheck")
68+
}
69+
}
70+
71+
@Test
72+
fun `apiDump should not dump ignored classes, when class is excluded via ignored pattern`() {
73+
val runner = test {
74+
buildGradleKts {
75+
resolve("/examples/gradle/base/withPlugin.gradle.kts")
76+
resolve("/examples/gradle/configuration/ignored/oneValidPatternPackageClass.gradle.kts")
77+
}
78+
kotlin("BuildConfig.kt") {
79+
resolve("/examples/classes/BuildConfig.kt")
80+
}
81+
kotlin("AnotherBuildConfig.kt") {
82+
resolve("/examples/classes/AnotherBuildConfig.kt")
83+
}
84+
85+
runner {
86+
arguments.add(":apiDump")
87+
}
88+
}
89+
90+
runner.build().apply {
91+
assertTaskSuccess(":apiDump")
92+
93+
assertTrue(rootProjectApiDump.exists(), "api dump file should exist")
94+
95+
val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
96+
Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected)
97+
}
98+
}
99+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright 2016-2024 JetBrains s.r.o.
3+
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4+
*/
5+
6+
configure<kotlinx.validation.ApiValidationExtension> {
7+
ignoredPatterns.add("com\\/company\\/.+")
8+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
configure<kotlinx.validation.ApiValidationExtension> {
2+
ignoredPatterns.add(".+\\/BuildConfig")
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
configure<kotlinx.validation.ApiValidationExtension> {
2+
ignoredPatterns.add(".+\\/company\\/BuildConfig")
3+
}

src/main/kotlin/ApiValidationExtension.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ public open class ApiValidationExtension {
3838
*/
3939
public var ignoredClasses: MutableSet<String> = HashSet()
4040

41+
/**
42+
* Defines a regex pattern used to define classes and packages that are ignored by the API check.
43+
*
44+
* Example of such a package could be `.+\/internal\/.+`.
45+
* An example of such a class could be `.+\/BuildConfig`
46+
*/
47+
public var ignoredPatterns: MutableSet<String> = HashSet()
48+
4149
/**
4250
* Fully qualified names of annotations that can be used to explicitly mark public declarations.
4351
* If at least one of [publicMarkers], [publicPackages] or [publicClasses] is defined,

src/main/kotlin/BuildTaskBase.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import org.gradle.api.tasks.Input
1010
import org.gradle.api.tasks.Internal
1111
import org.gradle.workers.WorkParameters
1212
import org.gradle.workers.WorkerExecutor
13+
import java.util.regex.Pattern
1314
import javax.inject.Inject
1415

1516
public abstract class BuildTaskBase : WorkerAwareTaskBase() {
@@ -30,6 +31,18 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() {
3031
)
3132
}
3233

34+
private fun patternSetProperty(provider: ApiValidationExtension.() -> Set<Pattern>): SetProperty<Pattern> {
35+
return project.objects.setProperty(Pattern::class.java).convention(
36+
project.provider {
37+
if (extension == null) {
38+
emptySet()
39+
} else {
40+
provider(extension)
41+
}
42+
}
43+
)
44+
}
45+
3346
@get:Input
3447
public val ignoredPackages: SetProperty<String> = stringSetProperty { ignoredPackages }
3548

@@ -39,6 +52,9 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() {
3952
@get:Input
4053
public val ignoredClasses: SetProperty<String> = stringSetProperty { ignoredClasses }
4154

55+
@get:Input
56+
public val ignoredPatterns: SetProperty<Pattern> = patternSetProperty { ignoredPatterns.map { Pattern.compile(it) }.toSet() }
57+
4258
@get:Input
4359
public val publicPackages: SetProperty<String> = stringSetProperty { publicPackages }
4460

@@ -55,6 +71,7 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() {
5571
params.ignoredPackages.set(ignoredPackages)
5672
params.nonPublicMarkers.set(nonPublicMarkers)
5773
params.ignoredClasses.set(ignoredClasses)
74+
params.ignoredPatterns.set(ignoredPatterns)
5875
params.publicPackages.set(publicPackages)
5976
params.publicMarkers.set(publicMarkers)
6077
params.publicClasses.set(publicClasses)
@@ -65,6 +82,7 @@ internal interface BuildParametersBase : WorkParameters {
6582
val ignoredPackages: SetProperty<String>
6683
val nonPublicMarkers: SetProperty<String>
6784
val ignoredClasses: SetProperty<String>
85+
val ignoredPatterns: SetProperty<Pattern>
6886
val publicPackages: SetProperty<String>
6987
val publicMarkers: SetProperty<String>
7088
val publicClasses: SetProperty<String>

src/main/kotlin/KotlinApiBuildTask.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import org.gradle.api.*
1010
import org.gradle.api.file.*
1111
import org.gradle.api.tasks.*
1212
import org.gradle.workers.WorkAction
13-
import org.gradle.workers.WorkerExecutor
1413
import java.io.File
1514
import java.util.jar.JarFile
1615
import javax.inject.Inject
@@ -94,6 +93,7 @@ internal abstract class AbiBuildWorker : WorkAction<ApiBuildParameters> {
9493
val nonPublicMarkers = parameters.nonPublicMarkers.get()
9594
val ignoredClasses = parameters.ignoredClasses.get()
9695
val ignoredPackages = parameters.ignoredPackages.get()
96+
val ignoredPattern = parameters.ignoredPatterns.get()
9797

9898
val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet())
9999
val ignoredPackagesNames =
@@ -103,7 +103,7 @@ internal abstract class AbiBuildWorker : WorkAction<ApiBuildParameters> {
103103
.retainExplicitlyIncludedIfDeclared(
104104
publicPackages + publicPackagesNames, publicClasses, publicMarkers
105105
)
106-
.filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses)
106+
.filterOutNonPublic(ignoredPattern, ignoredPackages + ignoredPackagesNames, ignoredClasses)
107107
.filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet())
108108

109109
parameters.outputApiFile.asFile.get().bufferedWriter().use { writer ->

src/main/kotlin/api/KotlinSignaturesLoading.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import org.objectweb.asm.tree.*
1212
import java.io.*
1313
import java.util.*
1414
import java.util.jar.*
15+
import java.util.regex.Pattern
1516
import kotlin.metadata.KmProperty
16-
import kotlin.metadata.visibility
1717

1818
@ExternalApi
1919
@Suppress("unused")
@@ -280,6 +280,7 @@ public fun List<ClassBinarySignature>.extractAnnotatedPackages(targetAnnotations
280280

281281
@ExternalApi
282282
public fun List<ClassBinarySignature>.filterOutNonPublic(
283+
nonPublicPatterns: Collection<Pattern> = emptyList(),
283284
nonPublicPackages: Collection<String> = emptyList(),
284285
nonPublicClasses: Collection<String> = emptyList()
285286
): List<ClassBinarySignature> {
@@ -288,6 +289,8 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(
288289

289290
val classByName = associateBy { it.name }
290291

292+
fun ClassBinarySignature.isNonPublic() = nonPublicPatterns.any { it.matcher(name).matches() }
293+
291294
fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
292295
isEffectivelyPublic &&
293296
(outerName == null || classByName[outerName]?.let { outerClass ->
@@ -314,7 +317,7 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(
314317
}
315318

316319
return filter {
317-
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible()
320+
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible() && !it.isNonPublic()
318321
}
319322
.map { it.flattenNonPublicBases() }
320323
.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }

0 commit comments

Comments
 (0)