From e96d6274cef4757e9998c719722aef5fb49019c8 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 3 Jul 2024 14:15:45 +0200 Subject: [PATCH 1/7] Use Workers API to run all ABI-related tasks --- api/binary-compatibility-validator.api | 18 +++- build.gradle.kts | 13 ++- .../validation/test/KlibVerificationTests.kt | 12 +-- src/main/kotlin/-Utils.kt | 7 ++ .../BinaryCompatibilityValidatorPlugin.kt | 102 +++++++++++++++--- src/main/kotlin/BuildTaskBase.kt | 13 ++- src/main/kotlin/KotlinApiBuildTask.kt | 64 ++++++++--- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 51 +++++++-- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 50 +++++++-- src/main/kotlin/KotlinKlibInferAbiTask.kt | 58 ++++++++-- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 41 +++++-- src/main/kotlin/WorkerAwareTaskBase.kt | 15 +++ 12 files changed, 368 insertions(+), 76 deletions(-) create mode 100644 src/main/kotlin/WorkerAwareTaskBase.kt diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 0c58f030..e8396ee8 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -30,7 +30,7 @@ public final class kotlinx/validation/BinaryCompatibilityValidatorPlugin : org/g public fun apply (Lorg/gradle/api/Project;)V } -public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultTask { +public abstract class kotlinx/validation/BuildTaskBase : kotlinx/validation/WorkerAwareTaskBase { public fun ()V public final fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; public final fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; @@ -64,6 +64,7 @@ public class kotlinx/validation/KlibValidationSettings { public abstract class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase { public fun ()V + public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getInputClassesDirs ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getInputDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; @@ -80,34 +81,43 @@ public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTas public abstract class kotlinx/validation/KotlinKlibAbiBuildTask : kotlinx/validation/BuildTaskBase { public fun ()V + public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getKlibFile ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getSignatureVersion ()Lorg/gradle/api/provider/Property; public abstract fun getTarget ()Lorg/gradle/api/provider/Property; } -public abstract class kotlinx/validation/KotlinKlibExtractAbiTask : org/gradle/api/DefaultTask { +public abstract class kotlinx/validation/KotlinKlibExtractAbiTask : kotlinx/validation/WorkerAwareTaskBase { public fun ()V + public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getInputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getStrictValidation ()Lorg/gradle/api/provider/Property; public abstract fun getTargetsToRemove ()Lorg/gradle/api/provider/SetProperty; } -public abstract class kotlinx/validation/KotlinKlibInferAbiTask : org/gradle/api/DefaultTask { +public abstract class kotlinx/validation/KotlinKlibInferAbiTask : kotlinx/validation/WorkerAwareTaskBase { public fun ()V + public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getInputDumps ()Lorg/gradle/api/provider/SetProperty; public abstract fun getOldMergedKlibDump ()Lorg/gradle/api/file/RegularFileProperty; public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public abstract fun getTarget ()Lorg/gradle/api/provider/Property; } -public abstract class kotlinx/validation/KotlinKlibMergeAbiTask : org/gradle/api/DefaultTask { +public abstract class kotlinx/validation/KotlinKlibMergeAbiTask : kotlinx/validation/WorkerAwareTaskBase { public fun ()V public abstract fun getDumps ()Lorg/gradle/api/provider/SetProperty; + public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getMergedApiFile ()Lorg/gradle/api/file/RegularFileProperty; } +public abstract class kotlinx/validation/WorkerAwareTaskBase : org/gradle/api/DefaultTask { + public fun ()V + public abstract fun getRuntimeClasspath ()Lorg/gradle/api/file/ConfigurableFileCollection; +} + public final class kotlinx/validation/_UtilsKt { public static final fun toKlibTarget (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;)Lkotlinx/validation/api/klib/KlibTarget; } diff --git a/build.gradle.kts b/build.gradle.kts index e75f2758..aaa1a83e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -64,10 +64,10 @@ val createClasspathManifest = tasks.register("createClasspathManifest") { dependencies { implementation(gradleApi()) - implementation(libs.kotlinx.metadata) + compileOnly(libs.kotlinx.metadata) compileOnly(libs.kotlin.compiler.embeddable) - implementation(libs.ow2.asm) - implementation(libs.ow2.asmTree) + compileOnly(libs.ow2.asm) + compileOnly(libs.ow2.asmTree) implementation(libs.javaDiffUtils) compileOnly(libs.gradlePlugin.kotlin) @@ -168,12 +168,17 @@ testing { implementation(project()) implementation(libs.assertJ.core) implementation(libs.kotlin.test) - implementation(libs.kotlin.compiler.embeddable) } } val test by getting(JvmTestSuite::class) { description = "Regular unit tests" + dependencies { + implementation(libs.kotlinx.metadata) + implementation(libs.kotlin.compiler.embeddable) + implementation(libs.ow2.asm) + implementation(libs.ow2.asmTree) + } } val functionalTest by creating(JvmTestSuite::class) { diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index 64e1ebb8..0e40c2d4 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -11,10 +11,8 @@ import kotlinx.validation.api.resolve import kotlinx.validation.api.test import org.assertj.core.api.Assertions import org.gradle.testkit.runner.BuildResult -import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.konan.target.KonanTarget -import org.jetbrains.kotlin.utils.addToStdlib.butIf -import org.junit.Assert +//import org.jetbrains.kotlin.konan.target.HostManager +//import org.jetbrains.kotlin.konan.target.KonanTarget import org.junit.Assume import org.junit.Test import java.io.File @@ -263,7 +261,8 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { @Test fun `apiDump should work for Apple-targets`() { - Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + // Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + // TODO val runner = test { baseProjectSetting() additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") @@ -276,7 +275,8 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { @Test fun `apiCheck should work for Apple-targets`() { - Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + // Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) + // TODO val runner = test { baseProjectSetting() additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") diff --git a/src/main/kotlin/-Utils.kt b/src/main/kotlin/-Utils.kt index 98ee94e8..030c40fb 100644 --- a/src/main/kotlin/-Utils.kt +++ b/src/main/kotlin/-Utils.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.targets.js.KotlinWasmTargetType import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget +import java.io.File import java.io.Serializable /** @@ -60,3 +61,9 @@ public class KlibDumpMetadata( @get:PathSensitive(PathSensitivity.RELATIVE) public val dumpFile: RegularFileProperty ) : Serializable + +// Workaround for serialization exception occurring when KlibDumpMetadata is supplied to WorkerParameters. +internal class KlibMetadataLocal( + val target: KlibTarget, + val dumpFile: File +) : Serializable diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 3930a340..f7be13b8 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -7,6 +7,7 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibTarget import org.gradle.api.* +import org.gradle.api.artifacts.Configuration import org.gradle.api.plugins.* import org.gradle.api.provider.* import org.gradle.api.tasks.* @@ -51,9 +52,10 @@ public class BinaryCompatibilityValidatorPlugin : Plugin { } private fun configureProject(project: Project, extension: ApiValidationExtension) { - configureKotlinPlugin(project, extension) - configureAndroidPlugin(project, extension) - configureMultiplatformPlugin(project, extension) + val jvmRuntimeClasspath = project.prepareJvmValidationClasspath() + configureKotlinPlugin(project, extension, jvmRuntimeClasspath) + configureAndroidPlugin(project, extension, jvmRuntimeClasspath) + configureMultiplatformPlugin(project, extension, jvmRuntimeClasspath) } private fun configurePlugin( @@ -68,7 +70,8 @@ public class BinaryCompatibilityValidatorPlugin : Plugin { private fun configureMultiplatformPlugin( project: Project, - extension: ApiValidationExtension + extension: ApiValidationExtension, + jvmRuntimeClasspath: NamedDomainObjectProvider ) = configurePlugin("kotlin-multiplatform", project, extension) { if (project.name in extension.ignoredProjects) return@configurePlugin val kotlin = project.kotlinMultiplatform @@ -102,13 +105,16 @@ public class BinaryCompatibilityValidatorPlugin : Plugin { val targetConfig = TargetConfig(project, extension, target.name, jvmDirConfig) if (target.platformType == KotlinPlatformType.jvm) { target.mainCompilationOrNull?.also { - project.configureKotlinCompilation(it, extension, targetConfig, commonApiDump, commonApiCheck) + project.configureKotlinCompilation( + it, extension, jvmRuntimeClasspath, targetConfig, commonApiDump, commonApiCheck + ) } } else if (target.platformType == KotlinPlatformType.androidJvm) { target.compilations.matching { it.name == "release" }.all { project.configureKotlinCompilation( it, extension, + jvmRuntimeClasspath, targetConfig, commonApiDump, commonApiCheck, @@ -122,30 +128,32 @@ public class BinaryCompatibilityValidatorPlugin : Plugin { private fun configureAndroidPlugin( project: Project, - extension: ApiValidationExtension + extension: ApiValidationExtension, + jvmRuntimeClasspath: NamedDomainObjectProvider ) { - configureAndroidPluginForKotlinLibrary(project, extension) - + configureAndroidPluginForKotlinLibrary(project, extension, jvmRuntimeClasspath) } private fun configureAndroidPluginForKotlinLibrary( project: Project, - extension: ApiValidationExtension + extension: ApiValidationExtension, + jvmRuntimeClasspath: NamedDomainObjectProvider ) = configurePlugin("kotlin-android", project, extension) { val androidExtension = project.extensions .getByName("kotlin") as KotlinAndroidProjectExtension androidExtension.target.compilations.matching { it.compilationName == "release" }.all { - project.configureKotlinCompilation(it, extension, useOutput = true) + project.configureKotlinCompilation(it, extension, jvmRuntimeClasspath, useOutput = true) } } private fun configureKotlinPlugin( project: Project, - extension: ApiValidationExtension + extension: ApiValidationExtension, + jvmRuntimeClasspath: NamedDomainObjectProvider ) = configurePlugin("kotlin", project, extension) { - project.configureApiTasks(extension, TargetConfig(project, extension)) + project.configureApiTasks(extension, TargetConfig(project, extension), jvmRuntimeClasspath) } } @@ -203,6 +211,7 @@ private enum class DirConfig { private fun Project.configureKotlinCompilation( compilation: KotlinCompilation, extension: ApiValidationExtension, + jvmRuntimeClasspath: NamedDomainObjectProvider, targetConfig: TargetConfig = TargetConfig(this, extension), commonApiDump: TaskProvider? = null, commonApiCheck: TaskProvider? = null, @@ -226,6 +235,7 @@ private fun Project.configureKotlinCompilation( inputDependencies.from(compilation.compileDependencyFiles) } outputApiFile.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) + runtimeClasspath.from(jvmRuntimeClasspath) } configureCheckTasks(apiBuild, extension, targetConfig, commonApiDump, commonApiCheck) } @@ -249,6 +259,7 @@ private fun klibAbiCheckEnabled(projectName: String, extension: ApiValidationExt private fun Project.configureApiTasks( extension: ApiValidationExtension, targetConfig: TargetConfig = TargetConfig(this, extension), + jvmRuntimeClasspath: NamedDomainObjectProvider, ) { val projectName = project.name val dumpFileName = project.jvmDumpFileName @@ -266,6 +277,7 @@ private fun Project.configureApiTasks( "Builds Kotlin API for 'main' compilations of $projectName. Complementary task and shouldn't be called manually" inputClassesDirs.from(sourceSetsOutputsProvider) outputApiFile.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) + runtimeClasspath.from(jvmRuntimeClasspath) } configureCheckTasks(apiBuild, extension, targetConfig) @@ -371,10 +383,21 @@ private class KlibValidationPipelineBuilder( val klibMergeInferredDir = projectBuildDir.flatMap { pd -> klibInferDumpConfig.apiDir.map { pd.resolve(it) } } val klibExtractedFileDir = klibMergeInferredDir.map { it.resolve("extracted") } + val runtimeClasspath = project.prepareKlibValidationClasspath() + val klibMerge = project.mergeKlibsUmbrellaTask(klibDumpConfig, klibMergeDir) + klibMerge.configure { + it.runtimeClasspath.from(runtimeClasspath) + } val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir) + klibMergeInferred.configure { + it.runtimeClasspath.from(runtimeClasspath) + } val klibDump = project.dumpKlibsTask(klibDumpConfig) val klibExtractAbiForSupportedTargets = project.extractAbi(klibDumpConfig, klibApiDir, klibExtractedFileDir) + klibExtractAbiForSupportedTargets.configure { + it.runtimeClasspath.from(runtimeClasspath) + } val klibCheck = project.checkKlibsTask(klibDumpConfig) klibDump.configure { it.from.set(klibMergeInferred.flatMap { it.mergedApiFile }) @@ -387,7 +410,7 @@ private class KlibValidationPipelineBuilder( } commonApiDump.configure { it.dependsOn(klibDump) } commonApiCheck.configure { it.dependsOn(klibCheck) } - project.configureTargets(klibApiDir, klibMerge, klibMergeInferred) + project.configureTargets(klibApiDir, klibMerge, klibMergeInferred, runtimeClasspath) } private fun Project.checkKlibsTask(klibDumpConfig: TargetConfig) = @@ -467,7 +490,8 @@ private class KlibValidationPipelineBuilder( fun Project.configureTargets( klibApiDir: Provider, mergeTask: TaskProvider, - mergeInferredTask: TaskProvider + mergeInferredTask: TaskProvider, + runtimeClasspath: NamedDomainObjectProvider ) { val kotlin = project.kotlinMultiplatform @@ -496,6 +520,9 @@ private class KlibValidationPipelineBuilder( mainCompilation, extension, targetConfig, target, apiBuildDir ) + buildTargetAbi.configure { + it.runtimeClasspath.from(runtimeClasspath) + } generatedDumps.add( KlibDumpMetadata(target, objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) }) @@ -507,6 +534,7 @@ private class KlibValidationPipelineBuilder( val unsupportedTargetStub = mergeDependencyForUnsupportedTarget(targetConfig) mergeTask.configure { it.dependsOn(unsupportedTargetStub) + it.runtimeClasspath.from(runtimeClasspath) } // The actual merge will happen here, where we'll try to infer a dump for the unsupported target and merge // it with other supported target dumps. @@ -629,3 +657,49 @@ private val Project.jvmDumpFileName: String get() = "$name.api" private val Project.klibDumpFileName: String get() = "$name.klib.api" + +private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider { + val cp = + project.configurations.register("bcv-rt-klib-cp") { + it.description = "Runtime classpath for running binary-compatibility-validator." + it.isCanBeResolved = false + it.isCanBeConsumed = false + it.isCanBeDeclared = true + it.isVisible = false + it.defaultDependencies { + it.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.22")) + } + } + return project.configurations.register("bcv-rt-klib-cp-resolver") { + it.description = "Resolve the runtime classpath for running binary-compatibility-validator." + it.isCanBeResolved = true + it.isCanBeConsumed = false + it.isCanBeDeclared = false + it.isVisible = false + it.extendsFrom(cp.get()) + } +} + +private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider { + val cp = + project.configurations.register("bcv-rt-jvm-cp") { + it.description = "Runtime classpath for running binary-compatibility-validator." + it.isCanBeResolved = false + it.isCanBeConsumed = false + it.isCanBeDeclared = true + it.isVisible = false + it.defaultDependencies { + it.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2")) + it.add(project.dependencies.create("org.ow2.asm:asm:9.6")) + it.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) + } + } + return project.configurations.register("bcv-rt-jvm-cp-resolver") { + it.description = "Resolve the runtime classpath for running binary-compatibility-validator." + it.isCanBeResolved = true + it.isCanBeConsumed = false + it.isCanBeDeclared = false + it.isVisible = false + it.extendsFrom(cp.get()) + } +} diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index e4dd551c..4c129ca1 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -5,12 +5,12 @@ package kotlinx.validation -import org.gradle.api.DefaultTask import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal +import org.gradle.workers.WorkParameters -public abstract class BuildTaskBase : DefaultTask() { +public abstract class BuildTaskBase : WorkerAwareTaskBase() { private val extension = project.apiValidationExtensionOrNull private fun stringSetProperty(provider: ApiValidationExtension.() -> Set): SetProperty { @@ -46,3 +46,12 @@ public abstract class BuildTaskBase : DefaultTask() { @get:Internal internal val projectName = project.name } + +internal interface BuildParametersBase : WorkParameters { + val ignoredPackages: SetProperty + val nonPublicMarkers: SetProperty + val ignoredClasses: SetProperty + val publicPackages: SetProperty + val publicMarkers: SetProperty + val publicClasses: SetProperty +} diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 9cdf97c1..5c66bf77 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -9,12 +9,16 @@ import kotlinx.validation.api.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkerExecutor import java.io.File import java.util.jar.JarFile import javax.inject.Inject -private const val MIGRATION_GUIDE_LINK = "https://github.com/Kotlin/binary-compatibility-validator/blob/master/docs/design/0.15.0-migration-guide.md" -private const val OUTPUT_API_DIR_ERROR = "Property outputApiDir was replaced with outputApiFile. Please refer to the migration guide for migration details: $MIGRATION_GUIDE_LINK" +private const val MIGRATION_GUIDE_LINK = + "https://github.com/Kotlin/binary-compatibility-validator/blob/master/docs/design/0.15.0-migration-guide.md" +private const val OUTPUT_API_DIR_ERROR = + "Property outputApiDir was replaced with outputApiFile. Please refer to the migration guide for migration details: $MIGRATION_GUIDE_LINK" public abstract class KotlinApiBuildTask @Inject constructor( ) : BuildTaskBase() { @@ -42,13 +46,42 @@ public abstract class KotlinApiBuildTask @Inject constructor( @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val inputDependencies: ConfigurableFileCollection + @get:Inject + public abstract val executor: WorkerExecutor + @TaskAction internal fun generate() { - val inputClassesDirs = inputClassesDirs + val q = executor.classLoaderIsolation { + it.classpath.from(runtimeClasspath) + } + q.submit(AbiBuildWorker::class.java) { params -> + params.ignoredClasses.set(ignoredClasses) + params.ignoredPackages.set(ignoredPackages) + params.nonPublicMarkers.set(nonPublicMarkers) + params.publicClasses.set(publicClasses) + params.publicPackages.set(publicPackages) + params.publicMarkers.set(publicMarkers) + + params.inputJar.set(inputJar) + params.inputClassesDirs.from(inputClassesDirs) + params.outputApiFile.set(outputApiFile) + } + } +} + +internal interface ApiBuildParameters : BuildParametersBase { + val outputApiFile: RegularFileProperty + val inputClassesDirs: ConfigurableFileCollection + val inputJar: RegularFileProperty +} + +internal abstract class AbiBuildWorker : WorkAction { + override fun execute() { + val inputClassesDirs = parameters.inputClassesDirs val signatures = when { // inputJar takes precedence if specified - inputJar.isPresent -> - JarFile(inputJar.get().asFile).use { it.loadApiFromJvmClasses() } + parameters.inputJar.isPresent -> + JarFile(parameters.inputJar.get().asFile).use { it.loadApiFromJvmClasses() } inputClassesDirs.any() -> inputClassesDirs.asFileTree.asSequence() @@ -62,21 +95,26 @@ public abstract class KotlinApiBuildTask @Inject constructor( throw GradleException("KotlinApiBuildTask should have either inputClassesDirs, or inputJar property set") } - val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.get().map(::replaceDots).toSet()) + val publicMarkers = parameters.publicMarkers.get() + val publicClasses = parameters.publicClasses.get() + val publicPackages = parameters.publicPackages.get() + val nonPublicMarkers = parameters.nonPublicMarkers.get() + val ignoredClasses = parameters.ignoredClasses.get() + val ignoredPackages = parameters.ignoredPackages.get() + + val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet()) val ignoredPackagesNames = - signatures.extractAnnotatedPackages(nonPublicMarkers.get().map(::replaceDots).toSet()) + signatures.extractAnnotatedPackages(nonPublicMarkers.map(::replaceDots).toSet()) val filteredSignatures = signatures .retainExplicitlyIncludedIfDeclared( - publicPackages.get() + publicPackagesNames, - publicClasses.get(), publicMarkers.get() + publicPackages + publicPackagesNames, publicClasses, publicMarkers ) - .filterOutNonPublic(ignoredPackages.get() + ignoredPackagesNames, ignoredClasses.get()) - .filterOutAnnotated(nonPublicMarkers.get().map(::replaceDots).toSet()) + .filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses) + .filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet()) - outputApiFile.asFile.get().bufferedWriter().use { writer -> + parameters.outputApiFile.asFile.get().bufferedWriter().use { writer -> filteredSignatures.dump(writer) } } } - diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index f5fbe7c8..9215bfeb 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -10,6 +10,9 @@ import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject /** * Generates a text file with a KLib ABI dump for a single klib. @@ -51,19 +54,51 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { @get:OutputFile public abstract val outputAbiFile: RegularFileProperty - @OptIn(ExperimentalBCVApi::class) + @get:Inject + public abstract val executor: WorkerExecutor + @TaskAction internal fun generate() { - val outputFile = outputAbiFile.asFile.get() + val q = executor.classLoaderIsolation { + it.classpath.from(runtimeClasspath) + } + q.submit(KlibAbiBuildWorker::class.java) { params -> + params.ignoredClasses.set(ignoredClasses) + params.ignoredPackages.set(ignoredPackages) + params.nonPublicMarkers.set(nonPublicMarkers) + params.publicClasses.set(publicClasses) + params.publicPackages.set(publicPackages) + params.publicMarkers.set(publicMarkers) + + params.klibFile.from(klibFile) + params.target.set(target) + params.signatureVersion.set(signatureVersion) + params.outputAbiFile.set(outputAbiFile) + } + } +} + +internal interface KlibAbiBuildParameters : BuildParametersBase { + val klibFile: ConfigurableFileCollection + val signatureVersion: Property + val target: Property + val outputAbiFile: RegularFileProperty +} + +internal abstract class KlibAbiBuildWorker : WorkAction { + @OptIn(ExperimentalBCVApi::class) + override fun execute() { + val outputFile = parameters.outputAbiFile.asFile.get() outputFile.delete() outputFile.parentFile.mkdirs() - val dump = KlibDump.fromKlib(klibFile.singleFile, target.get().configurableName, KlibDumpFilters { - ignoredClasses.addAll(this@KotlinKlibAbiBuildTask.ignoredClasses.get()) - ignoredPackages.addAll(this@KotlinKlibAbiBuildTask.ignoredPackages.get()) - nonPublicMarkers.addAll(this@KotlinKlibAbiBuildTask.nonPublicMarkers.get()) - signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion.get() - }) + val dump = KlibDump.fromKlib(parameters.klibFile.singleFile, parameters.target.get().configurableName, + KLibDumpFilters { + ignoredClasses.addAll(parameters.ignoredClasses.get()) + ignoredPackages.addAll(parameters.ignoredPackages.get()) + nonPublicMarkers.addAll(parameters.nonPublicMarkers.get()) + signatureVersion = parameters.signatureVersion.get() + }) dump.saveTo(outputFile) } diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index 18920da4..4408a792 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -8,13 +8,17 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibDump import kotlinx.validation.api.klib.KlibTarget import kotlinx.validation.api.klib.saveTo -import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor import java.nio.file.Files import java.nio.file.StandardCopyOption +import javax.inject.Inject /** * Extracts dump for targets supported by the host compiler from a merged API dump stored in a project. @@ -23,7 +27,7 @@ import java.nio.file.StandardCopyOption * only supported tasks could be extracted for further validation. */ @CacheableTask -public abstract class KotlinKlibExtractAbiTask : DefaultTask() { +public abstract class KotlinKlibExtractAbiTask : WorkerAwareTaskBase() { /** * Merged KLib dump that should be filtered by this task. */ @@ -49,12 +53,40 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { @get:OutputFile public abstract val outputAbiFile: RegularFileProperty + @get:Inject + public abstract val executor: WorkerExecutor + private val rootDir = project.rootDir - @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - val inputFile = inputAbiFile.asFile.get() + val q = executor.classLoaderIsolation { + it.classpath.from(runtimeClasspath) + } + q.submit(KlibExtractAbiWorker::class.java) { params -> + params.inputAbiFile.set(inputAbiFile) + params.targetsToRemove.set(targetsToRemove) + params.strictValidation.set(strictValidation) + params.outputAbiFile.set(outputAbiFile) + params.rootDir.set(rootDir) + } + q.await() + } +} + +internal interface KlibExtractAbiParameters : WorkParameters { + val inputAbiFile: RegularFileProperty + val targetsToRemove: SetProperty + val strictValidation: Property + val outputAbiFile: RegularFileProperty + val rootDir: DirectoryProperty +} + +internal abstract class KlibExtractAbiWorker : WorkAction { + @OptIn(ExperimentalBCVApi::class) + override fun execute() { + val inputFile = parameters.inputAbiFile.asFile.get() + val rootDir = parameters.rootDir.asFile.get() if (!inputFile.exists()) { error("File with project's API declarations '${inputFile.relativeTo(rootDir)}' does not exist.\n" + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") @@ -64,16 +96,16 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { return } val dump = KlibDump.from(inputFile) - val unsupportedTargets = targetsToRemove.get().map(KlibTarget::targetName).toSet() + val unsupportedTargets = parameters.targetsToRemove.get().map(KlibTarget::targetName).toSet() // Filter out only unsupported files. // That ensures that target renaming will be caught and reported as a change. - if (unsupportedTargets.isNotEmpty() && strictValidation.get()) { + if (unsupportedTargets.isNotEmpty() && parameters.strictValidation.get()) { throw IllegalStateException( - "Validation could not be performed as some targets (namely, $targetsToRemove) are not available " + - "and the strictValidation mode was enabled." + "Validation could not be performed as some targets (namely, ${parameters.targetsToRemove}) " + + "are not available and the strictValidation mode was enabled." ) } dump.remove(unsupportedTargets.map(KlibTarget::parse)) - dump.saveTo(outputAbiFile.asFile.get()) + dump.saveTo(parameters.outputAbiFile.asFile.get()) } } diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index 889b33ec..990b248d 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -12,6 +12,10 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject /** * Task infers a possible KLib ABI dump for an unsupported target. @@ -24,7 +28,7 @@ import org.gradle.api.tasks.* * The resulting dump is then used as an inferred dump for the unsupported target. */ @CacheableTask -public abstract class KotlinKlibInferAbiTask : DefaultTask() { +public abstract class KotlinKlibInferAbiTask : WorkerAwareTaskBase() { /** * The name of a target to infer a dump for. */ @@ -50,15 +54,42 @@ public abstract class KotlinKlibInferAbiTask : DefaultTask() { @get:OutputFile public abstract val outputAbiFile: RegularFileProperty - @OptIn(ExperimentalBCVApi::class) + @get:Inject + public abstract val executor: WorkerExecutor + @TaskAction internal fun generate() { - val availableDumps = inputDumps.get().map { - it.target to it.dumpFile.asFile.get() + val q = executor.classLoaderIsolation { + it.classpath.from(runtimeClasspath) + } + q.submit(KlibInferAbiWorker::class.java) { params -> + params.target.set(target) + params.inputDumps.set(inputDumps.get().map { + KlibMetadataLocal(it.target, it.dumpFile.get().asFile) + }) + params.oldMergedKlibDump.set(oldMergedKlibDump) + params.outputAbiFile.set(outputAbiFile) + } + q.await() + } +} + +internal interface KlibInferAbiParameters : WorkParameters { + val target: Property + val inputDumps: SetProperty + val oldMergedKlibDump: RegularFileProperty + val outputAbiFile: RegularFileProperty +} + +internal abstract class KlibInferAbiWorker : WorkAction { + @OptIn(ExperimentalBCVApi::class) + override fun execute() { + val availableDumps = parameters.inputDumps.get().map { + it.target to it.dumpFile }.filter { it.second.exists() }.toMap() // 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()) + val matchingTargets = findMatchingTargets(availableDumps.keys, parameters.target.get()) // Load dumps that are a good fit for inference val supportedTargetDumps = matchingTargets.map { target -> val dumpFile = availableDumps[target]!! @@ -69,28 +100,35 @@ public abstract class KotlinKlibInferAbiTask : DefaultTask() { // Load an old dump, if any var image: KlibDump? = null - val oldDumpFile = oldMergedKlibDump.asFile.get() + val oldDumpFile = parameters.oldMergedKlibDump.asFile.get() if (oldDumpFile.exists()) { if (oldDumpFile.length() > 0L) { image = KlibDump.from(oldDumpFile) } else { + // TODO + /* logger.warn( "Project's ABI file exists, but empty: $oldDumpFile. " + "The file will be ignored during ABI dump inference for the unsupported target " + - target.get() + parameters.target.get() ) + + */ } } - inferAbi(target.get(), supportedTargetDumps, image).saveTo(outputAbiFile.asFile.get()) + inferAbi(parameters.target.get(), supportedTargetDumps, image).saveTo(parameters.outputAbiFile.asFile.get()) + // TODO: + /* logger.warn( - "An ABI dump for target ${target.get()} was inferred from the ABI generated for the following targets " + + "An ABI dump for target ${parameters.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()}. " + + "Inferred dump may not reflect an actual ABI for the target ${parameters.target.get()}. " + "It is recommended to regenerate the dump on the host supporting all required compilation target." ) + */ } private fun findMatchingTargets( diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 0a136338..2ac04504 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -6,17 +6,23 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibDump +import kotlinx.validation.api.klib.KlibTarget import kotlinx.validation.api.klib.saveTo -import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* +import org.gradle.workers.WorkAction +import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor +import java.io.File +import java.io.Serializable +import javax.inject.Inject /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ @CacheableTask -public abstract class KotlinKlibMergeAbiTask : DefaultTask() { +public abstract class KotlinKlibMergeAbiTask : WorkerAwareTaskBase() { /** * Dumps to merge. * @@ -31,16 +37,39 @@ public abstract class KotlinKlibMergeAbiTask : DefaultTask() { @get:OutputFile public abstract val mergedApiFile: RegularFileProperty - @OptIn(ExperimentalBCVApi::class) + @get:Inject + public abstract val executor: WorkerExecutor + @TaskAction internal fun merge() { + val q = executor.classLoaderIsolation { + it.classpath.from(runtimeClasspath) + } + q.submit(KlibMergeAbiWorker::class.java) { params -> + params.dumps.set(dumps.get().map { + KlibMetadataLocal(it.target, it.dumpFile.get().asFile) + }) + params.mergedApiFile.set(mergedApiFile) + } + q.await() + } +} + +internal interface KlibMergeAbiParameters : WorkParameters { + val dumps: SetProperty + val mergedApiFile: RegularFileProperty +} + +internal abstract class KlibMergeAbiWorker : WorkAction { + @OptIn(ExperimentalBCVApi::class) + override fun execute() { KlibDump().apply { - dumps.get().forEach { dump -> - val dumpFile = dump.dumpFile.asFile.get() + parameters.dumps.get().forEach { dump -> + val dumpFile = dump.dumpFile if (dumpFile.exists()) { merge(dumpFile, dump.target.configurableName) } } - }.saveTo(mergedApiFile.asFile.get()) + }.saveTo(parameters.mergedApiFile.asFile.get()) } } diff --git a/src/main/kotlin/WorkerAwareTaskBase.kt b/src/main/kotlin/WorkerAwareTaskBase.kt new file mode 100644 index 00000000..c34fe297 --- /dev/null +++ b/src/main/kotlin/WorkerAwareTaskBase.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2016-2024 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +package kotlinx.validation + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.tasks.Classpath + +public abstract class WorkerAwareTaskBase : DefaultTask() { + @get:Classpath + public abstract val runtimeClasspath: ConfigurableFileCollection +} From 8bb95e11e1133e16af4ae5d56c22815d8f3eb2b7 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Mon, 15 Jul 2024 15:39:40 +0200 Subject: [PATCH 2/7] Add all compileOnly dependencies into the workers classpath --- .../BinaryCompatibilityValidatorPlugin.kt | 17 +++++++++++++++-- src/main/kotlin/BuildTaskBase.kt | 9 +++++++++ src/main/kotlin/KotlinApiBuildTask.kt | 11 +++-------- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 13 ++++--------- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 2 +- 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index f7be13b8..9309c14c 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -14,10 +14,12 @@ import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.libsDirectory import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader import org.jetbrains.kotlin.library.abi.LibraryAbiReader import java.io.* +import kotlin.KotlinVersion @OptIn(ExperimentalBCVApi::class, ExperimentalLibraryAbiReader::class) public class BinaryCompatibilityValidatorPlugin : Plugin { @@ -666,8 +668,19 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< it.isCanBeConsumed = false it.isCanBeDeclared = true it.isVisible = false - it.defaultDependencies { - it.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.22")) + + it.defaultDependencies { dependencySet -> + dependencySet.add(project.dependencies.create("org.ow2.asm:asm:9.6")) + dependencySet.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) + + if (KotlinVersion.CURRENT.major > 2) { + dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:${KotlinVersion.CURRENT}")) + } else { + // use older 0.6.2 kotlinx metadata version for Kotlin < 2.0 + dependencySet.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2")) + } + + dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:${KotlinVersion.CURRENT}")) } } return project.configurations.register("bcv-rt-klib-cp-resolver") { diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index 4c129ca1..724b30fa 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -45,6 +45,15 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() { @get:Internal internal val projectName = project.name + + internal fun fillParams(params: BuildParametersBase) { + params.ignoredPackages.set(ignoredPackages) + params.nonPublicMarkers.set(nonPublicMarkers) + params.ignoredClasses.set(ignoredClasses) + params.publicPackages.set(publicPackages) + params.publicMarkers.set(publicMarkers) + params.publicClasses.set(publicClasses) + } } internal interface BuildParametersBase : WorkParameters { diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 5c66bf77..89dfcb67 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -51,16 +51,11 @@ public abstract class KotlinApiBuildTask @Inject constructor( @TaskAction internal fun generate() { - val q = executor.classLoaderIsolation { + val workQueue = executor.classLoaderIsolation { it.classpath.from(runtimeClasspath) } - q.submit(AbiBuildWorker::class.java) { params -> - params.ignoredClasses.set(ignoredClasses) - params.ignoredPackages.set(ignoredPackages) - params.nonPublicMarkers.set(nonPublicMarkers) - params.publicClasses.set(publicClasses) - params.publicPackages.set(publicPackages) - params.publicMarkers.set(publicMarkers) + workQueue.submit(AbiBuildWorker::class.java) { params -> + fillParams(params) params.inputJar.set(inputJar) params.inputClassesDirs.from(inputClassesDirs) diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index 9215bfeb..c97d357b 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -59,16 +59,11 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { @TaskAction internal fun generate() { - val q = executor.classLoaderIsolation { + val workQueue = executor.classLoaderIsolation { it.classpath.from(runtimeClasspath) } - q.submit(KlibAbiBuildWorker::class.java) { params -> - params.ignoredClasses.set(ignoredClasses) - params.ignoredPackages.set(ignoredPackages) - params.nonPublicMarkers.set(nonPublicMarkers) - params.publicClasses.set(publicClasses) - params.publicPackages.set(publicPackages) - params.publicMarkers.set(publicMarkers) + workQueue.submit(KlibAbiBuildWorker::class.java) { params -> + fillParams(params) params.klibFile.from(klibFile) params.target.set(target) @@ -93,7 +88,7 @@ internal abstract class KlibAbiBuildWorker : WorkAction outputFile.parentFile.mkdirs() val dump = KlibDump.fromKlib(parameters.klibFile.singleFile, parameters.target.get().configurableName, - KLibDumpFilters { + KlibDumpFilters { ignoredClasses.addAll(parameters.ignoredClasses.get()) ignoredPackages.addAll(parameters.ignoredPackages.get()) nonPublicMarkers.addAll(parameters.nonPublicMarkers.get()) diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index 4408a792..b6391748 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -92,7 +92,7 @@ internal abstract class KlibExtractAbiWorker : WorkAction Date: Mon, 15 Jul 2024 18:41:23 +0200 Subject: [PATCH 3/7] ~fix tests and review fixes --- build.gradle.kts | 2 + .../validation/test/KlibVerificationTests.kt | 10 ++--- .../BinaryCompatibilityValidatorPlugin.kt | 37 ++++++++----------- src/main/kotlin/KotlinKlibInferAbiTask.kt | 10 ++--- 4 files changed, 25 insertions(+), 34 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index aaa1a83e..f8b1dbbd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -188,6 +188,8 @@ testing { dependencies { implementation(files(createClasspathManifest)) + implementation(libs.kotlinx.metadata) + implementation(libs.kotlin.compiler.embeddable) implementation(gradleApi()) implementation(gradleTestKit()) } diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index 0e40c2d4..8941d307 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -11,8 +11,8 @@ import kotlinx.validation.api.resolve import kotlinx.validation.api.test import org.assertj.core.api.Assertions import org.gradle.testkit.runner.BuildResult -//import org.jetbrains.kotlin.konan.target.HostManager -//import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget import org.junit.Assume import org.junit.Test import java.io.File @@ -261,8 +261,7 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { @Test fun `apiDump should work for Apple-targets`() { - // Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) - // TODO + Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) val runner = test { baseProjectSetting() additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") @@ -275,8 +274,7 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { @Test fun `apiCheck should work for Apple-targets`() { - // Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) - // TODO + Assume.assumeTrue(HostManager().isEnabled(KonanTarget.MACOS_ARM64)) val runner = test { baseProjectSetting() additionalBuildConfig("/examples/gradle/configuration/appleTargets/targets.gradle.kts") diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 9309c14c..d8ca467f 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -14,7 +14,6 @@ import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.libsDirectory import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader import org.jetbrains.kotlin.library.abi.LibraryAbiReader @@ -661,8 +660,8 @@ private val Project.klibDumpFileName: String get() = "$name.klib.api" private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider { - val cp = - project.configurations.register("bcv-rt-klib-cp") { + val dependencyConfiguration = + project.configurations.create("bcv-rt-klib-cp") { it.description = "Runtime classpath for running binary-compatibility-validator." it.isCanBeResolved = false it.isCanBeConsumed = false @@ -670,16 +669,6 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< it.isVisible = false it.defaultDependencies { dependencySet -> - dependencySet.add(project.dependencies.create("org.ow2.asm:asm:9.6")) - dependencySet.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) - - if (KotlinVersion.CURRENT.major > 2) { - dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:${KotlinVersion.CURRENT}")) - } else { - // use older 0.6.2 kotlinx metadata version for Kotlin < 2.0 - dependencySet.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2")) - } - dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:${KotlinVersion.CURRENT}")) } } @@ -689,22 +678,28 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< it.isCanBeConsumed = false it.isCanBeDeclared = false it.isVisible = false - it.extendsFrom(cp.get()) + it.extendsFrom(dependencyConfiguration) } } private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider { - val cp = - project.configurations.register("bcv-rt-jvm-cp") { + val dependencyConfiguration = + project.configurations.create("bcv-rt-jvm-cp") { it.description = "Runtime classpath for running binary-compatibility-validator." it.isCanBeResolved = false it.isCanBeConsumed = false it.isCanBeDeclared = true it.isVisible = false - it.defaultDependencies { - it.add(project.dependencies.create("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2")) - it.add(project.dependencies.create("org.ow2.asm:asm:9.6")) - it.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) + it.defaultDependencies { dependencySet -> + dependencySet.add(project.dependencies.create("org.ow2.asm:asm:9.6")) + dependencySet.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) + + if (KotlinVersion.CURRENT.major >= 2) { + dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:${KotlinVersion.CURRENT}")) + } else { + // for older 0.6.2 kotlin versions (< 2.0.0) use nearest metadata library + dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:2.0.0")) + } } } return project.configurations.register("bcv-rt-jvm-cp-resolver") { @@ -713,6 +708,6 @@ private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider { + private val logger = LoggerFactory.getLogger(KlibInferAbiWorker::class.java) + @OptIn(ExperimentalBCVApi::class) override fun execute() { val availableDumps = parameters.inputDumps.get().map { @@ -105,22 +108,16 @@ internal abstract class KlibInferAbiWorker : WorkAction if (oldDumpFile.length() > 0L) { image = KlibDump.from(oldDumpFile) } else { - // TODO - /* logger.warn( "Project's ABI file exists, but empty: $oldDumpFile. " + "The file will be ignored during ABI dump inference for the unsupported target " + parameters.target.get() ) - - */ } } inferAbi(parameters.target.get(), supportedTargetDumps, image).saveTo(parameters.outputAbiFile.asFile.get()) - // TODO: - /* logger.warn( "An ABI dump for target ${parameters.target.get()} was inferred from the ABI generated for the following targets " + "as the former target is not supported by the host compiler: " + @@ -128,7 +125,6 @@ internal abstract class KlibInferAbiWorker : WorkAction "Inferred dump may not reflect an actual ABI for the target ${parameters.target.get()}. " + "It is recommended to regenerate the dump on the host supporting all required compilation target." ) - */ } private fun findMatchingTargets( From 6b0ab167de5e18b893ac8b9363e3911029c2df6d Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Mon, 15 Jul 2024 18:54:47 +0200 Subject: [PATCH 4/7] ~added tests with Kotlin 2.0.0 --- .../validation/test/DefaultConfigTests.kt | 23 +++++++++++ .../validation/test/KlibVerificationTests.kt | 16 ++++++++ .../gradle/base/withNativePluginK2.gradle.kts | 38 +++++++++++++++++++ .../gradle/base/withPluginK2.gradle.kts | 17 +++++++++ 4 files changed, 94 insertions(+) create mode 100644 src/functionalTest/resources/examples/gradle/base/withNativePluginK2.gradle.kts create mode 100644 src/functionalTest/resources/examples/gradle/base/withPluginK2.gradle.kts diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt index 1b68f12d..b9041f62 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt @@ -97,6 +97,29 @@ internal class DefaultConfigTests : BaseKotlinGradleTest() { } } + @Test + fun `apiCheck should succeed when public classes match api file with K2`() { + val runner = test { + buildGradleKts { + resolve("/examples/gradle/base/withPluginK2.gradle.kts") + } + kotlin("AnotherBuildConfig.kt") { + resolve("/examples/classes/AnotherBuildConfig.kt") + } + apiFile(projectName = rootProjectDir.name) { + resolve("/examples/classes/AnotherBuildConfig.dump") + } + + runner { + arguments.add(":apiCheck") + } + } + + runner.build().apply { + assertTaskSuccess(":apiCheck") + } + } + @Test fun `apiCheck should fail when public classes match api file ignoring case`() { Assume.assumeTrue(underlyingFsIsCaseSensitive()) diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index 8941d307..b47fe4e4 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -89,6 +89,22 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { checkKlibDump(runner.build(), "/examples/classes/TopLevelDeclarations.klib.with.linux.dump") } + @Test + fun `apiDump for native targets in K2`() { + val runner = test { + settingsGradleKts { + resolve("/examples/gradle/settings/settings-name-testproject.gradle.kts") + } + buildGradleKts { + resolve("/examples/gradle/base/withNativePluginK2.gradle.kts") + } + addToSrcSet("/examples/classes/TopLevelDeclarations.kt") + runApiDump() + } + + checkKlibDump(runner.build(), "/examples/classes/TopLevelDeclarations.klib.with.linux.dump") + } + @Test fun `apiCheck for native targets`() { val runner = test { diff --git a/src/functionalTest/resources/examples/gradle/base/withNativePluginK2.gradle.kts b/src/functionalTest/resources/examples/gradle/base/withNativePluginK2.gradle.kts new file mode 100644 index 00000000..8ec43d3c --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/base/withNativePluginK2.gradle.kts @@ -0,0 +1,38 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +plugins { + kotlin("multiplatform") version "2.0.0" + id("org.jetbrains.kotlinx.binary-compatibility-validator") +} + +repositories { + mavenCentral() +} + +kotlin { + linuxX64() + linuxArm64() + mingwX64() + androidNativeArm32() + androidNativeArm64() + androidNativeX64() + androidNativeX86() + + sourceSets { + val commonMain by getting + val commonTest by getting { + dependencies { + implementation(kotlin("stdlib")) + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + } +} + +apiValidation { + klib.enabled = true +} diff --git a/src/functionalTest/resources/examples/gradle/base/withPluginK2.gradle.kts b/src/functionalTest/resources/examples/gradle/base/withPluginK2.gradle.kts new file mode 100644 index 00000000..3fbc72e2 --- /dev/null +++ b/src/functionalTest/resources/examples/gradle/base/withPluginK2.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. + * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. + */ + +plugins { + kotlin("jvm") version "2.0.0" + id("org.jetbrains.kotlinx.binary-compatibility-validator") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) +} From 6565a51ac4a0f90315eb63add64b380a61a65aec Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Thu, 18 Jul 2024 18:15:25 +0200 Subject: [PATCH 5/7] ~review fixes --- api/binary-compatibility-validator.api | 2 - .../kotlin/kotlinx/validation/api/TestDsl.kt | 1 + .../BinaryCompatibilityValidatorPlugin.kt | 137 +++++++++++++----- src/main/kotlin/BuildTaskBase.kt | 7 +- src/main/kotlin/KotlinApiBuildTask.kt | 4 +- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 7 +- 6 files changed, 109 insertions(+), 49 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index e8396ee8..6586f4f0 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -64,7 +64,6 @@ public class kotlinx/validation/KlibValidationSettings { public abstract class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase { public fun ()V - public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getInputClassesDirs ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getInputDependencies ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; @@ -81,7 +80,6 @@ public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTas public abstract class kotlinx/validation/KotlinKlibAbiBuildTask : kotlinx/validation/BuildTaskBase { public fun ()V - public abstract fun getExecutor ()Lorg/gradle/workers/WorkerExecutor; public abstract fun getKlibFile ()Lorg/gradle/api/file/ConfigurableFileCollection; public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getSignatureVersion ()Lorg/gradle/api/provider/Property; diff --git a/src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt b/src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt index 43d96770..5bd29cc4 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/api/TestDsl.kt @@ -190,6 +190,7 @@ internal class AppendableScope(val filePath: String) { internal class Runner(withConfigurationCache: Boolean = true) { val arguments: MutableList = mutableListOf().apply { + add("--stacktrace") if (!koverEnabled && withConfigurationCache) { // Configuration cache is incompatible with javaagents being enabled for Gradle // See https://github.com/gradle/gradle/issues/25979 diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index d8ca467f..ac8d5e79 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -18,7 +18,7 @@ import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader import org.jetbrains.kotlin.library.abi.LibraryAbiReader import java.io.* -import kotlin.KotlinVersion +import java.util.* @OptIn(ExperimentalBCVApi::class, ExperimentalLibraryAbiReader::class) public class BinaryCompatibilityValidatorPlugin : Plugin { @@ -386,19 +386,11 @@ private class KlibValidationPipelineBuilder( val runtimeClasspath = project.prepareKlibValidationClasspath() - val klibMerge = project.mergeKlibsUmbrellaTask(klibDumpConfig, klibMergeDir) - klibMerge.configure { - it.runtimeClasspath.from(runtimeClasspath) - } - val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir) - klibMergeInferred.configure { - it.runtimeClasspath.from(runtimeClasspath) - } + val klibMerge = project.mergeKlibsUmbrellaTask(klibDumpConfig, klibMergeDir, runtimeClasspath) + val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir, runtimeClasspath) val klibDump = project.dumpKlibsTask(klibDumpConfig) - val klibExtractAbiForSupportedTargets = project.extractAbi(klibDumpConfig, klibApiDir, klibExtractedFileDir) - klibExtractAbiForSupportedTargets.configure { - it.runtimeClasspath.from(runtimeClasspath) - } + val klibExtractAbiForSupportedTargets = + project.extractAbi(klibDumpConfig, klibApiDir, klibExtractedFileDir, runtimeClasspath) val klibCheck = project.checkKlibsTask(klibDumpConfig) klibDump.configure { it.from.set(klibMergeInferred.flatMap { it.mergedApiFile }) @@ -436,7 +428,8 @@ private class KlibValidationPipelineBuilder( private fun Project.extractAbi( klibDumpConfig: TargetConfig, klibApiDir: Provider, - klibOutputDir: Provider + klibOutputDir: Provider, + runtimeClasspath: NamedDomainObjectProvider ) = project.task( klibDumpConfig.apiTaskName("ExtractForValidation") ) @@ -449,11 +442,13 @@ private class KlibValidationPipelineBuilder( targetsToRemove.addAll(unsupportedTargets()) inputAbiFile.fileProvider(klibApiDir.map { it.resolve(klibDumpFileName) }) outputAbiFile.fileProvider(klibOutputDir.map { it.resolve(klibDumpFileName) }) + this.runtimeClasspath.from(runtimeClasspath) } private fun Project.mergeInferredKlibsUmbrellaTask( klibDumpConfig: TargetConfig, klibMergeDir: Provider, + runtimeClasspath: NamedDomainObjectProvider ) = project.task( klibDumpConfig.apiTaskName("MergeInferred") ) @@ -463,16 +458,19 @@ private class KlibValidationPipelineBuilder( "different targets (including inferred dumps for unsupported targets) " + "into a single merged KLib ABI dump" mergedApiFile.fileProvider(klibMergeDir.map { it.resolve(klibDumpFileName) }) + this.runtimeClasspath.from(runtimeClasspath) } private fun Project.mergeKlibsUmbrellaTask( klibDumpConfig: TargetConfig, - klibMergeDir: Provider + klibMergeDir: Provider, + runtimeClasspath: NamedDomainObjectProvider ) = project.task(klibDumpConfig.apiTaskName("Merge")) { isEnabled = klibAbiCheckEnabled(project.name, extension) description = "Merges multiple KLib ABI dump files generated for " + "different targets into a single merged KLib ABI dump" mergedApiFile.fileProvider(klibMergeDir.map { it.resolve(klibDumpFileName) }) + this.runtimeClasspath.from(runtimeClasspath) } fun Project.bannedTargets(): Set { @@ -518,12 +516,13 @@ private class KlibValidationPipelineBuilder( // If a target is supported, the workflow is simple: create a dump, then merge it along with other dumps. if (targetSupported) { val buildTargetAbi = configureKlibCompilation( - mainCompilation, extension, targetConfig, - target, apiBuildDir + mainCompilation, + extension, + targetConfig, + target, + apiBuildDir, + runtimeClasspath ) - buildTargetAbi.configure { - it.runtimeClasspath.from(runtimeClasspath) - } generatedDumps.add( KlibDumpMetadata(target, objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) }) @@ -535,7 +534,6 @@ private class KlibValidationPipelineBuilder( val unsupportedTargetStub = mergeDependencyForUnsupportedTarget(targetConfig) mergeTask.configure { it.dependsOn(unsupportedTargetStub) - it.runtimeClasspath.from(runtimeClasspath) } // The actual merge will happen here, where we'll try to infer a dump for the unsupported target and merge // it with other supported target dumps. @@ -586,7 +584,8 @@ private class KlibValidationPipelineBuilder( extension: ApiValidationExtension, targetConfig: TargetConfig, target: KlibTarget, - apiBuildDir: Provider + apiBuildDir: Provider, + runtimeClasspath: NamedDomainObjectProvider ): TaskProvider { val projectName = project.name val buildTask = project.task(targetConfig.apiTaskName("Build")) { @@ -598,6 +597,7 @@ private class KlibValidationPipelineBuilder( klibFile.from(compilation.output.classesDirs) signatureVersion.set(extension.klib.signatureVersion) outputAbiFile.fileProvider(apiBuildDir.map { it.resolve(klibDumpFileName) }) + this.runtimeClasspath.from(runtimeClasspath) } return buildTask } @@ -660,6 +660,11 @@ private val Project.klibDumpFileName: String get() = "$name.klib.api" private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider { + val compilerVersion = project.objects.property(String::class.java).convention("2.0.0") + project.withKotlinPluginVersion { + compilerVersion.set(it) + } + val dependencyConfiguration = project.configurations.create("bcv-rt-klib-cp") { it.description = "Runtime classpath for running binary-compatibility-validator." @@ -667,11 +672,10 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< it.isCanBeConsumed = false it.isCanBeDeclared = true it.isVisible = false - - it.defaultDependencies { dependencySet -> - dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-compiler-embeddable:${KotlinVersion.CURRENT}")) - } } + + project.dependencies.addProvider(dependencyConfiguration.name, compilerVersion.map { version -> "org.jetbrains.kotlin:kotlin-compiler-embeddable:$version" }) + return project.configurations.register("bcv-rt-klib-cp-resolver") { it.description = "Resolve the runtime classpath for running binary-compatibility-validator." it.isCanBeResolved = true @@ -683,6 +687,14 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< } private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider { + val metadataDependencyVersion = project.objects.property(String::class.java).convention("2.0.0") + project.withKotlinPluginVersion { + if (!it.startsWith("1.")) { + metadataDependencyVersion.set(it) + } + } + + val dependencyConfiguration = project.configurations.create("bcv-rt-jvm-cp") { it.description = "Runtime classpath for running binary-compatibility-validator." @@ -690,18 +702,12 @@ private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider - dependencySet.add(project.dependencies.create("org.ow2.asm:asm:9.6")) - dependencySet.add(project.dependencies.create("org.ow2.asm:asm-tree:9.6")) - - if (KotlinVersion.CURRENT.major >= 2) { - dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:${KotlinVersion.CURRENT}")) - } else { - // for older 0.6.2 kotlin versions (< 2.0.0) use nearest metadata library - dependencySet.add(project.dependencies.create("org.jetbrains.kotlin:kotlin-metadata-jvm:2.0.0")) - } - } } + + project.dependencies.add(dependencyConfiguration.name, "org.ow2.asm:asm:9.6") + project.dependencies.add(dependencyConfiguration.name, "org.ow2.asm:asm-tree:9.6") + project.dependencies.addProvider(dependencyConfiguration.name, metadataDependencyVersion.map { version -> "org.jetbrains.kotlin:kotlin-metadata-jvm:$version" }) + return project.configurations.register("bcv-rt-jvm-cp-resolver") { it.description = "Resolve the runtime classpath for running binary-compatibility-validator." it.isCanBeResolved = true @@ -711,3 +717,60 @@ private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider Unit) { + // execute block if any of a Kotlin plugin applied + plugins.withId("kotlin") { + block(readVersion()) + } + plugins.withId("kotlin-multiplatform") { + block(readVersion()) + } + plugins.withId("kotlin-android") { + block(readVersion()) + } + plugins.withId("org.jetbrains.kotlin.jvm") { + block(readVersion()) + } + plugins.withId("org.jetbrains.kotlin.multiplatform") { + block(readVersion()) + } + plugins.withId("org.jetbrains.kotlin.android") { + block(readVersion()) + } +} + + +private fun Project.readVersion(): String { + val extension = extensions.findByName("kotlin") + if (extension == null) { + logger.warn("Binary compatibility plugin could not find Kotlin extension") + return "2.0.0" + } + + val inputStream = + extension::class.java.classLoader.getResourceAsStream("project.properties") + + if (inputStream == null) { + logger.warn("Binary compatibility plugin could not find resource 'project.properties'") + return "2.0.0" + } + + val properties = Properties() + try { + inputStream.use { + properties.load(it) + } + } catch (e: Exception) { + logger.warn("Binary compatibility plugin could not read resource 'project.properties'") + return "2.0.0" + } + + val version = properties.getProperty("project.version") + if (version == null) { + logger.warn("Binary compatibility plugin could not find property 'project.version' in resource 'project.properties'") + return "2.0.0" + } + + return version +} diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index 724b30fa..8e93bd61 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -9,10 +9,15 @@ import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.workers.WorkParameters +import org.gradle.workers.WorkerExecutor +import javax.inject.Inject public abstract class BuildTaskBase : WorkerAwareTaskBase() { private val extension = project.apiValidationExtensionOrNull + @get:Inject + internal abstract val executor: WorkerExecutor + private fun stringSetProperty(provider: ApiValidationExtension.() -> Set): SetProperty { return project.objects.setProperty(String::class.java).convention( project.provider { @@ -46,7 +51,7 @@ public abstract class BuildTaskBase : WorkerAwareTaskBase() { @get:Internal internal val projectName = project.name - internal fun fillParams(params: BuildParametersBase) { + internal fun fillCommonParams(params: BuildParametersBase) { params.ignoredPackages.set(ignoredPackages) params.nonPublicMarkers.set(nonPublicMarkers) params.ignoredClasses.set(ignoredClasses) diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 89dfcb67..87a780fb 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -46,8 +46,6 @@ public abstract class KotlinApiBuildTask @Inject constructor( @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val inputDependencies: ConfigurableFileCollection - @get:Inject - public abstract val executor: WorkerExecutor @TaskAction internal fun generate() { @@ -55,7 +53,7 @@ public abstract class KotlinApiBuildTask @Inject constructor( it.classpath.from(runtimeClasspath) } workQueue.submit(AbiBuildWorker::class.java) { params -> - fillParams(params) + fillCommonParams(params) params.inputJar.set(inputJar) params.inputClassesDirs.from(inputClassesDirs) diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index c97d357b..7c0861f9 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -11,8 +11,6 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* import org.gradle.workers.WorkAction -import org.gradle.workers.WorkerExecutor -import javax.inject.Inject /** * Generates a text file with a KLib ABI dump for a single klib. @@ -54,16 +52,13 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { @get:OutputFile public abstract val outputAbiFile: RegularFileProperty - @get:Inject - public abstract val executor: WorkerExecutor - @TaskAction internal fun generate() { val workQueue = executor.classLoaderIsolation { it.classpath.from(runtimeClasspath) } workQueue.submit(KlibAbiBuildWorker::class.java) { params -> - fillParams(params) + fillCommonParams(params) params.klibFile.from(klibFile) params.target.set(target) From 3aec73779a9ce0bf7937ab4d57b4a4568921ade4 Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Fri, 19 Jul 2024 13:13:42 +0200 Subject: [PATCH 6/7] ~review fixes --- .../BinaryCompatibilityValidatorPlugin.kt | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index ac8d5e79..46c1af4d 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -661,8 +661,10 @@ private val Project.klibDumpFileName: String private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider { val compilerVersion = project.objects.property(String::class.java).convention("2.0.0") - project.withKotlinPluginVersion { - compilerVersion.set(it) + project.withKotlinPluginVersion { version -> + if (version != null) { + compilerVersion.set(version) + } } val dependencyConfiguration = @@ -688,9 +690,9 @@ private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider< private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider { val metadataDependencyVersion = project.objects.property(String::class.java).convention("2.0.0") - project.withKotlinPluginVersion { - if (!it.startsWith("1.")) { - metadataDependencyVersion.set(it) + project.withKotlinPluginVersion { version -> + if (version != null && !version.startsWith("1.")) { + metadataDependencyVersion.set(version) } } @@ -718,17 +720,8 @@ private fun Project.prepareJvmValidationClasspath(): NamedDomainObjectProvider Unit) { +private fun Project.withKotlinPluginVersion(block: (String?) -> Unit) { // execute block if any of a Kotlin plugin applied - plugins.withId("kotlin") { - block(readVersion()) - } - plugins.withId("kotlin-multiplatform") { - block(readVersion()) - } - plugins.withId("kotlin-android") { - block(readVersion()) - } plugins.withId("org.jetbrains.kotlin.jvm") { block(readVersion()) } @@ -740,12 +733,15 @@ private fun Project.withKotlinPluginVersion(block: (String) -> Unit) { } } - -private fun Project.readVersion(): String { +/** + * Explicitly reading the version from the resources because the BCV and the Kotlin plugins may be located in different class loaders, + * and we cannot invoke functions from KGP directly. + */ +private fun Project.readVersion(): String? { val extension = extensions.findByName("kotlin") if (extension == null) { logger.warn("Binary compatibility plugin could not find Kotlin extension") - return "2.0.0" + return null } val inputStream = @@ -753,7 +749,7 @@ private fun Project.readVersion(): String { if (inputStream == null) { logger.warn("Binary compatibility plugin could not find resource 'project.properties'") - return "2.0.0" + return null } val properties = Properties() @@ -763,13 +759,13 @@ private fun Project.readVersion(): String { } } catch (e: Exception) { logger.warn("Binary compatibility plugin could not read resource 'project.properties'") - return "2.0.0" + return null } val version = properties.getProperty("project.version") if (version == null) { logger.warn("Binary compatibility plugin could not find property 'project.version' in resource 'project.properties'") - return "2.0.0" + return null } return version From 8ee3e18207b0f2d598d88b2afc31654fbd46c0ab Mon Sep 17 00:00:00 2001 From: "Sergey.Shanshin" Date: Fri, 19 Jul 2024 13:28:44 +0200 Subject: [PATCH 7/7] ~review fixes --- src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 46c1af4d..97d76475 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -662,9 +662,7 @@ private val Project.klibDumpFileName: String private fun Project.prepareKlibValidationClasspath(): NamedDomainObjectProvider { val compilerVersion = project.objects.property(String::class.java).convention("2.0.0") project.withKotlinPluginVersion { version -> - if (version != null) { - compilerVersion.set(version) - } + compilerVersion.set(version) } val dependencyConfiguration =