diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredProjectsTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredProjectsTests.kt new file mode 100644 index 00000000..c2672b8f --- /dev/null +++ b/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredProjectsTests.kt @@ -0,0 +1,133 @@ +/* + * 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.assertTaskSuccess +import kotlinx.validation.api.buildGradleKts +import kotlinx.validation.api.resolve +import kotlinx.validation.api.runner +import kotlinx.validation.api.test +import org.junit.Test + +internal class IgnoredProjectsTests : BaseKotlinGradleTest() { + /** + * Creates a single project with 2 (Kotlin and Java Android Library) modules, applies + * the plugin on the root project. + */ + + @Test + fun `apiCheck should succeed when a project does not have an api file but is ignored via ignoredProjects`() { + val runner = test { + createProjectHierarchyWithPluginOnRoot() + + // Ignore "sub1" project + buildGradleKts { + resolve("examples/gradle/configuration/ignoredProjects/ignoreSub1.gradle.kts") + } + + emptyApiFile(projectName = rootProjectDir.name) + + dir("sub1") { + dir("subsub1") { + emptyApiFile(projectName = "subsub1") + } + dir("subsub2") { + emptyApiFile(projectName = "subsub2") + } + } + + dir("sub2") { + emptyApiFile(projectName = "sub2") + } + + runner { + arguments.add("check") + } + } + + runner.build().apply { + assertTaskSuccess(":apiCheck") + assertTaskSuccess(":sub1:subsub1:apiCheck") + assertTaskSuccess(":sub1:subsub2:apiCheck") + assertTaskSuccess(":sub2:apiCheck") + } + } + + @Test + fun `apiCheck should succeed ignoring a project and its subprojects`() { + val runner = test { + createProjectHierarchyWithPluginOnRoot() + + // Ignore "sub1" project and its subprojects ("subsub1" and "subsub2") + buildGradleKts { + resolve("examples/gradle/configuration/ignoredProjects/ignoreSub1.gradle.kts") + resolve("examples/gradle/configuration/ignoredProjects/ignoreSubProjects.gradle.kts") + } + + emptyApiFile(projectName = rootProjectDir.name) + + dir("sub1") { + dir("subsub1") {} + dir("subsub2") {} + } + + dir("sub2") { + emptyApiFile(projectName = "sub2") + } + + runner { + arguments.add("check") + } + } + + runner.build().apply { + assertTaskSuccess(":apiCheck") + assertTaskSuccess(":sub2:apiCheck") + } + } + + /** + * Sets up a project hierarchy like this: + * ``` + * build.gradle.kts (with the plugin) + * settings.gradle.kts (including refs to 4 subprojects) + * sub1/ + * build.gradle.kts + * subsub1/build.gradle.kts + * subsub2/build.gradle.kts + * sub2/build.gradle.kts + * ``` + */ + private fun BaseKotlinScope.createProjectHierarchyWithPluginOnRoot() { + settingsGradleKts { + resolve("examples/gradle/settings/settings-with-hierarchy.gradle.kts") + } + buildGradleKts { + resolve("examples/gradle/base/withPlugin.gradle.kts") + } + dir("sub1") { + buildGradleKts { + resolve("examples/gradle/base/withoutPlugin-noKotlinVersion.gradle.kts") + } + dir("subsub1") { + buildGradleKts { + resolve("examples/gradle/base/withoutPlugin-noKotlinVersion.gradle.kts") + } + } + dir("subsub2") { + buildGradleKts { + resolve("examples/gradle/base/withoutPlugin-noKotlinVersion.gradle.kts") + } + } + } + dir("sub2") { + buildGradleKts { + resolve("examples/gradle/base/withoutPlugin-noKotlinVersion.gradle.kts") + } + } + } +} diff --git a/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSub1.gradle.kts b/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSub1.gradle.kts new file mode 100644 index 00000000..acc2956f --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSub1.gradle.kts @@ -0,0 +1,3 @@ +configure { + ignoredProjects.add("sub1") +} diff --git a/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSubprojects.gradle.kts b/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSubprojects.gradle.kts new file mode 100644 index 00000000..8614ee93 --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/configuration/ignoredProjects/ignoreSubprojects.gradle.kts @@ -0,0 +1,3 @@ +configure { + ignoreSubprojects = true +} diff --git a/src/main/kotlin/ApiValidationExtension.kt b/src/main/kotlin/ApiValidationExtension.kt index deb85435..c9abecc8 100644 --- a/src/main/kotlin/ApiValidationExtension.kt +++ b/src/main/kotlin/ApiValidationExtension.kt @@ -23,6 +23,11 @@ open class ApiValidationExtension { */ public var ignoredProjects: MutableSet = HashSet() + /** + * Should projects specified in [ignoredProjects] also have their subprojects ignored + */ + public var ignoreSubprojects: Boolean = false + /** * Fully qualified names of annotations that effectively exclude declarations from being public. * Example of such annotation could be `kotlinx.coroutines.InternalCoroutinesApi`. diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index c0217ae4..03db00ca 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -27,6 +27,17 @@ class BinaryCompatibilityValidatorPlugin : Plugin { private fun Project.validateExtension(extension: ApiValidationExtension) { afterEvaluate { + val ignoredSubprojects = extension.ignoredProjects + .flatMap { projectName -> + allprojects + .filter { project -> project.name == projectName } + .flatMap { project -> project.subprojects.map { it.name }}} + .takeIf { extension.ignoreSubprojects } + + ignoredSubprojects?.let { + extension.ignoredProjects.addAll(it) + } + val ignored = extension.ignoredProjects val all = allprojects.map { it.name } for (project in ignored) {