Skip to content

Commit

Permalink
- introduce new configuration option to ignore by pattern
Browse files Browse the repository at this point in the history
- introduce tests to verify excluding by pattern works
  • Loading branch information
mikepenz committed Jul 26, 2024
1 parent 14ebd02 commit 15ad935
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 6 deletions.
7 changes: 5 additions & 2 deletions api/binary-compatibility-validator.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public class kotlinx/validation/ApiValidationExtension {
public final fun getApiDumpDirectory ()Ljava/lang/String;
public final fun getIgnoredClasses ()Ljava/util/Set;
public final fun getIgnoredPackages ()Ljava/util/Set;
public final fun getIgnoredPatterns ()Ljava/util/Set;
public final fun getIgnoredProjects ()Ljava/util/Set;
public final fun getKlib ()Lkotlinx/validation/KlibValidationSettings;
public final fun getNonPublicMarkers ()Ljava/util/Set;
Expand All @@ -16,6 +17,7 @@ public class kotlinx/validation/ApiValidationExtension {
public final fun setApiDumpDirectory (Ljava/lang/String;)V
public final fun setIgnoredClasses (Ljava/util/Set;)V
public final fun setIgnoredPackages (Ljava/util/Set;)V
public final fun setIgnoredPatterns (Ljava/util/Set;)V
public final fun setIgnoredProjects (Ljava/util/Set;)V
public final fun setNonPublicMarkers (Ljava/util/Set;)V
public final fun setPublicClasses (Ljava/util/Set;)V
Expand All @@ -34,6 +36,7 @@ public abstract class kotlinx/validation/BuildTaskBase : kotlinx/validation/Work
public fun <init> ()V
public final fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty;
public final fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty;
public final fun getIgnoredPatterns ()Lorg/gradle/api/provider/SetProperty;
public final fun getNonPublicMarkers ()Lorg/gradle/api/provider/SetProperty;
public final fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty;
public final fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty;
Expand Down Expand Up @@ -133,8 +136,8 @@ public final class kotlinx/validation/api/KotlinSignaturesLoadingKt {
public static final fun dump (Ljava/util/List;Ljava/lang/Appendable;)Ljava/lang/Appendable;
public static final fun extractAnnotatedPackages (Ljava/util/List;Ljava/util/Set;)Ljava/util/List;
public static final fun filterOutAnnotated (Ljava/util/List;Ljava/util/Set;)Ljava/util/List;
public static final fun filterOutNonPublic (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;)Ljava/util/List;
public static synthetic fun filterOutNonPublic$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List;
public static final fun filterOutNonPublic (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;)Ljava/util/List;
public static synthetic fun filterOutNonPublic$default (Ljava/util/List;Ljava/util/Collection;Ljava/util/Collection;Ljava/util/Collection;ILjava/lang/Object;)Ljava/util/List;
public static final fun loadApiFromJvmClasses (Ljava/util/jar/JarFile;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static final fun loadApiFromJvmClasses (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
public static synthetic fun loadApiFromJvmClasses$default (Ljava/util/jar/JarFile;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/List;
Expand Down
99 changes: 99 additions & 0 deletions src/functionalTest/kotlin/kotlinx/validation/test/IgnoredTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2016-2020 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

package kotlinx.validation.test

import kotlinx.validation.api.*
import kotlinx.validation.api.BaseKotlinGradleTest
import kotlinx.validation.api.assertTaskSuccess
import kotlinx.validation.api.buildGradleKts
import kotlinx.validation.api.kotlin
import kotlinx.validation.api.readFileList
import kotlinx.validation.api.resolve
import kotlinx.validation.api.runner
import kotlinx.validation.api.test
import org.assertj.core.api.Assertions
import org.junit.Test
import kotlin.test.assertTrue

internal class IgnoredTests : BaseKotlinGradleTest() {

@Test
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPattern.gradle.kts")
}

kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}

emptyApiFile(projectName = rootProjectDir.name)

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun `apiCheck should succeed, when given class is not in api-File, but is ignored via ignored pattern (based on package)`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPackagePattern.gradle.kts")
}

kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}

emptyApiFile(projectName = rootProjectDir.name)

runner {
arguments.add(":apiCheck")
}
}

runner.build().apply {
assertTaskSuccess(":apiCheck")
}
}

@Test
fun `apiDump should not dump ignored classes, when class is excluded via ignored pattern`() {
val runner = test {
buildGradleKts {
resolve("/examples/gradle/base/withPlugin.gradle.kts")
resolve("/examples/gradle/configuration/ignored/oneValidPatternPackageClass.gradle.kts")
}
kotlin("BuildConfig.kt") {
resolve("/examples/classes/BuildConfig.kt")
}
kotlin("AnotherBuildConfig.kt") {
resolve("/examples/classes/AnotherBuildConfig.kt")
}

runner {
arguments.add(":apiDump")
}
}

runner.build().apply {
assertTaskSuccess(":apiDump")

assertTrue(rootProjectApiDump.exists(), "api dump file should exist")

val expected = readFileList("/examples/classes/AnotherBuildConfig.dump")
Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2016-2024 JetBrains s.r.o.
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
*/

configure<kotlinx.validation.ApiValidationExtension> {
ignoredPatterns.add("com\\/company\\/.+")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
configure<kotlinx.validation.ApiValidationExtension> {
ignoredPatterns.add(".+\\/BuildConfig")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
configure<kotlinx.validation.ApiValidationExtension> {
ignoredPatterns.add(".+\\/company\\/BuildConfig")
}
8 changes: 8 additions & 0 deletions src/main/kotlin/ApiValidationExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ public open class ApiValidationExtension {
*/
public var ignoredClasses: MutableSet<String> = HashSet()

/**
* Defines a regex pattern used to define classes and packages that are ignored by the API check.
*
* Example of such a package could be `.+\/internal\/.+`.
* An example of such a class could be `.+\/BuildConfig`
*/
public var ignoredPatterns: MutableSet<String> = HashSet()

/**
* Fully qualified names of annotations that can be used to explicitly mark public declarations.
* If at least one of [publicMarkers], [publicPackages] or [publicClasses] is defined,
Expand Down
18 changes: 18 additions & 0 deletions src/main/kotlin/BuildTaskBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.util.regex.Pattern
import javax.inject.Inject

public abstract class BuildTaskBase : WorkerAwareTaskBase() {
Expand All @@ -30,6 +31,18 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() {
)
}

private fun patternSetProperty(provider: ApiValidationExtension.() -> Set<Pattern>): SetProperty<Pattern> {
return project.objects.setProperty(Pattern::class.java).convention(
project.provider {
if (extension == null) {
emptySet()
} else {
provider(extension)
}
}
)
}

@get:Input
public val ignoredPackages: SetProperty<String> = stringSetProperty { ignoredPackages }

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

@get:Input
public val ignoredPatterns: SetProperty<Pattern> = patternSetProperty { ignoredPatterns.map { Pattern.compile(it) }.toSet() }

@get:Input
public val publicPackages: SetProperty<String> = stringSetProperty { publicPackages }

Expand All @@ -55,6 +71,7 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() {
params.ignoredPackages.set(ignoredPackages)
params.nonPublicMarkers.set(nonPublicMarkers)
params.ignoredClasses.set(ignoredClasses)
params.ignoredPatterns.set(ignoredPatterns)
params.publicPackages.set(publicPackages)
params.publicMarkers.set(publicMarkers)
params.publicClasses.set(publicClasses)
Expand All @@ -65,6 +82,7 @@ internal interface BuildParametersBase : WorkParameters {
val ignoredPackages: SetProperty<String>
val nonPublicMarkers: SetProperty<String>
val ignoredClasses: SetProperty<String>
val ignoredPatterns: SetProperty<Pattern>
val publicPackages: SetProperty<String>
val publicMarkers: SetProperty<String>
val publicClasses: SetProperty<String>
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/KotlinApiBuildTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.gradle.api.*
import org.gradle.api.file.*
import org.gradle.api.tasks.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkerExecutor
import java.io.File
import java.util.jar.JarFile
import javax.inject.Inject
Expand Down Expand Up @@ -94,6 +93,7 @@ internal abstract class AbiBuildWorker : WorkAction<ApiBuildParameters> {
val nonPublicMarkers = parameters.nonPublicMarkers.get()
val ignoredClasses = parameters.ignoredClasses.get()
val ignoredPackages = parameters.ignoredPackages.get()
val ignoredPattern = parameters.ignoredPatterns.get()

val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet())
val ignoredPackagesNames =
Expand All @@ -103,7 +103,7 @@ internal abstract class AbiBuildWorker : WorkAction<ApiBuildParameters> {
.retainExplicitlyIncludedIfDeclared(
publicPackages + publicPackagesNames, publicClasses, publicMarkers
)
.filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses)
.filterOutNonPublic(ignoredPattern, ignoredPackages + ignoredPackagesNames, ignoredClasses)
.filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet())

parameters.outputApiFile.asFile.get().bufferedWriter().use { writer ->
Expand Down
7 changes: 5 additions & 2 deletions src/main/kotlin/api/KotlinSignaturesLoading.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import org.objectweb.asm.tree.*
import java.io.*
import java.util.*
import java.util.jar.*
import java.util.regex.Pattern
import kotlin.metadata.KmProperty
import kotlin.metadata.visibility

@ExternalApi
@Suppress("unused")
Expand Down Expand Up @@ -280,6 +280,7 @@ public fun List<ClassBinarySignature>.extractAnnotatedPackages(targetAnnotations

@ExternalApi
public fun List<ClassBinarySignature>.filterOutNonPublic(
nonPublicPatterns: Collection<Pattern> = emptyList(),
nonPublicPackages: Collection<String> = emptyList(),
nonPublicClasses: Collection<String> = emptyList()
): List<ClassBinarySignature> {
Expand All @@ -288,6 +289,8 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(

val classByName = associateBy { it.name }

fun ClassBinarySignature.isNonPublic() = nonPublicPatterns.any { it.matcher(name).matches() }

fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
isEffectivelyPublic &&
(outerName == null || classByName[outerName]?.let { outerClass ->
Expand All @@ -314,7 +317,7 @@ public fun List<ClassBinarySignature>.filterOutNonPublic(
}

return filter {
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible()
!it.isInPackages(nonPublicPackagePaths) && !it.isInClasses(excludedClasses) && it.isPublicAndAccessible() && !it.isNonPublic()
}
.map { it.flattenNonPublicBases() }
.filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }
Expand Down

0 comments on commit 15ad935

Please sign in to comment.