diff --git a/.github/workflows/run_publish_maven.yml b/.github/workflows/run_publish_maven.yml index 3d0cf4a..c48da70 100644 --- a/.github/workflows/run_publish_maven.yml +++ b/.github/workflows/run_publish_maven.yml @@ -40,5 +40,5 @@ jobs: with: runs-on: macos-latest # only macOS supports building all Kotlin targets gradle-task: >- - publishAllPublicationsToSonatypeReleaseRepository --stacktrace --no-configuration-cache --no-parallel + publishAllPublicationsToSonatypeReleaseRepository --stacktrace --no-parallel checkout-ref: ${{ inputs.checkout-ref }} diff --git a/README.md b/README.md index 434be9f..f4ba888 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ or (**experimentally**) [as a Settings plugin](#settings-plugin) in `settings.gr #### Requirements -The minimal supported Gradle version is 7.6. +The minimal supported Gradle version is 7.6.4. -By default, BCV-MU uses BCV version `0.13.2`, which can be overridden, but may introduce runtime +By default, BCV-MU uses BCV version `0.15.1`, which can be overridden, but may introduce runtime errors. ### Build plugin @@ -49,7 +49,7 @@ plugins { } ``` -To initialise the API declarations, run the Gradle task +To initialize the API declarations, run the Gradle task ```shell ./gradlew apiDump @@ -74,7 +74,7 @@ BCV-MU can be configured in a similar manner to BCV: // build.gradle.kts plugins { - kotlin("jvm") version "1.8.10" + kotlin("jvm") version "1.9.22" id("dev.adamko.kotlin.binary-compatibility-validator") version "$bcvMuVersion" } @@ -100,7 +100,7 @@ binaryCompatibilityValidator { bcvEnabled.set(true) // Override the default BCV version - kotlinxBinaryCompatibilityValidatorVersion.set("0.13.2") + kotlinxBinaryCompatibilityValidatorVersion.set("0.15.1") } ``` @@ -113,7 +113,7 @@ these `BCVTarget`s can be specifically modified, or manually defined, for fine-g // build.gradle.kts plugins { - kotlin("jvm") version "1.8.10" + kotlin("jvm") version "1.9.22" id("dev.adamko.kotlin.binary-compatibility-validator") version "$bcvMuVersion" `java-test-fixtures` } @@ -214,7 +214,7 @@ 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-api:1.8.10") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.22") } } diff --git a/build.gradle.kts b/build.gradle.kts index 6e678c1..5a486a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ -import buildsrc.utils.generatedKotlinDslAccessorDirs +import buildsrc.utils.excludeProjectConfigurationDirs +import buildsrc.utils.filterContains plugins { buildsrc.conventions.base @@ -13,20 +14,17 @@ project.version = object { idea { module { - excludeDirs = excludeDirs + - layout.generatedKotlinDslAccessorDirs() + - layout.files( - ".idea", - "gradle/wrapper", - ) + excludeProjectConfigurationDirs(layout, providers) } } + val readmeCheck by tasks.registering { group = LifecycleBasePlugin.VERIFICATION_GROUP val readme = providers.fileContents(layout.projectDirectory.file("README.md")).asText val supportedGradleVersion = libs.versions.supportedGradleVersion val kotlinBcvVersion = libs.versions.kotlinx.bcv + val kgpVersion = embeddedKotlinVersion doLast { readme.get().let { readme -> @@ -39,6 +37,20 @@ val readmeCheck by tasks.registering { require("The minimal supported Gradle version is ${supportedGradleVersion.get()}" in readme) { "Incorrect Gradle version in README" } + readme.lines() + .filterContains("kotlin(\"jvm\") version ") + .forEach { line -> + require("kotlin(\"jvm\") version \"$kgpVersion\"" in line) { + "Incorrect Kotlin JVM plugin (expected $kgpVersion) version in README\n $line" + } + } + readme.lines() + .filterContains("""org.jetbrains.kotlin:kotlin-gradle-plugin-api:""") + .forEach { line -> + require("""classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kgpVersion")""" in line) { + "Incorrect kotlin-gradle-plugin-api version (expected $kgpVersion) version in README\n $line" + } + } } } } @@ -50,7 +62,7 @@ tasks.check { val projectVersion by tasks.registering { description = "prints the project version" group = "help" - val version = providers.provider { project.version } + val version = providers.provider { project.version.toString() } inputs.property("version", version) outputs.cacheIf("logging task, it should always run") { false } doLast { diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts deleted file mode 100644 index 334374f..0000000 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/gradle-plugin-variants.gradle.kts +++ /dev/null @@ -1,57 +0,0 @@ -package buildsrc.conventions - -import org.gradle.api.attributes.plugin.GradlePluginApiVersion.GRADLE_PLUGIN_API_VERSION_ATTRIBUTE -import buildsrc.utils.configurationNames - -plugins { - id("buildsrc.conventions.base") - `java-gradle-plugin` -} - -configurations - .matching { it.isCanBeConsumed && it.name in sourceSets.main.get().configurationNames() } - .configureEach { - attributes { - attribute(GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, objects.named("7.6")) - } - } - -fun createGradleVariantSourceSet(name: String, gradleVersion: String) { - val variantSources = sourceSets.create(name) - - java { - registerFeature(variantSources.name) { - usingSourceSet(variantSources) - capability("${project.group}", "${project.name}", "${project.version}") - - withJavadocJar() - withSourcesJar() - } - } - - configurations - .matching { it.isCanBeConsumed && it.name in variantSources.configurationNames() } - .configureEach { - attributes { - attribute(GRADLE_PLUGIN_API_VERSION_ATTRIBUTE, objects.named(gradleVersion)) - } - } - - tasks.named(variantSources.processResourcesTaskName) { - val copyPluginDescriptors = rootSpec.addChild() -// copyPluginDescriptors.into("META-INF/gradle-plugins") - copyPluginDescriptors.into(tasks.pluginDescriptors.map { it.outputDirectory.asFile.get().invariantSeparatorsPath }) - copyPluginDescriptors.from(tasks.pluginDescriptors) - } - - dependencies { - variantSources.compileOnlyConfigurationName(gradleApi()) - } - - project.tasks.named(variantSources.compileJavaTaskName).configure { - classpath += sourceSets.main.get().compileClasspath - } -} - -//val mainGradle = registerGradleVariant("mainGradle", "7.6") -createGradleVariantSourceSet("mainGradle8", "8.1") diff --git a/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts index 4dcac80..5381a17 100644 --- a/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/conventions/maven-publishing.gradle.kts @@ -2,6 +2,7 @@ package buildsrc.conventions import buildsrc.settings.MavenPublishingSettings + plugins { `maven-publish` signing @@ -16,7 +17,7 @@ publishing { publications.withType().configureEach { pom { name.convention("Binary Compatibility Validator MU") - description.convention("BCV-MU is a Gradle Plugin that validates the public JVM binary API of libraries, to make sure that breaking changes are tracked.") + description.convention("BCV-MU is a Gradle Plugin that validates the public API of libraries, to make sure that breaking changes are tracked.") url.convention("https://github.com/adamko-dev/kotlin-binary-compatibility-validator-mu") scm { @@ -89,23 +90,35 @@ signing { //endregion -//region Fix Gradle warning about signing tasks using publishing task outputs without explicit dependencies -// https://youtrack.jetbrains.com/issue/KT-46466 https://github.com/gradle/gradle/issues/26091 tasks.withType().configureEach { + + //region Fix Gradle warning about signing tasks using publishing task outputs without explicit dependencies + // https://youtrack.jetbrains.com/issue/KT-46466 https://github.com/gradle/gradle/issues/26091 val signingTasks = tasks.withType() mustRunAfter(signingTasks) -} -//endregion - + //endregion -//region publishing logging -tasks.withType().configureEach { + //region publishing logging val publicationGAV = provider { publication?.run { "$group:$artifactId:$version" } } doLast("log publication GAV") { if (publicationGAV.isPresent) { logger.info("[task: ${path}] ${publicationGAV.get()}") } } + //endregion +} + + +//region Maven Central can't handle parallel uploads, so limit parallel uploads with a service. +abstract class MavenPublishLimiter : BuildService + +val mavenPublishLimiter = + gradle.sharedServices.registerIfAbsent("mavenPublishLimiter", MavenPublishLimiter::class) { + maxParallelUsages = 1 + } + +tasks.withType().configureEach { + usesService(mavenPublishLimiter) } //endregion diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt b/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt index f18860f..bcba024 100644 --- a/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt +++ b/buildSrc/src/main/kotlin/buildsrc/utils/gradle.kt @@ -1,10 +1,8 @@ package buildsrc.utils -import java.io.File import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.component.AdhocComponentWithVariants -import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RelativePath import org.gradle.api.tasks.SourceSet import org.gradle.kotlin.dsl.* @@ -142,22 +140,3 @@ fun SourceSet.configurationNames() = javadocElementsConfigurationName, sourcesElementsConfigurationName, ) - -/** exclude generated Gradle code, so it doesn't clog up search results */ -fun ProjectLayout.generatedKotlinDslAccessorDirs(): Set { - - val generatedSrcDirs = listOf( - "kotlin-dsl-accessors", - "kotlin-dsl-external-plugin-spec-builders", - "kotlin-dsl-plugins", - ) - - return projectDirectory.dir("buildSrc/build/generated-sources") - .asFile - .walk() - .filter { it.isDirectory && it.parentFile.name in generatedSrcDirs } - .flatMap { file -> - file.walk().maxDepth(1).filter { it.isDirectory }.toList() - } - .toSet() -} diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt b/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt new file mode 100644 index 0000000..837b358 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/utils/intellij.kt @@ -0,0 +1,74 @@ +package buildsrc.utils + +import java.io.File +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.ProjectLayout +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters +import org.gradle.kotlin.dsl.* +import org.gradle.plugins.ide.idea.model.IdeaModule + +/** + * Exclude directories containing + * + * - generated Gradle code, + * - IDE files, + * - Gradle config, + * + * so they don't clog up search results. + */ +fun IdeaModule.excludeProjectConfigurationDirs( + layout: ProjectLayout, + providers: ProviderFactory, +) { + val excludedDirs = providers.of(IdeaExcludedDirectoriesSource::class) { + parameters.projectDir.set(layout.projectDirectory) + } + + excludeDirs.addAll(excludedDirs.get()) +} + +// We have to use a ValueSource to find the files, otherwise Gradle +// considers _all files_ an input for configuration cache 🙄 +internal abstract class IdeaExcludedDirectoriesSource + : ValueSource, IdeaExcludedDirectoriesSource.Parameters> { + + interface Parameters : ValueSourceParameters { + val projectDir: DirectoryProperty + } + + override fun obtain(): Set { + val projectDir = parameters.projectDir.get().asFile + + val excludedDirs = setOf( + ".git", + ".gradle", + ".idea", + ".kotlin", + ) + + val generatedSrcDirs = listOf( + "kotlin-dsl-accessors", + "kotlin-dsl-external-plugin-spec-builders", + "kotlin-dsl-plugins", + ) + + val generatedDirs = projectDir + .walk() + .onEnter { it.name !in excludedDirs && it.parentFile.name !in generatedSrcDirs } + .filter { it.isDirectory } + .filter { it.parentFile.name in generatedSrcDirs } + .flatMap { file -> + file.walk().maxDepth(1).filter { it.isDirectory }.toList() + } + .toSet() + + // can't use buildSet {} https://github.com/gradle/gradle/issues/28325 + return mutableSetOf().apply { + addAll(generatedDirs) + add(projectDir.resolve(".idea")) + add(projectDir.resolve("gradle/wrapper")) + } + } +} diff --git a/buildSrc/src/main/kotlin/buildsrc/utils/kotlinStdlib.kt b/buildSrc/src/main/kotlin/buildsrc/utils/kotlinStdlib.kt index 8c9b795..8557fca 100644 --- a/buildSrc/src/main/kotlin/buildsrc/utils/kotlinStdlib.kt +++ b/buildSrc/src/main/kotlin/buildsrc/utils/kotlinStdlib.kt @@ -7,3 +7,6 @@ fun String.titlecaseFirstChar(): String = else -> it.toString() } } + +fun List.filterContains(substring: String): List = + filter { substring in it } diff --git a/gradle.properties b/gradle.properties index 5023d3a..464a7dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,8 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx4g -XX:+HeapDumpOnOutOfMemoryError org.gradle.caching=true - -org.gradle.unsafe.configuration-cache=true -org.gradle.unsafe.configuration-cache-problems=warn - +org.gradle.configuration-cache=true +org.gradle.configuration-cache-problems=warn org.gradle.parallel=true org.gradle.welcome=never diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 72570be..705859f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,14 +5,14 @@ kotlinGradle = "1.9.24" javaDiffUtils = "4.12" junit = "5.10.2" kotest = "5.9.0" -kotlinx-bcv = "0.13.2" +kotlinx-bcv = "0.15.1" gradlePluginPublishPlugin = "1.2.1" shadowPlugin = "8.1.1" devPublish = "0.3.0" bcvMu = "main-SNAPSHOT" -supportedGradleVersion = "7.6" # the minimal supported Gradle plugin version, used in functional tests +supportedGradleVersion = "7.6.4" # the minimal supported Gradle plugin version, used in functional tests [libraries] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49..e644113 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts index 432191e..70a0893 100644 --- a/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/build.gradle.kts @@ -20,6 +20,8 @@ dependencies { testFixturesApi(libs.kotest.assertionsCore) testFixturesApi(libs.kotest.property) + testFixturesApi("org.jetbrains.kotlin:kotlin-gradle-plugin:$embeddedKotlinVersion") + testFixturesApi(testFixtures(projects.modules.bcvGradlePlugin)) } @@ -57,6 +59,14 @@ testing.suites { "devMavenRepoDir", devPublish.devMavenRepo.asFile.get().invariantSeparatorsPath, ) + val testTempDir = layout.buildDirectory.dir("test-temp").get().asFile + systemProperty( + "testTempDir", + testTempDir.invariantSeparatorsPath + ) + doFirst { + testTempDir.mkdirs() + } } } } 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 f168e08..208616c 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 @@ -1,8 +1,8 @@ package kotlinx.validation.test -import dev.adamko.kotlin.binary_compatibility_validator.test.* -import dev.adamko.kotlin.binary_compatibility_validator.test.utils.* 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.shouldHaveRunTask import java.io.File import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.Disabled @@ -20,7 +20,6 @@ internal class AndroidLibraryTest : BaseKotlinGradleTest() { createProjectWithSubModules() runner { arguments.add(":kotlin-library:apiDump") - arguments.add("--full-stacktrace") } } @@ -53,7 +52,6 @@ internal class AndroidLibraryTest : BaseKotlinGradleTest() { createProjectWithSubModules() runner { arguments.add(":java-library:apiDump") - arguments.add("--full-stacktrace") } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt index 57306d7..ac56e0d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt @@ -5,8 +5,7 @@ import dev.adamko.kotlin.binary_compatibility_validator.test.utils.api.* import io.kotest.matchers.comparables.shouldBeEqualComparingTo import io.kotest.matchers.file.shouldBeEmpty import io.kotest.matchers.string.shouldContain -import org.gradle.testkit.runner.TaskOutcome.FAILED -import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.gradle.testkit.runner.TaskOutcome.* import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -160,7 +159,7 @@ internal class DefaultConfigTests : BaseKotlinGradleTest() { } runner.build { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) assertTrue( rootProjectApiDump.exists(), @@ -187,7 +186,7 @@ internal class DefaultConfigTests : BaseKotlinGradleTest() { } runner.build { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) val apiDumpFile = rootProjectDir.resolve("api/testproject.api") assertTrue(apiDumpFile.exists(), "api dump file ${apiDumpFile.path} should exist") @@ -217,13 +216,12 @@ internal class DefaultConfigTests : BaseKotlinGradleTest() { } runner.build { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) assertTrue(rootProjectApiDump.exists(), "api dump file should exist") val expected = readResourceFile("/examples/classes/AnotherBuildConfig.dump") rootProjectApiDump.readText().invariantNewlines().shouldBeEqualComparingTo(expected) - //Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected) } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredClassesTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredClassesTests.kt index 0c59131..6944983 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredClassesTests.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/IgnoredClassesTests.kt @@ -4,6 +4,7 @@ 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.shouldHaveRunTask import io.kotest.matchers.comparables.shouldBeEqualComparingTo +import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -78,13 +79,41 @@ internal class IgnoredClassesTests : BaseKotlinGradleTest() { } runner.build { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) assertTrue(rootProjectApiDump.exists(), "api dump file should exist") val expected = readResourceFile("/examples/classes/AnotherBuildConfig.dump") rootProjectApiDump.readText().shouldBeEqualComparingTo(expected) -// Assertions.assertThat(rootProjectApiDump.readText()).isEqualToIgnoringNewLines(expected) + } + } + + @Test + fun `apiDump should dump class whose name is a subsset of another class that is excluded via ignoredClasses`() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts") + } + kotlin("BuildConfig.kt") { + resolve("/examples/classes/BuildConfig.kt") + } + kotlin("BuildCon.kt") { + resolve("/examples/classes/BuildCon.kt") + } + + runner { + arguments.add(":apiDump") + } + } + + runner.build().apply { + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) + + assertTrue(rootProjectApiDump.exists(), "api dump file should exist") + + val expected = readResourceFile("/examples/classes/BuildCon.dump") + rootProjectApiDump.readText().shouldBeEqualComparingTo(expected) } } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaVersionsCompatibilityTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaVersionsCompatibilityTest.kt new file mode 100644 index 0000000..3f91338 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JavaVersionsCompatibilityTest.kt @@ -0,0 +1,81 @@ +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.shouldHaveTaskWithOutcome +import org.gradle.internal.impldep.org.junit.Assume.assumeFalse +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.Test + +class JavaVersionsCompatibilityTest : BaseKotlinGradleTest() { + private fun skipInDebug(runner: GradleRunner) { + assumeFalse( + "The test requires a separate Gradle process as it uses a different JVM version, so it cannot be executed with debug turned on.", + runner.isDebug, + ) + } + + private fun checkCompatibility(useMaxSupportedJdk: Boolean): Unit = checkCompatibility { + buildGradleKts { + resolve("/examples/gradle/base/jdkCompatibility.gradle.kts") + } + runner { + arguments.add("-PuseMaxSupportedJdk=$useMaxSupportedJdk") + } + } + + private fun checkCompatibility(jvmTarget: String): Unit = checkCompatibility { + buildGradleKts { + resolve("/examples/gradle/base/jdkCompatibilityWithExactVersion.gradle.kts") + } + runner { + arguments.add("-PjvmTarget=$jvmTarget") + } + } + + private fun checkCompatibility(configure: BaseKotlinScope.() -> Unit = {}) { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/jdk-provisioning.gradle.kts") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + apiFile(projectName = rootProjectDir.name) { + resolve("/examples/classes/AnotherBuildConfig.dump") + } + + runner { + gradleVersion = "8.5" + arguments.add(":apiCheck") + } + + configure() + } + + skipInDebug(runner) + + runner.build { + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } + + @Test + fun testMaxSupportedJvmVersion(): Unit = checkCompatibility(true) + + @Test + fun testMinSupportedJvmVersion(): Unit = checkCompatibility(false) + + @Test + fun testJvm8(): Unit = checkCompatibility("1.8") + + @Test + fun testJvm11(): Unit = checkCompatibility("11") + + @Test + fun testJvm17(): Unit = checkCompatibility("17") + + @Test + fun testJvm21(): Unit = checkCompatibility("21") +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt new file mode 100644 index 0000000..defe40d --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt @@ -0,0 +1,55 @@ +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.shouldHaveTaskWithOutcome +import io.kotest.matchers.shouldBe +import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.Test + +class JvmProjectTests : BaseKotlinGradleTest() { + + @Test + fun `apiDump for a project with generated sources only`() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts") + } + runner { + // TODO: enable configuration cache back when we start skipping tasks correctly + //configurationCache = false + arguments.add(":apiDump") + } + } + runner.build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + + val expected = readResourceFile("/examples/classes/GeneratedSources.dump") + rootProjectApiDump.readText().invariantNewlines() shouldBe expected + } + } + + @Test + fun `apiCheck for a project with generated sources only`() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts") + } + apiFile(projectName = rootProjectDir.name) { + resolve("/examples/classes/GeneratedSources.dump") + } + runner { + // TODO: enable configuration cache back when we start skipping tasks correctly + //configurationCache = false + arguments.add(":apiCheck") + } + } + runner.build { + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt new file mode 100644 index 0000000..ea5e60f --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -0,0 +1,873 @@ +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.shouldBeEmptyDirectory +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import kotlin.io.path.Path +import kotlin.io.path.name +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome.* +import org.intellij.lang.annotations.Language +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget +import org.junit.jupiter.api.Assumptions.assumeTrue +import org.junit.jupiter.api.Test + + +internal class KlibVerificationTests : BaseKotlinGradleTest() { + + private fun BaseKotlinScope.baseProjectSetting() { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + gradleProperties { + addLine("kotlin.mpp.stability.nowarn=true") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePlugin.gradle.kts") + } + } + + private fun BaseKotlinScope.additionalBuildConfig(config: String) { + buildGradleKts { + resolve(config) + } + } + + private fun BaseKotlinScope.addToSrcSet(pathTestFile: String, sourceSet: String = "commonMain") { + val fileName = Path(pathTestFile).name + kotlin(fileName, sourceSet) { + resolve(pathTestFile) + } + } + + @Test + fun `apiDump for native targets`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + + runner { + buildCache = false + configurationCache = false + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/TopLevelDeclarations.klib.with.linux.dump") + } + } + + @Test + fun `apiCheck for native targets`() { + val runner = test { + baseProjectSetting() + + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + + abiFile(projectName = "testproject") { + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiCheck for native targets should fail when a class is not in a dump`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/BuildConfig.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/Empty.klib.dump") + } + runner { + arguments.add(":apiCheck") + } + } + + runner.buildAndFail { + shouldHaveRunTask(":apiCheck", FAILED) + output shouldContain "+final class com.company/BuildConfig { // com.company/BuildConfig|null[0]" + } + } + + @Test + fun `apiDump should include target-specific sources`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxArm64Main") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfigLinuxArm64Extra.klib.dump") + } + } + + @Test + fun `apiDump with native targets along with JVM target`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/base/enableJvmInWithNativePlugin.gradle.kts") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + + val jvmApiDump = rootProjectDir.resolve("$API_DIR/testproject.api") + jvmApiDump.shouldExist() + + val expected = readResourceFile("/examples/classes/AnotherBuildConfig.dump") + jvmApiDump.readText().invariantNewlines().shouldBe(expected.invariantNewlines()) + } + } + + @Test + fun `apiDump should ignore a class listed in ignoredClasses`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts") + addToSrcSet("/examples/classes/BuildConfig.kt") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + } + } + + @Test + fun `apiDump should succeed if a class listed in ignoredClasses is not found`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + } + } + + @Test + fun `apiDump should ignore all entities from a package listed in ingoredPackages`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/ignoredPackages/oneValidPackage.gradle.kts") + addToSrcSet("/examples/classes/BuildConfig.kt") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + addToSrcSet("/examples/classes/SubPackage.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + } + } + + @Test + fun `apiDump should ignore all entities annotated with non-public markers`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/nonPublicMarkers/klib.gradle.kts") + addToSrcSet("/examples/classes/HiddenDeclarations.kt") + addToSrcSet("/examples/classes/NonPublicMarkers.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/HiddenDeclarations.klib.dump") + } + } + + @Test + fun `apiDump should not dump subclasses excluded via ignoredClasses`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/ignoreSubclasses/ignore.gradle.kts") + addToSrcSet("/examples/classes/Subclasses.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/Subclasses.klib.dump") + } + } + + @Test + fun `apiCheck for native targets using v1 signatures`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/signatures/v1.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + + abiFile(projectName = "testproject") { + resolve("/examples/classes/TopLevelDeclarations.klib.v1.dump") + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiDump for native targets should fail when using invalid signature version`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/signatures/invalid.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiDump", FAILED) + output shouldContain "Unsupported KLib signature version '100500'" + } + } + + @Test + fun `apiDump should work for Apple-targets`() { + assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/TopLevelDeclarations.klib.all.dump") + } + } + + @Test + fun `apiCheck should work for Apple-targets`() { + assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/TopLevelDeclarations.klib.all.dump") + } + runner { + arguments.add(":apiCheck") + gradleJvmArgs += "-Xmx4g" +// gradleJvmArgs += "-XX:MaxMetaspaceSize=1g" + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiCheck should not fail if a target is not supported`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + disableKLibTargets("linuxArm64") + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiCheck should ignore unsupported targets by default`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + // note that the regular dump is used, where linuxArm64 is presented + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + disableKLibTargets("linuxArm64") + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiCheck should fail for unsupported targets with strict mode turned on`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/unsupported/enforce.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + // note that the regular dump is used, where linuxArm64 is presented + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + disableKLibTargets("linuxArm64") + runner { + arguments.add(":apiCheck") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiCheck", FAILED) + } + } + + @Test + fun `klibDump should infer a dump for unsupported target from similar enough target`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxArm64Main") + disableKLibTargets("linuxArm64") + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/TopLevelDeclarations.klib.with.linux.dump") + } + } + + @Test + fun `infer a dump for a target with custom name`() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts") + } + additionalBuildConfig("/examples/gradle/configuration/grouping/clashingTargetNames.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxMain") + + disableKLibTargets("linux") + + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/TopLevelDeclarations.klib.with.guessed.linux.dump") + } + } + + @Test + fun `klibDump should fail when the only target in the project is disabled`() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePluginAndSingleTarget.gradle.kts") + } + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxArm64Main") + + disableKLibTargets("linuxArm64") + + runner { + arguments.add(":apiDump") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiDump", FAILED) +// shouldHaveTaskWithOutcome(":linuxArm64ApiInfer", FAILED) + output shouldContain "The target linuxArm64 is not supported by the host compiler and there are no targets similar to linuxArm64 to infer a dump from it." + } + } + + @Test + fun `klibDump if all klib-targets are unavailable`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + disableKLibTargets( + "linuxArm64", + "linuxX64", + "mingwX64", + "androidNativeArm32", + "androidNativeArm64", + "androidNativeX64", + "androidNativeX86", + ) + runner { + arguments.add(":apiDump") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiDump", FAILED) + output shouldContain "is not supported by the host compiler and there are no targets similar to" + } + } + + @Test + fun `klibCheck should not fail if all klib-targets are unavailable`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + // note that the regular dump is used, where linuxArm64 is presented + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + disableKLibTargets( + "linuxArm64", + "linuxX64", + "mingwX64", + "androidNativeArm32", + "androidNativeArm64", + "androidNativeX64", + "androidNativeX86", + ) + runner { + arguments.add(":apiCheck") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiCheck", FAILED) + output shouldContain "KLib ABI dump/validation requires at least one enabled klib target, but none were found." + } + } + + @Test + fun `klibCheck should fail with strict validation if all klib-targets are unavailable`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/unsupported/enforce.gradle.kts") + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + abiFile(projectName = "testproject") { + // note that the regular dump is used, where linuxArm64 is presented + resolve("/examples/classes/TopLevelDeclarations.klib.dump") + } + + disableKLibTargets( + "linuxArm64", + "linuxX64", + "mingwX64", + "androidNativeArm32", + "androidNativeArm64", + "androidNativeX64", + "androidNativeX86", + ) + runner { + arguments.add(":apiCheck") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiCheck", FAILED) + output shouldContain "KLib ABI dump/validation requires at least one enabled klib target, but none were found." + } + } + + @Test + fun `target name clashing with a group name`() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts") + resolve("/examples/gradle/configuration/grouping/clashingTargetNames.gradle.kts") + } + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxArm64Main") + kotlin("AnotherBuildConfigLinuxX64.kt", "linuxMain") { + resolve("/examples/classes/AnotherBuildConfigLinuxArm64.kt") + } + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.clash.dump") + } + } + + @Test + fun `target name grouping with custom target names`() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts") + resolve("/examples/gradle/configuration/grouping/customTargetNames.gradle.kts") + } + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.custom.dump") + } + } + + @Test + fun `target name grouping`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + addToSrcSet("/examples/classes/AnotherBuildConfigLinuxArm64.kt", "linuxArm64Main") + kotlin("AnotherBuildConfigLinuxX64.kt", "linuxX64Main") { + resolve("/examples/classes/AnotherBuildConfigLinuxArm64.kt") + } + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfigLinux.klib.grouping.dump") + } + } + + @Test + fun `apiDump should work with web targets`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/nonNativeKlibTargets/targets.gradle.kts") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.web.dump") + } + } + + @Test + fun `apiCheck should work with web targets`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/nonNativeKlibTargets/targets.gradle.kts") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/AnotherBuildConfig.klib.web.dump") + } + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `check dump is updated on added declaration`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + } + + // Update the source file by adding a declaration + val updatedSourceFile = readResourceFile("/examples/classes/AnotherBuildConfigModified.kt") + val existingSource = runner.projectDir.resolve("src/commonMain/kotlin/AnotherBuildConfig.kt") + existingSource.writeText(updatedSourceFile) + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfigModified.klib.dump") + } + } + + @Test + fun `check dump is updated on a declaration added to some source sets`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.dump") + // Update the source file by adding a declaration + val updatedSourceFile = readResourceFile("/examples/classes/AnotherBuildConfigLinuxArm64.kt") + val existingSource = + runner.projectDir.resolve("src/linuxArm64Main/kotlin/AnotherBuildConfigLinuxArm64.kt") + existingSource.parentFile.mkdirs() + existingSource.writeText(updatedSourceFile) + + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfigLinuxArm64Extra.klib.dump") + } + } + } + + @Test + fun `re-validate dump after sources updated`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/AnotherBuildConfig.klib.dump") + } + runner { + arguments.add(":apiCheck") + } + } + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + + // Update the source file by adding a declaration + val updatedSourceFile = readResourceFile("/examples/classes/AnotherBuildConfigModified.kt") + val existingSource = runner.projectDir.resolve("src/commonMain/kotlin/AnotherBuildConfig.kt") + existingSource.writeText(updatedSourceFile) + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiCheck", FAILED) + } + } + + @Test + fun `validation should fail on target rename`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt") + abiFile(projectName = "testproject") { + resolve("/examples/classes/AnotherBuildConfig.klib.renamedTarget.dump") + } + runner { + arguments.add(":apiCheck") + } + } + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiCheck", FAILED) + output shouldContain " -// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64.linux, linuxX64, mingwX64]" + } + } + + @Test + fun `apiDump should not fail for empty project`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "commonTest") + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + rootProjectApiDump.parentFile.shouldBeEmptyDirectory() + } + } + + @Test + fun `apiDump should remove dump file if the project does not contain sources anymore`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "commonTest") + abiFile(projectName = "testproject") { + resolve("/examples/classes/AnotherBuildConfig.klib.dump") + } + + runner { + arguments.add(":apiDump") + } + } + runner.build { + shouldHaveRunTask(":apiDump", SUCCESS) + } + rootProjectApiDump.parentFile.shouldBeEmptyDirectory() +// assertFalse(runner.projectDir.resolve("api").resolve("testproject.klib.api").exists()) + } + + @Test + fun `apiDump should not fail if there is only one target`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "commonTest") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "linuxX64Main") + + runner { + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/AnotherBuildConfig.klib.linuxX64Only.dump") + } + } + + @Test + fun `apiCheck should not fail for empty project`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "commonTest") + runner { + arguments.add(":apiCheck") + } + } + runner.build { + shouldHaveRunTask(":apiCheck", SKIPPED) + } + } + + @Test + fun `apiDump for a project with generated sources only`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts") + runner { + // TODO: enable configuration cache back when we start skipping tasks correctly +// configurationCache = false + arguments.add(":apiDump") + } + } + runner.build { + checkKLibDump("/examples/classes/GeneratedSources.klib.dump") + } + } + + @Test + fun `apiCheck for a project with generated sources only`() { + val runner = test { + baseProjectSetting() + additionalBuildConfig("/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts") + abiFile(projectName = "testproject") { + resolve("/examples/classes/GeneratedSources.klib.dump") + } + runner { + // TODO: enable configuration cache back when we start skipping tasks correctly + //configurationCache = false + arguments.add(":apiCheck") + } + } + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun `apiCheck should fail after a source set was removed`() { + val runner = test { + baseProjectSetting() + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", "linuxX64Main") + addToSrcSet("/examples/classes/AnotherBuildConfig.kt", "linuxArm64Main") + abiFile(projectName = "testproject") { + resolve("/examples/classes/AnotherBuildConfig.klib.dump") + } + runner { + arguments.add(":apiCheck") + } + } + runner.buildAndFail { + shouldHaveRunTask(":apiCheck", FAILED) + } + } + + private fun BuildResult.checkKLibDump( + @Language("file-reference") + expected: String, + projectName: String = "testproject", + ) { + withClue(output) { + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) + + val generatedDump = rootProjectAbiDump(projectName) + + generatedDump.shouldExist() + + val expectedFile = readResourceFile(expected) + generatedDump.readText().invariantNewlines() shouldBe expectedFile + } + } + + companion object { + private fun BaseKotlinScope.disableKLibTargets(vararg targetNames: String) { + buildGradleKts { + val disabledTargets = + targetNames.joinToString(", ") { "\"$it\"" } + addText( + """ + |binaryCompatibilityValidator.targets + | .withType() + | .matching { it.targetName in setOf($disabledTargets) } + | .configureEach { supportedByCurrentHost.set(false) } + | + """.trimMargin() + ) + } + } + } +} 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 3f9a931..f5e6003 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 @@ -7,8 +7,7 @@ import io.kotest.matchers.file.shouldBeAFile import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.io.File -import org.gradle.testkit.runner.TaskOutcome.FAILED -import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.gradle.testkit.runner.TaskOutcome.* import org.junit.jupiter.api.Test internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { @@ -27,7 +26,6 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { createProjectHierarchyWithPluginOnRoot() runner { arguments.add(":apiCheck") - arguments.add("--stacktrace") } dir("api/") { @@ -56,9 +54,7 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { val runner = test { createProjectHierarchyWithPluginOnRoot() runner { - arguments.add("--continue") arguments.add(":check") - arguments.add("--stacktrace") } dir("api/") { @@ -107,7 +103,6 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { runner { arguments.add(":apiDump") - arguments.add("--stacktrace") } dir("src/jvmMain/kotlin") {} @@ -120,7 +115,7 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { } runner.build { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) val mainExpectedApi = """ |${readResourceFile("/examples/classes/Subsub1Class.dump").trim()} @@ -135,5 +130,45 @@ internal class MultiPlatformSingleJvmTargetTest : BaseKotlinGradleTest() { } } + @Test + fun testApiDumpPassesForEmptyProject() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts") + } + + runner { + arguments.add(":apiDump") + } + } + + runner.build { +// shouldHaveTaskWithOutcome(":jvmApiDump", SKIPPED) +// shouldHaveTaskWithOutcome(":apiDump", UP_TO_DATE) + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + } + } + + @Test + fun testApiCheckPassesForEmptyProject() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/multiplatformWithSingleJvmTarget.gradle.kts") + } + + emptyApiFile(projectName = rootProjectDir.name) + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { +// shouldHaveTaskWithOutcome(":jvmApiCheck", SKIPPED) +// shouldHaveTaskWithOutcome(":apiCheck", UP_TO_DATE) + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } + private val jvmApiDump: File get() = rootProjectDir.resolve("api/testproject.api") } 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 6cac93c..9763888 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 @@ -7,8 +7,7 @@ import io.kotest.matchers.file.shouldBeAFile import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import java.io.File -import org.gradle.testkit.runner.TaskOutcome.FAILED -import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.gradle.testkit.runner.TaskOutcome.* import org.junit.jupiter.api.Test internal class MultipleJvmTargetsTest : BaseKotlinGradleTest() { @@ -62,9 +61,7 @@ internal class MultipleJvmTargetsTest : BaseKotlinGradleTest() { val runner = test { createProjectHierarchyWithPluginOnRoot() runner { - arguments.add("--continue") arguments.add(":check") - arguments.add("--stacktrace") } dir("api/jvm/") { @@ -119,7 +116,7 @@ internal class MultipleJvmTargetsTest : BaseKotlinGradleTest() { } runner.build { withClue(output) { - shouldHaveRunTask(":apiDump", SUCCESS) + shouldHaveRunTask(":apiDump", SUCCESS, FROM_CACHE) val anotherExpectedApi = readResourceFile("/examples/classes/Subsub1Class.dump") anotherApiDump.shouldBeAFile() diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt index 9f39c8e..e7f73f1 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/NonPublicMarkersTest.kt @@ -3,7 +3,11 @@ 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.shouldHaveTaskWithOutcome +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.shouldBe +import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class NonPublicMarkersTest : BaseKotlinGradleTest() { @@ -33,4 +37,85 @@ class NonPublicMarkersTest : BaseKotlinGradleTest() { shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) } } + + @Test + @Disabled("https://youtrack.jetbrains.com/issue/KT-62259") + fun testIgnoredMarkersOnPropertiesForNativeTargets() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + + buildGradleKts { + resolve("/examples/gradle/base/withNativePlugin.gradle.kts") + resolve("/examples/gradle/configuration/nonPublicMarkers/markers.gradle.kts") + } + + kotlin("Properties.kt", sourceSet = "commonMain") { + resolve("/examples/classes/Properties.kt") + } + + commonNativeTargets.forEach { + abiFile(projectName = "testproject", target = it) { + resolve("/examples/classes/Properties.klib.dump") + } + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } + + @Test + fun testFiltrationByPackageLevelAnnotations() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts") + } + java("annotated/PackageAnnotation.java") { + resolve("/examples/classes/PackageAnnotation.java") + } + java("annotated/package-info.java") { + resolve("/examples/classes/package-info.java") + } + kotlin("ClassFromAnnotatedPackage.kt") { + resolve("/examples/classes/ClassFromAnnotatedPackage.kt") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + runner { + arguments.add(":apiDump") + } + } + + runner + .forwardOutput() + .build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + + rootProjectApiDump.shouldExist() + + val dumpFile = readResourceFile("/examples/classes/AnotherBuildConfig.dump") + rootProjectApiDump.readText() shouldBe dumpFile + } + } + + companion object { + private val commonNativeTargets: Set = setOf( + "linuxX64", + "linuxArm64", + "mingwX64", + "androidNativeArm32", + "androidNativeArm64", + "androidNativeX64", + "androidNativeX86" + ) + } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/OutputDirectoryTests.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/OutputDirectoryTests.kt new file mode 100644 index 0000000..6cadb0d --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/OutputDirectoryTests.kt @@ -0,0 +1,166 @@ +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.buildAndFail +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.shouldHaveTaskWithOutcome +import io.kotest.assertions.withClue +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.gradle.testkit.runner.TaskOutcome.* +import org.junit.jupiter.api.Test + +class OutputDirectoryTests : BaseKotlinGradleTest() { + + @Test + fun dumpIntoCustomDirectory() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts") + } + + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + dir("api") { + file("letMeBe.txt") + } + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + + val dumpFile = rootProjectDir.resolve("custom/${rootProjectDir.name}.api") + dumpFile.shouldExist() + + val expected = readResourceFile("/examples/classes/AnotherBuildConfig.dump") + dumpFile.readText() shouldBe expected + + val fileInsideDir = rootProjectDir.resolve("api").resolve("letMeBe.txt") + withClue("existing api directory should not be overwritten") { + fileInsideDir.shouldExist() + } + } + } + + @Test + fun validateDumpFromACustomDirectory() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/outputDirectory/different.gradle.kts") + } + + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + dir("custom") { + file("${rootProjectDir.name}.api") { + resolve("/examples/classes/AnotherBuildConfig.dump") + } + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } + + @Test + fun dumpIntoSubdirectory() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts") + } + + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + + runner { + arguments.add(":apiDump") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + + val dumpFile = rootProjectDir.resolve("validation/api/${rootProjectDir.name}.api") + dumpFile.shouldExist() + + val expected = readResourceFile("/examples/classes/AnotherBuildConfig.dump") + dumpFile.readText() shouldBe expected + } + } + + @Test + fun validateDumpFromASubdirectory() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts") + } + + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + dir("validation") { + dir("api") { + file("${rootProjectDir.name}.api") { + resolve("/examples/classes/AnotherBuildConfig.dump") + } + } + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) + } + } + + @Test + fun dumpIntoParentDirectory() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/outputDirectory/outer.gradle.kts") + } + + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + + runner { + arguments.add(":apiDump") + } + } + + runner.buildAndFail { + shouldHaveTaskWithOutcome(":apiDump", FAILED) + + output shouldContain /* language=text */ """ + |> Error: Invalid output apiDirectory + | + | apiDirectory is set to a custom directory, outside of the current project directory. + | This is not permitted. apiDirectory must be a subdirectory of project ':' (the root project) directory. + | + | Remove the custom apiDirectory, or update apiDirectory to be a project subdirectory. + """.trimMargin() + } + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt index dea8f24..809054b 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/kotlin/kotlinx/validation/test/PublicMarkersTest.kt @@ -2,7 +2,13 @@ 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.shouldHaveRunTask import dev.adamko.kotlin.binary_compatibility_validator.test.utils.shouldHaveTaskWithOutcome +import io.kotest.matchers.file.shouldBeAFile +import io.kotest.matchers.file.shouldExist +import io.kotest.matchers.shouldBe +import org.gradle.testkit.runner.TaskOutcome.FROM_CACHE import org.gradle.testkit.runner.TaskOutcome.SUCCESS import org.junit.jupiter.api.Test @@ -15,19 +21,15 @@ class PublicMarkersTest : BaseKotlinGradleTest() { resolve("/examples/gradle/base/withPlugin.gradle.kts") resolve("/examples/gradle/configuration/publicMarkers/markers.gradle.kts") } - kotlin("ClassWithPublicMarkers.kt") { resolve("/examples/classes/ClassWithPublicMarkers.kt") } - kotlin("ClassInPublicPackage.kt") { resolve("/examples/classes/ClassInPublicPackage.kt") } - apiFile(projectName = rootProjectDir.name) { resolve("/examples/classes/ClassWithPublicMarkers.dump") } - runner { arguments.add(":apiCheck") } @@ -37,4 +39,73 @@ class PublicMarkersTest : BaseKotlinGradleTest() { shouldHaveTaskWithOutcome(":apiCheck", SUCCESS) } } + + /** ⚠️ Public markers are not supported in KLib ABI dumps */ + @Test + fun testPublicMarkersForNativeTargets() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + + buildGradleKts { + resolve("/examples/gradle/base/withNativePlugin.gradle.kts") + resolve("/examples/gradle/configuration/publicMarkers/markers.gradle.kts") + } + + kotlin("ClassWithPublicMarkers.kt", sourceSet = "commonMain") { + resolve("/examples/classes/ClassWithPublicMarkers.kt") + } + + kotlin("ClassInPublicPackage.kt", sourceSet = "commonMain") { + resolve("/examples/classes/ClassInPublicPackage.kt") + } + + abiFile(projectName = "testproject") { + resolve("/examples/classes/ClassWithPublicMarkers.klib.dump") + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build { + shouldHaveRunTask(":apiCheck", SUCCESS) + } + } + + @Test + fun testFiltrationByPackageLevelAnnotations() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPlugin.gradle.kts") + resolve("/examples/gradle/configuration/publicMarkers/packages.gradle.kts") + } + java("annotated/PackageAnnotation.java") { + resolve("/examples/classes/PackageAnnotation.java") + } + java("annotated/package-info.java") { + resolve("/examples/classes/package-info.java") + } + kotlin("ClassFromAnnotatedPackage.kt") { + resolve("/examples/classes/ClassFromAnnotatedPackage.kt") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + runner { + arguments.add(":apiDump") + } + } + + runner.build { + shouldHaveTaskWithOutcome(":apiDump", SUCCESS, FROM_CACHE) + + rootProjectApiDump.shouldExist() + rootProjectApiDump.shouldBeAFile() + val expected = readResourceFile("/examples/classes/AnnotatedPackage.dump") + rootProjectApiDump.readText().invariantNewlines() shouldBe expected + } + } } 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 index bdc6029..f6fc871 100644 --- 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 @@ -20,16 +20,19 @@ internal class SettingsPluginDslTest : FunSpec({ projectType = "Kotlin/JVM", project = kotlinJvmProjectWithBcvSettingsPlugin(), expectedPrintedBCVTargets = """ + |type: class dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget_Decorated |name: kotlinJvm - |platformType: kotlinJvm |enabled: true |ignoredClasses: [com.package.MyIgnoredClass] |ignoredMarkers: [com.package.MyInternalApiAnnotationMarker] |ignoredPackages: [com.package.my_ignored_package] - |inputClasses: [main, main] + |publicMarkers: [com.package.MyIgnoredClass] + |publicPackages: [com.package.MyInternalApiAnnotationMarker] + |publicClasses: [com.package.my_ignored_package] + |platformType: kotlinJvm |inputJar: null + |inputClasses: [main, main] |------------------------------ - | """.trimMargin() ) @@ -37,16 +40,19 @@ internal class SettingsPluginDslTest : FunSpec({ projectType = "Kotlin/Multiplatform", project = kotlinMultiplatformProjectWithBcvSettingsPlugin(), expectedPrintedBCVTargets = """ + |type: class dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget_Decorated |name: jvm - |platformType: jvm |enabled: true |ignoredClasses: [com.package.MyIgnoredClass] |ignoredMarkers: [com.package.MyInternalApiAnnotationMarker] |ignoredPackages: [com.package.my_ignored_package] - |inputClasses: [main] + |publicMarkers: [com.package.MyIgnoredClass] + |publicPackages: [com.package.MyInternalApiAnnotationMarker] + |publicClasses: [com.package.my_ignored_package] + |platformType: jvm |inputJar: null + |inputClasses: [main] |------------------------------ - | """.trimMargin() ) @@ -79,22 +85,22 @@ internal class SettingsPluginDslTest : FunSpec({ apiDump.shouldExist() apiDump.shouldBeAFile() apiDump.readText().invariantNewlines() shouldBe /* language=TEXT */ """ - | - """.trimMargin() + | + """.trimMargin() } testCase.project.projectDir.resolve("sub2/api/sub2.api").asClue { apiDump -> apiDump.shouldExist() apiDump.shouldBeAFile() apiDump.readText().invariantNewlines() shouldBe /* language=TEXT */ """ - | - """.trimMargin() + | + """.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 + output.invariantNewlines().trim() shouldBe testCase.expectedPrintedBCVTargets.trim() } } } @@ -148,7 +154,7 @@ private fun kotlinMultiplatformProjectWithBcvSettingsPlugin() = private val settingsGradleKtsWithBcvPlugin = """ buildscript { dependencies { - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.20") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.23") } } @@ -176,7 +182,7 @@ binaryCompatibilityValidator { @Language("kts") private val buildGradleKtsWithKotlinJvmAndBcvConfig = """ plugins { - kotlin("jvm") version "1.7.20" + kotlin("jvm") version "1.9.23" } // check that the DSL is available: @@ -190,7 +196,7 @@ binaryCompatibilityValidator { } @Language("kts") private val buildGradleKtsWithKotlinMultiplatformJvmAndBcvConfig = """ plugins { - kotlin("multiplatform") version "1.7.20" + kotlin("multiplatform") version "1.9.23" } kotlin { @@ -204,23 +210,40 @@ 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) + bcvTargets.forEach { t -> + println("type: " + t::class.toString()) + println("name: " + t.name) + println("enabled: " + t.enabled.get()) + println("ignoredClasses: " + t.ignoredClasses.get()) + println("ignoredMarkers: " + t.ignoredMarkers.get()) + println("ignoredPackages: " + t.ignoredPackages.get()) + println("publicMarkers: " + t.ignoredClasses.get()) + println("publicPackages: " + t.ignoredMarkers.get()) + println("publicClasses: " + t.ignoredPackages.get()) + when (t) { + + is dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget -> { + println("platformType: " + t.platformType) + println("inputJar: " + t.inputJar.orNull) + println("inputClasses: " + t.inputClasses.files.map { f -> f.name }) + } + + is dev.adamko.kotlin.binary_compatibility_validator.targets.BCVKLibTarget -> { + println("klibFile: " + t.klibFile.files.map { f -> f.name }) + println("compilationDependencies: " + t.compilationDependencies.files.map { f -> f.name }) + println("currentPlatform: " + t.currentPlatform.orNull) + println("supportedByCurrentHost: " + t.supportedByCurrentHost.orNull) + println("hasKotlinSources: " + t.hasKotlinSources.orNull) + } + } println("------------------------------") } } } - """ diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump new file mode 100644 index 0000000..0dcd804 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnnotatedPackage.dump @@ -0,0 +1,7 @@ +public final class annotated/ClassFromAnnotatedPackage { + public fun ()V +} + +public abstract interface annotation class annotated/PackageAnnotation : java/lang/annotation/Annotation { +} + diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.clash.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.clash.dump new file mode 100644 index 0000000..78f09ce --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.clash.dump @@ -0,0 +1,17 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64.linux, mingwX64] +// Alias: linux => [linuxArm64, linuxX64.linux] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} +// Targets: [linux] +final fun (org.different.pack/BuildConfig).org.different.pack/linuxArm64Specific(): kotlin/Int // org.different.pack/linuxArm64Specific|linuxArm64Specific@org.different.pack.BuildConfig(){}[0] diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.custom.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.custom.dump new file mode 100644 index 0000000..1adbdac --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.custom.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [linuxX64.linuxA, linuxX64.linuxB] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.dump new file mode 100644 index 0000000..9511bef --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.linuxX64Only.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.linuxX64Only.dump new file mode 100644 index 0000000..7c6117d --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.linuxX64Only.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [linuxX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.renamedTarget.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.renamedTarget.dump new file mode 100644 index 0000000..8b84f00 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.renamedTarget.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64.linux, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.web.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.web.dump new file mode 100644 index 0000000..bebc349 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfig.klib.web.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, js, linuxArm64, linuxX64, mingwX64, wasmJs, wasmWasi] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinux.klib.grouping.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinux.klib.grouping.dump new file mode 100644 index 0000000..f67ac44 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinux.klib.grouping.dump @@ -0,0 +1,17 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Alias: linux => [linuxArm64, linuxX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} +// Targets: [linux] +final fun (org.different.pack/BuildConfig).org.different.pack/linuxArm64Specific(): kotlin/Int // org.different.pack/linuxArm64Specific|linuxArm64Specific@org.different.pack.BuildConfig(){}[0] diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64.kt new file mode 100644 index 0000000..7a54055 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64.kt @@ -0,0 +1,3 @@ +package org.different.pack + +fun BuildConfig.linuxArm64Specific(): Int = 42 diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64Extra.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64Extra.klib.dump new file mode 100644 index 0000000..20292d7 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigLinuxArm64Extra.klib.dump @@ -0,0 +1,16 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} +// Targets: [linuxArm64] +final fun (org.different.pack/BuildConfig).org.different.pack/linuxArm64Specific(): kotlin/Int // org.different.pack/linuxArm64Specific|linuxArm64Specific@org.different.pack.BuildConfig(){}[0] diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.klib.dump new file mode 100644 index 0000000..75eb66b --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.klib.dump @@ -0,0 +1,15 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class org.different.pack/BuildConfig { // org.different.pack/BuildConfig|null[0] + constructor () // org.different.pack/BuildConfig.|(){}[0] + final fun f1(): kotlin/Int // org.different.pack/BuildConfig.f1|f1(){}[0] + final fun f2(): kotlin/Int // org.different.pack/BuildConfig.f2|f2(){}[0] + final val p1 // org.different.pack/BuildConfig.p1|{}p1[0] + final fun (): kotlin/Int // org.different.pack/BuildConfig.p1.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.kt new file mode 100644 index 0000000..1127802 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/AnotherBuildConfigModified.kt @@ -0,0 +1,9 @@ +package org.different.pack + +public class BuildConfig { + public val p1 = 1 + + public fun f1() = p1 + + public fun f2() = p1 +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.dump new file mode 100644 index 0000000..b21a6b7 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.dump @@ -0,0 +1,6 @@ +public final class com/company/BuildCon { + public fun ()V + public final fun f1 ()I + public final fun getP1 ()I +} + diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.kt new file mode 100644 index 0000000..f9c476e --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/BuildCon.kt @@ -0,0 +1,7 @@ +package com.company + +public class BuildCon { + public val p1 = 1 + + public fun f1() = p1 +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt new file mode 100644 index 0000000..ebd1bef --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassFromAnnotatedPackage.kt @@ -0,0 +1,4 @@ +package annotated + +class ClassFromAnnotatedPackage { +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassWithPublicMarkers.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassWithPublicMarkers.klib.dump new file mode 100644 index 0000000..b1e8f29 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/ClassWithPublicMarkers.klib.dump @@ -0,0 +1,45 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Alias: androidNative => [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86] +// Alias: linux => [linuxArm64, linuxX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class foo.api/ClassInPublicPackage { // foo.api/ClassInPublicPackage|null[0] + constructor () // foo.api/ClassInPublicPackage.|(){}[0] + final class Inner { // foo.api/ClassInPublicPackage.Inner|null[0] + constructor () // foo.api/ClassInPublicPackage.Inner.|(){}[0] + } +} +final class foo/ClassWithPublicMarkers { // foo/ClassWithPublicMarkers|null[0] + constructor () // foo/ClassWithPublicMarkers.|(){}[0] + final class MarkedClass { // foo/ClassWithPublicMarkers.MarkedClass|null[0] + constructor () // foo/ClassWithPublicMarkers.MarkedClass.|(){}[0] + final val bar1 // foo/ClassWithPublicMarkers.MarkedClass.bar1|{}bar1[0] + final fun (): kotlin/Int // foo/ClassWithPublicMarkers.MarkedClass.bar1.|(){}[0] + } + final class NotMarkedClass { // foo/ClassWithPublicMarkers.NotMarkedClass|null[0] + constructor () // foo/ClassWithPublicMarkers.NotMarkedClass.|(){}[0] + } + final var bar1 // foo/ClassWithPublicMarkers.bar1|{}bar1[0] + final fun (): kotlin/Int // foo/ClassWithPublicMarkers.bar1.|(){}[0] + final fun (kotlin/Int) // foo/ClassWithPublicMarkers.bar1.|(kotlin.Int){}[0] + final var bar2 // foo/ClassWithPublicMarkers.bar2|{}bar2[0] + final fun (): kotlin/Int // foo/ClassWithPublicMarkers.bar2.|(){}[0] + final fun (kotlin/Int) // foo/ClassWithPublicMarkers.bar2.|(kotlin.Int){}[0] + final var notMarkedPublic // foo/ClassWithPublicMarkers.notMarkedPublic|{}notMarkedPublic[0] + final fun (): kotlin/Int // foo/ClassWithPublicMarkers.notMarkedPublic.|(){}[0] + final fun (kotlin/Int) // foo/ClassWithPublicMarkers.notMarkedPublic.|(kotlin.Int){}[0] +} +open annotation class foo/PublicClass : kotlin/Annotation { // foo/PublicClass|null[0] + constructor () // foo/PublicClass.|(){}[0] +} +open annotation class foo/PublicField : kotlin/Annotation { // foo/PublicField|null[0] + constructor () // foo/PublicField.|(){}[0] +} +open annotation class foo/PublicProperty : kotlin/Annotation { // foo/PublicProperty|null[0] + constructor () // foo/PublicProperty.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Empty.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Empty.klib.dump new file mode 100644 index 0000000..40583d9 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Empty.klib.dump @@ -0,0 +1,6 @@ +// Klib ABI Dump +// Targets: [mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.dump new file mode 100644 index 0000000..f69e8d2 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.dump @@ -0,0 +1,5 @@ +public final class Generated { + public fun ()V + public final fun helloCreator ()I +} + diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.klib.dump new file mode 100644 index 0000000..97ba68f --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/GeneratedSources.klib.dump @@ -0,0 +1,12 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class /Generated { // /Generated|null[0] + constructor () // /Generated.|(){}[0] + final fun helloCreator(): kotlin/Int // /Generated.helloCreator|helloCreator(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.klib.dump new file mode 100644 index 0000000..bed1c06 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.klib.dump @@ -0,0 +1,11 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class examples.classes/VC { // examples.classes/VC|null[0] + final var prop // examples.classes/VC.prop|{}prop[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.kt new file mode 100644 index 0000000..7565370 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/HiddenDeclarations.kt @@ -0,0 +1,38 @@ +package examples.classes + +import annotations.* + +@HiddenFunction +public fun hidden() = Unit + +@HiddenProperty +public val v: Int = 42 + +@HiddenClass +public class HC + +public class VC @HiddenCtor constructor() { + @HiddenProperty + public val v: Int = 42 + + public var prop: Int = 0 + @HiddenGetter + get() = field + @HiddenSetter + set(value) { + field = value + } + + @HiddenProperty + public var fullyHiddenProp: Int = 0 + + @HiddenFunction + public fun m() = Unit +} + +@HiddenClass +public class HiddenOuterClass { + public class HiddenInnerClass { + + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/NonPublicMarkers.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/NonPublicMarkers.kt new file mode 100644 index 0000000..e99b28a --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/NonPublicMarkers.kt @@ -0,0 +1,29 @@ +package annotations + +@HiddenClass +@Target(AnnotationTarget.CLASS) +annotation class HiddenClass + +@HiddenClass +@Target(AnnotationTarget.FUNCTION) +annotation class HiddenFunction + +@HiddenClass +@Target(AnnotationTarget.CONSTRUCTOR) +annotation class HiddenCtor + +@HiddenClass +@Target(AnnotationTarget.PROPERTY) +annotation class HiddenProperty + +@HiddenClass +@Target(AnnotationTarget.FIELD) +annotation class HiddenField + +@HiddenClass +@Target(AnnotationTarget.PROPERTY_GETTER) +annotation class HiddenGetter + +@HiddenClass +@Target(AnnotationTarget.PROPERTY_SETTER) +annotation class HiddenSetter diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/PackageAnnotation.java b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/PackageAnnotation.java new file mode 100644 index 0000000..6e41985 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/PackageAnnotation.java @@ -0,0 +1,11 @@ +package annotated; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PACKAGE) +public @interface PackageAnnotation { +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Properties.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Properties.klib.dump new file mode 100644 index 0000000..6359372 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Properties.klib.dump @@ -0,0 +1,17 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class foo/ClassWithProperties { // foo/ClassWithProperties|null[0] + constructor () // foo/ClassWithProperties.|(){}[0] +} +open annotation class foo/HiddenField : kotlin/Annotation { // foo/HiddenField|null[0] + constructor () // foo/HiddenField.|(){}[0] +} +open annotation class foo/HiddenProperty : kotlin/Annotation { // foo/HiddenProperty|null[0] + constructor () // foo/HiddenProperty.|(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/SubPackage.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/SubPackage.kt new file mode 100644 index 0000000..fbaa691 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/SubPackage.kt @@ -0,0 +1,4 @@ +package com.company.division + +public class ClassWithinSubPackage { +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.dump new file mode 100644 index 0000000..04cb152 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.dump @@ -0,0 +1,7 @@ +public final class subclasses/A { + public fun ()V +} + +public final class subclasses/A$D { + public fun ()V +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.klib.dump new file mode 100644 index 0000000..e13fa3f --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.klib.dump @@ -0,0 +1,14 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final class subclasses/A { // subclasses/A|null[0] + constructor () // subclasses/A.|(){}[0] + final class D { // subclasses/A.D|null[0] + constructor () // subclasses/A.D.|(){}[0] + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.kt new file mode 100644 index 0000000..b50cd50 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/Subclasses.kt @@ -0,0 +1,11 @@ +package subclasses + +public class A { + public class B { + public class C + } + + public class D { + + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.all.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.all.dump new file mode 100644 index 0000000..05aa3cd --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.all.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, iosArm64, iosSimulatorArm64, iosX64, linuxArm64, linuxX64, macosArm64, macosX64, mingwX64, tvosArm64, tvosSimulatorArm64, tvosX64, watchosArm32, watchosArm64, watchosDeviceArm64, watchosSimulatorArm64, watchosX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|a(){}[0] + constructor () // examples.classes/AC.|(){}[0] + final fun b() // examples.classes/AC.b|b(){}[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|a(){}[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|(kotlin.Any){}[0] + final fun m() // examples.classes/C.m|m(){}[0] + final val v // examples.classes/C.v|{}v[0] + final fun (): kotlin/Any // examples.classes/C.v.|(){}[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|(kotlin.Int){}[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|component1(){}[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // examples.classes/D.toString|toString(){}[0] + final val x // examples.classes/D.x|{}x[0] + final fun (): kotlin/Int // examples.classes/D.x.|(){}[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|(){}[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|(){}[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|(){}[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|values#static(){}[0] + final val entries // examples.classes/Outer.Nested.NE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|#static(){}[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|(){}[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|(){}[0] + } + } +} +final const val examples.classes/con // examples.classes/con|{}con[0] + final fun (): kotlin/String // examples.classes/con.|(){}[0] +final const val examples.classes/intCon // examples.classes/intCon|{}intCon[0] + final fun (): kotlin/Int // examples.classes/intCon.|(){}[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/E.values|values#static(){}[0] + final val entries // examples.classes/E.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|#static(){}[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/EE.values|values#static(){}[0] + final val entries // examples.classes/EE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|#static(){}[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|consume(0:0){0§}[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|testFun(){}[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|testInlineFun(){}[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|{}a[0] + final fun (): kotlin/Any // examples.classes/a.|(){}[0] +final val examples.classes/i // examples.classes/i|{}i[0] + final fun (): kotlin/Int // examples.classes/i.|(){}[0] +final val examples.classes/l // examples.classes/l|{}l[0] + final fun (): kotlin/Long // examples.classes/l.|(){}[0] +final var examples.classes/d // examples.classes/d|{}d[0] + final fun (): kotlin/Double // examples.classes/d.|(){}[0] + final fun (kotlin/Double) // examples.classes/d.|(kotlin.Double){}[0] +final var examples.classes/r // examples.classes/r|{}r[0] + final fun (): kotlin/Float // examples.classes/r.|(){}[0] + final fun (kotlin/Float) // examples.classes/r.|(kotlin.Float){}[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|(){}[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|(){}[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|(){}[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|(kotlin.Int){}[0] + constructor (kotlin/Long) // examples.classes/OC.|(kotlin.Long){}[0] + constructor (kotlin/String) // examples.classes/OC.|(kotlin.String){}[0] + final fun c() // examples.classes/OC.c|c(){}[0] + final val ix // examples.classes/OC.ix|{}ix[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|(){}[0] + final val iy // examples.classes/OC.iy|{}iy[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|(){}[0] + final val iz // examples.classes/OC.iz|{}iz[0] + final fun (): kotlin/String // examples.classes/OC.iz.|(){}[0] + final var x // examples.classes/OC.x|{}x[0] + final fun (): kotlin/Int // examples.classes/OC.x.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.x.|(kotlin.Int){}[0] + final var y // examples.classes/OC.y|{}y[0] + final fun (): kotlin/Int // examples.classes/OC.y.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.y.|(kotlin.Int){}[0] + final var z // examples.classes/OC.z|{}z[0] + final fun (): kotlin/Int // examples.classes/OC.z.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.z.|(kotlin.Int){}[0] + open fun o(): kotlin/Int // examples.classes/OC.o|o(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.dump new file mode 100644 index 0000000..fdc5d28 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|a(){}[0] + constructor () // examples.classes/AC.|(){}[0] + final fun b() // examples.classes/AC.b|b(){}[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|a(){}[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|(kotlin.Any){}[0] + final fun m() // examples.classes/C.m|m(){}[0] + final val v // examples.classes/C.v|{}v[0] + final fun (): kotlin/Any // examples.classes/C.v.|(){}[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|(kotlin.Int){}[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|component1(){}[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // examples.classes/D.toString|toString(){}[0] + final val x // examples.classes/D.x|{}x[0] + final fun (): kotlin/Int // examples.classes/D.x.|(){}[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|(){}[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|(){}[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|(){}[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|values#static(){}[0] + final val entries // examples.classes/Outer.Nested.NE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|#static(){}[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|(){}[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|(){}[0] + } + } +} +final const val examples.classes/con // examples.classes/con|{}con[0] + final fun (): kotlin/String // examples.classes/con.|(){}[0] +final const val examples.classes/intCon // examples.classes/intCon|{}intCon[0] + final fun (): kotlin/Int // examples.classes/intCon.|(){}[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/E.values|values#static(){}[0] + final val entries // examples.classes/E.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|#static(){}[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/EE.values|values#static(){}[0] + final val entries // examples.classes/EE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|#static(){}[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|consume(0:0){0§}[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|testFun(){}[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|testInlineFun(){}[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|{}a[0] + final fun (): kotlin/Any // examples.classes/a.|(){}[0] +final val examples.classes/i // examples.classes/i|{}i[0] + final fun (): kotlin/Int // examples.classes/i.|(){}[0] +final val examples.classes/l // examples.classes/l|{}l[0] + final fun (): kotlin/Long // examples.classes/l.|(){}[0] +final var examples.classes/d // examples.classes/d|{}d[0] + final fun (): kotlin/Double // examples.classes/d.|(){}[0] + final fun (kotlin/Double) // examples.classes/d.|(kotlin.Double){}[0] +final var examples.classes/r // examples.classes/r|{}r[0] + final fun (): kotlin/Float // examples.classes/r.|(){}[0] + final fun (kotlin/Float) // examples.classes/r.|(kotlin.Float){}[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|(){}[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|(){}[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|(){}[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|(kotlin.Int){}[0] + constructor (kotlin/Long) // examples.classes/OC.|(kotlin.Long){}[0] + constructor (kotlin/String) // examples.classes/OC.|(kotlin.String){}[0] + final fun c() // examples.classes/OC.c|c(){}[0] + final val ix // examples.classes/OC.ix|{}ix[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|(){}[0] + final val iy // examples.classes/OC.iy|{}iy[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|(){}[0] + final val iz // examples.classes/OC.iz|{}iz[0] + final fun (): kotlin/String // examples.classes/OC.iz.|(){}[0] + final var x // examples.classes/OC.x|{}x[0] + final fun (): kotlin/Int // examples.classes/OC.x.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.x.|(kotlin.Int){}[0] + final var y // examples.classes/OC.y|{}y[0] + final fun (): kotlin/Int // examples.classes/OC.y.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.y.|(kotlin.Int){}[0] + final var z // examples.classes/OC.z|{}z[0] + final fun (): kotlin/Int // examples.classes/OC.z.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.z.|(kotlin.Int){}[0] + open fun o(): kotlin/Int // examples.classes/OC.o|o(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.unsup.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.unsup.dump new file mode 100644 index 0000000..f40c1ef --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.unsup.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|a(){}[0] + constructor () // examples.classes/AC.|(){}[0] + final fun b() // examples.classes/AC.b|b(){}[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|a(){}[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|(kotlin.Any){}[0] + final fun m() // examples.classes/C.m|m(){}[0] + final val v // examples.classes/C.v|{}v[0] + final fun (): kotlin/Any // examples.classes/C.v.|(){}[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|(kotlin.Int){}[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|component1(){}[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // examples.classes/D.toString|toString(){}[0] + final val x // examples.classes/D.x|{}x[0] + final fun (): kotlin/Int // examples.classes/D.x.|(){}[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|(){}[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|(){}[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|(){}[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|values#static(){}[0] + final val entries // examples.classes/Outer.Nested.NE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|#static(){}[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|(){}[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|(){}[0] + } + } +} +final const val examples.classes/con // examples.classes/con|{}con[0] + final fun (): kotlin/String // examples.classes/con.|(){}[0] +final const val examples.classes/intCon // examples.classes/intCon|{}intCon[0] + final fun (): kotlin/Int // examples.classes/intCon.|(){}[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/E.values|values#static(){}[0] + final val entries // examples.classes/E.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|#static(){}[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/EE.values|values#static(){}[0] + final val entries // examples.classes/EE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|#static(){}[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|consume(0:0){0§}[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|testFun(){}[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|testInlineFun(){}[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|{}a[0] + final fun (): kotlin/Any // examples.classes/a.|(){}[0] +final val examples.classes/i // examples.classes/i|{}i[0] + final fun (): kotlin/Int // examples.classes/i.|(){}[0] +final val examples.classes/l // examples.classes/l|{}l[0] + final fun (): kotlin/Long // examples.classes/l.|(){}[0] +final var examples.classes/d // examples.classes/d|{}d[0] + final fun (): kotlin/Double // examples.classes/d.|(){}[0] + final fun (kotlin/Double) // examples.classes/d.|(kotlin.Double){}[0] +final var examples.classes/r // examples.classes/r|{}r[0] + final fun (): kotlin/Float // examples.classes/r.|(){}[0] + final fun (kotlin/Float) // examples.classes/r.|(kotlin.Float){}[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|(){}[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|(){}[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|(){}[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|(kotlin.Int){}[0] + constructor (kotlin/Long) // examples.classes/OC.|(kotlin.Long){}[0] + constructor (kotlin/String) // examples.classes/OC.|(kotlin.String){}[0] + final fun c() // examples.classes/OC.c|c(){}[0] + final val ix // examples.classes/OC.ix|{}ix[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|(){}[0] + final val iy // examples.classes/OC.iy|{}iy[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|(){}[0] + final val iz // examples.classes/OC.iz|{}iz[0] + final fun (): kotlin/String // examples.classes/OC.iz.|(){}[0] + final var x // examples.classes/OC.x|{}x[0] + final fun (): kotlin/Int // examples.classes/OC.x.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.x.|(kotlin.Int){}[0] + final var y // examples.classes/OC.y|{}y[0] + final fun (): kotlin/Int // examples.classes/OC.y.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.y.|(kotlin.Int){}[0] + final var z // examples.classes/OC.z|{}z[0] + final fun (): kotlin/Int // examples.classes/OC.z.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.z.|(kotlin.Int){}[0] + open fun o(): kotlin/Int // examples.classes/OC.o|o(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.v1.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.v1.dump new file mode 100644 index 0000000..661d87f --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.v1.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 1 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|-4432112437378250461[0] + constructor () // examples.classes/AC.|-5645683436151566731[0] + final fun b() // examples.classes/AC.b|4789657038926421504[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|-4432112437378250461[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|4518179880532599055[0] + final fun m() // examples.classes/C.m|-1029306787563722981[0] + final val v // examples.classes/C.v|138869847852828796[0] + final fun (): kotlin/Any // examples.classes/C.v.|4964732996156868941[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|-5182794243525578284[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|162597135895221648[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|-6971662324481626298[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|4638265728071529943[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|3409210261493131192[0] + final fun toString(): kotlin/String // examples.classes/D.toString|-1522858123163872138[0] + final val x // examples.classes/D.x|-8060530855978347579[0] + final fun (): kotlin/Int // examples.classes/D.x.|1482705010654679335[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|-5645683436151566731[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|-5645683436151566731[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|-5645683436151566731[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|-4683474617854611729[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|-8715569000920726747[0] + final val entries // examples.classes/Outer.Nested.NE.entries|-5134227801081826149[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|-6068527377476727729[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|-5645683436151566731[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|-5645683436151566731[0] + } + } +} +final const val examples.classes/con // examples.classes/con|-2899158152154217071[0] + final fun (): kotlin/String // examples.classes/con.|-2604863570302238407[0] +final const val examples.classes/intCon // examples.classes/intCon|-4533540985615038728[0] + final fun (): kotlin/Int // examples.classes/intCon.|-7661624206875263703[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|-4683474617854611729[0] + final fun values(): kotlin/Array // examples.classes/E.values|-8715569000920726747[0] + final val entries // examples.classes/E.entries|-5134227801081826149[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|-6068527377476727729[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|-4683474617854611729[0] + final fun values(): kotlin/Array // examples.classes/EE.values|-8715569000920726747[0] + final val entries // examples.classes/EE.entries|-5134227801081826149[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|-6068527377476727729[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|8042761629495509481[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|6322333980269160703[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|-9193388292326484960[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|-1200697420457237799[0] + final fun (): kotlin/Any // examples.classes/a.|6785176174175479410[0] +final val examples.classes/i // examples.classes/i|5014384761142332495[0] + final fun (): kotlin/Int // examples.classes/i.|6945482638966853621[0] +final val examples.classes/l // examples.classes/l|3307215303229595169[0] + final fun (): kotlin/Long // examples.classes/l.|3795442967620585[0] +final var examples.classes/d // examples.classes/d|5174763769109925331[0] + final fun (): kotlin/Double // examples.classes/d.|-6701718004621354461[0] + final fun (kotlin/Double) // examples.classes/d.|-6916287485929380915[0] +final var examples.classes/r // examples.classes/r|-8117627916896159533[0] + final fun (): kotlin/Float // examples.classes/r.|-7424184448774736572[0] + final fun (kotlin/Float) // examples.classes/r.|9171637170963327464[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|-5645683436151566731[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|-5645683436151566731[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|-5645683436151566731[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|-5182794243525578284[0] + constructor (kotlin/Long) // examples.classes/OC.|5217973964116651322[0] + constructor (kotlin/String) // examples.classes/OC.|1280618353163213788[0] + final fun c() // examples.classes/OC.c|-2724918380551733646[0] + final val ix // examples.classes/OC.ix|5586787718776183610[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|6412451035637198934[0] + final val iy // examples.classes/OC.iy|-4863546984470764583[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|-4932930205028660775[0] + final val iz // examples.classes/OC.iz|-6916102702480625188[0] + final fun (): kotlin/String // examples.classes/OC.iz.|8935463596906645831[0] + final var x // examples.classes/OC.x|-8060530855978347579[0] + final fun (): kotlin/Int // examples.classes/OC.x.|1482705010654679335[0] + final fun (kotlin/Int) // examples.classes/OC.x.|-740209739415615559[0] + final var y // examples.classes/OC.y|3625903257357557171[0] + final fun (): kotlin/Int // examples.classes/OC.y.|-7902422373892128922[0] + final fun (kotlin/Int) // examples.classes/OC.y.|2154335559382602722[0] + final var z // examples.classes/OC.z|7549650372729116193[0] + final fun (): kotlin/Int // examples.classes/OC.z.|4925813204745917177[0] + final fun (kotlin/Int) // examples.classes/OC.z.|8486465404430625584[0] + open fun o(): kotlin/Int // examples.classes/OC.o|-3264635847192431671[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.guessed.linux.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.guessed.linux.dump new file mode 100644 index 0000000..cfcf695 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.guessed.linux.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64.linux, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|a(){}[0] + constructor () // examples.classes/AC.|(){}[0] + final fun b() // examples.classes/AC.b|b(){}[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|a(){}[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|(kotlin.Any){}[0] + final fun m() // examples.classes/C.m|m(){}[0] + final val v // examples.classes/C.v|{}v[0] + final fun (): kotlin/Any // examples.classes/C.v.|(){}[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|(kotlin.Int){}[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|component1(){}[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // examples.classes/D.toString|toString(){}[0] + final val x // examples.classes/D.x|{}x[0] + final fun (): kotlin/Int // examples.classes/D.x.|(){}[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|(){}[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|(){}[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|(){}[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|values#static(){}[0] + final val entries // examples.classes/Outer.Nested.NE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|#static(){}[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|(){}[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|(){}[0] + } + } +} +final const val examples.classes/con // examples.classes/con|{}con[0] + final fun (): kotlin/String // examples.classes/con.|(){}[0] +final const val examples.classes/intCon // examples.classes/intCon|{}intCon[0] + final fun (): kotlin/Int // examples.classes/intCon.|(){}[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/E.values|values#static(){}[0] + final val entries // examples.classes/E.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|#static(){}[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/EE.values|values#static(){}[0] + final val entries // examples.classes/EE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|#static(){}[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|consume(0:0){0§}[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|testFun(){}[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|testInlineFun(){}[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|{}a[0] + final fun (): kotlin/Any // examples.classes/a.|(){}[0] +final val examples.classes/i // examples.classes/i|{}i[0] + final fun (): kotlin/Int // examples.classes/i.|(){}[0] +final val examples.classes/l // examples.classes/l|{}l[0] + final fun (): kotlin/Long // examples.classes/l.|(){}[0] +final var examples.classes/d // examples.classes/d|{}d[0] + final fun (): kotlin/Double // examples.classes/d.|(){}[0] + final fun (kotlin/Double) // examples.classes/d.|(kotlin.Double){}[0] +final var examples.classes/r // examples.classes/r|{}r[0] + final fun (): kotlin/Float // examples.classes/r.|(){}[0] + final fun (kotlin/Float) // examples.classes/r.|(kotlin.Float){}[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|(){}[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|(){}[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|(){}[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|(kotlin.Int){}[0] + constructor (kotlin/Long) // examples.classes/OC.|(kotlin.Long){}[0] + constructor (kotlin/String) // examples.classes/OC.|(kotlin.String){}[0] + final fun c() // examples.classes/OC.c|c(){}[0] + final val ix // examples.classes/OC.ix|{}ix[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|(){}[0] + final val iy // examples.classes/OC.iy|{}iy[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|(){}[0] + final val iz // examples.classes/OC.iz|{}iz[0] + final fun (): kotlin/String // examples.classes/OC.iz.|(){}[0] + final var x // examples.classes/OC.x|{}x[0] + final fun (): kotlin/Int // examples.classes/OC.x.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.x.|(kotlin.Int){}[0] + final var y // examples.classes/OC.y|{}y[0] + final fun (): kotlin/Int // examples.classes/OC.y.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.y.|(kotlin.Int){}[0] + final var z // examples.classes/OC.z|{}z[0] + final fun (): kotlin/Int // examples.classes/OC.z.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.z.|(kotlin.Int){}[0] + open fun o(): kotlin/Int // examples.classes/OC.o|o(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.linux.dump b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.linux.dump new file mode 100644 index 0000000..fdc5d28 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.klib.with.linux.dump @@ -0,0 +1,128 @@ +// Klib ABI Dump +// Targets: [androidNativeArm32, androidNativeArm64, androidNativeX64, androidNativeX86, linuxArm64, linuxX64, mingwX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +abstract class examples.classes/AC { // examples.classes/AC|null[0] + abstract fun a() // examples.classes/AC.a|a(){}[0] + constructor () // examples.classes/AC.|(){}[0] + final fun b() // examples.classes/AC.b|b(){}[0] +} +abstract fun interface examples.classes/FI { // examples.classes/FI|null[0] + abstract fun a() // examples.classes/FI.a|a(){}[0] +} +abstract interface examples.classes/I // examples.classes/I|null[0] +abstract interface examples.classes/II // examples.classes/II|null[0] +final class examples.classes/C { // examples.classes/C|null[0] + constructor (kotlin/Any) // examples.classes/C.|(kotlin.Any){}[0] + final fun m() // examples.classes/C.m|m(){}[0] + final val v // examples.classes/C.v|{}v[0] + final fun (): kotlin/Any // examples.classes/C.v.|(){}[0] +} +final class examples.classes/D { // examples.classes/D|null[0] + constructor (kotlin/Int) // examples.classes/D.|(kotlin.Int){}[0] + final fun component1(): kotlin/Int // examples.classes/D.component1|component1(){}[0] + final fun copy(kotlin/Int =...): examples.classes/D // examples.classes/D.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // examples.classes/D.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // examples.classes/D.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // examples.classes/D.toString|toString(){}[0] + final val x // examples.classes/D.x|{}x[0] + final fun (): kotlin/Int // examples.classes/D.x.|(){}[0] +} +final class examples.classes/IC : examples.classes/II { // examples.classes/IC|null[0] + constructor () // examples.classes/IC.|(){}[0] +} +final class examples.classes/Outer { // examples.classes/Outer|null[0] + constructor () // examples.classes/Outer.|(){}[0] + final class Nested { // examples.classes/Outer.Nested|null[0] + constructor () // examples.classes/Outer.Nested.|(){}[0] + final enum class NE : kotlin/Enum { // examples.classes/Outer.Nested.NE|null[0] + enum entry A // examples.classes/Outer.Nested.NE.A|null[0] + enum entry B // examples.classes/Outer.Nested.NE.B|null[0] + enum entry C // examples.classes/Outer.Nested.NE.C|null[0] + final fun valueOf(kotlin/String): examples.classes/Outer.Nested.NE // examples.classes/Outer.Nested.NE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/Outer.Nested.NE.values|values#static(){}[0] + final val entries // examples.classes/Outer.Nested.NE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/Outer.Nested.NE.entries.|#static(){}[0] + } + final inner class Inner { // examples.classes/Outer.Nested.Inner|null[0] + constructor () // examples.classes/Outer.Nested.Inner.|(){}[0] + } + final inner class YetAnotherInner { // examples.classes/Outer.Nested.YetAnotherInner|null[0] + constructor () // examples.classes/Outer.Nested.YetAnotherInner.|(){}[0] + } + } +} +final const val examples.classes/con // examples.classes/con|{}con[0] + final fun (): kotlin/String // examples.classes/con.|(){}[0] +final const val examples.classes/intCon // examples.classes/intCon|{}intCon[0] + final fun (): kotlin/Int // examples.classes/intCon.|(){}[0] +final enum class examples.classes/E : kotlin/Enum { // examples.classes/E|null[0] + enum entry A // examples.classes/E.A|null[0] + enum entry B // examples.classes/E.B|null[0] + enum entry C // examples.classes/E.C|null[0] + final fun valueOf(kotlin/String): examples.classes/E // examples.classes/E.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/E.values|values#static(){}[0] + final val entries // examples.classes/E.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/E.entries.|#static(){}[0] +} +final enum class examples.classes/EE : kotlin/Enum { // examples.classes/EE|null[0] + enum entry AA // examples.classes/EE.AA|null[0] + enum entry BB // examples.classes/EE.BB|null[0] + enum entry CC // examples.classes/EE.CC|null[0] + final fun valueOf(kotlin/String): examples.classes/EE // examples.classes/EE.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // examples.classes/EE.values|values#static(){}[0] + final val entries // examples.classes/EE.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // examples.classes/EE.entries.|#static(){}[0] +} +final fun <#A: kotlin/Any?> examples.classes/consume(#A) // examples.classes/consume|consume(0:0){0§}[0] +final fun examples.classes/testFun(): kotlin/Int // examples.classes/testFun|testFun(){}[0] +final inline fun examples.classes/testInlineFun() // examples.classes/testInlineFun|testInlineFun(){}[0] +final object examples.classes/O // examples.classes/O|null[0] +final object examples.classes/OO // examples.classes/OO|null[0] +final val examples.classes/a // examples.classes/a|{}a[0] + final fun (): kotlin/Any // examples.classes/a.|(){}[0] +final val examples.classes/i // examples.classes/i|{}i[0] + final fun (): kotlin/Int // examples.classes/i.|(){}[0] +final val examples.classes/l // examples.classes/l|{}l[0] + final fun (): kotlin/Long // examples.classes/l.|(){}[0] +final var examples.classes/d // examples.classes/d|{}d[0] + final fun (): kotlin/Double // examples.classes/d.|(){}[0] + final fun (kotlin/Double) // examples.classes/d.|(kotlin.Double){}[0] +final var examples.classes/r // examples.classes/r|{}r[0] + final fun (): kotlin/Float // examples.classes/r.|(){}[0] + final fun (kotlin/Float) // examples.classes/r.|(kotlin.Float){}[0] +open annotation class examples.classes/A : kotlin/Annotation { // examples.classes/A|null[0] + constructor () // examples.classes/A.|(){}[0] +} +open annotation class examples.classes/AA : kotlin/Annotation { // examples.classes/AA|null[0] + constructor () // examples.classes/AA.|(){}[0] +} +open annotation class examples.classes/AAA : kotlin/Annotation { // examples.classes/AAA|null[0] + constructor () // examples.classes/AAA.|(){}[0] +} +open class examples.classes/OC { // examples.classes/OC|null[0] + constructor (kotlin/Int) // examples.classes/OC.|(kotlin.Int){}[0] + constructor (kotlin/Long) // examples.classes/OC.|(kotlin.Long){}[0] + constructor (kotlin/String) // examples.classes/OC.|(kotlin.String){}[0] + final fun c() // examples.classes/OC.c|c(){}[0] + final val ix // examples.classes/OC.ix|{}ix[0] + final fun (): kotlin/Int // examples.classes/OC.ix.|(){}[0] + final val iy // examples.classes/OC.iy|{}iy[0] + final fun (): kotlin/Long // examples.classes/OC.iy.|(){}[0] + final val iz // examples.classes/OC.iz|{}iz[0] + final fun (): kotlin/String // examples.classes/OC.iz.|(){}[0] + final var x // examples.classes/OC.x|{}x[0] + final fun (): kotlin/Int // examples.classes/OC.x.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.x.|(kotlin.Int){}[0] + final var y // examples.classes/OC.y|{}y[0] + final fun (): kotlin/Int // examples.classes/OC.y.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.y.|(kotlin.Int){}[0] + final var z // examples.classes/OC.z|{}z[0] + final fun (): kotlin/Int // examples.classes/OC.z.|(){}[0] + final fun (kotlin/Int) // examples.classes/OC.z.|(kotlin.Int){}[0] + open fun o(): kotlin/Int // examples.classes/OC.o|o(){}[0] +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.kt b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.kt new file mode 100644 index 0000000..4d0cb4b --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/TopLevelDeclarations.kt @@ -0,0 +1,71 @@ +@file:Suppress("unused", "UNUSED_PARAMETER", "RedundantVisibilityModifier", "MayBeConstant") + +package examples.classes + +public fun testFun(): Int = 42 +public fun consume(arg: T) = Unit +public inline fun testInlineFun() = Unit +public const val con: String = "I'm a constant!" +public const val intCon: Int = 42 +public val l: Long = 0xc001 +public val i: Int = 0xc002 +public var r: Float = 3.14f +public var d: Double = 3.14 +public val a = Any() + +public annotation class A +public annotation class AA +public annotation class AAA + +public interface I +public interface II +public fun interface FI { + fun a(): Unit +} + +public data class D(val x: Int) +public class C(public val v: Any) { + public fun m() = Unit +} + +public class IC : II + +public object O +public object OO + +public enum class E { A, B, C } +public enum class EE { AA, BB, CC } + +public abstract class AC { + public abstract fun a() + public fun b() = Unit +} + +public open class OC { + constructor(i: Int) + constructor(l: Long) + constructor(s: String) + + public var x: Int = 1 + public var y: Int = 2 + public var z: Int = 3 + public val ix: Int = 4 + public val iy: Long = 5L + public val iz: String = "" + public open fun o(): Int = 42 + public fun c() = Unit +} + +public class Outer { + public class Nested { + public inner class Inner { + + } + + public inner class YetAnotherInner { + + } + + enum class NE { A, B, C } + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/package-info.java b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/package-info.java new file mode 100644 index 0000000..b15e17d --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/classes/package-info.java @@ -0,0 +1,2 @@ +@PackageAnnotation +package annotated; diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts index dca231e..85851e5 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/androidProjectRoot.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.application").version("7.2.2").apply(false) id("com.android.library").version("7.2.2").apply(false) - id("org.jetbrains.kotlin.android").version("1.7.10").apply(false) + id("org.jetbrains.kotlin.android").version("1.9.24").apply(false) id("dev.adamko.kotlin.binary-compatibility-validator") version "+" apply false } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/enableJvmInWithNativePlugin.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/enableJvmInWithNativePlugin.gradle.kts new file mode 100644 index 0000000..993a178 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/enableJvmInWithNativePlugin.gradle.kts @@ -0,0 +1,3 @@ +kotlin { + jvm() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibility.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibility.gradle.kts new file mode 100644 index 0000000..b672459 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibility.gradle.kts @@ -0,0 +1,20 @@ +plugins { + kotlin("jvm") version "1.9.24" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" +} + +val minJvmTarget = org.jetbrains.kotlin.config.JvmTarget.supportedValues().minBy { it.majorVersion } +val maxJvmTarget = org.jetbrains.kotlin.config.JvmTarget.supportedValues().maxBy { it.majorVersion } + +val useMaxJdkVersion = providers.gradleProperty("useMaxJdkVersion").orNull.toBoolean() +val selectedJvmTarget = (if (useMaxJdkVersion) maxJvmTarget else minJvmTarget).toString() + +val toolchainVersion = selectedJvmTarget.split('.').last().toInt() + +kotlin { + jvmToolchain(toolchainVersion) + + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(selectedJvmTarget)) + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibilityWithExactVersion.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibilityWithExactVersion.gradle.kts new file mode 100644 index 0000000..a231434 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/jdkCompatibilityWithExactVersion.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("jvm") version "1.9.24" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" +} + +val selectedJvmTarget = providers.gradleProperty("jvmTarget").get() +val toolchainVersion = selectedJvmTarget.split('.').last().toInt() + +kotlin { + jvmToolchain(toolchainVersion) + + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget(selectedJvmTarget)) + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts index 895de7b..b60aa54 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/multiplatformWithJvmTargets.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.5.20" + kotlin("multiplatform") version "1.9.24" id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } @@ -12,6 +12,9 @@ kotlin { testRuns["test"].executionTask.configure { useJUnit() } + attributes { + attribute(Attribute.of("variant", String::class.java), "a") + } } jvm("anotherJvm") { compilations.all { @@ -20,11 +23,14 @@ kotlin { testRuns["test"].executionTask.configure { useJUnit() } + attributes { + attribute(Attribute.of("variant", String::class.java), "b") + } } } sourceSets { - val commonMain by getting - val commonTest by getting { + commonMain + commonTest { dependencies { implementation(kotlin("stdlib")) implementation(kotlin("test-common")) 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 8fcf6bf..7598d33 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 @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform") version "1.7.20" + kotlin("multiplatform") version "1.9.24" id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } @@ -18,7 +18,6 @@ kotlin { val commonMain by getting val commonTest by getting { dependencies { - implementation(kotlin("stdlib")) implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) } @@ -26,7 +25,6 @@ kotlin { val jvmMain by getting val jvmTest by getting { dependencies { - implementation(kotlin("stdlib")) implementation(kotlin("test-junit")) } } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePlugin.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePlugin.gradle.kts new file mode 100644 index 0000000..6ec54c5 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePlugin.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("multiplatform") version "1.9.24" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" +} + +kotlin { + linuxX64() + linuxArm64() + mingwX64() + androidNativeArm32() + androidNativeArm64() + androidNativeX64() + androidNativeX86() + + sourceSets { + commonMain {} + commonTest { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +binaryCompatibilityValidator { + klib.enable() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts new file mode 100644 index 0000000..b236ce6 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndNoTargets.gradle.kts @@ -0,0 +1,19 @@ +plugins { + kotlin("multiplatform") version "1.9.24" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" +} + +kotlin { + sourceSets { + commonMain {} + commonTest { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +binaryCompatibilityValidator { + klib.enable() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndSingleTarget.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndSingleTarget.gradle.kts new file mode 100644 index 0000000..c1a77ab --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withNativePluginAndSingleTarget.gradle.kts @@ -0,0 +1,20 @@ +plugins { + kotlin("multiplatform") version "1.9.24" + id("dev.adamko.kotlin.binary-compatibility-validator") version "+" +} + +kotlin { + linuxArm64() + + sourceSets { + commonTest { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +binaryCompatibilityValidator { + klib.enable() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts index 07272f7..e3d7618 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withPlugin.gradle.kts @@ -1,4 +1,4 @@ plugins { - kotlin("jvm") version "1.7.20" + kotlin("jvm") version "1.9.24" id("dev.adamko.kotlin.binary-compatibility-validator") version "+" } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withoutPlugin.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withoutPlugin.gradle.kts index 1ac35f3..599617a 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withoutPlugin.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/base/withoutPlugin.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("jvm") version "1.3.70" + kotlin("jvm") version "1.9.24" } dependencies { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/appleTargets/targets.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/appleTargets/targets.gradle.kts new file mode 100644 index 0000000..0bb199c --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/appleTargets/targets.gradle.kts @@ -0,0 +1,15 @@ +kotlin { + macosX64() + macosArm64() + iosX64() + iosArm64() + iosSimulatorArm64() + tvosX64() + tvosArm64() + tvosSimulatorArm64() + watchosArm32() + watchosArm64() + watchosX64() + watchosSimulatorArm64() + watchosDeviceArm64() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts new file mode 100644 index 0000000..5ca5f72 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts @@ -0,0 +1,23 @@ +val generateSources by tasks.registering { + val outputDir = layout.buildDirectory.dir("generated/kotlin") + + outputs.dir(outputDir).withPropertyName("outputDir") + + doLast { + outputDir.get().asFile.apply { + mkdirs() + resolve("Generated.kt").writeText( + """ + |public class Generated { + | public fun helloCreator(): Int = 42 + |} + | + """.trimMargin() + ) + } + } +} + +kotlin.sourceSets.main { + kotlin.srcDir(generateSources) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts new file mode 100644 index 0000000..e89a3b7 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts @@ -0,0 +1,23 @@ +val generateSources by tasks.registering { + val outputDir = layout.buildDirectory.dir("generated/kotlin") + + outputs.dir(outputDir).withPropertyName("outputDir") + + doLast { + outputDir.get().asFile.apply { + mkdirs() + resolve("Generated.kt").writeText( + """ + |public class Generated { + | public fun helloCreator(): Int = 42 + |} + | + """.trimMargin() + ) + } + } +} + +kotlin.sourceSets.getByName("commonMain") { + kotlin.srcDir(generateSources) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/clashingTargetNames.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/clashingTargetNames.gradle.kts new file mode 100644 index 0000000..026df98 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/clashingTargetNames.gradle.kts @@ -0,0 +1,9 @@ +kotlin { + linuxX64("linux") + linuxArm64() + mingwX64() + androidNativeArm32() + androidNativeArm64() + androidNativeX64() + androidNativeX86() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/customTargetNames.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/customTargetNames.gradle.kts new file mode 100644 index 0000000..ae59ace --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/grouping/customTargetNames.gradle.kts @@ -0,0 +1,12 @@ +kotlin { + linuxX64("linuxA") { + attributes { + attribute(Attribute.of("variant", String::class.java), "a") + } + } + linuxX64("linuxB") { + attributes { + attribute(Attribute.of("variant", String::class.java), "b") + } + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoreSubclasses/ignore.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoreSubclasses/ignore.gradle.kts new file mode 100644 index 0000000..508e5b9 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoreSubclasses/ignore.gradle.kts @@ -0,0 +1,4 @@ +binaryCompatibilityValidator { + // ignoredClasses.add("subclasses.A.B") + ignoredClasses.add("subclasses.A\$B") +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts index abda226..72d4c12 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredClasses/oneValidFullyQualifiedClass.gradle.kts @@ -1,3 +1,3 @@ -configure { +binaryCompatibilityValidator { ignoredClasses.add("com.company.BuildConfig") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredPackages/oneValidPackage.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredPackages/oneValidPackage.gradle.kts index 6292368..eae0cd1 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredPackages/oneValidPackage.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/ignoredPackages/oneValidPackage.gradle.kts @@ -1,3 +1,3 @@ -configure { +binaryCompatibilityValidator { ignoredPackages.add("com.company") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/jarAsInput/inputJar.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/jarAsInput/inputJar.gradle.kts index d894a22..a75c78d 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/jarAsInput/inputJar.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/jarAsInput/inputJar.gradle.kts @@ -1,6 +1,6 @@ tasks.jar { exclude("foo/HiddenField.class") - exclude("**/HiddenProperty.class") + exclude("foo/HiddenProperty.class") } binaryCompatibilityValidator { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonNativeKlibTargets/targets.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonNativeKlibTargets/targets.gradle.kts new file mode 100644 index 0000000..71931fa --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonNativeKlibTargets/targets.gradle.kts @@ -0,0 +1,5 @@ +kotlin { + wasmWasi() + wasmJs() + js() +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/klib.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/klib.gradle.kts new file mode 100644 index 0000000..8db64dd --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/klib.gradle.kts @@ -0,0 +1,8 @@ +binaryCompatibilityValidator { + nonPublicMarkers.add("annotations.HiddenClass") + nonPublicMarkers.add("annotations.HiddenCtor") + nonPublicMarkers.add("annotations.HiddenProperty") + nonPublicMarkers.add("annotations.HiddenGetter") + nonPublicMarkers.add("annotations.HiddenSetter") + nonPublicMarkers.add("annotations.HiddenFunction") +} 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 77e468f..2368b7f 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,4 +1,4 @@ -configure { +binaryCompatibilityValidator { ignoredMarkers.add("foo.HiddenField") ignoredMarkers.add("foo.HiddenProperty") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts new file mode 100644 index 0000000..10461af --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/nonPublicMarkers/packages.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + ignoredMarkers.add("annotated.PackageAnnotation") +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/different.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/different.gradle.kts new file mode 100644 index 0000000..169a7ce --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/different.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + outputApiDir.set(layout.projectDirectory.dir("custom")) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/outer.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/outer.gradle.kts new file mode 100644 index 0000000..fdc31df --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/outer.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + outputApiDir.set(layout.projectDirectory.dir("../api")) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts new file mode 100644 index 0000000..fd2d71a --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/outputDirectory/subdirectory.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + outputApiDir.set(layout.projectDirectory.dir("validation/api")) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/markers.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/markers.gradle.kts index 58c67f2..d2ecd7a 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/markers.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/markers.gradle.kts @@ -1,4 +1,4 @@ -configure { +binaryCompatibilityValidator { publicMarkers.add("foo.PublicClass") publicMarkers.add("foo.PublicField") publicMarkers.add("foo.PublicProperty") diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/mixedMarkers.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/mixedMarkers.gradle.kts index 61e6534..bd93777 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/mixedMarkers.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/mixedMarkers.gradle.kts @@ -1,4 +1,4 @@ -configure { +binaryCompatibilityValidator { ignoredMarkers.add("mixed.PrivateApi") publicMarkers.add("mixed.PublicApi") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts new file mode 100644 index 0000000..8142280 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/publicMarkers/packages.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + publicMarkers.add("annotated.PackageAnnotation") +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/invalid.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/invalid.gradle.kts new file mode 100644 index 0000000..1024b7d --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/invalid.gradle.kts @@ -0,0 +1,5 @@ +binaryCompatibilityValidator { + klib { + signatureVersion(100500) + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/v1.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/v1.gradle.kts new file mode 100644 index 0000000..e8c381a --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/signatures/v1.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + klib.signatureVersion(1) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/unsupported/enforce.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/unsupported/enforce.gradle.kts new file mode 100644 index 0000000..38834ca --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/configuration/unsupported/enforce.gradle.kts @@ -0,0 +1,3 @@ +binaryCompatibilityValidator { + klib.strictValidation.set(true) +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/jdk-provisioning.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/jdk-provisioning.gradle.kts new file mode 100644 index 0000000..1791349 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/jdk-provisioning.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-android-project.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-android-project.gradle.kts index 4b53276..72aad02 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-android-project.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-android-project.gradle.kts @@ -1,9 +1,8 @@ pluginManagement { repositories { - mavenLocal() + mavenCentral() gradlePluginPortal() google() - mavenCentral() } } @@ -16,5 +15,6 @@ dependencyResolutionManagement { } rootProject.name = "android-project" + include(":kotlin-library") include(":java-library") diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-name-testproject.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-name-testproject.gradle.kts index aa60750..6b8818c 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-name-testproject.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-name-testproject.gradle.kts @@ -1 +1 @@ -rootProject.name = "testproject" \ No newline at end of file +rootProject.name = "testproject" diff --git a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-with-hierarchy.gradle.kts b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-with-hierarchy.gradle.kts index b8c087f..d28e187 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-with-hierarchy.gradle.kts +++ b/modules/bcv-gradle-plugin-functional-tests/src/functionalTest/resources/examples/gradle/settings/settings-with-hierarchy.gradle.kts @@ -1,4 +1,4 @@ -include("sub1") -include("sub1:subsub1") -include("sub1:subsub2") -include("sub2") +include(":sub1") +include(":sub1:subsub1") +include(":sub1:subsub2") +include(":sub2") 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 1085709..7407011 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 @@ -163,7 +163,7 @@ fun gradleGroovyProjectTest( gradleProperties = """ |kotlin.mpp.stability.nowarn=true - |org.gradle.cache=true + | """.trimMargin() build() @@ -176,7 +176,7 @@ internal fun devMavenRepoKotlinDsl(): String { |exclusiveContent { | forRepository { | maven(file("$devMavenRepoPathString")) { - | name = "Dev Maven Repo" + | name = "DevMavenRepo" | } | } | filter { @@ -196,7 +196,7 @@ private fun devMavenRepoGroovyDsl(): String { | forRepository { | maven { | url = file("$devMavenRepoPathString") - | name = "Dev Maven Repo" + | name = "DevMavenRepo" | } | } | filter { diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/TempDirFactory.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/TempDirFactory.kt new file mode 100644 index 0000000..adf03f0 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/TempDirFactory.kt @@ -0,0 +1,52 @@ +package dev.adamko.kotlin.binary_compatibility_validator.test.utils + +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteRecursively +import kotlin.jvm.optionals.getOrNull +import org.junit.jupiter.api.extension.AnnotatedElementContext +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.io.TempDirFactory + +/** + * Create temp dir with a stable name based on the current test name. + * + * @see TempDirFactory.Standard + */ +class TempTestNameDirFactory : TempDirFactory { + + @OptIn(ExperimentalPathApi::class) + override fun createTempDirectory( + elementContext: AnnotatedElementContext, + extensionContext: ExtensionContext, + ): Path { + + // convert `${TestClass.fqn}.${testDisplayName}` + // to file path, e.g. `f/q/n/TestClass/test-display-name` + val path = listOfNotNull( + extensionContext.testClass.getOrNull()?.canonicalName, + extensionContext.displayName, + ).joinToString("/") { segment -> + segment + .split(".") + .joinToString(separator = "/") { + it + .map { c -> if (c.isLetterOrDigit()) c else '-' } + .dropLastWhile { c -> !c.isLetterOrDigit() } + .dropWhile { c -> !c.isLetterOrDigit() } + .joinToString("") + } + } + + val dir = testTempDir.resolve(path) + dir.deleteRecursively() + dir.createDirectories() + return dir + } + + companion object { + private val testTempDir: Path by systemProperty(::Path) + } +} diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/BaseKotlinGradleTest.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/BaseKotlinGradleTest.kt index d161d06..1e311a6 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/BaseKotlinGradleTest.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/BaseKotlinGradleTest.kt @@ -1,14 +1,22 @@ package dev.adamko.kotlin.binary_compatibility_validator.test.utils.api +import dev.adamko.kotlin.binary_compatibility_validator.test.utils.TempTestNameDirFactory import java.io.File import org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS import org.junit.jupiter.api.io.TempDir -open class BaseKotlinGradleTest { - @TempDir(cleanup = ON_SUCCESS) - lateinit var testProjectDir: File +abstract class BaseKotlinGradleTest { + @TempDir( + factory = TempTestNameDirFactory::class, + cleanup = ON_SUCCESS, + ) + lateinit var testTempDir: File - val rootProjectDir: File get() = testProjectDir + val rootProjectDir: File get() = testTempDir.resolve("bcv-test-project") - val rootProjectApiDump: File get() = rootProjectDir.resolve("api/${rootProjectDir.name}.api") + val rootProjectApiDump: File get() = rootProjectDir.resolve("$API_DIR/${rootProjectDir.name}.api") + + fun rootProjectAbiDump( + project: String = rootProjectDir.name + ): File = rootProjectDir.resolve("$API_DIR/$project.klib.api") } diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt index d7a5aff..b3ef375 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/api/TestDsl.kt @@ -37,6 +37,13 @@ fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner { fn(baseKotlinScope) + baseKotlinScope.gradleProperties { + addLine("org.gradle.jvmargs=" + baseKotlinScope.runner.gradleJvmArgs.joinToString(" ")) + baseKotlinScope.runner.gradleProperties.forEach { (k, v) -> + addLine("$k=$v") + } + } + baseKotlinScope.files.forEach { scope -> val fileWriteTo = rootProjectDir.resolve(scope.filePath) .apply { @@ -55,8 +62,8 @@ fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner { return GradleRunner.create() .withProjectDir(rootProjectDir) - .withGradleVersion(minimumGradleTestVersion) - .withArguments(baseKotlinScope.runner.arguments) + .withGradleVersion(baseKotlinScope.runner.gradleVersion) + .withArguments(baseKotlinScope.runner.buildArgs()) .apply { GradleProjectTest.gradleTestKitDir?.let { println("Using Gradle TestKit dir $it") @@ -66,7 +73,7 @@ fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner { } /** - * same as [file][FileContainer.file], but prepends "src/${sourceSet}/kotlin" before given `classFileName` + * same as [file][FileContainer.file], but prepends `src/${sourceSet}/kotlin` before given `classFileName` */ fun FileContainer.kotlin( classFileName: String, @@ -82,7 +89,7 @@ fun FileContainer.kotlin( } /** - * same as [file][FileContainer.file], but prepends "src/${sourceSet}/java" before given `classFileName` + * same as [file][FileContainer.file], but prepends `src/${sourceSet}/java` before given `classFileName` */ fun FileContainer.java( classFileName: String, @@ -113,6 +120,14 @@ fun FileContainer.settingsGradleKts(fn: AppendableScope.() -> Unit) { file(fileName, fn) } +/** + * Shortcut for creating a `gradle.properties` by using [file][FileContainer.file] + */ +fun FileContainer.gradleProperties(fn: AppendableScope.() -> Unit) { + val fileName = "gradle.properties" + file(fileName, fn) +} + /** * Declares a directory with the given [dirName] inside the current container. * All calls creating files within this scope will create the files nested in this directory. @@ -133,6 +148,23 @@ fun FileContainer.apiFile(projectName: String, fn: AppendableScope.() -> Unit) { } } +/** + * Shortcut for creating a `api//.klib.api` descriptor using [file][FileContainer.file] + */ +fun FileContainer.abiFile(projectName: String, target: String, fn: AppendableScope.() -> Unit) { + dir(API_DIR) { + dir(target) { + file("$projectName.klib.api", fn) + } + } +} + +fun FileContainer.abiFile(projectName: String, fn: AppendableScope.() -> Unit) { + dir(API_DIR) { + file("$projectName.klib.api", fn) + } +} + // not using default argument in apiFile for clarity in tests (explicit "empty" in the name) /** * Shortcut for creating an empty `api/.api` descriptor by using [file][FileContainer.file] @@ -142,10 +174,7 @@ fun FileContainer.emptyApiFile(projectName: String) { } fun BaseKotlinScope.runner(fn: Runner.() -> Unit) { - val runner = Runner() fn(runner) - - this.runner = runner } fun AppendableScope.resolve(@Language("file-reference") path: String) { @@ -156,11 +185,14 @@ fun AppendableScope.addText(text: String) { this.content.add(AppendableScope.AppendText(text)) } +fun AppendableScope.addLine(text: String): Unit = addText("$text\n") + interface FileContainer { - fun file(fileName: String, fn: AppendableScope.() -> Unit) + fun file(fileName: String, fn: AppendableScope.() -> Unit = {}) } class BaseKotlinScope : FileContainer { + var files: MutableList = mutableListOf() var runner: Runner = Runner() @@ -192,13 +224,37 @@ class AppendableScope(val filePath: String) { } class Runner { - val arguments: MutableList = mutableListOf( - "--configuration-cache", - "--info", - "--stacktrace", + var gradleVersion: String = minimumGradleTestVersion + /** JVM args used by Gradle. Set as `org.gradle.jvmargs` in `gradle.properties`. */ + val gradleJvmArgs: MutableSet = mutableSetOf( + "-Dfile.encoding=UTF-8", ) + val gradleProperties: MutableMap = mutableMapOf( + "org.gradle.welcome" to "never", + ) + var configurationCache: Boolean = true + var rerunTasks: Boolean = false + var rerunTask: Boolean = false + var buildCache: Boolean = true + var stacktrace: Boolean = true + var continues: Boolean = true + var parallel: Boolean = true + + val arguments: MutableList = mutableListOf() + + internal fun buildArgs(): List = buildList { + addAll(arguments) + if (configurationCache) add("--configuration-cache") else add("--no-configuration-cache") + if (buildCache) add("--build-cache") else add("--no-build-cache") + if (rerunTasks) add("--rerun-tasks") + if (rerunTask) add("--rerun-task") + if (stacktrace) add("--stacktrace") + if (continues) add("--continue") + if (parallel) add("--parallel") + }.distinct() } +/** Read a resources file as a [String]. */ fun readResourceFile(@Language("file-reference") path: String): String { val resource = BaseKotlinGradleTest::class.java.getResource(path) ?: error("Could not find resource '$path'") diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/gradleRunnerUtils.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/gradleRunnerUtils.kt index 59e57a2..ed574bb 100644 --- a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/gradleRunnerUtils.kt +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/gradleRunnerUtils.kt @@ -8,7 +8,7 @@ fun GradleRunner.withEnvironment(vararg map: Pair): GradleRunner withEnvironment(map.toMap()) -inline fun GradleRunner.build(block: BuildResult.() -> Unit) = build().block() -inline fun GradleRunner.buildAndFail(block: BuildResult.() -> Unit) = buildAndFail().block() +inline fun GradleRunner.build(block: BuildResult.() -> Unit): Unit = build().block() +inline fun GradleRunner.buildAndFail(block: BuildResult.() -> Unit): Unit = buildAndFail().block() @Suppress("UnstableApiUsage") -inline fun GradleRunner.run(block: BuildResult.() -> Unit) = run().block() +inline fun GradleRunner.run(block: BuildResult.() -> Unit): Unit = run().block() diff --git a/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/systemProperties.kt b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/systemProperties.kt new file mode 100644 index 0000000..acb2150 --- /dev/null +++ b/modules/bcv-gradle-plugin-functional-tests/src/testFixtures/kotlin/systemProperties.kt @@ -0,0 +1,14 @@ +package dev.adamko.kotlin.binary_compatibility_validator.test.utils + +import kotlin.properties.ReadOnlyProperty + + +internal fun systemProperty( + convert: (String) -> T +): ReadOnlyProperty = + ReadOnlyProperty { _, property -> + val value = requireNotNull(System.getProperty(property.name)) { + "system property ${property.name} is unavailable" + } + convert(value) + } diff --git a/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api b/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api index 8b660f6..96696be 100644 --- a/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api +++ b/modules/bcv-gradle-plugin/api/bcv-gradle-plugin.api @@ -1,3 +1,8 @@ +public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVKLibConventions : dev/adamko/kotlin/binary_compatibility_validator/targets/KLibValidationSpec { + public fun ()V + public abstract fun getStrictValidation ()Lorg/gradle/api/provider/Property; +} + public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVPlugin : org/gradle/api/Plugin { public static final field API_CHECK_TASK_NAME Ljava/lang/String; public static final field API_DIR Ljava/lang/String; @@ -5,6 +10,7 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVPlugin public static final field API_GENERATE_TASK_NAME Ljava/lang/String; public static final field Companion Ldev/adamko/kotlin/binary_compatibility_validator/BCVPlugin$Companion; public static final field EXTENSION_NAME Ljava/lang/String; + public static final field PREPARE_API_GENERATE_TASK_NAME Ljava/lang/String; public static final field RUNTIME_CLASSPATH_CONFIGURATION_NAME Ljava/lang/String; public static final field RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME Ljava/lang/String; public static final field TASK_GROUP Ljava/lang/String; @@ -15,11 +21,12 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVPlugin public final class dev/adamko/kotlin/binary_compatibility_validator/BCVPlugin$Companion { } -public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjectExtension : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetSpec, org/gradle/api/plugins/ExtensionAware { +public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjectExtension : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetBaseSpec, org/gradle/api/plugins/ExtensionAware { public abstract fun getEnabled ()Lorg/gradle/api/provider/Property; public abstract fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getKotlinCompilerEmbeddableVersion ()Lorg/gradle/api/provider/Property; public abstract fun getKotlinxBinaryCompatibilityValidatorVersion ()Lorg/gradle/api/provider/Property; public abstract fun getNonPublicMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getOutputApiDir ()Lorg/gradle/api/file/DirectoryProperty; @@ -27,7 +34,7 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjec public abstract fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; - public final fun getTargets ()Lorg/gradle/api/NamedDomainObjectContainer; + public final fun getTargets ()Lorg/gradle/api/ExtensiblePolymorphicDomainObjectContainer; } public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVProjectPlugin : org/gradle/api/Plugin { @@ -45,9 +52,8 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVSettin } public abstract class dev/adamko/kotlin/binary_compatibility_validator/BCVSettingsPlugin$Extension { - public fun (Ldev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetSpec;)V - public final fun defaultTargetValues (Lkotlin/jvm/functions/Function1;)V - public final fun getDefaultTargetValues ()Ldev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetSpec; + public final fun defaultTargetValues (Lorg/gradle/api/Action;)V + public final fun getDefaultTargetValues ()Ldev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetDefaults; public abstract fun getIgnoredProjects ()Lorg/gradle/api/provider/SetProperty; } @@ -56,28 +62,76 @@ public final class dev/adamko/kotlin/binary_compatibility_validator/BCVSettingsP public final synthetic fun execute (Ljava/lang/Object;)V } -public abstract class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetSpec, java/io/Serializable, org/gradle/api/Named { - public fun (Ljava/lang/String;)V +public final class dev/adamko/kotlin/binary_compatibility_validator/adapters/AndroidAdapterKt$createKotlinAndroidTargets$1$1$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { + public fun (Lkotlin/jvm/functions/Function1;)V + public final synthetic fun execute (Ljava/lang/Object;)V +} + +public final class dev/adamko/kotlin/binary_compatibility_validator/adapters/JavaTestFixturesKt$createJavaTestFixtureTargets$1$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { + public fun (Lkotlin/jvm/functions/Function1;)V + public final synthetic fun execute (Ljava/lang/Object;)V +} + +public final class dev/adamko/kotlin/binary_compatibility_validator/adapters/KotlinJvmAdapterKt$createKotlinJvmTargets$1$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { + public fun (Lkotlin/jvm/functions/Function1;)V + public final synthetic fun execute (Ljava/lang/Object;)V +} + +public final class dev/adamko/kotlin/binary_compatibility_validator/adapters/KotlinMultiplatformAdapterKt$inlined$sam$i$org_gradle_api_Action$0 : org/gradle/api/Action { + public fun (Lkotlin/jvm/functions/Function1;)V + public final synthetic fun execute (Ljava/lang/Object;)V +} + +public abstract interface annotation class dev/adamko/kotlin/binary_compatibility_validator/internal/BCVExperimentalApi : java/lang/annotation/Annotation { +} + +public abstract class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVJvmTarget : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget, org/gradle/api/Named { + public abstract fun getInputClasses ()Lorg/gradle/api/file/ConfigurableFileCollection; + public abstract fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; + public final fun getPlatformType ()Ljava/lang/String; +} + +public abstract class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVKLibTarget : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget, dev/adamko/kotlin/binary_compatibility_validator/targets/KLibValidationSpec, java/io/Serializable, org/gradle/api/Named { + public abstract fun getCompilationDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection; + public abstract fun getCurrentPlatform ()Lorg/gradle/api/provider/Property; + public abstract fun getHasKotlinSources ()Lorg/gradle/api/provider/Property; + public abstract fun getKlibFile ()Lorg/gradle/api/file/ConfigurableFileCollection; + public final fun getTargetName ()Ljava/lang/String; +} + +public abstract class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetBaseSpec, java/io/Serializable, java/lang/Comparable, org/gradle/api/Named, org/gradle/api/plugins/ExtensionAware { + public static final field Companion Ldev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget$Companion; + public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun compareTo (Ldev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget;)I + public synthetic fun compareTo (Ljava/lang/Object;)I public abstract fun getEnabled ()Lorg/gradle/api/provider/Property; public abstract fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; - public abstract fun getInputClasses ()Lorg/gradle/api/file/ConfigurableFileCollection; - public abstract fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; public fun getName ()Ljava/lang/String; - public final fun getPlatformType ()Ljava/lang/String; public abstract fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; } -public abstract interface class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetSpec : java/io/Serializable { +public final class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTarget$Companion { +} + +public abstract interface class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetBaseSpec : java/io/Serializable { + public abstract fun getEnabled ()Lorg/gradle/api/provider/Property; + public abstract fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getIgnoredMarkers ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; +} + +public abstract class dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetDefaults : dev/adamko/kotlin/binary_compatibility_validator/targets/BCVTargetBaseSpec, java/io/Serializable, org/gradle/api/plugins/ExtensionAware { public abstract fun getEnabled ()Lorg/gradle/api/provider/Property; public abstract fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; - public abstract fun getInputClasses ()Lorg/gradle/api/file/ConfigurableFileCollection; - public abstract fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; public abstract fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; public abstract fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; @@ -87,6 +141,7 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/tasks/BCV public abstract fun getApiBuildDir ()Lorg/gradle/api/file/DirectoryProperty; public abstract fun getExpectedApiDirPath ()Lorg/gradle/api/provider/Property; public final fun getProjectApiDir ()Lorg/gradle/api/provider/Provider; + public final fun getTargets ()Lorg/gradle/api/NamedDomainObjectContainer; public final fun verify ()V } @@ -96,12 +151,20 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/tasks/BCV public abstract fun getApiDumpFiles ()Lorg/gradle/api/file/ConfigurableFileCollection; } +public abstract class dev/adamko/kotlin/binary_compatibility_validator/tasks/BCVApiGeneratePreparationTask : dev/adamko/kotlin/binary_compatibility_validator/tasks/BCVDefaultTask { + public final fun action ()V + public abstract fun getApiDirectory ()Lorg/gradle/api/file/DirectoryProperty; + public abstract fun getApiDumpFiles ()Lorg/gradle/api/file/ConfigurableFileCollection; +} + public abstract class dev/adamko/kotlin/binary_compatibility_validator/tasks/BCVApiGenerateTask : dev/adamko/kotlin/binary_compatibility_validator/tasks/BCVDefaultTask { public final fun generate ()V public abstract fun getInputDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getOutputApiBuildDir ()Lorg/gradle/api/file/DirectoryProperty; + public abstract fun getProjectApiDumpDir ()Lorg/gradle/api/file/DirectoryProperty; public abstract fun getProjectName ()Lorg/gradle/api/provider/Property; public abstract fun getRuntimeClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection; + public abstract fun getStrictKLibTargetValidation ()Lorg/gradle/api/provider/Property; public final fun getTargets ()Lorg/gradle/api/NamedDomainObjectContainer; } @@ -109,7 +172,11 @@ public abstract class dev/adamko/kotlin/binary_compatibility_validator/tasks/BCV public abstract fun getBcvEnabled ()Lorg/gradle/api/provider/Property; } -public final class dev/adamko/kotlin/binary_compatibility_validator/workers/BCVSignaturesWorker$Companion { +public final class dev/adamko/kotlin/binary_compatibility_validator/workers/JvmSignaturesWorker$Companion { +} + +public final class org/gradle/kotlin/dsl/AssignKt { + public static final fun assign (Lorg/gradle/api/provider/Property;I)V } public final class org/gradle/kotlin/dsl/BcvGradleDslAccessorsKt { diff --git a/modules/bcv-gradle-plugin/build.gradle.kts b/modules/bcv-gradle-plugin/build.gradle.kts index 557ff59..d008798 100644 --- a/modules/bcv-gradle-plugin/build.gradle.kts +++ b/modules/bcv-gradle-plugin/build.gradle.kts @@ -8,8 +8,6 @@ plugins { buildsrc.conventions.`maven-publishing` id("dev.adamko.dev-publish") `java-test-fixtures` - //com.github.johnrengelman.shadow - //buildsrc.conventions.`gradle-plugin-variants` dev.adamko.kotlin.`binary-compatibility-validator` } @@ -17,8 +15,9 @@ dependencies { implementation(libs.javaDiffUtils) compileOnly(libs.kotlinx.bcv) -// compileOnly(libs.kotlin.gradlePlugin) - compileOnly(libs.kotlin.gradlePluginApi) + + compileOnly(libs.kotlin.gradlePlugin) +// compileOnly(libs.kotlin.gradlePluginApi) testFixturesApi(gradleTestKit()) @@ -85,13 +84,6 @@ configurations 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("") -//} - tasks.withType>().configureEach { compilerOptions { freeCompilerArgs.addAll( @@ -101,7 +93,10 @@ tasks.withType>().configureEach { } binaryCompatibilityValidator { - ignoredMarkers.add("dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi") + ignoredMarkers.addAll( + "dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi", + "dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi", + ) } @@ -130,3 +125,38 @@ publishing { } } } + +val createBCVProperties by tasks.registering { + val bcvVersion = libs.versions.kotlinx.bcv + inputs.property("bcvVersion", bcvVersion) + val kotlinVersion = libs.versions.kotlinGradle + inputs.property("kotlinVersion", kotlinVersion) + + val generatedSource = layout.buildDirectory.dir("generated-src/main/kotlin/") + outputs.dir(generatedSource) + .withPropertyName("generatedSource") + + doLast { + val bcvMuBuildPropertiesFile = generatedSource.get() + .file("dev/adamko/kotlin/binary_compatibility_validator/internal/BCVProperties.kt") + + bcvMuBuildPropertiesFile.asFile.apply { + parentFile.mkdirs() + writeText( + """ + |package dev.adamko.kotlin.binary_compatibility_validator.internal + | + |internal object BCVProperties { + | const val bcvVersion: String = "${bcvVersion.get()}" + | const val kotlinVersion: String = "${kotlinVersion.get()}" + |} + | + """.trimMargin() + ) + } + } +} + +kotlin.sourceSets.main { + kotlin.srcDir(createBCVProperties) +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVKLibConventions.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVKLibConventions.kt new file mode 100644 index 0000000..5d10aa8 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVKLibConventions.kt @@ -0,0 +1,17 @@ +package dev.adamko.kotlin.binary_compatibility_validator + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.targets.KLibValidationSpec +import org.gradle.api.provider.Property + +@OptIn(BCVExperimentalApi::class) +abstract class BCVKLibConventions : KLibValidationSpec { + + /** + * Fail validation if some build targets are not supported by the host compiler. + * + * By default, ABI dumped only for supported files will be validated. This option makes + * validation behavior stricter and treats having unsupported targets as an error. + */ + abstract val strictValidation: Property +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt index 3163de2..dd6bbcc 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVPlugin.kt @@ -29,5 +29,6 @@ constructor() : Plugin { const val API_CHECK_TASK_NAME = "apiCheck" const val API_DUMP_TASK_NAME = "apiDump" const val API_GENERATE_TASK_NAME = "apiGenerate" + const val PREPARE_API_GENERATE_TASK_NAME = "prepareApiGenerate" } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectExtension.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectExtension.kt index bbbe293..4ac4d86 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectExtension.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectExtension.kt @@ -1,25 +1,23 @@ package dev.adamko.kotlin.binary_compatibility_validator -import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi -import dev.adamko.kotlin.binary_compatibility_validator.internal.adding -import dev.adamko.kotlin.binary_compatibility_validator.internal.domainObjectContainer +import dev.adamko.kotlin.binary_compatibility_validator.internal.* import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTarget -import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetSpec +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetBaseSpec import javax.inject.Inject -import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.file.DirectoryProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.model.ReplacedBy import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty +import org.gradle.kotlin.dsl.* abstract class BCVProjectExtension @BCVInternalApi @Inject constructor( private val objects: ObjectFactory -) : BCVTargetSpec, ExtensionAware { +) : BCVTargetBaseSpec, ExtensionAware { /** Sets the default [BCVTarget.enabled] value for all [targets]. */ abstract override val enabled: Property @@ -46,17 +44,24 @@ constructor( /** Sets the default [BCVTarget.ignoredClasses] value for all [targets]. */ abstract override val ignoredClasses: SetProperty + @BCVExperimentalApi + val klib: BCVKLibConventions = + extensions.adding("klib") { + objects.newInstance(BCVKLibConventions::class) + } + /** * The directory that contains the API declarations. * - * Defaults to [BCVPlugin.API_DIR]. + * Defaults to `$projectDir/`[BCVPlugin.API_DIR]. */ abstract val outputApiDir: DirectoryProperty abstract val projectName: Property abstract val kotlinxBinaryCompatibilityValidatorVersion: Property + abstract val kotlinCompilerEmbeddableVersion: Property - val targets: NamedDomainObjectContainer = - extensions.adding("targets") { objects.domainObjectContainer() } + val targets: BCVTargetsContainer = + extensions.adding("targets") { objects.bcvTargetsContainer() } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt index 080fd12..0dc81dc 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVProjectPlugin.kt @@ -5,16 +5,19 @@ import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_ import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_DUMP_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_GENERATE_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.EXTENSION_NAME +import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.PREPARE_API_GENERATE_TASK_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.RUNTIME_CLASSPATH_CONFIGURATION_NAME import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME -import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi -import dev.adamko.kotlin.binary_compatibility_validator.internal.declarable -import dev.adamko.kotlin.binary_compatibility_validator.internal.resolvable -import dev.adamko.kotlin.binary_compatibility_validator.internal.sourceSets -import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiCheckTask -import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiDumpTask -import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVApiGenerateTask -import dev.adamko.kotlin.binary_compatibility_validator.tasks.BCVDefaultTask +import dev.adamko.kotlin.binary_compatibility_validator.adapters.createJavaTestFixtureTargets +import dev.adamko.kotlin.binary_compatibility_validator.adapters.createKotlinAndroidTargets +import dev.adamko.kotlin.binary_compatibility_validator.adapters.createKotlinJvmTargets +import dev.adamko.kotlin.binary_compatibility_validator.adapters.createKotlinMultiplatformTargets +import dev.adamko.kotlin.binary_compatibility_validator.internal.* +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVKLibTarget +import dev.adamko.kotlin.binary_compatibility_validator.targets.KLibSignatureVersion +import dev.adamko.kotlin.binary_compatibility_validator.tasks.* +import java.io.File import javax.inject.Inject import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin @@ -22,15 +25,11 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.file.ProjectLayout import org.gradle.api.logging.Logging +import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ProviderFactory -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.gradle.language.base.plugins.LifecycleBasePlugin.CHECK_TASK_NAME -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer -import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainer abstract class BCVProjectPlugin @@ -39,6 +38,7 @@ abstract class BCVProjectPlugin constructor( private val providers: ProviderFactory, private val layout: ProjectLayout, + private val objects: ObjectFactory, ) : Plugin { override fun apply(project: Project) { @@ -49,42 +49,9 @@ constructor( val bcvGenerateClasspath = createBcvMuClasspath(project, extension) - project.tasks.withType().configureEach { - bcvEnabled.convention(extension.enabled) - onlyIf("BCV is disabled") { bcvEnabled.get() } - } - - project.tasks.withType().configureEach { - runtimeClasspath.from(bcvGenerateClasspath) - targets.addAllLater(providers.provider { extension.targets }) - onlyIf("Must have at least one target") { targets.isNotEmpty() } - outputApiBuildDir.convention(layout.buildDirectory.dir("bcv-api")) - projectName.convention(extension.projectName) - } - - project.tasks.withType().configureEach { - outputs.dir(temporaryDir) // dummy output, so up-to-date checks work - expectedProjectName.convention(extension.projectName) - expectedApiDirPath.convention(extension.outputApiDir.map { it.asFile.canonicalFile.absolutePath }) - } - - project.tasks.withType().configureEach { - apiDirectory.convention(extension.outputApiDir) - } - - val apiGenerateTask = project.tasks.register(API_GENERATE_TASK_NAME, BCVApiGenerateTask::class) + configureBcvTaskConventions(project, extension, bcvGenerateClasspath) - project.tasks.register(API_DUMP_TASK_NAME, BCVApiDumpTask::class) { - apiDumpFiles.from(apiGenerateTask.map { it.outputApiBuildDir }) - } - - val apiCheckTask = project.tasks.register(API_CHECK_TASK_NAME, BCVApiCheckTask::class) { - apiBuildDir.convention(apiGenerateTask.flatMap { it.outputApiBuildDir }) - } - - project.tasks.named(CHECK_TASK_NAME).configure { - dependsOn(apiCheckTask) - } + registerBcvTasks(project) createKotlinJvmTargets(project, extension) createKotlinAndroidTargets(project, extension) @@ -92,32 +59,69 @@ constructor( createJavaTestFixtureTargets(project, extension) } + private fun createExtension(project: Project): BCVProjectExtension { val extension = project.extensions.create(EXTENSION_NAME, BCVProjectExtension::class).apply { enabled.convention(true) outputApiDir.convention(layout.projectDirectory.dir(API_DIR)) projectName.convention(providers.provider { project.name }) - kotlinxBinaryCompatibilityValidatorVersion.convention("0.13.1") + kotlinxBinaryCompatibilityValidatorVersion.convention(BCVProperties.bcvVersion) + kotlinCompilerEmbeddableVersion.convention(BCVProperties.kotlinVersion) + + // have to set conventions because otherwise .add("...") doesn't work + ignoredMarkers.convention(emptyList()) + publicPackages.convention(emptyList()) + publicClasses.convention(emptyList()) + publicMarkers.convention(emptyList()) + ignoredClasses.convention(emptyList()) + @Suppress("DEPRECATION") + nonPublicMarkers.convention(null) + + @OptIn(BCVExperimentalApi::class) + klib.apply { + enabled.convention(false) + signatureVersion.convention(KLibSignatureVersion.Latest) + strictValidation.convention(false) + } } - extension.targets.configureEach { - enabled.convention(true) + extension.targets.apply { + registerBinding(BCVJvmTarget::class, BCVJvmTarget::class) + registerBinding(BCVKLibTarget::class, BCVKLibTarget::class) - publicMarkers.convention(extension.publicMarkers) - publicPackages.convention(extension.publicPackages) - publicClasses.convention(extension.publicClasses) + withType().configureEach { + enabled.convention(true) + inputClasses.setFrom(emptyList()) + inputJar.convention(null) + } - ignoredClasses.convention(extension.ignoredClasses) - ignoredMarkers.convention( - @Suppress("DEPRECATION") - extension.ignoredMarkers.orElse(extension.nonPublicMarkers) - ) - ignoredPackages.convention(extension.ignoredPackages) + @OptIn(BCVExperimentalApi::class) + withType().configureEach { + enabled.convention(extension.klib.enabled) + + signatureVersion.convention(extension.klib.signatureVersion) +// strictValidation.convention(extension.klib.strictValidation) + supportedByCurrentHost.convention(false) + } + + configureEach { + publicMarkers.convention(extension.publicMarkers) + publicPackages.convention(extension.publicPackages) + publicClasses.convention(extension.publicClasses) + + ignoredClasses.convention(extension.ignoredClasses) + ignoredMarkers.convention( + @Suppress("DEPRECATION") + extension.ignoredMarkers.orElse(extension.nonPublicMarkers) + ) + ignoredPackages.convention(extension.ignoredPackages) + } } return extension } + private fun createBcvMuClasspath( project: Project, extension: BCVProjectExtension, @@ -135,92 +139,89 @@ constructor( ) } ) + addLater( + extension.kotlinCompilerEmbeddableVersion.map { version -> + project.dependencies.create( + "org.jetbrains.kotlin:kotlin-compiler-embeddable:$version" + ) + } + ) } } return project.configurations.register(RUNTIME_CLASSPATH_RESOLVER_CONFIGURATION_NAME) { description = "Resolve the runtime classpath for running binary-compatibility-validator." resolvable() - isVisible = false extendsFrom(bcvGenerateClasspath.get()) } } - private fun createKotlinJvmTargets( + + private fun configureBcvTaskConventions( project: Project, extension: BCVProjectExtension, + bcvGenerateClasspath: NamedDomainObjectProvider ) { - project.pluginManager.withPlugin("kotlin") { - extension.targets.create("kotlinJvm") { - project - .sourceSets - .matching { it.name == SourceSet.MAIN_SOURCE_SET_NAME } - .all { - inputClasses.from(output.classesDirs) - } - } + project.tasks.withType().configureEach { + bcvEnabled.convention(extension.enabled) + + onlyIf("BCV is enabled") { bcvEnabled.get() } } - } - private fun createKotlinAndroidTargets( - project: Project, - extension: BCVProjectExtension, - ) { - project.pluginManager.withPlugin("kotlin-android") { - val kotlinSourceSetsContainer = project.extensions.getByType() - kotlinSourceSetsContainer.sourceSets.all { - extension.targets.create(name) { - inputClasses.from(kotlin.classesDirectory) - } - } +// project.tasks.withType().configureEach { +// apiDumpFiles.from(extension.outputApiDir) +// apiDirectory.convention(objects.directoryProperty().fileValue(temporaryDir)) +// } + + project.tasks.withType().configureEach { + runtimeClasspath.from(bcvGenerateClasspath) + targets.addAllLater(providers.provider { extension.targets }) + outputApiBuildDir.convention(layout.buildDirectory.dir("bcv-api")) + projectName.convention(extension.projectName) + + @OptIn(BCVExperimentalApi::class) + strictKLibTargetValidation.convention(extension.klib.strictValidation) + + projectApiDumpDir.convention(extension.outputApiDir) + + onlyIf("Must have at least one target") { targets.isNotEmpty() } } - } - private fun createKotlinMultiplatformTargets( - project: Project, - extension: BCVProjectExtension, - ) { - project.pluginManager.withPlugin("kotlin-multiplatform") { - 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) - } - } - } + project.tasks.withType().configureEach { + targets.addAllLater(providers.provider { extension.targets }) +// outputs.dir(temporaryDir) // dummy output, so up-to-date checks work + expectedProjectName.convention(extension.projectName) + expectedApiDirPath.convention( + extension.outputApiDir.map { it.asFile.canonicalFile.invariantSeparatorsPath } + ) +// runtimeClasspath.from(bcvGenerateClasspath) + } + + project.tasks.withType().configureEach { + apiDirectory.convention(extension.outputApiDir) + } + + project.tasks.named(CHECK_TASK_NAME).configure { + dependsOn(project.tasks.withType()) } } - private fun createJavaTestFixtureTargets( - project: Project, - extension: BCVProjectExtension, - ) { - project.pluginManager.withPlugin("java-test-fixtures") { - extension.targets.register(TEST_FIXTURE_SOURCESET_NAME) { - // don't enable by default - requiring an API spec for test-fixtures is pretty unusual - enabled.convention(false) - project - .sourceSets - .matching { it.name == TEST_FIXTURE_SOURCESET_NAME } - .all { - inputClasses.from(output.classesDirs) - } + + private fun registerBcvTasks(project: Project) { +// val prepareApiGenerateTask = +// project.tasks.register(PREPARE_API_GENERATE_TASK_NAME, BCVApiGeneratePreparationTask::class) + + val apiGenerateTask = + project.tasks.register(API_GENERATE_TASK_NAME, BCVApiGenerateTask::class) { +// projectApiDumpDir.convention(prepareApiGenerateTask.flatMap { it.apiDirectory }) } + + project.tasks.register(API_DUMP_TASK_NAME, BCVApiDumpTask::class) { + apiDumpFiles.from(apiGenerateTask.map { it.outputApiBuildDir }) + } + + project.tasks.register(API_CHECK_TASK_NAME, BCVApiCheckTask::class) { + apiBuildDir.convention(apiGenerateTask.flatMap { it.outputApiBuildDir }) } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt b/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt index ed01a2e..fc735a4 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/BCVSettingsPlugin.kt @@ -1,9 +1,13 @@ package dev.adamko.kotlin.binary_compatibility_validator +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi import dev.adamko.kotlin.binary_compatibility_validator.internal.globToRegex -import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetSpec +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetBaseSpec +import dev.adamko.kotlin.binary_compatibility_validator.targets.KLibSignatureVersion import javax.inject.Inject +import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.initialization.Settings @@ -21,7 +25,7 @@ constructor( override fun apply(settings: Settings) { val extension = settings.extensions.create( "bcvSettings", - objects.newInstance(), + objects.newInstance(), ).apply { ignoredProjects.convention(emptySet()) @@ -30,6 +34,13 @@ constructor( ignoredClasses.convention(emptySet()) ignoredMarkers.convention(emptySet()) ignoredPackages.convention(emptySet()) + + @OptIn(BCVExperimentalApi::class) + klib.apply { + enabled.convention(false) + signatureVersion.convention(KLibSignatureVersion.Latest) + strictValidation.convention(false) + } } } @@ -50,13 +61,16 @@ constructor( } } - abstract class Extension @Inject constructor( + abstract class Extension + @BCVInternalApi + @Inject + constructor( /** - * Set [BCVTargetSpec] values that will be used as defaults for all + * Set [BCVTargetBaseSpec] values that will be used as defaults for all * [BCVProjectExtension.targets] in subprojects. */ - val defaultTargetValues: BCVTargetSpec + val defaultTargetValues: BCVTargetDefaults ) { /** @@ -70,8 +84,7 @@ constructor( */ abstract val ignoredProjects: SetProperty - - fun defaultTargetValues(configure: BCVTargetSpec.() -> Unit) = - defaultTargetValues.configure() + fun defaultTargetValues(configure: Action): Unit = + configure.execute(defaultTargetValues) } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/adapters/AndroidAdapter.kt b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/AndroidAdapter.kt new file mode 100644 index 0000000..9fcd173 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/AndroidAdapter.kt @@ -0,0 +1,22 @@ +package dev.adamko.kotlin.binary_compatibility_validator.adapters + +import dev.adamko.kotlin.binary_compatibility_validator.BCVProjectExtension +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import org.gradle.api.Project +import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer + + +internal fun createKotlinAndroidTargets( + project: Project, + extension: BCVProjectExtension, +) { + project.pluginManager.withPlugin("kotlin-android") { + val kotlinSourceSetsContainer = project.extensions.getByType() + kotlinSourceSetsContainer.sourceSets.all { + extension.targets.register(name) { + inputClasses.from(kotlin.classesDirectory) + } + } + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/adapters/JavaTestFixtures.kt b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/JavaTestFixtures.kt new file mode 100644 index 0000000..da70cba --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/JavaTestFixtures.kt @@ -0,0 +1,27 @@ +package dev.adamko.kotlin.binary_compatibility_validator.adapters + +import dev.adamko.kotlin.binary_compatibility_validator.BCVProjectExtension +import dev.adamko.kotlin.binary_compatibility_validator.internal.sourceSets +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import org.gradle.api.Project +import org.gradle.internal.component.external.model.TestFixturesSupport.TEST_FIXTURE_SOURCESET_NAME +import org.gradle.kotlin.dsl.* + + +internal fun createJavaTestFixtureTargets( + project: Project, + extension: BCVProjectExtension, +) { + project.pluginManager.withPlugin("java-test-fixtures") { + extension.targets.register(TEST_FIXTURE_SOURCESET_NAME) { + // don't enable by default - requiring an API spec for test-fixtures is a little unusual, and might be surprising + enabled.convention(false) + project + .sourceSets + .matching { it.name == TEST_FIXTURE_SOURCESET_NAME } + .all { + inputClasses.from(output.classesDirs) + } + } + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinJvmAdapter.kt b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinJvmAdapter.kt new file mode 100644 index 0000000..cbe14e8 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinJvmAdapter.kt @@ -0,0 +1,25 @@ +package dev.adamko.kotlin.binary_compatibility_validator.adapters + +import dev.adamko.kotlin.binary_compatibility_validator.BCVProjectExtension +import dev.adamko.kotlin.binary_compatibility_validator.internal.sourceSets +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import org.gradle.api.Project +import org.gradle.api.tasks.SourceSet +import org.gradle.kotlin.dsl.* + + +internal fun createKotlinJvmTargets( + project: Project, + extension: BCVProjectExtension, +) { + project.pluginManager.withPlugin("kotlin") { + extension.targets.register("kotlinJvm") { + project + .sourceSets + .matching { it.name == SourceSet.MAIN_SOURCE_SET_NAME } + .all { + inputClasses.from(output.classesDirs) + } + } + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinMultiplatformAdapter.kt b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinMultiplatformAdapter.kt new file mode 100644 index 0000000..56c58a0 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/adapters/KotlinMultiplatformAdapter.kt @@ -0,0 +1,129 @@ +package dev.adamko.kotlin.binary_compatibility_validator.adapters + +import dev.adamko.kotlin.binary_compatibility_validator.BCVProjectExtension +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVKLibTarget +import java.io.File +import org.gradle.api.Project +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.ProviderFactory +import org.gradle.kotlin.dsl.* +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.* +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetsContainer +import org.jetbrains.kotlin.konan.target.HostManager + +private val logger: Logger = + Logging.getLogger("dev.adamko.kotlin.binary_compatibility_validator.adapters.KotlinMultiplatformAdapter") + +internal fun createKotlinMultiplatformTargets( + project: Project, + extension: BCVProjectExtension, +) { + project.pluginManager.withPlugin("kotlin-multiplatform") { + val kotlinTargetsContainer = project.extensions.getByType() + + kotlinTargetsContainer.targets + .matching { it.platformType != common } + .configureEach target@{ + when (platformType) { + common, + -> { + // no-op + } + + js, + native, + wasm, + -> registerKotlinKLibCompilation(extension, this@target, project.providers) + + androidJvm, + jvm, + -> registerKotlinJvmCompilations(extension, this@target) + } + } + } +} + + +private fun registerKotlinJvmCompilations( + extension: BCVProjectExtension, + target: KotlinTarget, +) { + val targetPlatformType = target.platformType + + extension.targets.register(target.targetName) { + logger.lifecycle("registering JVM target ${target.targetName}") +// enabled.convention(true) + + target.compilations + .matching { + when (targetPlatformType) { + jvm -> it.name == KotlinCompilation.MAIN_COMPILATION_NAME + androidJvm -> it.name == "release" + else -> false + } + }.all { + inputClasses.from(output.classesDirs) + } + } +} + + +private fun registerKotlinKLibCompilation( + extension: BCVProjectExtension, + target: KotlinTarget, + providers: ProviderFactory, +) { + extension.targets.register(target.targetName) { + logger.info("BCV: registering KLib target ${target.targetName}") +// enabled.convention(false) + + target.compilations + .matching { it.name == KotlinCompilation.MAIN_COMPILATION_NAME } + .all { + klibFile.from(output.classesDirs) + compilationDependencies.from(providers.provider { compileDependencyFiles }) + currentPlatform.convention(HostManager.platformName()) + supportedByCurrentHost.set(target.isSupportedByCurrentHost()) + hasKotlinSources.convention( + providers.provider { + allKotlinSourceSets.any { it.kotlin.srcDirs.any(File::exists) } + } + ) + } + } +} + +private fun KotlinTarget.isSupportedByCurrentHost(): Boolean { + return when (this) { + is org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -> + HostManager().isEnabled(konanTarget) + + else -> true + } +} + +//private fun extractUnderlyingTarget(target: KotlinTarget): String { +// if (target is KotlinNativeTarget) { +// return konanTargetNameMapping[target.konanTarget.name]!! +// } +// return when (target.platformType) { +// KotlinPlatformType.js -> "js" +// KotlinPlatformType.wasm -> when ((target as KotlinJsIrTarget).wasmTargetType) { +// KotlinWasmTargetType.WASI -> "wasmWasi" +// KotlinWasmTargetType.JS -> "wasmJs" +// else -> throw IllegalStateException("Unreachable") +// } +// +// else -> throw IllegalArgumentException("Unsupported platform type: ${target.platformType}") +// } +//} +// +// +//internal fun KotlinCompilation.hasKotlinSources(): Provider = +// project.provider { +// allKotlinSourceSets.any { it.kotlin.srcDirs.any(File::exists) } +// } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/dsl/assign.kt b/modules/bcv-gradle-plugin/src/main/kotlin/dsl/assign.kt new file mode 100644 index 0000000..fdc9a72 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/dsl/assign.kt @@ -0,0 +1,18 @@ +@file:Suppress("PackageDirectoryMismatch") + +package org.gradle.kotlin.dsl + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.targets.KLibSignatureVersion +import org.gradle.api.provider.Property + + +@OptIn(BCVExperimentalApi::class) +// TODO test Property.assign +fun Property.assign(version: Int) { + this.set(KLibSignatureVersion.of(version)) +} +// +//@OptIn(BCVExperimentalApi::class) +//fun KLibSignatureVersion(version: Int): KLibSignatureVersion = +// KLibSignatureVersion.of(version) diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVExperimentalApi.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVExperimentalApi.kt new file mode 100644 index 0000000..5b334f2 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/BCVExperimentalApi.kt @@ -0,0 +1,19 @@ +package dev.adamko.kotlin.binary_compatibility_validator.internal + +import kotlin.annotation.AnnotationRetention.BINARY +import kotlin.annotation.AnnotationTarget.* + +/** + * Marks an API that is still experimental in BCV and may change in the future. + * There are no guarantees on preserving the behavior of the API until its stabilization. + */ +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@Retention(BINARY) +@Target( + CLASS, + FUNCTION, + PROPERTY, + CONSTRUCTOR, +) +@MustBeDocumented +annotation class BCVExperimentalApi diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/GradlePath.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/GradlePath.kt index f633c57..3c8fcfd 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/internal/GradlePath.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/GradlePath.kt @@ -6,7 +6,8 @@ internal typealias GradlePath = org.gradle.util.Path internal fun GradlePath(path: String): GradlePath = GradlePath.path(path) -internal val Project.isRootProject get() = this == rootProject +internal val Project.isRootProject: Boolean + get() = this == rootProject internal val Project.fullPath: String get() = when { diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt index fe36551..7bea8f8 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/gradleUtils.kt @@ -1,10 +1,13 @@ package dev.adamko.kotlin.binary_compatibility_validator.internal +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTarget +import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectFactory import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.ExtensionAware import org.gradle.api.plugins.ExtensionContainer import org.gradle.kotlin.dsl.* import org.gradle.util.GradleVersion @@ -37,7 +40,7 @@ internal fun Configuration.declarable( ) { isCanBeResolved = false isCanBeConsumed = false - canBeDeclared(true) + canBeDeclared = true isVisible = visible } @@ -52,11 +55,11 @@ internal fun Configuration.declarable( * ``` */ internal fun Configuration.consumable( - visible: Boolean = true, + visible: Boolean = false, ) { isCanBeResolved = false isCanBeConsumed = true - canBeDeclared(false) + canBeDeclared = false isVisible = visible } @@ -75,7 +78,7 @@ internal fun Configuration.resolvable( ) { isCanBeResolved = true isCanBeConsumed = false - canBeDeclared(false) + canBeDeclared = false isVisible = visible } @@ -87,12 +90,13 @@ internal fun Configuration.resolvable( * This function should be removed when the minimal supported Gradle version is 8.2. */ @Suppress("UnstableApiUsage") -private fun Configuration.canBeDeclared(value: Boolean) { - if (CurrentGradleVersion >= "8.2") { - isCanBeDeclared = value +private var Configuration.canBeDeclared: Boolean + get() = isCanBeDeclaredSupported && isCanBeDeclared + set(value) { + if (isCanBeDeclaredSupported) isCanBeDeclared = value } -} +private val isCanBeDeclaredSupported = CurrentGradleVersion >= "8.2" /** * Create a new [NamedDomainObjectContainer], using @@ -112,6 +116,35 @@ internal inline fun ObjectFactory.domainObjectContainer( } +/** + * Create a new [ExtensiblePolymorphicDomainObjectContainer], using + * [org.gradle.kotlin.dsl.polymorphicDomainObjectContainer] + * (but [T] is `reified`). + * + * @see org.gradle.kotlin.dsl.polymorphicDomainObjectContainer + */ +internal inline fun ObjectFactory.polymorphicDomainObjectContainer() + : ExtensiblePolymorphicDomainObjectContainer = + polymorphicDomainObjectContainer(T::class) + + + +/** Create a new [BCVTargetsContainer] instance. */ +internal fun ObjectFactory.bcvTargetsContainer(): BCVTargetsContainer { + val container = polymorphicDomainObjectContainer() + container.whenObjectAdded { + // workaround for https://github.com/gradle/gradle/issues/24972 + (container as ExtensionAware).extensions.add(name, this) + } + return container +} + + +typealias BCVTargetsContainer = + ExtensiblePolymorphicDomainObjectContainer + + + /** * [Add][ExtensionContainer.add] a value (from [valueProvider]) with [name], and return the value. * diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/internal/kotlinTime.kt b/modules/bcv-gradle-plugin/src/main/kotlin/internal/kotlinTime.kt new file mode 100644 index 0000000..649540c --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/internal/kotlinTime.kt @@ -0,0 +1,11 @@ +package dev.adamko.kotlin.binary_compatibility_validator.internal + +import kotlin.time.Duration +import kotlin.time.Duration.Companion.nanoseconds + +// can't use kotlin.time.measureTime {} because Gradle forces the language level to be low. +internal fun measureTime(block: () -> Unit): Duration = + System.nanoTime().let { startTime -> + block() + (System.nanoTime() - startTime).nanoseconds + } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVJvmTarget.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVJvmTarget.kt new file mode 100644 index 0000000..71bb32b --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVJvmTarget.kt @@ -0,0 +1,46 @@ +package dev.adamko.kotlin.binary_compatibility_validator.targets + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import javax.inject.Inject +import org.gradle.api.Named +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional + +abstract class BCVJvmTarget +@BCVInternalApi +@Inject +constructor( + /** + * The JVM platform being targeted. + * + * Targets with the same [platformType] will be grouped together into a single API declaration. + */ + @get:Internal + val platformType: String, + @get:Internal + internal val objects: ObjectFactory +) : BCVTarget(platformType), Named { + + @get:Classpath + @get:Optional +// @get:Internal + abstract val inputClasses: ConfigurableFileCollection + + @get:Classpath + @get:Optional +// @get:Internal + abstract val inputJar: RegularFileProperty + +// // create a new property to track the inputs, because Gradle sucks and doesn't allow for +// // @Optional & @Classpath on a property +// @get:Classpath +// protected val inputClasspath: FileCollection +// get() = objects.fileCollection() +// .from(inputClasses) +// .from(inputJar) +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVKLibTarget.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVKLibTarget.kt new file mode 100644 index 0000000..dfc568e --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVKLibTarget.kt @@ -0,0 +1,45 @@ +package dev.adamko.kotlin.binary_compatibility_validator.targets + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import java.io.Serializable +import org.gradle.api.Named +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal + +@OptIn(BCVExperimentalApi::class) +abstract class BCVKLibTarget +@BCVInternalApi +constructor( + @get:Internal + val targetName: String, +) : BCVTarget(targetName), KLibValidationSpec, Named, Serializable { + + @get:Classpath + abstract val klibFile: ConfigurableFileCollection + + @get:Classpath + abstract val compilationDependencies: ConfigurableFileCollection + + @get:Input + abstract val currentPlatform: Property // for up-to-date checks? + + @BCVInternalApi // should only be used in tests + @get:Input + abstract val supportedByCurrentHost: Property + + @get:Input + abstract val hasKotlinSources: Property + +// @get:Internal // TODO +// abstract val inputAbiFile: RegularFileProperty +// @get:Internal // TODO +// abstract val outputAbiFile: RegularFileProperty +// abstract val supportedTargets: SetProperty + +// @Internal +// override fun getName(): String = targetName +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTarget.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTarget.kt index 639a838..ab7609d 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTarget.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTarget.kt @@ -1,38 +1,34 @@ package dev.adamko.kotlin.binary_compatibility_validator.targets +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi import java.io.Serializable import javax.inject.Inject import org.gradle.api.Named -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty +import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional -abstract class BCVTarget +sealed class BCVTarget +@BCVInternalApi @Inject constructor( - /** - * The JVM platform being targeted. - * - * Targets with the same [platformType] will be grouped together into a single API declaration. - */ - @get:Input - val platformType: String -) : BCVTargetSpec, Serializable, Named { + private val named: String, +) : BCVTargetBaseSpec, Serializable, Named, ExtensionAware, Comparable { @get:Input @get:Optional abstract override val enabled: Property - @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) - abstract override val inputClasses: ConfigurableFileCollection - - @get:InputFile - @get:Optional - @get:PathSensitive(PathSensitivity.RELATIVE) - abstract override val inputJar: RegularFileProperty +// @get:InputFiles +// @get:PathSensitive(RELATIVE) +// abstract override val inputClasses: ConfigurableFileCollection +// +// @get:InputFile +// @get:Optional +// @get:PathSensitive(RELATIVE) +// abstract override val inputJar: RegularFileProperty /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTarget.publicMarkers */ @get:Input @@ -64,6 +60,16 @@ constructor( @get:Optional abstract override val ignoredClasses: SetProperty - @Internal - override fun getName(): String = platformType + override fun compareTo(other: BCVTarget): Int = + this.string().compareTo(other.string()) + + @Input + override fun getName(): String = named + + companion object { + private fun BCVTarget.string(): String = when (this) { + is BCVJvmTarget -> "BCVJvmTarget(${named})" + is BCVKLibTarget -> "BCVKLibTarget(${named})" + } + } } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetSpec.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetBaseSpec.kt similarity index 79% rename from modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetSpec.kt rename to modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetBaseSpec.kt index 1d1fd5e..f90d330 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetSpec.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetBaseSpec.kt @@ -1,30 +1,14 @@ package dev.adamko.kotlin.binary_compatibility_validator.targets import java.io.Serializable -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty -interface BCVTargetSpec : Serializable { +interface BCVTargetBaseSpec : Serializable { /** Enables or disables API generation and validation for this target */ val enabled: Property - /** - * The classes to generate signatures for. - * - * Note that if [inputJar] has a value, the contents of [inputClasses] will be ignored - */ - val inputClasses: ConfigurableFileCollection - - /** - * A JAR that contains the classes to generate signatures for. - * - * Note that if [inputJar] has a value, the contents of [inputClasses] will be ignored - */ - val inputJar: RegularFileProperty - /** * Fully qualified names of annotations that can be used to explicitly mark public declarations. * diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetDefaults.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetDefaults.kt new file mode 100644 index 0000000..5919bc3 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/BCVTargetDefaults.kt @@ -0,0 +1,51 @@ +package dev.adamko.kotlin.binary_compatibility_validator.targets + +import dev.adamko.kotlin.binary_compatibility_validator.BCVKLibConventions +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.adding +import java.io.Serializable +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.ExtensionAware +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.kotlin.dsl.* + +abstract class BCVTargetDefaults +@BCVInternalApi +@Inject +constructor( + private val objects: ObjectFactory, +) : BCVTargetBaseSpec, Serializable, ExtensionAware { + + abstract override val enabled: Property + +// abstract override val inputClasses: ConfigurableFileCollection + +// abstract override val inputJar: RegularFileProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.publicMarkers */ + abstract override val publicMarkers: SetProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.publicPackages */ + abstract override val publicPackages: SetProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.publicClasses */ + abstract override val publicClasses: SetProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.ignoredMarkers */ + abstract override val ignoredMarkers: SetProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.ignoredPackages */ + abstract override val ignoredPackages: SetProperty + + /** @see dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTargetDefaults.ignoredClasses */ + abstract override val ignoredClasses: SetProperty + + @BCVExperimentalApi + val klib: BCVKLibConventions = + extensions.adding("klib") { + objects.newInstance(BCVKLibConventions::class) + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibSignatureVersion.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibSignatureVersion.kt new file mode 100644 index 0000000..07c201a --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibSignatureVersion.kt @@ -0,0 +1,36 @@ +package dev.adamko.kotlin.binary_compatibility_validator.targets + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import java.io.Serializable + +@BCVExperimentalApi +sealed interface KLibSignatureVersion { + + val version: Int + + fun isLatest(): Boolean = version == Latest.version + + @BCVExperimentalApi + companion object { + fun of(value: Int): KLibSignatureVersion { + require(value >= 1) { + "Invalid version value, expected positive value: $value" + } + return KLibSignatureVersionImpl(value) + } + + val Latest: KLibSignatureVersion = KLibSignatureVersionImpl(Int.MIN_VALUE) + } +} + + +@OptIn(BCVExperimentalApi::class) +private data class KLibSignatureVersionImpl( + override val version: Int, +) : Serializable, KLibSignatureVersion { + + override fun toString(): String { + val version = if (isLatest()) "Latest" else "$version" + return "KLibSignatureVersion($version)" + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibValidationSpec.kt b/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibValidationSpec.kt new file mode 100644 index 0000000..eb82bc2 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/targets/KLibValidationSpec.kt @@ -0,0 +1,45 @@ +package dev.adamko.kotlin.binary_compatibility_validator.targets + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional + +/** + * Settings affecting KLib ABI validation. + */ +@BCVExperimentalApi +interface KLibValidationSpec { + + /** Enables KLib ABI validation checks. */ + @get:Input + @get:Optional + val enabled: Property + + /** Enables KLib ABI validation checks. */ + fun enable(): Unit = enabled.set(true) + + /** + * Specifies which version of signature KLib ABI dump should contain. + * By default, or when explicitly set to null, the latest supported version will be used. + * + * This option covers some advanced scenarios and does not require any configuration by default. + * + * A linker uses signatures to look up symbols, thus signature changes brake binary compatibility + * and should be tracked. Signature format itself is not stabilized yet and may change in the + * future. In that case, a new version of a signature will be introduced. Change of a signature + * version will be reflected in a dump causing a validation failure even if declarations itself + * remained unchanged. However, if a KLib supports multiple signature versions simultaneously, + * one may explicitly specify the version that will be dumped to prevent changes in a dump file. + */ + @get:Input + @get:Optional + val signatureVersion: Property + + /** + * Sets the value of [signatureVersion]. + * @see signatureVersion + */ + fun signatureVersion(version: Int): Unit = + signatureVersion.set(KLibSignatureVersion.of(version)) +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt index 539595f..6f2a9d1 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiCheckTask.kt @@ -3,12 +3,16 @@ package dev.adamko.kotlin.binary_compatibility_validator.tasks import com.github.difflib.DiffUtils import com.github.difflib.UnifiedDiffUtils import dev.adamko.kotlin.binary_compatibility_validator.BCVPlugin.Companion.API_DUMP_TASK_NAME -import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi -import dev.adamko.kotlin.binary_compatibility_validator.internal.GradlePath -import dev.adamko.kotlin.binary_compatibility_validator.internal.fullPath +import dev.adamko.kotlin.binary_compatibility_validator.internal.* +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVKLibTarget +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTarget +import dev.adamko.kotlin.binary_compatibility_validator.workers.KLibExtractWorker import java.io.File import java.util.TreeMap import javax.inject.Inject +import kotlin.io.path.listDirectoryEntries +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileVisitDetails import org.gradle.api.file.RelativePath @@ -18,16 +22,24 @@ import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.* import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.kotlin.dsl.* +import org.gradle.workers.WorkQueue +import org.gradle.workers.WorkerExecutor @CacheableTask abstract class BCVApiCheckTask @BCVInternalApi @Inject constructor( + private val workers: WorkerExecutor, private val objects: ObjectFactory, private val providers: ProviderFactory, ) : BCVDefaultTask() { + @get:Nested + val targets: NamedDomainObjectContainer = + extensions.adding("targets") { objects.domainObjectContainer() } + @get:InputDirectory @get:Optional @get:PathSensitive(RELATIVE) @@ -42,69 +54,132 @@ constructor( @get:InputDirectory @get:PathSensitive(RELATIVE) abstract val apiBuildDir: DirectoryProperty +// +// @get:LocalState +// internal val tempDir: File get() = temporaryDir @get:Input internal abstract val expectedProjectName: Property +// +// @get:Input +// abstract val strictKLibTargetValidation: Property // Project and tasks paths are used for creating better error messages - private val projectFullPath = project.fullPath - private val apiDumpTaskPath = GradlePath(project.path).child(API_DUMP_TASK_NAME) + private val projectFullPath: String = project.fullPath + private val apiDumpTaskPath: GradlePath = GradlePath(project.path).child(API_DUMP_TASK_NAME) + + private val rootDir: File = project.rootProject.rootDir + +// @get:Classpath +// abstract val runtimeClasspath: ConfigurableFileCollection - private val rootDir = project.rootProject.rootDir + init { + super.onlyIf { task -> + require(task is BCVApiCheckTask) + task.apiBuildDir.orNull?.asFile + ?.takeIf(File::exists) + ?.toPath() + ?.listDirectoryEntries() + ?.isNotEmpty() == true + } + } @TaskAction fun verify() { val projectApiDir = projectApiDir.orNull ?: error( """ - Expected folder with API declarations '${expectedApiDirPath.get()}' does not exist. - Please ensure that task '$apiDumpTaskPath' was executed in order to get API dump to compare the build against + Expected folder with API declarations '${expectedApiDirPath.get()}' does not exist. + Please ensure that task '$apiDumpTaskPath' was executed in order to get API dump to compare the build against. """.trimIndent() ) val apiBuildDir = apiBuildDir.get().asFile - val checkApiDeclarationPaths = projectApiDir.relativePathsOfContent { !isDirectory } - val builtApiDeclarationPaths = apiBuildDir.relativePathsOfContent { !isDirectory } - logger.info("checkApiDeclarationPaths: $checkApiDeclarationPaths") + verifyJvm(projectApiDir, apiBuildDir) + +// val klibTargets = targets.withType().filter { it.enabled.get() } +// verifyKLib(projectApiDir, apiBuildDir, klibTargets) + + // TODO need to verify that all .api files in projectApiDir have a match + } + + private fun verifyJvm( + projectApiDir: File, + apiBuildDir: File, + ) { + - checkApiDeclarationPaths.forEach { checkApiDeclarationPath -> - logger.info("---------------------------") +// val jvmTargets = targets.withType().filter { it.enabled.get() } +// +// +// jvmTargets.forEach { target -> +// if (jvmTargets.size > 1) { +// val expectedApiFile = projectApiDir +// .resolve(target.name) +// .resolve(target.platformType) +// val actualApiFile = apiBuildDir +// .resolve(target.name) +// .resolve(target.platformType) +// checkTarget( +// expectedApiDeclaration = expectedApiFile, +// actualApiDeclaration = actualApiFile, +// ) +// } else { +// val expectedApiFile = projectApiDir +// .resolve(target.platformType) +// val actualApiFile = apiBuildDir +// .resolve(target.platformType) +// checkTarget( +// expectedApiDeclaration = expectedApiFile, +// actualApiDeclaration = actualApiFile, +// ) +// } +// } + + val expectedApiFiles = projectApiDir.relativePathsOfContent { + file.name.substringAfter(".") == "api" + } + val actualApiFiles = apiBuildDir.relativePathsOfContent { + file.name.substringAfter(".") == "api" + } + logger.info("[$path] expectedApiFiles: $expectedApiFiles") + + expectedApiFiles.forEach { expectedApiFile -> checkTarget( - checkApiDeclaration = checkApiDeclarationPath.getFile(projectApiDir), + expectedApiDeclaration = expectedApiFile.getFile(projectApiDir), // fetch the builtFile, using the case-insensitive map - builtApiDeclaration = builtApiDeclarationPaths[checkApiDeclarationPath]?.getFile(apiBuildDir) + actualApiDeclaration = actualApiFiles[expectedApiFile]?.getFile(apiBuildDir) ) - logger.info("---------------------------") } } private fun checkTarget( - checkApiDeclaration: File, - builtApiDeclaration: File?, + expectedApiDeclaration: File, + actualApiDeclaration: File?, ) { - logger.info("checkApiDeclaration: $checkApiDeclaration") - logger.info("builtApiDeclaration: $builtApiDeclaration") + logger.info("[$path] expectedApiDeclaration: $expectedApiDeclaration") + logger.info("[$path] actualApiDeclaration: $actualApiDeclaration") - val allBuiltFilePaths = builtApiDeclaration?.parentFile.relativePathsOfContent() - val allCheckFilePaths = checkApiDeclaration.parentFile.relativePathsOfContent() + val allBuiltFilePaths = actualApiDeclaration?.parentFile.relativePathsOfContent() + val allCheckFilePaths = expectedApiDeclaration.parentFile.relativePathsOfContent() - logger.info("allBuiltPaths: $allBuiltFilePaths") - logger.info("allCheckFiles: $allCheckFilePaths") + logger.info("[$path] allBuiltPaths: $allBuiltFilePaths") + logger.info("[$path] allCheckFiles: $allCheckFilePaths") val builtFilePath = allBuiltFilePaths.singleOrNull() - ?: error("Expected a single file ${expectedProjectName.get()}.api, but found ${allBuiltFilePaths.size}: $allBuiltFilePaths") + ?: error("[$path] Expected a single file ${expectedProjectName.get()}.api, but found ${allBuiltFilePaths.size}: $allBuiltFilePaths") - if (builtApiDeclaration == null || builtFilePath !in allCheckFilePaths) { + if (actualApiDeclaration == null || builtFilePath !in allCheckFilePaths) { val relativeDirPath = projectApiDir.get().toRelativeString(rootDir) + File.separator error( - "File ${builtFilePath.lastName} is missing from ${relativeDirPath}, please run '$apiDumpTaskPath' task to generate one" + "[$path] File ${builtFilePath.lastName} is missing from ${relativeDirPath}, please run '$apiDumpTaskPath' task to generate one" ) } val diffText = compareFiles( - checkFile = checkApiDeclaration, - builtFile = builtApiDeclaration, + checkFile = expectedApiDeclaration, + builtFile = actualApiDeclaration, )?.trim() if (!diffText.isNullOrBlank()) { @@ -114,13 +189,13 @@ constructor( | |$diffText | - |You can run '$apiDumpTaskPath' task to overwrite API declarations + |You can run '$apiDumpTaskPath' task to overwrite API declarations. """.trimMargin() ) } } - /** Get the relative paths of all files and folders inside a directory */ + /** Get the relative paths of all files inside a directory. */ private fun File?.relativePathsOfContent( filter: FileVisitDetails.() -> Boolean = { true }, ): RelativePaths { @@ -153,25 +228,67 @@ constructor( ) return diff.joinToString("\n") } + + +// private fun verifyKLib( +// projectApiDir: File, +// apiBuildDir: File, +// targets: List +// ) { +// if (targets.isEmpty()) return +// val klibFile = projectApiDir.resolve(expectedProjectName.get()) +// +// } +// private fun prepareWorkQueue(): WorkQueue { +// return workers.classLoaderIsolation { +// classpath.from(runtimeClasspath) +// } +// } + + +// @OptIn(BCVExperimentalApi::class) +// private fun WorkQueue.extract( +// target: BCVKLibTarget, +// targetDumpFiles: Set, +// outputDir: File, +// ) { +// val task = this@BCVApiCheckTask +// +// @OptIn(BCVInternalApi::class) +// submit(KLibExtractWorker::class) worker@{ +// this@worker.taskPath.set(task.path) +// this@worker.strictValidation.set(task.strictKLibTargetValidation) +// +//// this@worker.inputAbiFile.set() +//// this@worker.outputAbiFile.set() +//// this@worker.supportedTargets.set() +// } +// } } -/* +/** * We use case-insensitive comparison to workaround issues with case-insensitive OSes and Gradle * behaving slightly different on different platforms. We neither know original sensitivity of - * existing .api files, not build ones, because projectName that is part of the path can have any - * sensitivity. To work around that, we replace paths we are looking for the same paths that + * existing `.api` files, not build ones, because `projectName` that is part of the path can have + * any sensitivity. To work around that, we replace paths we are looking for the same paths that * actually exist on the FS. */ private class RelativePaths( private val map: TreeMap = caseInsensitiveMap() ) : Set by map.keys { - operator fun plusAssign(path: RelativePath) { - map[path] = path - } + operator fun plusAssign(path: RelativePath): Unit = map.set(path, path) operator fun get(path: RelativePath): RelativePath? = map[path] + override fun toString(): String = + map.keys.joinToString( + prefix = "RelativePaths(", + separator = "/", + postfix = ")", + transform = RelativePath::getPathString, + ) + companion object { private fun caseInsensitiveMap() = TreeMap { path1, path2 -> diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiDumpTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiDumpTask.kt index ee8f7eb..fa72906 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiDumpTask.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiDumpTask.kt @@ -1,29 +1,75 @@ package dev.adamko.kotlin.binary_compatibility_validator.tasks import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.isRootProject import javax.inject.Inject +import kotlin.io.path.invariantSeparatorsPathString import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.ProjectLayout import org.gradle.api.tasks.* +import org.gradle.api.tasks.PathSensitivity.RELATIVE @CacheableTask abstract class BCVApiDumpTask @BCVInternalApi @Inject constructor( - private val fs: FileSystemOperations + private val fs: FileSystemOperations, + private val layout: ProjectLayout, ) : BCVDefaultTask() { @get:InputFiles - @get:PathSensitive(PathSensitivity.RELATIVE) + @get:PathSensitive(RELATIVE) abstract val apiDumpFiles: ConfigurableFileCollection @get:OutputDirectory abstract val apiDirectory: DirectoryProperty + private val projectGradlePath: String = + if (project.isRootProject) { + "project ':' (the root project)" + } else { + "subproject '${project.path}'" + } + + init { + outputs.cacheIf { task -> + require(task is BCVApiDumpTask) + task.validateApiDir() + } + } + @TaskAction fun action() { + validateApiDir { msg -> error(msg) } + updateDumpDir() + } + + private fun validateApiDir(ifInvalid: (msg: String) -> Unit = {}): Boolean { + val projectDir = layout.projectDirectory.asFile.toPath().toAbsolutePath().normalize() + val apiDir = projectDir.resolve(apiDirectory.get().asFile.toPath()).normalize() + val valid = apiDir.startsWith(projectDir) + if (!valid) { + ifInvalid( + /* language=text */ """ + |Error: Invalid output apiDirectory + | + |apiDirectory is set to a custom directory, outside of the current project directory. + |This is not permitted. apiDirectory must be a subdirectory of $projectGradlePath directory. + | + |Remove the custom apiDirectory, or update apiDirectory to be a project subdirectory. + | + |Project directory: ${projectDir.invariantSeparatorsPathString} + |apiDirectory: ${apiDir.invariantSeparatorsPathString} + """.trimMargin() + ) + } + return valid + } + + private fun updateDumpDir() { fs.sync { from(apiDumpFiles) { include("**/*.api") diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGeneratePreparationTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGeneratePreparationTask.kt new file mode 100644 index 0000000..d76d0be --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGeneratePreparationTask.kt @@ -0,0 +1,40 @@ +package dev.adamko.kotlin.binary_compatibility_validator.tasks + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import javax.inject.Inject +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.api.tasks.TaskAction +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "Performs simple file operations, not worth caching") +abstract class BCVApiGeneratePreparationTask +// TODO This task probably isn't needed and can be removed +// The generate task can just directly use files in `$projectDir/api`, +// so long as it only reads them. +@BCVInternalApi +@Inject +constructor( + private val fs: FileSystemOperations, +) : BCVDefaultTask() { + + @get:InputFiles + @get:PathSensitive(RELATIVE) + abstract val apiDumpFiles: ConfigurableFileCollection + + @get:OutputDirectory + abstract val apiDirectory: DirectoryProperty + + @TaskAction + fun action() { + fs.sync { + from(apiDumpFiles) + into(apiDirectory) + } + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt index b77f0cb..f967608 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/tasks/BCVApiGenerateTask.kt @@ -1,18 +1,20 @@ package dev.adamko.kotlin.binary_compatibility_validator.tasks -import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi -import dev.adamko.kotlin.binary_compatibility_validator.internal.adding -import dev.adamko.kotlin.binary_compatibility_validator.internal.domainObjectContainer +import dev.adamko.kotlin.binary_compatibility_validator.internal.* +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVJvmTarget +import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVKLibTarget import dev.adamko.kotlin.binary_compatibility_validator.targets.BCVTarget -import dev.adamko.kotlin.binary_compatibility_validator.workers.BCVSignaturesWorker -import java.io.* +import dev.adamko.kotlin.binary_compatibility_validator.workers.* +import java.io.File import javax.inject.Inject -import kotlinx.validation.api.* -import org.gradle.api.* -import org.gradle.api.file.* +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.tasks.* +import org.gradle.api.tasks.PathSensitivity.RELATIVE import org.gradle.kotlin.dsl.* import org.gradle.workers.WorkQueue import org.gradle.workers.WorkerExecutor @@ -42,57 +44,129 @@ constructor( @get:Input abstract val projectName: Property + @get:Input + abstract val strictKLibTargetValidation: Property + + /** + * A directory containing any currently existing API Dump files. + */ +// * Provided by [BCVApiGeneratePreparationTask]. + @get:Internal + abstract val projectApiDumpDir: DirectoryProperty + + @get:InputFiles + @get:PathSensitive(RELATIVE) + @get:Optional + // Gradle sucks and doesn't allow an optional non-existing input dir 🙄 + internal val projectApiDumpDirFiles: List + get() = projectApiDumpDir.orNull?.asFile?.walk() + ?.filter { it.isFile } + ?.toList()?.sorted().orEmpty() + @get:OutputDirectory abstract val outputApiBuildDir: DirectoryProperty + @get:LocalState + internal val workDir: File get() = temporaryDir + + private val klibTargetsDir = object { + private val klibDir: File get() = workDir.resolve("klib") + val supported: File get() = klibDir.resolve("supported") + val unsupported: File get() = klibDir.resolve("unsupported") + val extracted: File get() = klibDir.resolve("extracted") + } + @TaskAction fun generate() { - val workQueue = prepareWorkQueue() + prepareDirectories() - val outputApiBuildDir = outputApiBuildDir.get() - fs.delete { delete(outputApiBuildDir) } - outputApiBuildDir.asFile.mkdirs() + val workQueue = prepareWorkQueue() - val enabledTargets = targets.asMap.values.filter { it.enabled.getOrElse(true) } + val enabledTargets = targets.matching { it.enabled.getOrElse(true) } - enabledTargets.forEach { target -> + logger.lifecycle("[$path] got ${targets.size} targets (${enabledTargets.size} enabled) : ${targets.joinToString { it.name }}") - val outputDir = if (enabledTargets.size == 1) { - outputApiBuildDir - } else { - outputApiBuildDir.dir(target.platformType) - } + val jvmTargets = enabledTargets.withType().sorted() + generateJvmTargets( + workQueue = workQueue, + jvmTargets = jvmTargets, + outputApiBuildDir = outputApiBuildDir.get().asFile, + ) - workQueue.submit( - target = target, - outputDir = outputDir.asFile, - ) - } + // TODO log when klib file doesn't exist + // TODO log warning when klibFile has >1 file + val klibTargets = enabledTargets.withType() + .filter { it.klibFile.singleOrNull()?.exists() == true } + .sorted() + generateKLibTargets( + workQueue = workQueue, + klibTargets = klibTargets, + outputApiBuildDir = outputApiBuildDir.get().asFile, + ) // The worker queue is asynchronous, so any code here won't wait for the workers to finish. // Any follow-up work must be done in another task. } - private fun prepareWorkQueue(): WorkQueue { - fs.delete { delete(temporaryDir) } - temporaryDir.mkdirs() + private fun prepareWorkQueue(): WorkQueue { return workers.classLoaderIsolation { classpath.from(runtimeClasspath) } } + private fun prepareDirectories() { + fs.delete { delete(outputApiBuildDir) } + outputApiBuildDir.get().asFile.mkdirs() + + fs.delete { delete(workDir) } + workDir.mkdirs() + klibTargetsDir.supported.mkdirs() + klibTargetsDir.unsupported.mkdirs() + klibTargetsDir.extracted.mkdirs() + } + + //region JVM + + private fun generateJvmTargets( + workQueue: WorkQueue, + outputApiBuildDir: File, + jvmTargets: Collection, + ) { + if (jvmTargets.isEmpty()) { + logger.info("[$path] No enabled JVM targets") + return + } + + logger.lifecycle("[$path] generating ${jvmTargets.size} JVM targets : ${jvmTargets.joinToString { it.name }}") + + jvmTargets.forEach { target -> + val outputDir = if (jvmTargets.size == 1) { + outputApiBuildDir + } else { + outputApiBuildDir.resolve(target.platformType) + } + + workQueue.submit( + target = target, + outputDir = outputDir, + ) + } + } + private fun WorkQueue.submit( - target: BCVTarget, + target: BCVJvmTarget, outputDir: File, ) { val task = this@BCVApiGenerateTask @OptIn(BCVInternalApi::class) - submit(BCVSignaturesWorker::class) worker@{ + submit(JvmSignaturesWorker::class) worker@{ this@worker.projectName.set(task.projectName) + this@worker.taskPath.set(task.path) this@worker.outputApiDir.set(outputDir) + this@worker.inputClasses.from(target.inputClasses) this@worker.inputJar.set(target.inputJar) @@ -105,4 +179,244 @@ constructor( this@worker.ignoredClasses.set(target.ignoredClasses) } } + //endregion + + + //region KLib + + private fun generateKLibTargets( + workQueue: WorkQueue, + outputApiBuildDir: File, + klibTargets: List, + ) { + if (klibTargets.isEmpty()) { + logger.info("[$path] No enabled KLib targets") + return + } + logger.lifecycle("[$path] generating ${klibTargets.size} KLib targets : ${klibTargets.joinToString { it.name }}") + + val (supportedKLibTargets, unsupportedKLibTargets) = + klibTargets.partition { it.supportedByCurrentHost.get() } + + generateSupportedKLibTargets(workQueue, supportedKLibTargets) + extractSupportedKLibs(workQueue, supportedKLibTargets) + generateUnsupportedKLibTargets(workQueue, unsupportedKLibTargets) + + val allTargetDumpFiles = buildSet { + addAll(klibTargetsDir.supported.walk().filter { it.isFile }) + addAll(klibTargetsDir.unsupported.walk().filter { it.isFile }) + } + + mergeDumpFiles( + workQueue = workQueue, + allTargetDumpFiles = allTargetDumpFiles, + outputApiBuildDir = outputApiBuildDir, + targets = klibTargets, + strictValidation = strictKLibTargetValidation.get(), + ) + +// workQueue.extract() + } + + private fun generateSupportedKLibTargets( + workQueue: WorkQueue, + supportedTargets: List + ) { + if (supportedTargets.isEmpty()) { + logger.info("[$path] No supported enabled KLib targets") + return + } + logger.lifecycle("[$path] generating ${supportedTargets.size} supported KLib targets : ${supportedTargets.joinToString { it.name }}") + + val duration = measureTime { + supportedTargets.forEach { target -> + workQueue.submit( + target = target, + outputDir = klibTargetsDir.supported, + ) + } + workQueue.await() + } + + logger.lifecycle("[$path] finished generating supported KLib targets in $duration") + } + + private fun extractSupportedKLibs( + workQueue: WorkQueue, + supportedTargets: List + ) { + if (supportedTargets.isEmpty()) { + logger.info("[$path] No supported enabled KLib targets for extraction") + return + } + logger.lifecycle("[$path] extracting ${supportedTargets.size} supported KLib targets : ${supportedTargets.joinToString { it.name }}") + + val duration = measureTime { + workQueue.extract( + supportedTargets = supportedTargets + ) + workQueue.await() + } + + logger.lifecycle("[$path] finished extracting supported KLib targets in $duration") + } + + private fun generateUnsupportedKLibTargets( + workQueue: WorkQueue, + unsupportedTargets: List + ) { + if (unsupportedTargets.isEmpty()) { + logger.info("[$path] No unsupported enabled KLib targets") + return + } + logger.lifecycle("[$path] generating ${unsupportedTargets.size} unsupported KLib targets : ${unsupportedTargets.joinToString { it.name }}") + + val duration = measureTime { + unsupportedTargets.forEach { target -> + workQueue.inferKLib( + target = target, + supportedTargetDumpFiles = klibTargetsDir.supported.walk().filter { it.isFile }.toSet(), + extantApiDumpFile = projectApiDumpDir.asFile.orNull?.walk()?.filter { it.isFile } + ?.firstOrNull(), + outputDir = klibTargetsDir.unsupported, + ) + } + workQueue.await() + } + + logger.lifecycle("[$path] finished generating unsupported KLib targets in $duration") + } + + + private fun mergeDumpFiles( + workQueue: WorkQueue, + allTargetDumpFiles: Set, + outputApiBuildDir: File, + targets: List, + strictValidation: Boolean, + ) { + logger.lifecycle("[$path] merging ${allTargetDumpFiles.size} dump files : ${allTargetDumpFiles.joinToString { it.name }}") + + val duration = measureTime { + workQueue.merge( + projectName.get(), + targetDumpFiles = allTargetDumpFiles, + outputDir = outputApiBuildDir, + supportedTargets = targets.filter { it.supportedByCurrentHost.get() }.map { it.targetName }, + strictValidation = strictValidation, + ) + workQueue.await() + } + + if (logger.isLifecycleEnabled) { + val fileNames = outputApiBuildDir.walk().filter { it.isFile }.toList() + logger.lifecycle("[$path] merged ${allTargetDumpFiles.size} dump files in $duration : $fileNames") + } + } + + + @OptIn(BCVExperimentalApi::class) + private fun WorkQueue.submit( + target: BCVKLibTarget, + outputDir: File, + ) { + val task = this@BCVApiGenerateTask + + @OptIn(BCVInternalApi::class) + submit(KLibSignaturesWorker::class) worker@{ + this@worker.targetName.set(target.targetName) + this@worker.taskPath.set(task.path) + + this@worker.outputApiDir.set(outputDir) + + this@worker.klib.set(target.klibFile.singleFile) + this@worker.signatureVersion.set(target.signatureVersion) +// this@worker.strictValidation.set(target.strictValidation) + +// this@worker.targets.addAll(klibTargets) + + this@worker.ignoredPackages.set(target.ignoredPackages) + this@worker.ignoredMarkers.set(target.ignoredMarkers) + this@worker.ignoredClasses.set(target.ignoredClasses) + } + } + + @OptIn(BCVExperimentalApi::class) + private fun WorkQueue.inferKLib( + target: BCVKLibTarget, + supportedTargetDumpFiles: Set, + extantApiDumpFile: File?, + outputDir: File, + ) { + val task = this@BCVApiGenerateTask + + @OptIn(BCVInternalApi::class) + submit(KLibInferSignaturesWorker::class) worker@{ + this@worker.targetName.set(target.name) + this@worker.taskPath.set(task.path) + + this@worker.outputApiDir.set(outputDir) + + this@worker.supportedTargetDumpFiles.from(supportedTargetDumpFiles) + this@worker.extantApiDumpFile.set(extantApiDumpFile) + } + } + + @OptIn(BCVExperimentalApi::class) + private fun WorkQueue.merge( + projectName: String, + targetDumpFiles: Set, + outputDir: File, + supportedTargets: List, + strictValidation: Boolean, + ) { + val task = this@BCVApiGenerateTask + + @OptIn(BCVInternalApi::class) + submit(KLibMergeWorker::class) worker@{ +// this@worker.projectName.set(projectName) + this@worker.taskPath.set(task.path) + +// this@worker.outputApiDir.set(outputDir) + this@worker.outputApiFile.set( + outputDir.resolve("$projectName.klib.api") + ) + +// this@worker.strictValidation.set(strictValidation) +// this@worker.supportedTargets.set(supportedTargets) + + this@worker.targetDumpFiles.from(targetDumpFiles) + } + } + + @OptIn(BCVExperimentalApi::class) + private fun WorkQueue.extract( +// target: BCVKLibTarget, +// targetDumpFiles: Set, +// outputDir: File, + supportedTargets: List, + ) { + val task = this@BCVApiGenerateTask + + val inputFile = projectApiDumpDir.file(projectName.map { "$it.klib.api" }).get().asFile + if (!inputFile.exists()) return + + @OptIn(BCVInternalApi::class) + submit(KLibExtractWorker::class) worker@{ + this@worker.taskPath.set(task.path) + this@worker.strictValidation.set(strictKLibTargetValidation) + + this@worker.inputAbiFile.set( + projectApiDumpDir.file(projectName.map { "$it.klib.api" }) + ) +// this@worker.outputAbiFile.set() + this@worker.supportedTargets.set( + supportedTargets.map { it.targetName } + ) + this@worker.outputAbiFile.set( + klibTargetsDir.extracted.resolve(projectName.map { "$it.klib.api" }.get()) + ) + } + } + //endregion } diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/workers/BCVSignaturesWorker.kt b/modules/bcv-gradle-plugin/src/main/kotlin/workers/JvmSignaturesWorker.kt similarity index 65% rename from modules/bcv-gradle-plugin/src/main/kotlin/workers/BCVSignaturesWorker.kt rename to modules/bcv-gradle-plugin/src/main/kotlin/workers/JvmSignaturesWorker.kt index 45da136..3ea992b 100644 --- a/modules/bcv-gradle-plugin/src/main/kotlin/workers/BCVSignaturesWorker.kt +++ b/modules/bcv-gradle-plugin/src/main/kotlin/workers/JvmSignaturesWorker.kt @@ -14,7 +14,7 @@ import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters @BCVInternalApi -abstract class BCVSignaturesWorker : WorkAction { +abstract class JvmSignaturesWorker : WorkAction { private val logger = Logging.getLogger(this::class.java) @@ -34,8 +34,16 @@ abstract class BCVSignaturesWorker : WorkAction val ignoredClasses: SetProperty val projectName: Property + + /** + * [Task path][org.gradle.api.Task.getPath] of the task that invoked this worker, + * for log messages + */ + val taskPath: Property } + private val logTag: String by lazy { "[${parameters.taskPath.get()}:BCVSignaturesWorker]" } + override fun execute() { val projectName = parameters.projectName.get() @@ -56,13 +64,13 @@ abstract class BCVSignaturesWorker : WorkAction writeSignatures( outputApiDir = parameters.outputApiDir.get().asFile, projectName = parameters.projectName.get(), - signatures = signatures + signatures = signatures, ) signatures.count() } - logger.info("BCVSignaturesWorker generated $signaturesCount signatures for $projectName in $duration") + logger.info("$logTag generated $signaturesCount signatures for $projectName in $duration") } private fun generateSignatures( @@ -75,33 +83,59 @@ abstract class BCVSignaturesWorker : WorkAction ignoredMarkers: Set, ignoredPackages: Set, ): List { + + logger.info( + """ + $logTag inputJar : $inputJar + $logTag publicMarkers : $publicMarkers + $logTag publicPackages : $publicPackages + $logTag publicClasses : $publicClasses + $logTag ignoredClasses : $ignoredClasses + $logTag ignoredMarkers : $ignoredMarkers + $logTag ignoredPackages : $ignoredPackages + """.trimIndent() + ) + val signatures = when { // inputJar takes precedence if specified inputJar != null -> JarFile(inputJar.asFile).use { it.loadApiFromJvmClasses() } !inputClasses.isEmpty -> { - logger.info("inputClasses: ${inputClasses.files}") + logger.info("$logTag inputClasses: ${inputClasses.files}") val filteredInputClasses = inputClasses.asFileTree.matching { exclude("META-INF/**") include("**/*.class") } - logger.info("filteredInputClasses: ${filteredInputClasses.files}") + logger.info("$logTag filteredInputClasses: ${filteredInputClasses.files}") - filteredInputClasses.asSequence() + filteredInputClasses + .asSequence() .map(File::inputStream) .loadApiFromJvmClasses() } else -> - error("BCVSignaturesWorker should have either inputClassesDirs, or inputJar property set") + error("$logTag should have either inputClassesDirs, or inputJar property set") } + val publicPackagesNames = + signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet()) + val ignoredPackagesNames = + signatures.extractAnnotatedPackages(ignoredMarkers.map(::replaceDots).toSet()) + return signatures - .retainExplicitlyIncludedIfDeclared(publicPackages, publicClasses, publicMarkers) - .filterOutNonPublic(ignoredPackages, ignoredClasses) + .retainExplicitlyIncludedIfDeclared( + publicPackages = publicPackages + publicPackagesNames, + publicClasses = publicClasses, + publicMarkerAnnotations = publicMarkers, + ) + .filterOutNonPublic( + nonPublicPackages = ignoredPackages + ignoredPackagesNames, + nonPublicClasses = ignoredClasses, + ) .filterOutAnnotated(ignoredMarkers.map(::replaceDots).toSet()) } @@ -112,19 +146,11 @@ abstract class BCVSignaturesWorker : WorkAction ) { outputApiDir.mkdirs() - outputApiDir - .resolve("$projectName.api") - .bufferedWriter().use { writer -> - signatures - .sortedBy { it.name } - .forEach { api -> - writer.append(api.signature).appendLine(" {") - api.memberSignatures - .sortedWith(MEMBER_SORT_ORDER) - .forEach { writer.append("\t").appendLine(it.signature) } - writer.appendLine("}\n") - } - } + val apiFile = outputApiDir.resolve("$projectName.api") + + apiFile.bufferedWriter().use { writer -> + signatures.dump(writer) + } } companion object { diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibExtractWorker.kt b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibExtractWorker.kt new file mode 100644 index 0000000..a5d4fda --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibExtractWorker.kt @@ -0,0 +1,67 @@ +package dev.adamko.kotlin.binary_compatibility_validator.workers + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import java.io.Serializable +import kotlinx.validation.ExperimentalBCVApi +import kotlinx.validation.KlibValidationSettings +import kotlinx.validation.api.klib.KlibDump +import kotlinx.validation.api.klib.KlibTarget +import kotlinx.validation.api.klib.saveTo +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters + +@BCVInternalApi +@BCVExperimentalApi +@OptIn(ExperimentalBCVApi::class) +abstract class KLibExtractWorker : WorkAction { + + @BCVInternalApi + interface Parameters : WorkParameters, Serializable { +// val projectName: Property + /** Merged KLib dump that should be filtered by this task. */ + val inputAbiFile: RegularFileProperty + /** A path to the resulting dump file. */ + val outputAbiFile: RegularFileProperty + /** Provider returning targets supported by the host compiler. */ + val supportedTargets: SetProperty + /** Refer to [KlibValidationSettings.strictValidation] for details. */ + val strictValidation: Property + /** + * [Task path][org.gradle.api.Task.getPath] of the task that invoked this worker, + * for log messages + */ + val taskPath: Property + } + + override fun execute() { + val inputAbiFile = parameters.inputAbiFile.get().asFile + val supportedTargets = parameters.supportedTargets.get() + val strictValidation = parameters.strictValidation.getOrElse(false) + val outputAbiFile = parameters.outputAbiFile.get().asFile + + if (inputAbiFile.length() == 0L) { + error("Project ABI file $inputAbiFile is empty") + } + val dump = KlibDump.from(inputAbiFile) + val enabledTargets = supportedTargets.map { KlibTarget.parse(it).targetName } + // Filter out only unsupported files. + // That ensures that target renaming will be caught and reported as a change. + val targetsToRemove = dump.targets.filter { it.targetName !in enabledTargets } + if (targetsToRemove.isNotEmpty() && strictValidation) { + error("Validation could not be performed as some targets are not available and strictValidation mode is enabled") + } + dump.remove(targetsToRemove) + dump.saveTo(outputAbiFile) + } + + @BCVInternalApi + companion object { + private val logger: Logger = Logging.getLogger(KLibExtractWorker::class.java) + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibInferSignaturesWorker.kt b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibInferSignaturesWorker.kt new file mode 100644 index 0000000..2eb0628 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibInferSignaturesWorker.kt @@ -0,0 +1,125 @@ +package dev.adamko.kotlin.binary_compatibility_validator.workers + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import java.io.Serializable +import kotlinx.validation.ExperimentalBCVApi +import kotlinx.validation.api.klib.KlibDump +import kotlinx.validation.api.klib.KlibTarget +import kotlinx.validation.api.klib.inferAbi +import kotlinx.validation.api.klib.saveTo +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters + +/** + * Infers a possible KLib ABI dump for an unsupported target. + * + * To infer a dump, walk up the default targets hierarchy tree starting from the unsupported + * target until it finds a node corresponding to a group containing least one supported target. + * + * After that, dumps generated for such supported targets are merged and declarations that are + * common to all of them are considered as a common ABI that most likely will be shared by the + * unsupported target. + * + * At the next step, if a project contains an old dump, declarations specific to the unsupported + * target are copied from it and merged into the common ABI extracted previously. + * + * The resulting dump is then used as an inferred dump for the unsupported target. + */ +@BCVInternalApi +@BCVExperimentalApi +@OptIn(ExperimentalBCVApi::class) +abstract class KLibInferSignaturesWorker : WorkAction { + + @BCVInternalApi + interface Parameters : WorkParameters, Serializable { + val targetName: Property + + val outputApiDir: DirectoryProperty + + val supportedTargetDumpFiles: ConfigurableFileCollection + val extantApiDumpFile: RegularFileProperty + + /** + * [Task path][org.gradle.api.Task.getPath] of the task that invoked this worker, + * for log messages + */ + val taskPath: Property + } + + private val taskPath: String = parameters.taskPath.get() + + override fun execute() { + // Find a set of supported targets that are closer to unsupported target in the hierarchy. + // Note that dumps are stored using configurable name, but grouped by the canonical target name. +// val matchingTargets = findMatchingTargets(availableDumps.keys, target.get()) + // Load dumps that are a good fit for inference + val supportedTargetDumps = + parameters.supportedTargetDumpFiles.asFileTree.map { dumpFile -> + KlibDump.from(dumpFile, dumpFile.name.substringBefore(".klib.api")) +// .also { +// check(it.targets.single() == target) +// } + } + + val extantApiDumpFile = parameters.extantApiDumpFile.orNull?.asFile + + // Load an old dump, if any + val extantImage: KlibDump? = + extantApiDumpFile?.let { extantApiDump -> + KlibDump.from(extantApiDump) + } + if (extantImage == null) { + logger.warn( + "[$taskPath] Project's ABI file exists, but empty: ${extantApiDumpFile}. " + + "The file will be ignored during ABI dump inference for the unsupported target " +// + target.get() + ) + } + + val target = KlibTarget.parse(parameters.targetName.get()) + inferAbi(target, supportedTargetDumps, extantImage) + .saveTo(parameters.outputApiDir.get().asFile.resolve(parameters.targetName.get())) + +// logger.warn( +// "An ABI dump for target ${target.get()} was inferred from the ABI generated for the following targets " + +// "as the former target is not supported by the host compiler: " + +// "[${matchingTargets.joinToString(",")}]. " + +// "Inferred dump may not reflect an actual ABI for the target ${target.get()}. " + +// "It is recommended to regenerate the dump on the host supporting all required compilation target." +// ) + } + + +// private fun findMatchingTargets( +// supportedTargets: Set, +// unsupportedTarget: KlibTarget +// ): Collection { +// var currentGroup: String? = unsupportedTarget.targetName +// while (currentGroup != null) { +// // If a current group has some supported targets, use them. +// val groupTargets = TargetHierarchy.targets(currentGroup) +// val matchingTargets = supportedTargets.filter { groupTargets.contains(it.targetName) } +// if (matchingTargets.isNotEmpty()) { +// return matchingTargets +// } +// // Otherwise, walk up the target hierarchy. +// currentGroup = TargetHierarchy.parent(currentGroup) +// } +// throw IllegalStateException( +// "The target $unsupportedTarget is not supported by the host compiler " + +// "and there are no targets similar to $unsupportedTarget to infer a dump from it." +// ) +// } + + @BCVInternalApi + companion object { + private val logger: Logger = Logging.getLogger(KLibInferSignaturesWorker::class.java) + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibMergeWorker.kt b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibMergeWorker.kt new file mode 100644 index 0000000..e575e8c --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibMergeWorker.kt @@ -0,0 +1,79 @@ +package dev.adamko.kotlin.binary_compatibility_validator.workers + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import java.io.Serializable +import kotlinx.validation.ExperimentalBCVApi +import kotlinx.validation.KlibValidationSettings +import kotlinx.validation.api.klib.KlibDump +import kotlinx.validation.api.klib.KlibTarget +import kotlinx.validation.api.klib.saveTo +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters + +@BCVInternalApi +@BCVExperimentalApi +@OptIn(ExperimentalBCVApi::class) +abstract class KLibMergeWorker : WorkAction { + + @BCVInternalApi + interface Parameters : WorkParameters, Serializable { +// val projectName: Property + + val outputApiFile: RegularFileProperty + + val targetDumpFiles: ConfigurableFileCollection + +// /** Provider returning targets supported by the host compiler. */ +// val supportedTargets: SetProperty +// +// /** Refer to [KlibValidationSettings.strictValidation] for details. */ +// val strictValidation: Property + + /** + * [Task path][org.gradle.api.Task.getPath] of the task that invoked this worker, + * for log messages + */ + val taskPath: Property + } + + private val taskPath: String get() = parameters.taskPath.get() + + override fun execute() { + logger.info("[${taskPath}] merging dump files ${parameters.targetDumpFiles} ") + + val sourceFiles = parameters.targetDumpFiles.asFileTree +// val outputFile = parameters.outputApiDir.get().asFile.resolve(parameters.projectName.get() + ".klib.api") + val outputApiFile = parameters.outputApiFile.get().asFile + +// val supportedTargets = parameters.supportedTargets.get().map { KlibTarget.parse(it).targetName } + + val dump = KlibDump() + + sourceFiles.forEach { dumpFile -> + dump.merge(dumpFile, dumpFile.name.substringBefore(".klib.api")) + } + +// // Filter out only unsupported files. +// // That ensures that target renaming will be caught and reported as a change. +// val unsupportedTargets = dump.targets.filter { it.targetName !in supportedTargets } +// +//// if (targetsToRemove.isNotEmpty() && parameters.strictValidation.get()) { +//// error("Validation could not be performed as some targets are not available and strictValidation mode is enabled") +//// } +// dump.remove(unsupportedTargets) + + dump.saveTo(outputApiFile) + } + + @BCVInternalApi + companion object { + private val logger: Logger = Logging.getLogger(KLibMergeWorker::class.java) + } +} diff --git a/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibSignaturesWorker.kt b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibSignaturesWorker.kt new file mode 100644 index 0000000..96c12f8 --- /dev/null +++ b/modules/bcv-gradle-plugin/src/main/kotlin/workers/KLibSignaturesWorker.kt @@ -0,0 +1,117 @@ +package dev.adamko.kotlin.binary_compatibility_validator.workers + +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVExperimentalApi +import dev.adamko.kotlin.binary_compatibility_validator.internal.BCVInternalApi +import dev.adamko.kotlin.binary_compatibility_validator.targets.KLibSignatureVersion +import java.io.File +import java.io.Serializable +import kotlinx.validation.ExperimentalBCVApi +import kotlinx.validation.api.klib.* +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters + +@BCVInternalApi +@BCVExperimentalApi +@OptIn(ExperimentalBCVApi::class) +abstract class KLibSignaturesWorker : WorkAction { + + @BCVInternalApi + interface Parameters : WorkParameters, Serializable { + val klib: RegularFileProperty + val targetName: Property + + val outputApiDir: DirectoryProperty + + val ignoredPackages: SetProperty + val ignoredMarkers: SetProperty + val ignoredClasses: SetProperty + + val signatureVersion: Property +// val strictValidation: Property + val supportedByCurrentHost: Property + + /** + * [Task path][org.gradle.api.Task.getPath] of the task that invoked this worker, + * for log messages + */ + val taskPath: Property + } + + override fun execute() { + val outputFile = parameters.outputApiDir.asFile.get() + .resolve("${parameters.targetName.get()}.klib.api") + + dump(outputFile) + +// extract(outputFile) + } + + private fun dump( + outputAbiFile: File, + ) { + val filters = KLibDumpFilters { + ignoredClasses += parameters.ignoredClasses.get() + ignoredPackages += parameters.ignoredPackages.get() + nonPublicMarkers += parameters.ignoredMarkers.get() + signatureVersion = parameters.signatureVersion.get().convert() + } + + logger.lifecycle("[${parameters.taskPath.get()}:KLibSignaturesWorker] ${filters.toPrettyString()}}") + + val dump = KlibDump.fromKlib( + klibFile = parameters.klib.get().asFile, + configurableTargetName = parameters.targetName.get(), + filters = filters, + ) + dump.saveTo(outputAbiFile) + } + + private fun extract( + abiFile: File, + ) { +// val supportedTargets = parameters.supportedTargets.get() +// val strictValidation = parameters.strictValidation.getOrElse(false) + + if (abiFile.length() == 0L) { + error("Project ABI file $abiFile is empty") + } + val dump = KlibDump.from(abiFile) + val enabledTarget = KlibTarget.parse(parameters.targetName.get()) + // Filter out only unsupported files. + // That ensures that target renaming will be caught and reported as a change. +// val targetsToRemove = dump.targets.filter { it.targetName !in enabledTargets } +// if (targetsToRemove.isNotEmpty() && strictValidation) { +// error("Validation could not be performed as some targets are not available and strictValidation mode is enabled") +// } +// dump.remove(targetsToRemove) + dump.saveTo(abiFile) + } + + @BCVInternalApi + companion object { + private val logger: Logger = Logging.getLogger(KLibSignaturesWorker::class.java) + + private fun KLibSignatureVersion.convert(): KlibSignatureVersion = + when { + isLatest() -> KlibSignatureVersion.LATEST + else -> KlibSignatureVersion.of(version) + } + + private fun KlibTarget(configName: String, targetName: String): KlibTarget = + KlibTarget.parse("${configName}.${targetName}") + + private fun KlibDumpFilters.toPrettyString(): String = + buildList { + add("ignoredPackages=$ignoredPackages") + add("ignoredClasses=$ignoredClasses") + add("nonPublicMarkers=$nonPublicMarkers") + add("signatureVersion=$signatureVersion") + }.joinToString(", ", prefix = "KLibDumpFilters(", postfix = ")") + } +} diff --git a/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt b/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt index 76e10a9..c88aa15 100644 --- a/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt +++ b/modules/bcv-gradle-plugin/src/testFixtures/kotlin/kotestGradleAssertions.kt @@ -34,14 +34,18 @@ infix fun BuildResult?.shouldHaveRunTask(taskPath: String): BuildTask { return this?.task(taskPath)!! } -/** Assert that a task ran, with an [expected outcome][expectedOutcome]. */ +/** Assert that a task ran, with any [expected outcome][expectedOutcomes]. */ fun BuildResult?.shouldHaveRunTask( taskPath: String, - expectedOutcome: TaskOutcome + vararg expectedOutcomes: TaskOutcome ): BuildTask { this should haveTask(taskPath) val task = this?.task(taskPath)!! - task should haveOutcome(expectedOutcome) + if (expectedOutcomes.size == 1) { + task should haveOutcome(expectedOutcomes.single()) + } else { + task should haveAnyOutcome(expectedOutcomes.toList()) + } return task } @@ -55,18 +59,26 @@ infix fun BuildResult?.shouldNotHaveRunTask(taskPath: String) { } private fun haveTask(taskPath: String): Matcher = - neverNullMatcher { value -> + neverNullMatcher { result -> MatcherResult( - value.task(taskPath) != null, - { "BuildResult should have run task $taskPath. All tasks: ${value.tasks.joinToString { it.path }}" }, - { "BuildResult should not have run task $taskPath. All tasks: ${value.tasks.joinToString { it.path }}" }, + result.task(taskPath) != null, + { "BuildResult should have run task $taskPath. All tasks: ${result.tasks.toPathAndOutcomeString()}" }, + { "BuildResult should not have run task $taskPath. All tasks: ${result.tasks.toPathAndOutcomeString()}" }, ) } +internal fun Collection.toPathAndOutcomeString(): String = + joinToString { "${it.path} (${it.outcome})" } + .ifEmpty { "" } + infix fun BuildTask?.shouldHaveOutcome(outcome: TaskOutcome) { this should haveOutcome(outcome) } +fun BuildTask?.shouldHaveAnyOutcome(vararg outcomes: TaskOutcome) { + this should haveAnyOutcome(outcomes.toList()) +} + infix fun BuildTask?.shouldNotHaveOutcome(outcome: TaskOutcome) { this shouldNot haveOutcome(outcome) } @@ -75,11 +87,23 @@ private fun haveOutcome(outcome: TaskOutcome): Matcher = neverNullMatcher { value -> MatcherResult( value.outcome == outcome, - { "Task ${value.path} should have outcome $outcome" }, - { "Task ${value.path} should not have outcome $outcome" }, + { "Task ${value.path} should have outcome $outcome, but was ${value.outcome}" }, + { "Task ${value.path} should not have outcome $outcome, but was ${value.outcome}" }, ) } -fun BuildResult.shouldHaveTaskWithOutcome(taskPath: String, outcome: TaskOutcome) { - this shouldHaveRunTask taskPath shouldHaveOutcome outcome +private fun haveAnyOutcome(outcomes: List): Matcher = + neverNullMatcher { value -> + MatcherResult( + value.outcome in outcomes, + { "Task ${value.path} should have any outcome ${outcomes.sorted()}, but was ${value.outcome}" }, + { "Task ${value.path} should not have any outcome ${outcomes.sorted()}, but was ${value.outcome}" }, + ) + } + +fun BuildResult.shouldHaveTaskWithOutcome( + taskPath: String, + vararg outcomes: TaskOutcome +) { + (this shouldHaveRunTask taskPath).shouldHaveAnyOutcome(outcomes = outcomes) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 7da05be..c18b1bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,32 +30,30 @@ enableFeaturePreview("STABLE_CONFIGURATION_CACHE") //region git versioning val gitDescribe: Provider = - providers - .exec { - workingDir(rootDir) - commandLine( - "git", - "describe", - "--always", - "--tags", - "--dirty=-DIRTY", - "--broken=-BROKEN", - "--match=v[0-9]*\\.[0-9]*\\.[0-9]*", - ) - isIgnoreExitValue = true - }.standardOutput.asText.map { it.trim() } + providers.exec { + workingDir(rootDir) + commandLine( + "git", + "describe", + "--always", + "--tags", + "--dirty=-DIRTY", + "--broken=-BROKEN", + "--match=v[0-9]*\\.[0-9]*\\.[0-9]*", + ) + isIgnoreExitValue = true + }.standardOutput.asText.map { it.trim() } val currentBranchName: Provider = - providers - .exec { - workingDir(rootDir) - commandLine( - "git", - "branch", - "--show-current", - ) - isIgnoreExitValue = true - }.standardOutput.asText.map { it.trim() } + providers.exec { + workingDir(rootDir) + commandLine( + "git", + "branch", + "--show-current", + ) + isIgnoreExitValue = true + }.standardOutput.asText.map { it.trim() } val currentCommitHash: Provider = providers.exec {