From 3a03ae1c411091d194590aba6c3db185174f9014 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Fri, 3 Mar 2023 13:23:09 +0100 Subject: [PATCH] Improve settings plugin (#2) * allow for nested project paths in GradleProjectTest util, and minor doc update * rename testGradleVersion to supportedGradleVersion * make testMavenPublication task dependency more explicit * update from ignoredMarkers to nonPublicMarkers in test case * update from using KGP to KGP-api * allow setting default BCV target values in BCVSettingsPlugin * update test assertions * Add settings plugin test * update README example for settings plugin * try using Shadow plugin... which didn't work, but it might with different config? https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu/issues/1#issuecomment-1453080617 * add kotest-datatest in libs.versions.toml * commit code style * update formatting --- .gitignore | 4 +- .idea/codeStyles/Project.xml | 616 ++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 + README.md | 59 +- build.gradle.kts | 4 +- buildSrc/build.gradle.kts | 1 + gradle/libs.versions.toml | 6 +- .../build.gradle.kts | 3 +- .../validation/test/AndroidLibraryTest.kt | 4 +- .../test/MultiPlatformSingleJvmTargetTest.kt | 26 +- .../validation/test/MultipleJvmTargetsTest.kt | 9 +- .../validation/test/SettingsPluginDslTest.kt | 227 +++++++ .../test/SubprojectsWithPluginOnSubTests.kt | 38 +- ...ultiplatformWithSingleJvmTarget.gradle.kts | 2 +- .../nonPublicMarkers/markers.gradle.kts | 9 +- .../testFixtures/kotlin/GradleTestKitUtils.kt | 15 +- modules/bcv-gradle-plugin/build.gradle.kts | 15 +- .../src/main/kotlin/BCVPlugin.kt | 2 +- .../src/main/kotlin/BCVProjectPlugin.kt | 53 +- .../src/main/kotlin/BCVSettingsPlugin.kt | 43 +- .../main/kotlin/internal/gradleAccessors.kt | 2 +- .../kotlin/kotestGradleAssertions.kt | 11 + 22 files changed, 1053 insertions(+), 101 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt diff --git a/.gitignore b/.gitignore index 09f04f3..d8726fc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,9 @@ replay_pid* ### IntelliJ ### -.idea +.idea/** +!.idea/codeStyles/ +!.idea/codeStyles/** ### Eclipse ### diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..0270e59 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,616 @@ + + + + + diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 8686eb3..c9f5ec9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ # Kotlin Binary Compatibility Validator (Mirror Universe) -[BCV-MU](https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu) is a re-imagined [Gradle](https://gradle.org/) Plugin for +[BCV-MU](https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu) is a +re-imagined [Gradle](https://gradle.org/) Plugin for [Kotlin/binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator). This plugin validates the public JVM binary API of libraries to make sure that breaking changes are @@ -194,8 +195,8 @@ All subprojects are included by default, and can be excluded using BCV-MU config buildscript { dependencies { - // BCV-MU requires the Kotlin Gradle Plugin classes are present - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10") + // BCV-MU requires the Kotlin Gradle Plugin classes are present + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10") } } @@ -203,19 +204,47 @@ plugins { id("dev.adamko.kotlin.binary-compatibility-validator") version "$bcvMuVersion" } -extensions - .getByType() - .apply { - ignoredProjects.addAll( - - // ignore subprojects explicitly - ":some-subproject", - - // or ignore using a glob pattern - ":internal-dependencies:**", - ":*-tasks:**", - ) +binaryCompatibilityValidator { + ignoredProjects.addAll( + + ":", // ignore root project + ":some-subproject", // ignore subprojects explicitly + + // or ignore using a glob pattern + ":internal-dependencies:*", + ":*-tasks:**", + ) + + // set the default values for all targets in all enabled-subprojects + defaultTargetValues { + enabled.convention(true) + ignoredClasses.set(listOf("com.package.MyIgnoredClass")) + ignoredMarkers.set(listOf("com.package.MyInternalApiAnnotationMarker")) + ignoredPackages.set(listOf("com.package.my_ignored_package")) } +} + +include( + // these projects will have BCV-MU automatically applied + ":common", + ":internal-dependencies:alpha:nested", + ":internal-dependencies:alpha:nested", + ":a-task", + + // this subproject is explicitly excluded from BCV + ":some-subproject", + + // these subprojects will be excluded by glob pattern + ":internal-dependencies", + ":internal-dependencies:alpha", + ":internal-dependencies:beta", + ":x-tasks", + ":x-tasks:sub1", + ":x-tasks:sub1:sub2", + ":z-tasks", + ":z-tasks:sub1", + ":z-tasks:sub1:sub2", +) ``` ## License diff --git a/build.gradle.kts b/build.gradle.kts index f57004d..79d3b0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ idea { val readmeCheck by tasks.registering { group = LifecycleBasePlugin.VERIFICATION_GROUP val readme = providers.fileContents(layout.projectDirectory.file("README.md")).asText - val minimumGradleTestVersion = libs.versions.testGradleVersion + val supportedGradleVersion = libs.versions.supportedGradleVersion val kotlinBcvVersion = libs.versions.kotlinx.bcv doLast { @@ -34,7 +34,7 @@ val readmeCheck by tasks.registering { require("kotlinxBinaryCompatibilityValidatorVersion.set(\"${kotlinBcvVersion.get()}\")" in readme) { "Incorrect BCV version in README" } - require("The minimal supported Gradle version is ${minimumGradleTestVersion.get()}" in readme) { + require("The minimal supported Gradle version is ${supportedGradleVersion.get()}" in readme) { "Incorrect Gradle version in README" } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 931a956..06d5b9e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { implementation("org.gradle.kotlin:gradle-kotlin-dsl-plugins:$expectedKotlinDslPluginsVersion") implementation(libs.gradlePlugin.pluginPublishing) + implementation(libs.gradlePlugin.shadow) } java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3ca12c7..249494c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,20 +8,23 @@ kotest = "5.5.5" kotlinx-bcv = "0.13.0" gradlePluginPublishPlugin = "1.1.0" +shadowPlugin = "8.1.0" -testGradleVersion = "7.6" # the minimal supported Gradle plugin version, used in functional tests +supportedGradleVersion = "7.6" # the minimal supported Gradle plugin version, used in functional tests [libraries] javaDiffUtils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "javaDiffUtils" } kotlinx-bcv = { module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "kotlinx-bcv" } kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradle" } +kotlin-gradlePluginApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlinGradle" } ## region Test Libraries kotest-bom = { module = "io.kotest:kotest-bom", version.ref = "kotest" } kotest-runnerJUnit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } kotest-assertionsCore = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } +kotest-datatest = { module = "io.kotest:kotest-framework-datatest", version.ref = "kotest" } junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } @@ -29,4 +32,5 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun ## region Gradle Plugins gradlePlugin-pluginPublishing = { module = "com.gradle.publish:plugin-publish-plugin", version.ref = "gradlePluginPublishPlugin" } +gradlePlugin-shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadowPlugin" } ## endregion diff --git a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts index 81d1e4d..aa4051d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts @@ -33,7 +33,7 @@ testing.suites { inputs.property("projectTestTempDir", projectTestTempDirPath) systemProperty("projectTestTempDir", projectTestTempDirPath) systemProperty("integrationTestProjectsDir", "$projectDir/projects") - systemProperty("minimumGradleTestVersion", libs.versions.testGradleVersion.get()) + systemProperty("minimumGradleTestVersion", libs.versions.supportedGradleVersion.get()) } } } @@ -54,6 +54,7 @@ testing.suites { testTask.configure { shouldRunAfter(test) dependsOn(project.configurations.testMavenPublication) + inputs.files(project.configurations.testMavenPublication) systemProperty("testMavenRepoDir", file(mavenPublishTest.testMavenRepo).canonicalPath) } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/AndroidLibraryTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/AndroidLibraryTest.kt index 6f03a8a..0352e75 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/AndroidLibraryTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/AndroidLibraryTest.kt @@ -37,7 +37,7 @@ internal class AndroidLibraryTest : BaseKotlinGradleTest() { runner { arguments.add(":kotlin-library:apiCheck") } - }.build().apply { + }.build { task(":kotlin-library:apiCheck") shouldHaveOutcome SUCCESS } } @@ -70,7 +70,7 @@ internal class AndroidLibraryTest : BaseKotlinGradleTest() { runner { arguments.add(":java-library:apiCheck") } - }.build().apply { + }.build { task(":java-library:apiCheck") shouldHaveOutcome SUCCESS } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt index 4b77fd3..e0ded0f 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt @@ -2,6 +2,7 @@ package kotlinx.validation.test import dev.adamko.kotlin.binary_compatibility_validator.test.utils.* import dev.adamko.kotlin.binary_compatibility_validator.test.utils.api.* +import io.kotest.assertions.withClue import io.kotest.matchers.comparables.shouldBeEqualComparingTo import io.kotest.matchers.string.shouldContain import java.io.File @@ -42,7 +43,6 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { kotlin("Subsub2Class.kt", "jvmMain") { resolve("/examples/classes/Subsub2Class.kt") } - } runner.build { @@ -74,13 +74,28 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { kotlin("Subsub2Class.kt", "jvmMain") { resolve("/examples/classes/Subsub2Class.kt") } - + dir("src/jvmTest/kotlin") {} + kotlin("Subsub2ClassTest.kt", "jvmTest") { + addText(/*language=kotlin*/ """ + |package com.company.test + | + |class SubSub2Test { + | fun blah() { + | println("test") + | } + |} + | + """.trimMargin() + ) + } } runner.buildAndFail { - task(":apiCheck") shouldHaveOutcome FAILED - output shouldContain "API check failed for project :testproject" - shouldNotHaveRunTask(":check") + withClue(output) { + shouldHaveRunTask(":apiCheck", FAILED) + output shouldContain "API check failed for project :testproject" + shouldNotHaveRunTask(":check") + } } } @@ -101,7 +116,6 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { kotlin("Subsub2Class.kt", "jvmMain") { resolve("/examples/classes/Subsub2Class.kt") } - } runner.build { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultipleJvmTargetsTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultipleJvmTargetsTest.kt index 5cebfe0..67e6f2d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultipleJvmTargetsTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/MultipleJvmTargetsTest.kt @@ -2,6 +2,7 @@ package kotlinx.validation.test import dev.adamko.kotlin.binary_compatibility_validator.test.utils.* import dev.adamko.kotlin.binary_compatibility_validator.test.utils.api.* +import io.kotest.assertions.withClue import io.kotest.matchers.file.shouldBeAFile import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain @@ -90,9 +91,11 @@ internal class MultipleJvmTargetsTest : BaseKotlinGradleTest() { } runner.buildAndFail { - task(":apiCheck") shouldHaveOutcome FAILED - output shouldContain "API check failed for project :testproject" - shouldNotHaveRunTask(":check") + withClue(output) { + shouldHaveRunTask(":apiCheck") shouldHaveOutcome FAILED + output shouldContain "API check failed for project :testproject" + shouldNotHaveRunTask(":check") + } } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt new file mode 100644 index 0000000..ad78e3b --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SettingsPluginDslTest.kt @@ -0,0 +1,227 @@ +package kotlinx.validation.test + +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.* +import io.kotest.assertions.asClue +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.paths.shouldBeAFile +import io.kotest.matchers.paths.shouldExist +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import kotlin.io.path.readText +import org.gradle.testkit.runner.TaskOutcome +import org.intellij.lang.annotations.Language + +internal class SettingsPluginDslTest : FunSpec({ + + context("when BCV-MU is applied as a settings plugin") { + + val kotlinJvmTest = TestCase( + projectType = "Kotlin/JVM", + project = kotlinJvmProjectWithBcvSettingsPlugin(), + expectedPrintedBCVTargets = """ + |name: kotlinJvm + |platformType: kotlinJvm + |enabled: true + |ignoredClasses: [com.package.MyIgnoredClass] + |ignoredMarkers: [com.package.MyInternalApiAnnotationMarker] + |ignoredPackages: [com.package.my_ignored_package] + |inputClasses: [main, main] + |inputJar: null + |------------------------------ + | + """.trimMargin() + ) + + val kotlinMultiplatformTest = TestCase( + projectType = "Kotlin/Multiplatform", + project = kotlinMultiplatformProjectWithBcvSettingsPlugin(), + expectedPrintedBCVTargets = """ + |name: jvm + |platformType: jvm + |enabled: true + |ignoredClasses: [com.package.MyIgnoredClass] + |ignoredMarkers: [com.package.MyInternalApiAnnotationMarker] + |ignoredPackages: [com.package.my_ignored_package] + |inputClasses: [main] + |inputJar: null + |------------------------------ + | + """.trimMargin() + ) + + listOf( + kotlinJvmTest, + kotlinMultiplatformTest, + ).forEach { testCase -> + context("in a ${testCase.projectType} project") { + + test("apiDump should be generated in all non-excluded subprojects") { + testCase.project.projectDir.toFile() + .walk() + .filter { it.isDirectory && it.name == "api" } + .forEach { it.deleteRecursively() } + + testCase.project.runner + .withArguments("apiDump", "--stacktrace") + .build { + output shouldContain "SUCCESSFUL" + + withClue("root project is excluded from BCV") { + shouldNotHaveRunTask(":apiDump") + } + + task(":sub1:apiDump") shouldHaveOutcome TaskOutcome.SUCCESS + task(":sub2:apiDump") shouldHaveOutcome TaskOutcome.SUCCESS + } + + testCase.project.projectDir.resolve("sub1/api/sub1.api").asClue { apiDump -> + apiDump.shouldExist() + apiDump.shouldBeAFile() + apiDump.readText().invariantNewlines() shouldBe /* language=TEXT */ """ + | + """.trimMargin() + } + + testCase.project.projectDir.resolve("sub2/api/sub2.api").asClue { apiDump -> + apiDump.shouldExist() + apiDump.shouldBeAFile() + apiDump.readText().invariantNewlines() shouldBe /* language=TEXT */ """ + | + """.trimMargin() + } + } + + test("expect the conventions set in the settings plugin are used in the subprojects") { + testCase.project.runner.withArguments("printBCVTargets", "-q", "--stacktrace").build { + output.invariantNewlines() shouldBe testCase.expectedPrintedBCVTargets + } + } + } + } + } +}) + +private data class TestCase( + val projectType: String, + val project: GradleProjectTest, + @Language("TEXT") + val expectedPrintedBCVTargets: String, +) + +private fun kotlinJvmProjectWithBcvSettingsPlugin() = + gradleKtsProjectTest("settings-plugin-dsl-test/kotlin-jvm") { + + settingsGradleKts += settingsGradleKtsWithBcvPlugin + + buildGradleKts = "\n" + + dir("sub1") { + buildGradleKts = buildGradleKtsWithKotlinJvmAndBcvConfig + } + + dir("sub2") { + buildGradleKts = buildGradleKtsWithKotlinJvmAndBcvConfig + buildGradleKts += printBcvTargetsTask + } + } + + +private fun kotlinMultiplatformProjectWithBcvSettingsPlugin() = + gradleKtsProjectTest("settings-plugin-dsl-test/kotlin-multiplatform-jvm") { + + settingsGradleKts += settingsGradleKtsWithBcvPlugin + + buildGradleKts = "\n" + + dir("sub1") { + buildGradleKts = buildGradleKtsWithKotlinMultiplatformJvmAndBcvConfig + } + + dir("sub2") { + buildGradleKts = buildGradleKtsWithKotlinMultiplatformJvmAndBcvConfig + buildGradleKts += printBcvTargetsTask + } + } + +@Language("kts") +private val settingsGradleKtsWithBcvPlugin = """ +buildscript { + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.20") + } +} + + +plugins { + id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.3-SNAPSHOT" +} + +include( + ":sub1", + ":sub2", +) + +binaryCompatibilityValidator { + ignoredProjects.add(":") + defaultTargetValues { + enabled.convention(true) + ignoredClasses.set(listOf("com.package.MyIgnoredClass")) + ignoredMarkers.set(listOf("com.package.MyInternalApiAnnotationMarker")) + ignoredPackages.set(listOf("com.package.my_ignored_package")) + } +} + +""" + +@Language("kts") +private val buildGradleKtsWithKotlinJvmAndBcvConfig = """ +plugins { + kotlin("jvm") version "1.7.20" +} + +// check that the DSL is available: +binaryCompatibilityValidator { } + +""" + +/** + * Note: the `kotlin("multiplatform")` version should be set in [settingsGradleKtsWithBcvPlugin]. + */ +@Language("kts") +private val buildGradleKtsWithKotlinMultiplatformJvmAndBcvConfig = """ +plugins { + kotlin("multiplatform") version "1.7.20" +} + +kotlin { + jvm() +} + +// check that the DSL is available: +binaryCompatibilityValidator { } + +""" + +@Language("kts") +private val printBcvTargetsTask = """ +val printBCVTargets by tasks.registering { + + val bcvTargets = binaryCompatibilityValidator.targets + + doLast { + bcvTargets.forEach { + println("name: " + it.name) + println("platformType: " + it.platformType) + println("enabled: " + it.enabled.get()) + println("ignoredClasses: " + it.ignoredClasses.get()) + println("ignoredMarkers: " + it.ignoredMarkers.get()) + println("ignoredPackages: " + it.ignoredPackages.get()) + println("inputClasses: " + it.inputClasses.files.map { f -> f.name }) + println("inputJar: " + it.inputJar.orNull) + println("------------------------------") + } + } +} + +""" diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnSubTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnSubTests.kt index 4f91a2c..188b88d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnSubTests.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/SubprojectsWithPluginOnSubTests.kt @@ -3,7 +3,7 @@ package kotlinx.validation.test import dev.adamko.kotlin.binary_compatibility_validator.test.utils.api.* import dev.adamko.kotlin.binary_compatibility_validator.test.utils.build import dev.adamko.kotlin.binary_compatibility_validator.test.utils.invariantNewlines -import dev.adamko.kotlin.binary_compatibility_validator.test.utils.shouldHaveOutcome +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.shouldHaveRunTask import dev.adamko.kotlin.binary_compatibility_validator.test.utils.shouldNotHaveRunTask import io.kotest.matchers.comparables.shouldBeEqualComparingTo import io.kotest.matchers.file.shouldBeEmpty @@ -81,9 +81,9 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { runner.build { shouldNotHaveRunTask(":apiCheck") - task(":sub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub2:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub2:apiCheck", SUCCESS) shouldNotHaveRunTask(":sub2:apiCheck") } } @@ -112,9 +112,9 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { runner.build { shouldNotHaveRunTask(":apiCheck") - task(":sub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub2:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub2:apiCheck", SUCCESS) shouldNotHaveRunTask(":sub2:apiCheck") } } @@ -134,7 +134,7 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { } runner.build { - task(":sub1:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiCheck", SUCCESS) } } @@ -155,7 +155,7 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { } runner.build { - task(":sub1:subsub2:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:subsub2:apiCheck", SUCCESS) } } @@ -181,7 +181,7 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { } runner.build { - task(":sub1:subsub2:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:subsub2:apiCheck", SUCCESS) } } @@ -218,9 +218,9 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { runner.build { shouldNotHaveRunTask(":apiCheck") - task(":sub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub1:apiCheck") shouldHaveOutcome SUCCESS - task(":sub1:subsub2:apiCheck") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub1:apiCheck", SUCCESS) + shouldHaveRunTask(":sub1:subsub2:apiCheck", SUCCESS) shouldNotHaveRunTask(":sub2:apiCheck") } } @@ -236,13 +236,12 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { } runner.build { - task(":sub1:apiDump") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiDump", SUCCESS) val apiDumpFile = rootProjectDir.resolve("sub1/api/sub1.api") assertTrue(apiDumpFile.exists(), "api dump file ${apiDumpFile.path} should exist") apiDumpFile.shouldBeEmpty() - //Assertions.assertThat(apiDumpFile.readText()).isEqualToIgnoringNewLines("") } } @@ -271,9 +270,9 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { runner.build { shouldNotHaveRunTask(":apiDump") - task(":sub1:apiDump") shouldHaveOutcome SUCCESS - task(":sub1:subsub1:apiDump") shouldHaveOutcome SUCCESS - task(":sub1:subsub2:apiDump") shouldHaveOutcome SUCCESS + shouldHaveRunTask(":sub1:apiDump", SUCCESS) + shouldHaveRunTask(":sub1:subsub1:apiDump", SUCCESS) + shouldHaveRunTask(":sub1:subsub2:apiDump", SUCCESS) shouldNotHaveRunTask(":sub2:apiDump") assertFalse( @@ -284,19 +283,16 @@ internal class SubprojectsWithPluginOnSubTests : BaseKotlinGradleTest() { val apiSub1 = rootProjectDir.resolve("sub1/api/sub1.api") assertTrue(apiSub1.exists(), "api dump file ${apiSub1.path} should exist") apiSub1.shouldBeEmpty() - //Assertions.assertThat(apiSub1.readText()).isEqualToIgnoringNewLines("") val apiSubsub1 = rootProjectDir.resolve("sub1/subsub1/api/subsub1.api") assertTrue(apiSubsub1.exists(), "api dump file ${apiSubsub1.path} should exist") val apiSubsub1Expected = readResourceFile("/examples/classes/Subsub1Class.dump") apiSubsub1.readText().invariantNewlines().shouldBeEqualComparingTo(apiSubsub1Expected) - //Assertions.assertThat(apiSubsub1.readText()).isEqualToIgnoringNewLines(apiSubsub1Expected) val apiSubsub2 = rootProjectDir.resolve("sub1/subsub2/api/subsub2.api") assertTrue(apiSubsub2.exists(), "api dump file ${apiSubsub2.path} should exist") val apiSubsub2Expected = readResourceFile("/examples/classes/Subsub2Class.dump") apiSubsub2.readText().invariantNewlines().shouldBeEqualComparingTo(apiSubsub2Expected) - //Assertions.assertThat(apiSubsub2.readText()).isEqualToIgnoringNewLines(apiSubsub2Expected) val apiSub2 = rootProjectDir.resolve("sub2/api/sub2.api") assertFalse(apiSub2.exists(), "api dump file ${apiSub2.path} should NOT exist") diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts index bab4c82..c1c4ab2 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts @@ -4,7 +4,7 @@ */ plugins { - kotlin("multiplatform") version "1.5.20" + kotlin("multiplatform") version "1.7.20" //id("org.jetbrains.kotlinx.binary-compatibility-validator") id("dev.adamko.kotlin.binary-compatibility-validator") version "0.0.3-SNAPSHOT" } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts index f4e2da4..2195984 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts @@ -1,9 +1,4 @@ -/* - * Copyright 2016-2022 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 { - nonPublicMarkers.add("foo.HiddenField") - nonPublicMarkers.add("foo.HiddenProperty") + ignoredMarkers.add("foo.HiddenField") + ignoredMarkers.add("foo.HiddenProperty") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt index 544905a..ca3891f 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/GradleTestKitUtils.kt @@ -6,6 +6,7 @@ import dev.adamko.kotlin.binary_compatibility_validator.test.utils.GradleProject import java.io.File import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.name import kotlin.properties.PropertyDelegateProvider import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty @@ -28,6 +29,8 @@ class GradleProjectTest( val runner: GradleRunner = GradleRunner.create().withProjectDir(projectDir.toFile()) + val projectName by projectDir::name + companion object { /** file-based Maven Repo that contains the published plugin */ @@ -76,7 +79,7 @@ fun gradleKtsProjectTest( return GradleProjectTest(baseDir = baseDir, testProjectName = testProjectName).apply { settingsGradleKts = """ - |rootProject.name = "$testProjectName" + |rootProject.name = "$projectName" | |@Suppress("UnstableApiUsage") |dependencyResolutionManagement { @@ -225,27 +228,27 @@ fun ProjectDirectoryScope.findFiles(matcher: (File) -> Boolean): Sequence projectDir.toFile().walk().filter(matcher) -/** Set the content of `settings.gradle.kts` */ +/** Get or set the content of `settings.gradle.kts` */ @delegate:Language("kts") var ProjectDirectoryScope.settingsGradleKts: String by TestProjectFileDelegate("settings.gradle.kts") -/** Set the content of `build.gradle.kts` */ +/** Get or set the content of `build.gradle.kts` */ @delegate:Language("kts") var ProjectDirectoryScope.buildGradleKts: String by TestProjectFileDelegate("build.gradle.kts") -/** Set the content of `settings.gradle` */ +/** Get or set the content of `settings.gradle` */ @delegate:Language("groovy") var ProjectDirectoryScope.settingsGradle: String by TestProjectFileDelegate("settings.gradle") -/** Set the content of `build.gradle` */ +/** Get or set the content of `build.gradle` */ @delegate:Language("groovy") var ProjectDirectoryScope.buildGradle: String by TestProjectFileDelegate("build.gradle") -/** Set the content of `gradle.properties` */ +/** Get or set the content of `gradle.properties` */ @delegate:Language("properties") var ProjectDirectoryScope.gradleProperties: String by TestProjectFileDelegate("gradle.properties") diff --git a/modules/bcv-gradle-plugin/build.gradle.kts b/modules/bcv-gradle-plugin/build.gradle.kts index 9251700..d7c6a7c 100644 --- a/modules/bcv-gradle-plugin/build.gradle.kts +++ b/modules/bcv-gradle-plugin/build.gradle.kts @@ -6,14 +6,16 @@ plugins { buildsrc.conventions.`kotlin-gradle-plugin` buildsrc.conventions.`maven-publish-test` `java-test-fixtures` -// buildsrc.conventions.`gradle-plugin-variants` + //com.github.johnrengelman.shadow + //buildsrc.conventions.`gradle-plugin-variants` } dependencies { implementation(libs.javaDiffUtils) compileOnly(libs.kotlinx.bcv) - compileOnly(libs.kotlin.gradlePlugin) +// compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.kotlin.gradlePluginApi) testFixturesApi(gradleTestKit()) @@ -73,9 +75,16 @@ configurations attributes { attribute( GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, - objects.named(libs.versions.testGradleVersion.get()) + objects.named(libs.versions.supportedGradleVersion.get()) ) } } skipTestFixturesPublications() + +// Shadow plugin doesn't seem to help with https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu/issues/1 +//tasks.shadowJar { +// minimize() +// isEnableRelocation = false +// archiveClassifier.set("") +//} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt index 92d0cbd..9c1bc01 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt @@ -4,7 +4,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.initialization.Settings import org.gradle.api.plugins.PluginAware -import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.* abstract class BCVPlugin : Plugin { diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt index 2a86e8e..accd60b 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt @@ -17,9 +17,9 @@ import org.gradle.api.tasks.SourceSet import org.gradle.internal.component.external.model.TestFixturesSupport.TEST_FIXTURE_SOURCESET_NAME import org.gradle.kotlin.dsl.* import org.gradle.language.base.plugins.LifecycleBasePlugin -import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainer abstract class BCVProjectPlugin @Inject constructor( @@ -95,9 +95,6 @@ abstract class BCVProjectPlugin @Inject constructor( private fun createExtension(project: Project): BCVProjectExtension { val extension = project.extensions.create(EXTENSION_NAME, BCVProjectExtension::class).apply { enabled.convention(true) - ignoredPackages.convention(emptySet()) - ignoredMarkers.convention(emptySet()) - ignoredClasses.convention(emptySet()) outputApiDir.convention(layout.projectDirectory.dir(API_DIR)) projectName.convention(providers.provider { project.name }) kotlinxBinaryCompatibilityValidatorVersion.convention("0.13.0") @@ -106,10 +103,10 @@ abstract class BCVProjectPlugin @Inject constructor( extension.targets.configureEach { enabled.convention(true) ignoredClasses.convention(extension.ignoredClasses) - ignoredMarkers.convention(extension.ignoredMarkers.apply { + ignoredMarkers.convention( @Suppress("DEPRECATION") - addAll(extension.nonPublicMarkers) - }) + extension.ignoredMarkers.orElse(extension.nonPublicMarkers) + ) ignoredPackages.convention(extension.ignoredPackages) } @@ -141,11 +138,10 @@ abstract class BCVProjectPlugin @Inject constructor( extension: BCVProjectExtension, ) { project.pluginManager.withPlugin("kotlin-android") { - val androidExtension = project.extensions.getByType() - - extension.targets.create(androidExtension.target.name) { - androidExtension.target.compilations.all { - inputClasses.from(this) + val kotlinSourceSetsContainer = project.extensions.getByType() + kotlinSourceSetsContainer.sourceSets.all { + extension.targets.create(name) { + inputClasses.from(kotlin.classesDirectory) } } } @@ -156,19 +152,28 @@ abstract class BCVProjectPlugin @Inject constructor( extension: BCVProjectExtension, ) { project.pluginManager.withPlugin("kotlin-multiplatform") { - val kotlinExtension = project.extensions.getByType() - val kotlinJvmTargets = kotlinExtension.targets.matching { - it.platformType in arrayOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm) - } - - kotlinJvmTargets.all { - extension.targets.create(targetName) { - enabled.convention(true) - compilations.all { - inputClasses.from(output.classesDirs) + val kotlinTargetsContainer = project.extensions.getByType() + + kotlinTargetsContainer.targets + .matching { + it.platformType in arrayOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm) + }.all { + val targetPlatformType = platformType + + extension.targets.register(targetName) { + enabled.convention(true) + compilations + .matching { + when (targetPlatformType) { + KotlinPlatformType.jvm -> it.name == "main" + KotlinPlatformType.androidJvm -> it.name == "release" + else -> false + } + }.all { + inputClasses.from(output.classesDirs) + } } } - } } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt index de1604b..1386b97 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt @@ -1,18 +1,32 @@ package dev.adamko.kotlin.binary_compatibility_validator import dev.adamko.kotlin.binary_compatibility_validator.internal.globToRegex +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetSpec +import javax.inject.Inject import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.initialization.Settings +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.SetProperty -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.* -abstract class BCVSettingsPlugin : Plugin { +abstract class BCVSettingsPlugin @Inject constructor( + private val objects: ObjectFactory +) : Plugin { override fun apply(settings: Settings) { - val extension = settings.extensions.create("bcvSettings", Extension::class).apply { + val extension = settings.extensions.create( + "bcvSettings", + objects.newInstance(), + ).apply { ignoredProjects.convention(emptySet()) + + defaultTargetValues { + enabled.convention(true) + ignoredClasses.convention(emptySet()) + ignoredMarkers.convention(emptySet()) + ignoredPackages.convention(emptySet()) + } } settings.gradle.beforeProject { @@ -22,11 +36,24 @@ abstract class BCVSettingsPlugin : Plugin { } ) { project.pluginManager.apply(BCVProjectPlugin::class) + project.extensions.configure { + enabled.convention(extension.defaultTargetValues.enabled) + ignoredClasses.convention(extension.defaultTargetValues.ignoredClasses) + ignoredMarkers.convention(extension.defaultTargetValues.ignoredMarkers) + ignoredPackages.convention(extension.defaultTargetValues.ignoredPackages) + } } } } - interface Extension { + abstract class Extension @Inject constructor( + + /** + * Set [BCVTargetSpec] values that will be used as defaults for all + * [BCVProjectExtension.targets] in subprojects. + */ + val defaultTargetValues: BCVTargetSpec + ) { /** * Paths of projects. @@ -37,6 +64,10 @@ abstract class BCVSettingsPlugin : Plugin { * - `*` will match zero, or many characters, excluding `:` * - `**` will match 0 to many characters, including `:` */ - val ignoredProjects: SetProperty + abstract val ignoredProjects: SetProperty + + + fun defaultTargetValues(configure: BCVTargetSpec.() -> Unit) = + defaultTargetValues.configure() } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleAccessors.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleAccessors.kt index 7f02865..9f7a061 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleAccessors.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleAccessors.kt @@ -2,7 +2,7 @@ package dev.adamko.kotlin.binary_compatibility_validator.internal import org.gradle.api.Project import org.gradle.api.tasks.SourceSetContainer -import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.* internal val Project.sourceSets: SourceSetContainer diff --git a/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt b/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt index 9e10b56..76e10a9 100644 --- a/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt +++ b/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt @@ -34,6 +34,17 @@ infix fun BuildResult?.shouldHaveRunTask(taskPath: String): BuildTask { return this?.task(taskPath)!! } +/** Assert that a task ran, with an [expected outcome][expectedOutcome]. */ +fun BuildResult?.shouldHaveRunTask( + taskPath: String, + expectedOutcome: TaskOutcome +): BuildTask { + this should haveTask(taskPath) + val task = this?.task(taskPath)!! + task should haveOutcome(expectedOutcome) + return task +} + /** * Assert that a task did not run. *