From 3b2dcb7df37ab9ca5d342d67b85e9146e36b4f2a Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 09:09:11 +0100 Subject: [PATCH 01/26] Cleanup build task --- api/binary-compatibility-validator.api | 2 +- .../BinaryCompatibilityValidatorPlugin.kt | 5 ++-- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 27 +++++-------------- .../kotlin/api/klib/KlibSignatureVersion.kt | 7 ++++- 4 files changed, 15 insertions(+), 26 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index ea5804ca..7f968c43 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -162,7 +162,7 @@ public final class kotlinx/validation/api/klib/KlibDumpKt { public static final fun saveTo (Lkotlinx/validation/api/klib/KlibDump;Ljava/io/File;)V } -public final class kotlinx/validation/api/klib/KlibSignatureVersion { +public final class kotlinx/validation/api/klib/KlibSignatureVersion : java/io/Serializable { public static final field Companion Lkotlinx/validation/api/klib/KlibSignatureVersion$Companion; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index a34191e8..bdad9935 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -614,9 +614,8 @@ private class KlibValidationPipelineBuilder( // 'group' is not specified deliberately, so it will be hidden from ./gradlew tasks description = "Builds Kotlin KLib ABI dump for 'main' compilations of $projectName. " + "Complementary task and shouldn't be called manually" - klibFile = project.files(project.provider { compilation.output.classesDirs }) - compilationDependencies = project.files(project.provider { compilation.compileDependencyFiles }) - signatureVersion = SerializableSignatureVersion(extension.klib.signatureVersion) + klibFile.from(project.provider { compilation.output.classesDirs }) + signatureVersion = extension.klib.signatureVersion outputApiFile = apiBuildDir.resolve(klibDumpFileName) } return buildTask diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index c591c632..788f6dfb 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -9,18 +9,10 @@ import kotlinx.validation.api.klib.KLibDumpFilters import kotlinx.validation.api.klib.KlibDump import kotlinx.validation.api.klib.KlibSignatureVersion import kotlinx.validation.api.klib.saveTo -import org.gradle.api.file.FileCollection +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction -import java.io.Serializable - -internal class SerializableSignatureVersion(val version: Int) : Serializable { - constructor(version: KlibSignatureVersion) : this(version.version) - - fun toKlibSignatureVersion(): KlibSignatureVersion = KlibSignatureVersion(version) -} /** * Generates a text file with a KLib ABI dump for a single klib. @@ -28,23 +20,16 @@ internal class SerializableSignatureVersion(val version: Int) : Serializable { internal abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { /** - * Path to a klib to dump. - */ - @InputFiles - lateinit var klibFile: FileCollection - - /** - * Bind this task with a klib compilation. + * Collection consisting of a single path to compiled klib (either file, or directory). */ - @InputFiles - lateinit var compilationDependencies: FileCollection + @get:InputFiles + val klibFile: ConfigurableFileCollection = project.objects.fileCollection() /** * Refer to [KlibValidationSettings.signatureVersion] for details. */ - @Optional @get:Input - var signatureVersion: SerializableSignatureVersion = SerializableSignatureVersion(KlibSignatureVersion.LATEST) + var signatureVersion: KlibSignatureVersion = KlibSignatureVersion.LATEST /** * Name of a target [klibFile] was compiled for. @@ -62,7 +47,7 @@ internal abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { ignoredClasses.addAll(this@KotlinKlibAbiBuildTask.ignoredClasses) ignoredPackages.addAll(this@KotlinKlibAbiBuildTask.ignoredPackages) nonPublicMarkers.addAll(this@KotlinKlibAbiBuildTask.nonPublicMarkers) - signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion.toKlibSignatureVersion() + signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion }) dump.saveTo(outputApiFile) diff --git a/src/main/kotlin/api/klib/KlibSignatureVersion.kt b/src/main/kotlin/api/klib/KlibSignatureVersion.kt index b9c50e55..45ad7c4d 100644 --- a/src/main/kotlin/api/klib/KlibSignatureVersion.kt +++ b/src/main/kotlin/api/klib/KlibSignatureVersion.kt @@ -5,7 +5,9 @@ package kotlinx.validation.api.klib -public class KlibSignatureVersion internal constructor(internal val version: Int) { +import java.io.Serializable + +public class KlibSignatureVersion internal constructor(internal val version: Int) : Serializable { public companion object { public fun of(value: Int): KlibSignatureVersion { @@ -16,6 +18,9 @@ public class KlibSignatureVersion internal constructor(internal val version: Int } public val LATEST: KlibSignatureVersion = KlibSignatureVersion(Int.MIN_VALUE) + + @JvmStatic + private val serialVersionUID: Long = 1 } override fun equals(other: Any?): Boolean { From 31311b242b955b21c70fb88bfa28f0ae1fd2856f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 10:34:15 +0100 Subject: [PATCH 02/26] Cleanup merge task --- .../BinaryCompatibilityValidatorPlugin.kt | 12 +++---- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 36 +++++++++---------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index bdad9935..210571f9 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -522,12 +522,12 @@ private class KlibValidationPipelineBuilder( if (targetSupported) { val buildTargetAbi = configureKlibCompilation(mainCompilation, extension, targetConfig, apiBuildDir) mergeTask.configure { - it.addInput(targetName, apiBuildDir) - it.dependsOn(buildTargetAbi) + it.dumps.add(GeneratedDump(targetName, + objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) } mergeInferredTask.configure { - it.addInput(targetName, apiBuildDir) - it.dependsOn(buildTargetAbi) + it.dumps.add(GeneratedDump(targetName, + objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) } return@configureEach } @@ -546,8 +546,8 @@ private class KlibValidationPipelineBuilder( apiBuildDir, supportedTargetsProvider ) mergeInferredTask.configure { - it.addInput(targetName, apiBuildDir) - it.dependsOn(proxy) + it.dumps.add(GeneratedDump(targetName, + objects.fileProperty().fileProvider(proxy.map { it.outputFile }))) } } mergeTask.configure { diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 27ce0f27..4ea4fd9b 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -8,30 +8,29 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibDump import kotlinx.validation.api.klib.saveTo import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* import java.io.File +import java.io.Serializable + +internal class GeneratedDump( + @get:Input + val targetName: String, + + @get:InputFiles + val dumpFile: RegularFileProperty +) : Serializable /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { - private val targetToFile = mutableMapOf() - @get:Internal internal val projectName = project.name - /** - * Set of targets whose dumps should be merged. - */ - @get:Input - val targets: Set - get() = targetToFile.keys - - // Required to enforce task rerun on klibs update - @Suppress("UNUSED") - @get:InputFiles - internal val inputDumps: Collection - get() = targetToFile.values + @get:Nested + val dumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) /** * A path to a resulting merged dump. @@ -45,18 +44,15 @@ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { @Input lateinit var dumpFileName: String - internal fun addInput(target: String, file: File) { - targetToFile[target] = file - } @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun merge() { KlibDump().apply { - targetToFile.forEach { (targetName, dumpDir) -> - val dumpFile = dumpDir.resolve(dumpFileName) + dumps.get().forEach { dump -> + val dumpFile = dump.dumpFile.asFile.get() if (dumpFile.exists()) { - merge(dumpFile, targetName) + merge(dumpFile, dump.targetName) } } }.saveTo(mergedFile) From 6081700725ece231a046ca9f4511383f75805cd6 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 10:47:06 +0100 Subject: [PATCH 03/26] Cleanup extract and infer tasks --- api/binary-compatibility-validator.api | 2 +- .../BinaryCompatibilityValidatorPlugin.kt | 17 ++++++++--------- .../KotlinKlibExtractSupportedTargetsAbiTask.kt | 7 +++---- ...otlinKlibInferAbiForUnsupportedTargetTask.kt | 17 +++++------------ src/main/kotlin/api/klib/KlibTarget.kt | 7 ++++++- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 7f968c43..4ec98df5 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -174,7 +174,7 @@ public final class kotlinx/validation/api/klib/KlibSignatureVersion$Companion { public final fun of (I)Lkotlinx/validation/api/klib/KlibSignatureVersion; } -public final class kotlinx/validation/api/klib/KlibTarget { +public final class kotlinx/validation/api/klib/KlibTarget : java/io/Serializable { public static final field Companion Lkotlinx/validation/api/klib/KlibTarget$Companion; public fun equals (Ljava/lang/Object;)Z public final fun getConfigurableName ()Ljava/lang/String; diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 210571f9..7ab3d859 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -453,7 +453,7 @@ private class KlibValidationPipelineBuilder( "the golden file stored in the project" group = "other" strictValidation = extension.klib.strictValidation - supportedTargets = supportedTargets() + supportedTargets.addAll(supportedTargets()) inputAbiFile = klibApiDir.get().resolve(klibDumpFileName) outputAbiFile = klibOutputDir.resolve(klibDumpFileName) val hasCompilableTargets = project.hasCompilableTargetsPredicate() @@ -542,7 +542,7 @@ private class KlibValidationPipelineBuilder( val proxy = unsupportedTargetDumpProxy( mainCompilation, klibApiDir, targetConfig, - extractUnderlyingTarget(currentTarget), + KlibTarget(extractUnderlyingTarget(currentTarget), currentTarget.targetName), apiBuildDir, supportedTargetsProvider ) mergeInferredTask.configure { @@ -570,7 +570,7 @@ private class KlibValidationPipelineBuilder( } // Compilable targets supported by the host compiler - private fun Project.supportedTargets(): Provider> { + private fun Project.supportedTargets(): Provider> { val banned = bannedTargets() // for testing only return project.provider { val hm = HostManager() @@ -584,7 +584,7 @@ private class KlibValidationPipelineBuilder( true } } - .map { KlibTarget(extractUnderlyingTarget(it), it.targetName).toString() } + .map { KlibTarget(extractUnderlyingTarget(it), it.targetName) } .toSet() } } @@ -638,9 +638,9 @@ private class KlibValidationPipelineBuilder( compilation: KotlinCompilation, klibApiDir: Provider, targetConfig: TargetConfig, - underlyingTarget: String, + unsupportedTarget: KlibTarget, apiBuildDir: File, - supportedTargets: Provider> + supportedTargets: Provider> ): TaskProvider { val targetName = targetConfig.targetName!! return project.task(targetConfig.apiTaskName("Infer")) { @@ -650,12 +650,11 @@ private class KlibValidationPipelineBuilder( description = "Try to infer the dump for unsupported target $targetName using dumps " + "generated for supported targets." group = "other" - this.supportedTargets = supportedTargets + this.supportedTargets.addAll(supportedTargets) inputImageFile = klibApiDir.get().resolve(klibDumpFileName) outputApiDir = apiBuildDir.toString() outputFile = apiBuildDir.resolve(klibDumpFileName) - unsupportedTargetName = targetConfig.targetName - unsupportedTargetCanonicalName = underlyingTarget + this.unsupportedTarget = unsupportedTarget dumpFileName = klibDumpFileName dependsOn(project.tasks.withType(KotlinKlibAbiBuildTask::class.java)) } diff --git a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt b/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt index 58298a18..f37587fb 100644 --- a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt @@ -6,11 +6,10 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibDump -import kotlinx.validation.api.klib.KlibAbiDumpMerger import kotlinx.validation.api.klib.KlibTarget import kotlinx.validation.api.klib.saveTo import org.gradle.api.DefaultTask -import org.gradle.api.provider.Provider +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* import java.io.File @@ -37,7 +36,7 @@ internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() * Provider returning targets supported by the host compiler. */ @get:Input - lateinit var supportedTargets: Provider> + val supportedTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) /** * Refer to [KlibValidationSettings.strictValidation] for details. @@ -52,7 +51,7 @@ internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() error("Project ABI file $inputAbiFile is empty.") } val dump = KlibDump.from(inputAbiFile) - val enabledTargets = supportedTargets.get().map { KlibTarget.parse(it).targetName } + val enabledTargets = supportedTargets.get().map(KlibTarget::targetName).toSet() // 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 } diff --git a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt b/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt index af0228f0..511ef203 100644 --- a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt @@ -8,7 +8,7 @@ package kotlinx.validation import kotlinx.validation.api.klib.* import kotlinx.validation.api.klib.TargetHierarchy import org.gradle.api.DefaultTask -import org.gradle.api.provider.Provider +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* import java.io.File @@ -30,13 +30,7 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask * The name of a target to infer a dump for. */ @Input - lateinit var unsupportedTargetName: String - - /** - * The name of a target to infer a dump for. - */ - @Input - lateinit var unsupportedTargetCanonicalName: String + lateinit var unsupportedTarget: KlibTarget /** * A root directory containing dumps successfully generated for each supported target. @@ -48,8 +42,8 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask /** * Set of all supported targets. */ - @Input - lateinit var supportedTargets: Provider> + @get:Input + val supportedTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. @@ -72,8 +66,7 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - val unsupportedTarget = KlibTarget(unsupportedTargetCanonicalName, unsupportedTargetName) - val supportedTargetNames = supportedTargets.get().map { KlibTarget.parse(it) }.toSet() + val supportedTargetNames = supportedTargets.get().toSet() // 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(supportedTargetNames, unsupportedTarget) diff --git a/src/main/kotlin/api/klib/KlibTarget.kt b/src/main/kotlin/api/klib/KlibTarget.kt index bfcc78da..37f30bfc 100644 --- a/src/main/kotlin/api/klib/KlibTarget.kt +++ b/src/main/kotlin/api/klib/KlibTarget.kt @@ -5,6 +5,8 @@ package kotlinx.validation.api.klib +import java.io.Serializable + /** * Target name consisting of two parts: a [configurableName] that could be configured by a user, and an [targetName] @@ -25,7 +27,7 @@ public class KlibTarget internal constructor( * Usually, it's the same name as [targetName]. */ public val configurableName: String -) { +) : Serializable { init { require(!configurableName.contains(".")) { "Configurable name can't contain the '.' character: $configurableName" @@ -54,6 +56,9 @@ public class KlibTarget internal constructor( } return KlibTarget(parts[0], parts[1]) } + + @JvmStatic + private val serialVersionUID: Long = 1 } From f968b33eda455f701f6ea646f8a285fe667cf691 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 15:03:59 +0100 Subject: [PATCH 04/26] Continue cleaning tasks up --- api/binary-compatibility-validator.api | 4 ++ src/main/kotlin/-Utils.kt | 55 ++++++++++++++ .../BinaryCompatibilityValidatorPlugin.kt | 71 +++++++------------ ...otlinKlibExtractSupportedTargetsAbiTask.kt | 2 - ...linKlibInferAbiForUnsupportedTargetTask.kt | 28 ++------ src/main/kotlin/KotlinKlibMergeAbiTask.kt | 15 +--- 6 files changed, 95 insertions(+), 80 deletions(-) create mode 100644 src/main/kotlin/-Utils.kt diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 4ec98df5..baea99d2 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -85,6 +85,10 @@ public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTas public final fun setProjectApiFile (Ljava/io/File;)V } +public final class kotlinx/validation/_UtilsKt { + public static final fun toKlibTarget (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;)Lkotlinx/validation/api/klib/KlibTarget; +} + public final class kotlinx/validation/api/ClassBinarySignature { public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lkotlinx/validation/api/AccessFlags;ZZLjava/util/List;)Lkotlinx/validation/api/ClassBinarySignature; public static synthetic fun copy$default (Lkotlinx/validation/api/ClassBinarySignature;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Lkotlinx/validation/api/AccessFlags;ZZLjava/util/List;ILjava/lang/Object;)Lkotlinx/validation/api/ClassBinarySignature; diff --git a/src/main/kotlin/-Utils.kt b/src/main/kotlin/-Utils.kt new file mode 100644 index 00000000..3482a4e1 --- /dev/null +++ b/src/main/kotlin/-Utils.kt @@ -0,0 +1,55 @@ +/* + * 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 kotlinx.validation.api.klib.KlibTarget +import kotlinx.validation.api.klib.konanTargetNameMapping +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +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.Serializable + +/** + * Converts [KotlinTarget] to a [KlibTarget]. + */ +public fun KotlinTarget.toKlibTarget(): KlibTarget = KlibTarget(extractUnderlyingTarget(this), targetName) + +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}") + } +} + +/** + * Information about a generated klib dump. + */ +public class GeneratedDump( + /** + * The target the dump was generated for. + */ + @get:Input + public val target: KlibTarget, + + /** + * Path to a resulting dump file. + */ + @get:InputFiles + public val dumpFile: RegularFileProperty +) : Serializable diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 7ab3d859..79d4ddb0 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -511,6 +511,23 @@ private class KlibValidationPipelineBuilder( val kotlin = project.kotlinMultiplatform val supportedTargetsProvider = supportedTargets() + val generatedDumps = objects.listProperty(GeneratedDump::class.java) + val inferredDumps = objects.listProperty(GeneratedDump::class.java) + mergeTask.configure { + it.dumps.addAll(generatedDumps) + it.doFirst { + if (supportedTargetsProvider.get().isEmpty()) { + throw IllegalStateException( + "KLib ABI dump/validation requires at least one enabled klib target, but none were found." + ) + } + } + } + mergeInferredTask.configure { + it.dumps.addAll(generatedDumps) + it.dumps.addAll(inferredDumps) + } + kotlin.targets.matching { it.emitsKlib }.configureEach { currentTarget -> val mainCompilation = currentTarget.mainCompilationOrNull ?: return@configureEach @@ -521,14 +538,9 @@ 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, apiBuildDir) - mergeTask.configure { - it.dumps.add(GeneratedDump(targetName, - objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) - } - mergeInferredTask.configure { - it.dumps.add(GeneratedDump(targetName, - objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) - } + generatedDumps.add(GeneratedDump( + currentTarget.toKlibTarget(), + objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) return@configureEach } // If the target is unsupported, the regular merge task will only depend on a task complaining about @@ -542,22 +554,12 @@ private class KlibValidationPipelineBuilder( val proxy = unsupportedTargetDumpProxy( mainCompilation, klibApiDir, targetConfig, - KlibTarget(extractUnderlyingTarget(currentTarget), currentTarget.targetName), - apiBuildDir, supportedTargetsProvider + currentTarget.toKlibTarget(), + apiBuildDir ) - mergeInferredTask.configure { - it.dumps.add(GeneratedDump(targetName, - objects.fileProperty().fileProvider(proxy.map { it.outputFile }))) - } - } - mergeTask.configure { - it.doFirst { - if (supportedTargetsProvider.get().isEmpty()) { - throw IllegalStateException( - "KLib ABI dump/validation requires at least one enabled klib target, but none were found." - ) - } - } + proxy.configure { it.dumps.addAll(generatedDumps) } + inferredDumps.add(GeneratedDump(currentTarget.toKlibTarget(), + objects.fileProperty().fileProvider(proxy.map { it.outputFile }))) } } @@ -584,7 +586,7 @@ private class KlibValidationPipelineBuilder( true } } - .map { KlibTarget(extractUnderlyingTarget(it), it.targetName) } + .map { it.toKlibTarget() } .toSet() } } @@ -639,8 +641,7 @@ private class KlibValidationPipelineBuilder( klibApiDir: Provider, targetConfig: TargetConfig, unsupportedTarget: KlibTarget, - apiBuildDir: File, - supportedTargets: Provider> + apiBuildDir: File ): TaskProvider { val targetName = targetConfig.targetName!! return project.task(targetConfig.apiTaskName("Infer")) { @@ -650,12 +651,9 @@ private class KlibValidationPipelineBuilder( description = "Try to infer the dump for unsupported target $targetName using dumps " + "generated for supported targets." group = "other" - this.supportedTargets.addAll(supportedTargets) inputImageFile = klibApiDir.get().resolve(klibDumpFileName) - outputApiDir = apiBuildDir.toString() outputFile = apiBuildDir.resolve(klibDumpFileName) this.unsupportedTarget = unsupportedTarget - dumpFileName = klibDumpFileName dependsOn(project.tasks.withType(KotlinKlibAbiBuildTask::class.java)) } } @@ -675,21 +673,6 @@ private val KotlinTarget.jvmBased: Boolean return platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm } -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}") - } -} - private val Project.kotlinMultiplatform get() = extensions.getByName("kotlin") as KotlinMultiplatformExtension diff --git a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt b/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt index f37587fb..7f4d850c 100644 --- a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt @@ -17,8 +17,6 @@ import java.io.File * Extracts dump for targets supported by the host compiler from a merged API dump stored in a project. */ internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() { - @get:Internal - internal val projectName = project.name /** * Merged KLib dump that should be filtered by this task. diff --git a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt b/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt index 511ef203..d69dd38c 100644 --- a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt @@ -23,27 +23,17 @@ import java.io.File * The resulting dump is then used as an inferred dump for the unsupported target. */ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask() { - @get:Internal - internal val projectName = project.name - /** * The name of a target to infer a dump for. */ @Input lateinit var unsupportedTarget: KlibTarget - /** - * A root directory containing dumps successfully generated for each supported target. - * It is assumed that this directory contains subdirectories named after targets. - */ - @InputFiles - lateinit var outputApiDir: String - /** * Set of all supported targets. */ - @get:Input - val supportedTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) + @Nested + val dumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. @@ -51,12 +41,6 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask @InputFiles lateinit var inputImageFile: File - /** - * The name of a dump file. - */ - @Input - lateinit var dumpFileName: String - /** * A path to an inferred dump file. */ @@ -66,13 +50,15 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - val supportedTargetNames = supportedTargets.get().toSet() + val availableDumps = dumps.get().map { + it.target to it.dumpFile.asFile.get() + }.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(supportedTargetNames, unsupportedTarget) + val matchingTargets = findMatchingTargets(availableDumps.keys, unsupportedTarget) // Load dumps that are a good fit for inference val supportedTargetDumps = matchingTargets.map { target -> - val dumpFile = File(outputApiDir).parentFile.resolve(target.configurableName).resolve(dumpFileName) + val dumpFile = availableDumps[target]!! KlibDump.from(dumpFile, target.configurableName).also { check(it.targets.single() == target) } diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 4ea4fd9b..25e6fce8 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -6,29 +6,18 @@ 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.ListProperty import org.gradle.api.tasks.* import java.io.File -import java.io.Serializable - -internal class GeneratedDump( - @get:Input - val targetName: String, - - @get:InputFiles - val dumpFile: RegularFileProperty -) : Serializable /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { - @get:Internal - internal val projectName = project.name - @get:Nested val dumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) @@ -52,7 +41,7 @@ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { dumps.get().forEach { dump -> val dumpFile = dump.dumpFile.asFile.get() if (dumpFile.exists()) { - merge(dumpFile, dump.targetName) + merge(dumpFile, dump.target.configurableName) } } }.saveTo(mergedFile) From 54c14e8164dfaf09044f40b9ca91764627f8c243 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 15:53:58 +0100 Subject: [PATCH 05/26] Renamed tasks and its properties --- .../BinaryCompatibilityValidatorPlugin.kt | 25 +++++++-------- ...AbiTask.kt => KotlinKlibExtractAbiTask.kt} | 9 ++++-- ...argetTask.kt => KotlinKlibInferAbiTask.kt} | 32 +++++++++---------- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 2 -- 4 files changed, 33 insertions(+), 35 deletions(-) rename src/main/kotlin/{KotlinKlibExtractSupportedTargetsAbiTask.kt => KotlinKlibExtractAbiTask.kt} (80%) rename src/main/kotlin/{KotlinKlibInferAbiForUnsupportedTargetTask.kt => KotlinKlibInferAbiTask.kt} (80%) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 79d4ddb0..f8be44d7 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -6,7 +6,6 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibTarget -import kotlinx.validation.api.klib.konanTargetNameMapping import org.gradle.api.* import org.gradle.api.plugins.* import org.gradle.api.provider.* @@ -14,8 +13,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.targets.js.KotlinWasmTargetType -import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.library.abi.ExperimentalLibraryAbiReader import org.jetbrains.kotlin.library.abi.LibraryAbiReader @@ -354,8 +351,8 @@ private const val KLIB_INFERRED_DUMPS_DIRECTORY = "klib-all" * multiple `ApiBuild` tasks ([KotlinKlibAbiBuildTask]); `klibApiMergeInferred` depends on the same tasks * as `klibApiMerge`, but also have additional dependencies responsible for inferring KLib ABI dumps for targets not * supported by the host compiler (`ApiInfer` tasks - * instantiating [KotlinKlibInferAbiForUnsupportedTargetTask]); - * - `klibApiExtractForValidation` ([KotlinKlibExtractSupportedTargetsAbiTask]) is responsible for filtering out all + * instantiating [KotlinKlibInferAbiTask]); + * - `klibApiExtractForValidation` ([KotlinKlibExtractAbiTask]) is responsible for filtering out all * currently unsupported targets from the golden image, so that it could be compared with a merged dump; * - each `ApiInfer` task depends on all regular `ApiBuild` tasks; it searches for targets * that are suitable to ABI dump inference, merges them and then mixes in all declarations specific to the unsupported @@ -444,7 +441,7 @@ private class KlibValidationPipelineBuilder( klibDumpConfig: TargetConfig, klibApiDir: Provider, klibOutputDir: File - ) = project.task( + ) = project.task( klibDumpConfig.apiTaskName("ExtractForValidation") ) { @@ -453,7 +450,7 @@ private class KlibValidationPipelineBuilder( "the golden file stored in the project" group = "other" strictValidation = extension.klib.strictValidation - supportedTargets.addAll(supportedTargets()) + requiredTargets.addAll(supportedTargets()) inputAbiFile = klibApiDir.get().resolve(klibDumpFileName) outputAbiFile = klibOutputDir.resolve(klibDumpFileName) val hasCompilableTargets = project.hasCompilableTargetsPredicate() @@ -557,9 +554,9 @@ private class KlibValidationPipelineBuilder( currentTarget.toKlibTarget(), apiBuildDir ) - proxy.configure { it.dumps.addAll(generatedDumps) } + proxy.configure { it.inputDumps.addAll(generatedDumps) } inferredDumps.add(GeneratedDump(currentTarget.toKlibTarget(), - objects.fileProperty().fileProvider(proxy.map { it.outputFile }))) + objects.fileProperty().fileProvider(proxy.map { it.outputApiFile }))) } } @@ -642,18 +639,18 @@ private class KlibValidationPipelineBuilder( targetConfig: TargetConfig, unsupportedTarget: KlibTarget, apiBuildDir: File - ): TaskProvider { + ): TaskProvider { val targetName = targetConfig.targetName!! - return project.task(targetConfig.apiTaskName("Infer")) { + return project.task(targetConfig.apiTaskName("Infer")) { isEnabled = klibAbiCheckEnabled(project.name, extension) val hasSourcesPredicate = compilation.hasAnySourcesPredicate() onlyIf { hasSourcesPredicate.get() } description = "Try to infer the dump for unsupported target $targetName using dumps " + "generated for supported targets." group = "other" - inputImageFile = klibApiDir.get().resolve(klibDumpFileName) - outputFile = apiBuildDir.resolve(klibDumpFileName) - this.unsupportedTarget = unsupportedTarget + oldMergedKlibDump = klibApiDir.get().resolve(klibDumpFileName) + outputApiFile = apiBuildDir.resolve(klibDumpFileName) + this.target = unsupportedTarget dependsOn(project.tasks.withType(KotlinKlibAbiBuildTask::class.java)) } } diff --git a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt similarity index 80% rename from src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt rename to src/main/kotlin/KotlinKlibExtractAbiTask.kt index 7f4d850c..947f0fd7 100644 --- a/src/main/kotlin/KotlinKlibExtractSupportedTargetsAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -15,8 +15,11 @@ import java.io.File /** * Extracts dump for targets supported by the host compiler from a merged API dump stored in a project. + * + * If some targets the dump stored in a project directory was generated for are not supported by the host compiler, + * only supported tasks could be extracted for further validation. */ -internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() { +internal abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. @@ -34,7 +37,7 @@ internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() * Provider returning targets supported by the host compiler. */ @get:Input - val supportedTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) + val requiredTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) /** * Refer to [KlibValidationSettings.strictValidation] for details. @@ -49,7 +52,7 @@ internal abstract class KotlinKlibExtractSupportedTargetsAbiTask : DefaultTask() error("Project ABI file $inputAbiFile is empty.") } val dump = KlibDump.from(inputAbiFile) - val enabledTargets = supportedTargets.get().map(KlibTarget::targetName).toSet() + val enabledTargets = requiredTargets.get().map(KlibTarget::targetName).toSet() // 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 } diff --git a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt similarity index 80% rename from src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt rename to src/main/kotlin/KotlinKlibInferAbiTask.kt index d69dd38c..995b9d96 100644 --- a/src/main/kotlin/KotlinKlibInferAbiForUnsupportedTargetTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -22,40 +22,40 @@ import java.io.File * from it and merged into the common ABI extracted previously. * The resulting dump is then used as an inferred dump for the unsupported target. */ -internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask() { +internal abstract class KotlinKlibInferAbiTask : DefaultTask() { /** * The name of a target to infer a dump for. */ @Input - lateinit var unsupportedTarget: KlibTarget + lateinit var target: KlibTarget /** - * Set of all supported targets. + * Newly created dumps that will be used for ABI inference. */ @Nested - val dumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) + val inputDumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. */ @InputFiles - lateinit var inputImageFile: File + lateinit var oldMergedKlibDump: File /** * A path to an inferred dump file. */ @OutputFile - lateinit var outputFile: File + lateinit var outputApiFile: File @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - val availableDumps = dumps.get().map { + val availableDumps = inputDumps.get().map { it.target to it.dumpFile.asFile.get() }.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, unsupportedTarget) + val matchingTargets = findMatchingTargets(availableDumps.keys, target) // Load dumps that are a good fit for inference val supportedTargetDumps = matchingTargets.map { target -> val dumpFile = availableDumps[target]!! @@ -66,25 +66,25 @@ internal abstract class KotlinKlibInferAbiForUnsupportedTargetTask : DefaultTask // Load an old dump, if any var image: KlibDump? = null - if (inputImageFile.exists()) { - if (inputImageFile.length() > 0L) { - image = KlibDump.from(inputImageFile) + if (oldMergedKlibDump.exists()) { + if (oldMergedKlibDump.length() > 0L) { + image = KlibDump.from(oldMergedKlibDump) } else { logger.warn( - "Project's ABI file exists, but empty: $inputImageFile. " + + "Project's ABI file exists, but empty: $oldMergedKlibDump. " + "The file will be ignored during ABI dump inference for the unsupported target " + - unsupportedTarget + target ) } } - inferAbi(unsupportedTarget, supportedTargetDumps, image).saveTo(outputFile) + inferAbi(target, supportedTargetDumps, image).saveTo(outputApiFile) logger.warn( - "An ABI dump for target $unsupportedTarget was inferred from the ABI generated for the following targets " + + "An ABI dump for target $target 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 $unsupportedTarget. " + + "Inferred dump may not reflect an actual ABI for the target $target. " + "It is recommended to regenerate the dump on the host supporting all required compilation target." ) } diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 25e6fce8..1836cd33 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -6,10 +6,8 @@ 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.ListProperty import org.gradle.api.tasks.* import java.io.File From cc3b34dc9e900d74b0419bc17e29abafef3948fe Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 17:06:49 +0100 Subject: [PATCH 06/26] Use properties for task inputs and outputs --- api/binary-compatibility-validator.api | 12 ++++-- .../BinaryCompatibilityValidatorPlugin.kt | 39 ++++++++++--------- src/main/kotlin/BuildTaskBase.kt | 5 --- src/main/kotlin/KotlinApiBuildTask.kt | 3 ++ src/main/kotlin/KotlinKlibAbiBuildTask.kt | 33 +++++++++------- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 28 ++++++------- src/main/kotlin/KotlinKlibInferAbiTask.kt | 38 +++++++++--------- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 23 ++++++----- 8 files changed, 97 insertions(+), 84 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index baea99d2..6c463883 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -31,19 +31,16 @@ public final class kotlinx/validation/BinaryCompatibilityValidatorPlugin : org/g } public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultTask { - public field outputApiFile Ljava/io/File; public fun ()V public final fun getIgnoredClasses ()Ljava/util/Set; public final fun getIgnoredPackages ()Ljava/util/Set; public final fun getNonPublicMarkers ()Ljava/util/Set; - public final fun getOutputApiFile ()Ljava/io/File; public final fun getPublicClasses ()Ljava/util/Set; public final fun getPublicMarkers ()Ljava/util/Set; public final fun getPublicPackages ()Ljava/util/Set; public final fun setIgnoredClasses (Ljava/util/Set;)V public final fun setIgnoredPackages (Ljava/util/Set;)V public final fun setNonPublicMarkers (Ljava/util/Set;)V - public final fun setOutputApiFile (Ljava/io/File;)V public final fun setPublicClasses (Ljava/util/Set;)V public final fun setPublicMarkers (Ljava/util/Set;)V public final fun setPublicPackages (Ljava/util/Set;)V @@ -55,6 +52,12 @@ public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi public abstract interface annotation class kotlinx/validation/ExternalApi : java/lang/annotation/Annotation { } +public final class kotlinx/validation/GeneratedDump : java/io/Serializable { + public fun (Lkotlinx/validation/api/klib/KlibTarget;Lorg/gradle/api/file/RegularFileProperty;)V + public final fun getDumpFile ()Lorg/gradle/api/file/RegularFileProperty; + public final fun getTarget ()Lkotlinx/validation/api/klib/KlibTarget; +} + public class kotlinx/validation/KlibValidationSettings { public fun ()V public final fun getEnabled ()Z @@ -67,12 +70,15 @@ public class kotlinx/validation/KlibValidationSettings { public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase { public field inputDependencies Lorg/gradle/api/file/FileCollection; + public field outputApiFile Ljava/io/File; public fun ()V public final fun getInputClassesDirs ()Lorg/gradle/api/file/FileCollection; public final fun getInputDependencies ()Lorg/gradle/api/file/FileCollection; public final fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; + public final fun getOutputApiFile ()Ljava/io/File; public final fun setInputClassesDirs (Lorg/gradle/api/file/FileCollection;)V public final fun setInputDependencies (Lorg/gradle/api/file/FileCollection;)V + public final fun setOutputApiFile (Ljava/io/File;)V } public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask { diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index f8be44d7..a0e63751 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -449,10 +449,10 @@ private class KlibValidationPipelineBuilder( description = "Prepare a reference KLib ABI file by removing all unsupported targets from " + "the golden file stored in the project" group = "other" - strictValidation = extension.klib.strictValidation + strictValidation.set(extension.klib.strictValidation) requiredTargets.addAll(supportedTargets()) - inputAbiFile = klibApiDir.get().resolve(klibDumpFileName) - outputAbiFile = klibOutputDir.resolve(klibDumpFileName) + inputAbiFile.set(klibApiDir.get().resolve(klibDumpFileName)) + outputAbiFile.set(klibOutputDir.resolve(klibDumpFileName)) val hasCompilableTargets = project.hasCompilableTargetsPredicate() onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } } @@ -468,8 +468,7 @@ private class KlibValidationPipelineBuilder( description = "Merges multiple KLib ABI dump files generated for " + "different targets (including inferred dumps for unsupported targets) " + "into a single merged KLib ABI dump" - dumpFileName = klibDumpFileName - mergedFile = klibMergeDir.resolve(klibDumpFileName) + mergedApiFile.set(klibMergeDir.resolve(klibDumpFileName)) val hasCompilableTargets = project.hasCompilableTargetsPredicate() onlyIf("There are no dumps to merge") { hasCompilableTargets.get() } } @@ -481,8 +480,7 @@ private class KlibValidationPipelineBuilder( isEnabled = klibAbiCheckEnabled(project.name, extension) description = "Merges multiple KLib ABI dump files generated for " + "different targets into a single merged KLib ABI dump" - dumpFileName = klibDumpFileName - mergedFile = klibMergeDir.resolve(klibDumpFileName) + mergedApiFile.set(klibMergeDir.resolve(klibDumpFileName)) val hasCompilableTargets = project.hasCompilableTargetsPredicate() onlyIf("There are no dumps to merge") { hasCompilableTargets.get() } } @@ -528,16 +526,17 @@ private class KlibValidationPipelineBuilder( kotlin.targets.matching { it.emitsKlib }.configureEach { currentTarget -> val mainCompilation = currentTarget.mainCompilationOrNull ?: return@configureEach + val target = currentTarget.toKlibTarget() val targetName = currentTarget.targetName val targetConfig = TargetConfig(project, extension, targetName, intermediateFilesConfig) val apiBuildDir = targetConfig.apiDir.map { project.layout.buildDirectory.asFile.get().resolve(it) }.get() val targetSupported = targetIsSupported(currentTarget) // 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, apiBuildDir) - generatedDumps.add(GeneratedDump( - currentTarget.toKlibTarget(), - objects.fileProperty().fileProvider(buildTargetAbi.map { it.outputApiFile }))) + val buildTargetAbi = configureKlibCompilation(mainCompilation, extension, targetConfig, + target, apiBuildDir) + generatedDumps.add(GeneratedDump(target, + objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputApiFile }) })) return@configureEach } // If the target is unsupported, the regular merge task will only depend on a task complaining about @@ -556,7 +555,9 @@ private class KlibValidationPipelineBuilder( ) proxy.configure { it.inputDumps.addAll(generatedDumps) } inferredDumps.add(GeneratedDump(currentTarget.toKlibTarget(), - objects.fileProperty().fileProvider(proxy.map { it.outputApiFile }))) + objects.fileProperty().also { + it.set(proxy.flatMap { it.outputApiFile }) + })) } } @@ -601,11 +602,11 @@ private class KlibValidationPipelineBuilder( compilation: KotlinCompilation, extension: ApiValidationExtension, targetConfig: TargetConfig, + target: KlibTarget, apiBuildDir: File ): TaskProvider { val projectName = project.name val buildTask = project.task(targetConfig.apiTaskName("Build")) { - target = targetConfig.targetName!! // Do not enable task for empty umbrella modules isEnabled = klibAbiCheckEnabled(projectName, extension) val hasSourcesPredicate = compilation.hasAnySourcesPredicate() @@ -613,9 +614,10 @@ private class KlibValidationPipelineBuilder( // 'group' is not specified deliberately, so it will be hidden from ./gradlew tasks description = "Builds Kotlin KLib ABI dump for 'main' compilations of $projectName. " + "Complementary task and shouldn't be called manually" + this.target.set(target) klibFile.from(project.provider { compilation.output.classesDirs }) - signatureVersion = extension.klib.signatureVersion - outputApiFile = apiBuildDir.resolve(klibDumpFileName) + signatureVersion.set(extension.klib.signatureVersion) + outputApiFile.set(apiBuildDir.resolve(klibDumpFileName)) } return buildTask } @@ -648,10 +650,9 @@ private class KlibValidationPipelineBuilder( description = "Try to infer the dump for unsupported target $targetName using dumps " + "generated for supported targets." group = "other" - oldMergedKlibDump = klibApiDir.get().resolve(klibDumpFileName) - outputApiFile = apiBuildDir.resolve(klibDumpFileName) - this.target = unsupportedTarget - dependsOn(project.tasks.withType(KotlinKlibAbiBuildTask::class.java)) + target.set(unsupportedTarget) + oldMergedKlibDump.set(klibApiDir.get().resolve(klibDumpFileName)) + outputApiFile.set(apiBuildDir.resolve(klibDumpFileName)) } } } diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index a042f2d8..a5861c88 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -8,15 +8,10 @@ package kotlinx.validation import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputFile -import java.io.File public abstract class BuildTaskBase : DefaultTask() { private val extension = project.apiValidationExtensionOrNull - @OutputFile - public lateinit var outputApiFile: File - private var _ignoredPackages: Set? = null @get:Input public var ignoredPackages : Set diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 70881de4..8f0a6a20 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -9,11 +9,14 @@ import kotlinx.validation.api.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.api.tasks.* +import java.io.File import java.util.jar.JarFile import javax.inject.Inject public open class KotlinApiBuildTask @Inject constructor( ) : BuildTaskBase() { + @OutputFile + public lateinit var outputApiFile: File @InputFiles @Optional diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index 788f6dfb..b0464d86 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -5,51 +5,56 @@ package kotlinx.validation -import kotlinx.validation.api.klib.KLibDumpFilters -import kotlinx.validation.api.klib.KlibDump -import kotlinx.validation.api.klib.KlibSignatureVersion -import kotlinx.validation.api.klib.saveTo +import kotlinx.validation.api.klib.* import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction /** * Generates a text file with a KLib ABI dump for a single klib. */ internal abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { + @get:OutputFile + public abstract val outputApiFile: RegularFileProperty /** * Collection consisting of a single path to compiled klib (either file, or directory). */ @get:InputFiles - val klibFile: ConfigurableFileCollection = project.objects.fileCollection() + public abstract val klibFile: ConfigurableFileCollection /** * Refer to [KlibValidationSettings.signatureVersion] for details. */ @get:Input - var signatureVersion: KlibSignatureVersion = KlibSignatureVersion.LATEST + public val signatureVersion: Property = + project.objects.property(KlibSignatureVersion::class.java) + .convention(KlibSignatureVersion.LATEST) /** - * Name of a target [klibFile] was compiled for. + * A target [klibFile] was compiled for. */ - @Input - lateinit var target: String + @get:Input + public abstract val target: Property @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - outputApiFile.delete() - outputApiFile.parentFile.mkdirs() + val outputFile = outputApiFile.asFile.get() + outputFile.delete() + outputFile.parentFile.mkdirs() - val dump = KlibDump.fromKlib(klibFile.singleFile, target, KLibDumpFilters { + val dump = KlibDump.fromKlib(klibFile.singleFile, target.get().configurableName, KLibDumpFilters { ignoredClasses.addAll(this@KotlinKlibAbiBuildTask.ignoredClasses) ignoredPackages.addAll(this@KotlinKlibAbiBuildTask.ignoredPackages) nonPublicMarkers.addAll(this@KotlinKlibAbiBuildTask.nonPublicMarkers) - signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion + signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion.get() }) - dump.saveTo(outputApiFile) + dump.saveTo(outputFile) } } diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index 947f0fd7..be14efd5 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -9,9 +9,10 @@ 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.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import java.io.File /** * Extracts dump for targets supported by the host compiler from a merged API dump stored in a project. @@ -24,45 +25,46 @@ internal abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. */ - @InputFiles - lateinit var inputAbiFile: File + @get:InputFile + public abstract val inputAbiFile: RegularFileProperty /** * A path to the resulting dump file. */ - @OutputFile - lateinit var outputAbiFile: File + @get:OutputFile + public abstract val outputAbiFile: RegularFileProperty /** - * Provider returning targets supported by the host compiler. + * List of the targets that the resulting dump should contain. */ @get:Input - val requiredTargets: ListProperty = project.objects.listProperty(KlibTarget::class.java) + public abstract val requiredTargets: ListProperty /** * Refer to [KlibValidationSettings.strictValidation] for details. */ - @Input - var strictValidation: Boolean = false + @get:Input + public val strictValidation: Property = project.objects.property(Boolean::class.java).convention(false) @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - if (inputAbiFile.length() == 0L) { + val inputFile = inputAbiFile.asFile.get() + if (inputFile.length() == 0L) { error("Project ABI file $inputAbiFile is empty.") } - val dump = KlibDump.from(inputAbiFile) + val dump = KlibDump.from(inputFile) val enabledTargets = requiredTargets.get().map(KlibTarget::targetName).toSet() // 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) { + if (targetsToRemove.isNotEmpty() && strictValidation.get()) { throw IllegalStateException( "Validation could not be performed as some targets are not available " + "and the strictValidation mode was enabled." ) } dump.remove(targetsToRemove) - dump.saveTo(outputAbiFile) + dump.saveTo(outputAbiFile.asFile.get()) } } diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index 995b9d96..21d632d3 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -8,9 +8,10 @@ package kotlinx.validation import kotlinx.validation.api.klib.* import kotlinx.validation.api.klib.TargetHierarchy import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import java.io.File /** * Task infers a possible KLib ABI dump for an unsupported target. @@ -26,26 +27,26 @@ internal abstract class KotlinKlibInferAbiTask : DefaultTask() { /** * The name of a target to infer a dump for. */ - @Input - lateinit var target: KlibTarget + @get:Input + public abstract val target: Property /** * Newly created dumps that will be used for ABI inference. */ - @Nested - val inputDumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) + @get:Nested + public abstract val inputDumps: ListProperty /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. */ - @InputFiles - lateinit var oldMergedKlibDump: File + @get:InputFiles + public abstract val oldMergedKlibDump: RegularFileProperty /** * A path to an inferred dump file. */ - @OutputFile - lateinit var outputApiFile: File + @get:OutputFile + public abstract val outputApiFile: RegularFileProperty @OptIn(ExperimentalBCVApi::class) @TaskAction @@ -55,7 +56,7 @@ internal abstract class KotlinKlibInferAbiTask : DefaultTask() { }.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) + val matchingTargets = findMatchingTargets(availableDumps.keys, target.get()) // Load dumps that are a good fit for inference val supportedTargetDumps = matchingTargets.map { target -> val dumpFile = availableDumps[target]!! @@ -66,25 +67,26 @@ internal abstract class KotlinKlibInferAbiTask : DefaultTask() { // Load an old dump, if any var image: KlibDump? = null - if (oldMergedKlibDump.exists()) { - if (oldMergedKlibDump.length() > 0L) { - image = KlibDump.from(oldMergedKlibDump) + val oldDumpFile = oldMergedKlibDump.asFile.get() + if (oldDumpFile.exists()) { + if (oldDumpFile.length() > 0L) { + image = KlibDump.from(oldDumpFile) } else { logger.warn( - "Project's ABI file exists, but empty: $oldMergedKlibDump. " + + "Project's ABI file exists, but empty: $oldDumpFile. " + "The file will be ignored during ABI dump inference for the unsupported target " + - target + target.get() ) } } - inferAbi(target, supportedTargetDumps, image).saveTo(outputApiFile) + inferAbi(target.get(), supportedTargetDumps, image).saveTo(outputApiFile.asFile.get()) logger.warn( - "An ABI dump for target $target was inferred from the ABI generated for the following targets " + + "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. " + + "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." ) } diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 1836cd33..45071ad7 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -8,29 +8,28 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibDump import kotlinx.validation.api.klib.saveTo import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.* -import java.io.File /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { - @get:Nested - val dumps: ListProperty = project.objects.listProperty(GeneratedDump::class.java) - /** - * A path to a resulting merged dump. + * Dumps to merge. + * + * If a file referred by [GeneratedDump.dumpFile] does not exist, it will be ignored and corresponding + * target will not be mentioned in the resulting merged dump. */ - @OutputFile - lateinit var mergedFile: File + @get:Nested + public abstract val dumps: ListProperty /** - * The name of a dump file. + * A path to a resulting merged dump. */ - @Input - lateinit var dumpFileName: String - + @get:OutputFile + public abstract val mergedApiFile: RegularFileProperty @OptIn(ExperimentalBCVApi::class) @TaskAction @@ -42,6 +41,6 @@ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { merge(dumpFile, dump.target.configurableName) } } - }.saveTo(mergedFile) + }.saveTo(mergedApiFile.asFile.get()) } } From a09b259424a6fef7a29b368522d733005712c0a8 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Tue, 26 Mar 2024 17:10:46 +0100 Subject: [PATCH 07/26] Make tasks public --- api/binary-compatibility-validator.api | 30 +++++++++++++++++++ .../BinaryCompatibilityValidatorPlugin.kt | 8 ++--- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 14 +++++---- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 15 +++++----- src/main/kotlin/KotlinKlibInferAbiTask.kt | 6 ++-- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 4 +-- 6 files changed, 55 insertions(+), 22 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 6c463883..348c10a9 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -91,6 +91,36 @@ public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTas public final fun setProjectApiFile (Ljava/io/File;)V } +public abstract class kotlinx/validation/KotlinKlibAbiBuildTask : kotlinx/validation/BuildTaskBase { + public fun ()V + 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 fun ()V + public abstract fun getInputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; + public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; + public abstract fun getRequiredTargets ()Lorg/gradle/api/provider/ListProperty; + public final fun getStrictValidation ()Lorg/gradle/api/provider/Property; +} + +public abstract class kotlinx/validation/KotlinKlibInferAbiTask : org/gradle/api/DefaultTask { + public fun ()V + public abstract fun getInputDumps ()Lorg/gradle/api/provider/ListProperty; + 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 fun ()V + public abstract fun getDumps ()Lorg/gradle/api/provider/ListProperty; + public abstract fun getMergedApiFile ()Lorg/gradle/api/file/RegularFileProperty; +} + 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/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index a0e63751..be0ef69a 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -536,7 +536,7 @@ private class KlibValidationPipelineBuilder( val buildTargetAbi = configureKlibCompilation(mainCompilation, extension, targetConfig, target, apiBuildDir) generatedDumps.add(GeneratedDump(target, - objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputApiFile }) })) + objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) })) return@configureEach } // If the target is unsupported, the regular merge task will only depend on a task complaining about @@ -556,7 +556,7 @@ private class KlibValidationPipelineBuilder( proxy.configure { it.inputDumps.addAll(generatedDumps) } inferredDumps.add(GeneratedDump(currentTarget.toKlibTarget(), objects.fileProperty().also { - it.set(proxy.flatMap { it.outputApiFile }) + it.set(proxy.flatMap { it.outputAbiFile }) })) } } @@ -617,7 +617,7 @@ private class KlibValidationPipelineBuilder( this.target.set(target) klibFile.from(project.provider { compilation.output.classesDirs }) signatureVersion.set(extension.klib.signatureVersion) - outputApiFile.set(apiBuildDir.resolve(klibDumpFileName)) + outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) } return buildTask } @@ -652,7 +652,7 @@ private class KlibValidationPipelineBuilder( group = "other" target.set(unsupportedTarget) oldMergedKlibDump.set(klibApiDir.get().resolve(klibDumpFileName)) - outputApiFile.set(apiBuildDir.resolve(klibDumpFileName)) + outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) } } } diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index b0464d86..9546ee54 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -17,12 +17,10 @@ import org.gradle.api.tasks.TaskAction /** * Generates a text file with a KLib ABI dump for a single klib. */ -internal abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { - @get:OutputFile - public abstract val outputApiFile: RegularFileProperty +public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { /** - * Collection consisting of a single path to compiled klib (either file, or directory). + * Collection consisting of a single path to a compiled klib (either file, or directory). */ @get:InputFiles public abstract val klibFile: ConfigurableFileCollection @@ -41,10 +39,16 @@ internal abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { @get:Input public abstract val target: Property + /** + * A path to the resulting dump file. + */ + @get:OutputFile + public abstract val outputAbiFile: RegularFileProperty + @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { - val outputFile = outputApiFile.asFile.get() + val outputFile = outputAbiFile.asFile.get() outputFile.delete() outputFile.parentFile.mkdirs() diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index be14efd5..ce49d3db 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -20,20 +20,13 @@ import org.gradle.api.tasks.* * If some targets the dump stored in a project directory was generated for are not supported by the host compiler, * only supported tasks could be extracted for further validation. */ -internal abstract class KotlinKlibExtractAbiTask : DefaultTask() { - +public abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. */ @get:InputFile public abstract val inputAbiFile: RegularFileProperty - /** - * A path to the resulting dump file. - */ - @get:OutputFile - public abstract val outputAbiFile: RegularFileProperty - /** * List of the targets that the resulting dump should contain. */ @@ -46,6 +39,12 @@ internal abstract class KotlinKlibExtractAbiTask : DefaultTask() { @get:Input public val strictValidation: Property = project.objects.property(Boolean::class.java).convention(false) + /** + * A path to the resulting dump file. + */ + @get:OutputFile + public abstract val outputAbiFile: RegularFileProperty + @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index 21d632d3..f2be0af0 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -23,7 +23,7 @@ import org.gradle.api.tasks.* * from it and merged into the common ABI extracted previously. * The resulting dump is then used as an inferred dump for the unsupported target. */ -internal abstract class KotlinKlibInferAbiTask : DefaultTask() { +public abstract class KotlinKlibInferAbiTask : DefaultTask() { /** * The name of a target to infer a dump for. */ @@ -46,7 +46,7 @@ internal abstract class KotlinKlibInferAbiTask : DefaultTask() { * A path to an inferred dump file. */ @get:OutputFile - public abstract val outputApiFile: RegularFileProperty + public abstract val outputAbiFile: RegularFileProperty @OptIn(ExperimentalBCVApi::class) @TaskAction @@ -80,7 +80,7 @@ internal abstract class KotlinKlibInferAbiTask : DefaultTask() { } } - inferAbi(target.get(), supportedTargetDumps, image).saveTo(outputApiFile.asFile.get()) + inferAbi(target.get(), supportedTargetDumps, image).saveTo(outputAbiFile.asFile.get()) logger.warn( "An ABI dump for target ${target.get()} was inferred from the ABI generated for the following targets " + diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 45071ad7..219d8c8f 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -15,7 +15,7 @@ import org.gradle.api.tasks.* /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ -internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { +public abstract class KotlinKlibMergeAbiTask : DefaultTask() { /** * Dumps to merge. * @@ -26,7 +26,7 @@ internal abstract class KotlinKlibMergeAbiTask : DefaultTask() { public abstract val dumps: ListProperty /** - * A path to a resulting merged dump. + * A path to a resulting merged dump file. */ @get:OutputFile public abstract val mergedApiFile: RegularFileProperty From 308224df7ec116b00de5b13193695dbedab12d36 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 27 Mar 2024 10:04:20 +0100 Subject: [PATCH 08/26] Use properties in CopyFile task --- .../BinaryCompatibilityValidatorPlugin.kt | 23 ++++++++----------- src/main/kotlin/CopyFile.kt | 14 ++++++----- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index be0ef69a..fee6561a 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -308,8 +308,8 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "other" description = "Syncs the API file for $projectName" - from = apiBuildDir.get().resolve(dumpFileName) - to = apiCheckDir.get().resolve(dumpFileName) + from.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) + to.fileProvider(apiCheckDir.map { it.resolve(dumpFileName) }) dependsOn(apiBuild) } @@ -385,12 +385,15 @@ private class KlibValidationPipelineBuilder( val klibMerge = project.mergeKlibsUmbrellaTask(klibDumpConfig, klibMergeDir) val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir) - val klibDump = project.dumpKlibsTask(klibDumpConfig, klibApiDir, klibMergeInferredDir) + val klibDump = project.dumpKlibsTask(klibDumpConfig) val klibExtractAbiForSupportedTargets = project.extractAbi(klibDumpConfig, klibApiDir, klibExtractedFileDir) val klibCheck = project.checkKlibsTask(klibDumpConfig, project.provider { klibExtractedFileDir }, klibMergeDir) - + klibDump.configure { + it.from.set(klibMergeInferred.flatMap { it.mergedApiFile }) + val filename = project.klibDumpFileName + it.to.fileProvider(klibApiDir.map { it.resolve(filename) }) + } commonApiDump.configure { it.dependsOn(klibDump) } - commonApiCheck.configure { it.dependsOn(klibCheck) } klibDump.configure { it.dependsOn(klibMergeInferred) } // Extraction task depends on supportedTargets() provider which returns a set of targets supported @@ -404,7 +407,7 @@ private class KlibValidationPipelineBuilder( klibCheck.configure { it.dependsOn(klibExtractAbiForSupportedTargets) } - + commonApiCheck.configure { it.dependsOn(klibCheck) } project.configureTargets(klibApiDir, klibMerge, klibMergeInferred) } @@ -423,16 +426,10 @@ private class KlibValidationPipelineBuilder( onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } } - private fun Project.dumpKlibsTask( - klibDumpConfig: TargetConfig, - klibApiDir: Provider, - klibMergeDir: File - ) = project.task(klibDumpConfig.apiTaskName("Dump")) { + private fun Project.dumpKlibsTask(klibDumpConfig: TargetConfig) = project.task(klibDumpConfig.apiTaskName("Dump")) { isEnabled = klibAbiCheckEnabled(project.name, extension) description = "Syncs the KLib ABI file for ${project.name}" group = "other" - from = klibMergeDir.resolve(klibDumpFileName) - to = klibApiDir.get().resolve(klibDumpFileName) val hasCompilableTargets = project.hasCompilableTargetsPredicate() onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } } diff --git a/src/main/kotlin/CopyFile.kt b/src/main/kotlin/CopyFile.kt index df9f6d79..645d7149 100644 --- a/src/main/kotlin/CopyFile.kt +++ b/src/main/kotlin/CopyFile.kt @@ -6,6 +6,8 @@ package kotlinx.validation import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction @@ -17,15 +19,15 @@ import java.nio.file.StandardCopyOption // and registers it as an output dependency. If there's another task reading from that particular // directory or writing into it, their input/output dependencies would clash and as long as // there will be no explicit ordering or dependencies between these tasks, Gradle would be unhappy. -internal open class CopyFile : DefaultTask() { - @InputFiles - lateinit var from: File +internal abstract class CopyFile : DefaultTask() { + @get:InputFile + abstract val from: RegularFileProperty - @OutputFile - lateinit var to: File + @get:OutputFile + abstract val to: RegularFileProperty @TaskAction fun copy() { - Files.copy(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.copy(from.asFile.get().toPath(), to.asFile.get().toPath(), StandardCopyOption.REPLACE_EXISTING) } } From 853fabbe712abc0a87e23b0a0a9ff45f255fc504 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Wed, 27 Mar 2024 10:13:53 +0100 Subject: [PATCH 09/26] Renamed a class holding target name and path to a dump --- api/binary-compatibility-validator.api | 2 +- src/main/kotlin/-Utils.kt | 2 +- src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt | 8 ++++---- src/main/kotlin/KotlinKlibInferAbiTask.kt | 2 +- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 348c10a9..4108f275 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -52,7 +52,7 @@ public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi public abstract interface annotation class kotlinx/validation/ExternalApi : java/lang/annotation/Annotation { } -public final class kotlinx/validation/GeneratedDump : java/io/Serializable { +public final class kotlinx/validation/KlibDumpMetadata : java/io/Serializable { public fun (Lkotlinx/validation/api/klib/KlibTarget;Lorg/gradle/api/file/RegularFileProperty;)V public final fun getDumpFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getTarget ()Lkotlinx/validation/api/klib/KlibTarget; diff --git a/src/main/kotlin/-Utils.kt b/src/main/kotlin/-Utils.kt index 3482a4e1..bb89d721 100644 --- a/src/main/kotlin/-Utils.kt +++ b/src/main/kotlin/-Utils.kt @@ -40,7 +40,7 @@ private fun extractUnderlyingTarget(target: KotlinTarget): String { /** * Information about a generated klib dump. */ -public class GeneratedDump( +public class KlibDumpMetadata( /** * The target the dump was generated for. */ diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index fee6561a..2cc168d8 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -503,8 +503,8 @@ private class KlibValidationPipelineBuilder( val kotlin = project.kotlinMultiplatform val supportedTargetsProvider = supportedTargets() - val generatedDumps = objects.listProperty(GeneratedDump::class.java) - val inferredDumps = objects.listProperty(GeneratedDump::class.java) + val generatedDumps = objects.listProperty(KlibDumpMetadata::class.java) + val inferredDumps = objects.listProperty(KlibDumpMetadata::class.java) mergeTask.configure { it.dumps.addAll(generatedDumps) it.doFirst { @@ -532,7 +532,7 @@ private class KlibValidationPipelineBuilder( if (targetSupported) { val buildTargetAbi = configureKlibCompilation(mainCompilation, extension, targetConfig, target, apiBuildDir) - generatedDumps.add(GeneratedDump(target, + generatedDumps.add(KlibDumpMetadata(target, objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) })) return@configureEach } @@ -551,7 +551,7 @@ private class KlibValidationPipelineBuilder( apiBuildDir ) proxy.configure { it.inputDumps.addAll(generatedDumps) } - inferredDumps.add(GeneratedDump(currentTarget.toKlibTarget(), + inferredDumps.add(KlibDumpMetadata(currentTarget.toKlibTarget(), objects.fileProperty().also { it.set(proxy.flatMap { it.outputAbiFile }) })) diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index f2be0af0..f56a50ea 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -34,7 +34,7 @@ public abstract class KotlinKlibInferAbiTask : DefaultTask() { * Newly created dumps that will be used for ABI inference. */ @get:Nested - public abstract val inputDumps: ListProperty + public abstract val inputDumps: ListProperty /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 219d8c8f..0dd5f3a1 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -19,11 +19,11 @@ public abstract class KotlinKlibMergeAbiTask : DefaultTask() { /** * Dumps to merge. * - * If a file referred by [GeneratedDump.dumpFile] does not exist, it will be ignored and corresponding + * If a file referred by [KlibDumpMetadata.dumpFile] does not exist, it will be ignored and corresponding * target will not be mentioned in the resulting merged dump. */ @get:Nested - public abstract val dumps: ListProperty + public abstract val dumps: ListProperty /** * A path to a resulting merged dump file. From a3f077c8b3d96701ccae7ccfbfeeb71166b4efc0 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 5 Apr 2024 09:25:46 +0200 Subject: [PATCH 10/26] Use all supported targets when extracting dump --- .../validation/test/KlibVerificationTests.kt | 47 +++++++++++++++++-- .../BinaryCompatibilityValidatorPlugin.kt | 9 ---- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index b8ee412c..a1950484 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -13,6 +13,7 @@ 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.Assume import org.junit.Test import java.io.File @@ -451,7 +452,7 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { } @Test - fun `klibCheck if all klib-targets are unavailable`() { + fun `klibCheck should not fail if all klib-targets are unavailable`() { val runner = test { baseProjectSetting() addToSrcSet("/examples/classes/TopLevelDeclarations.kt") @@ -468,10 +469,32 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { } } + runner.build().apply { + assertTaskSuccess(":klibApiCheck") + } + } + + @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") + } + runner { + arguments.add( + "-P$BANNED_TARGETS_PROPERTY_NAME=linuxArm64,linuxX64,mingwX64," + + "androidNativeArm32,androidNativeArm64,androidNativeX64,androidNativeX86" + ) + arguments.add(":klibApiCheck") + } + } + runner.buildAndFail().apply { - Assertions.assertThat(output).contains( - "KLib ABI dump/validation requires at least one enabled klib target, but none were found." - ) + assertTaskFailure(":klibApiExtractForValidation") } } @@ -720,4 +743,20 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { } assertApiCheckPassed(runner.build()) } + + @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") + } + runApiCheck() + } + runner.buildAndFail().apply { + assertTaskFailure(":klibApiCheck") + } + } } diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 2cc168d8..db1dceaa 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -502,18 +502,10 @@ private class KlibValidationPipelineBuilder( ) { val kotlin = project.kotlinMultiplatform - val supportedTargetsProvider = supportedTargets() val generatedDumps = objects.listProperty(KlibDumpMetadata::class.java) val inferredDumps = objects.listProperty(KlibDumpMetadata::class.java) mergeTask.configure { it.dumps.addAll(generatedDumps) - it.doFirst { - if (supportedTargetsProvider.get().isEmpty()) { - throw IllegalStateException( - "KLib ABI dump/validation requires at least one enabled klib target, but none were found." - ) - } - } } mergeInferredTask.configure { it.dumps.addAll(generatedDumps) @@ -573,7 +565,6 @@ private class KlibValidationPipelineBuilder( val hm = HostManager() project.kotlinMultiplatform.targets.matching { it.emitsKlib } .asSequence() - .filter { it.mainCompilationOrNull?.hasAnySources() == true } .filter { if (it is KotlinNativeTarget) { hm.isEnabled(it.konanTarget) && it.targetName !in banned From 2d260c4874991cfbb1e01df556ea2172b949493a Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 5 Apr 2024 11:27:19 +0200 Subject: [PATCH 11/26] Avoid using hasAnySourcesPredicate-predicate to filter klib-related tasks --- .../validation/test/KlibVerificationTests.kt | 25 ++++- src/main/kotlin/-Utils.kt | 2 + .../BinaryCompatibilityValidatorPlugin.kt | 51 +++------- src/main/kotlin/KotlinApiCompareTask.kt | 93 +++++++++++++++++++ src/main/kotlin/KotlinKlibAbiBuildTask.kt | 2 + src/main/kotlin/KotlinKlibExtractAbiTask.kt | 3 +- src/main/kotlin/{CopyFile.kt => SyncFile.kt} | 16 +++- 7 files changed, 144 insertions(+), 48 deletions(-) rename src/main/kotlin/{CopyFile.kt => SyncFile.kt} (69%) diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index a1950484..fd0d5e8a 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -688,12 +688,29 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { runApiDump() } - runner.build().apply { + runner.withDebug(true).build().apply { assertTaskSkipped(":klibApiDump") } assertFalse(runner.projectDir.resolve("api").exists()) } + @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") + } + runApiDump() + } + + runner.build().apply { + assertTaskSuccess(":klibApiDump") + } + 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 { @@ -720,8 +737,7 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { val runner = test { baseProjectSetting() additionalBuildConfig("/examples/gradle/configuration/generatedSources/generatedSources.gradle.kts") - // TODO: enable configuration cache back when we start skipping tasks correctly - runner(withConfigurationCache = false) { + runner { arguments.add(":apiDump") } } @@ -736,8 +752,7 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { abiFile(projectName = "testproject") { resolve("/examples/classes/GeneratedSources.klib.dump") } - // TODO: enable configuration cache back when we start skipping tasks correctly - runner(withConfigurationCache = false) { + runner { arguments.add(":apiCheck") } } diff --git a/src/main/kotlin/-Utils.kt b/src/main/kotlin/-Utils.kt index bb89d721..dbeff2ed 100644 --- a/src/main/kotlin/-Utils.kt +++ b/src/main/kotlin/-Utils.kt @@ -10,6 +10,7 @@ import kotlinx.validation.api.klib.konanTargetNameMapping import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.SkipWhenEmpty import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget @@ -51,5 +52,6 @@ public class KlibDumpMetadata( * Path to a resulting dump file. */ @get:InputFiles + @get:SkipWhenEmpty public val dumpFile: RegularFileProperty ) : Serializable diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index db1dceaa..ba536663 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -304,7 +304,7 @@ private fun Project.configureCheckTasks( } val dumpFileName = project.jvmDumpFileName - val apiDump = task(targetConfig.apiTaskName("Dump")) { + val apiDump = task(targetConfig.apiTaskName("Dump")) { isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "other" description = "Syncs the API file for $projectName" @@ -343,7 +343,7 @@ private const val KLIB_INFERRED_DUMPS_DIRECTORY = "klib-all" * Here's how different tasks depend on each other: * - `klibApiCheck` ([KotlinApiCompareTask]) depends on `klibApiMerge` and `klibApiExtractForValidation` tasks; * this task itself does not perform anything except comparing the result of a merge, with a preprocessed golden value; - * - `klibApiDump` ([CopyFile]) depends on `klibApiMergeInferred` and simply moves the merged ABI dump into a configured + * - `klibApiDump` ([SyncFile]) depends on `klibApiMergeInferred` and simply moves the merged ABI dump into a configured * api directory within a project; * - `klibApiMerge` and `klibApiMergeInferred` are both [KotlinKlibMergeAbiTask] instances merging multiple individual * KLib ABI dumps into a single merged dump file; these tasks differs only by their dependencies and input dump files @@ -387,51 +387,36 @@ private class KlibValidationPipelineBuilder( val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir) val klibDump = project.dumpKlibsTask(klibDumpConfig) val klibExtractAbiForSupportedTargets = project.extractAbi(klibDumpConfig, klibApiDir, klibExtractedFileDir) - val klibCheck = project.checkKlibsTask(klibDumpConfig, project.provider { klibExtractedFileDir }, klibMergeDir) + val klibCheck = project.checkKlibsTask(klibDumpConfig) klibDump.configure { it.from.set(klibMergeInferred.flatMap { it.mergedApiFile }) val filename = project.klibDumpFileName it.to.fileProvider(klibApiDir.map { it.resolve(filename) }) } - commonApiDump.configure { it.dependsOn(klibDump) } - - klibDump.configure { it.dependsOn(klibMergeInferred) } - // Extraction task depends on supportedTargets() provider which returns a set of targets supported - // by the host compiler and having some sources. A set of sources for a target may change until the actual - // klib compilation will take place, so we may observe incorrect value if check source sets earlier. - // Merge task already depends on compilations, so instead of adding each compilation task to the extraction's - // dependency set, we can depend on the merge task itself. - klibExtractAbiForSupportedTargets.configure { - it.dependsOn(klibMerge) - } klibCheck.configure { - it.dependsOn(klibExtractAbiForSupportedTargets) + it.projectApiFile.set(klibExtractAbiForSupportedTargets.flatMap { it.outputAbiFile }) + it.generatedApiFile.set(klibMerge.flatMap { it.mergedApiFile }) } + commonApiDump.configure { it.dependsOn(klibDump) } commonApiCheck.configure { it.dependsOn(klibCheck) } project.configureTargets(klibApiDir, klibMerge, klibMergeInferred) } - private fun Project.checkKlibsTask( - klibDumpConfig: TargetConfig, - klibApiDir: Provider, - klibMergeDir: File - ) = project.task(klibDumpConfig.apiTaskName("Check")) { + private fun Project.checkKlibsTask(klibDumpConfig: TargetConfig) + = project.task(klibDumpConfig.apiTaskName("Check")) { isEnabled = klibAbiCheckEnabled(project.name, extension) group = "verification" - description = "Checks signatures of a public KLib ABI against the golden value in ABI folder for " + - project.name - projectApiFile = klibApiDir.get().resolve(klibDumpFileName) - generatedApiFile = klibMergeDir.resolve(klibDumpFileName) - val hasCompilableTargets = project.hasCompilableTargetsPredicate() - onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } + description = "Checks signatures of a public KLib ABI against the golden value in ABI folder for ${project.name}" } - private fun Project.dumpKlibsTask(klibDumpConfig: TargetConfig) = project.task(klibDumpConfig.apiTaskName("Dump")) { + private fun Project.dumpKlibsTask(klibDumpConfig: TargetConfig) = project.task(klibDumpConfig.apiTaskName("Dump")) { isEnabled = klibAbiCheckEnabled(project.name, extension) description = "Syncs the KLib ABI file for ${project.name}" group = "other" - val hasCompilableTargets = project.hasCompilableTargetsPredicate() - onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } + onlyIf { + it as SyncFile + it.to.get().asFile.exists() || it.from.get().asFile.exists() + } } private fun Project.extractAbi( @@ -450,8 +435,6 @@ private class KlibValidationPipelineBuilder( requiredTargets.addAll(supportedTargets()) inputAbiFile.set(klibApiDir.get().resolve(klibDumpFileName)) outputAbiFile.set(klibOutputDir.resolve(klibDumpFileName)) - val hasCompilableTargets = project.hasCompilableTargetsPredicate() - onlyIf("There are no klibs compiled for the project") { hasCompilableTargets.get() } } private fun Project.mergeInferredKlibsUmbrellaTask( @@ -466,8 +449,6 @@ private class KlibValidationPipelineBuilder( "different targets (including inferred dumps for unsupported targets) " + "into a single merged KLib ABI dump" mergedApiFile.set(klibMergeDir.resolve(klibDumpFileName)) - val hasCompilableTargets = project.hasCompilableTargetsPredicate() - onlyIf("There are no dumps to merge") { hasCompilableTargets.get() } } private fun Project.mergeKlibsUmbrellaTask( @@ -478,8 +459,6 @@ private class KlibValidationPipelineBuilder( description = "Merges multiple KLib ABI dump files generated for " + "different targets into a single merged KLib ABI dump" mergedApiFile.set(klibMergeDir.resolve(klibDumpFileName)) - val hasCompilableTargets = project.hasCompilableTargetsPredicate() - onlyIf("There are no dumps to merge") { hasCompilableTargets.get() } } fun Project.bannedTargets(): Set { @@ -597,8 +576,6 @@ private class KlibValidationPipelineBuilder( val buildTask = project.task(targetConfig.apiTaskName("Build")) { // Do not enable task for empty umbrella modules isEnabled = klibAbiCheckEnabled(projectName, extension) - val hasSourcesPredicate = compilation.hasAnySourcesPredicate() - onlyIf { hasSourcesPredicate.get() } // 'group' is not specified deliberately, so it will be hidden from ./gradlew tasks description = "Builds Kotlin KLib ABI dump for 'main' compilations of $projectName. " + "Complementary task and shouldn't be called manually" diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 72399b6a..87a650c3 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -75,3 +75,96 @@ public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { return diff.joinToString("\n") } } + +// TODO: decide what to do with to old compare task +internal abstract class KotlinApiCompareLazyTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { + + @get:SkipWhenEmpty + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val projectApiFile: RegularFileProperty + + @get:SkipWhenEmpty + @get:InputFiles + abstract val generatedApiFile: RegularFileProperty + + private val projectName = project.name + + private val rootDir = project.rootProject.rootDir + + @TaskAction + internal fun verify() { + val projectApiDir = projectApiFile.get().asFile.parentFile + if (!projectApiDir.exists()) { + error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") + } + val buildApiDir = generatedApiFile.get().asFile.parentFile + if (!buildApiDir.exists()) { + error("Expected folder with generate API declarations '$buildApiDir' does not exist.") + } + val subject = projectName + + /* + * 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 sensitvity. + * To workaround that, we replace paths we are looking for the same paths that + * actually exist on FS. + */ + fun caseInsensitiveMap() = TreeMap { rp, rp2 -> + rp.compareTo(rp2, true) + } + + val apiBuildDirFiles = caseInsensitiveMap() + val expectedApiFiles = caseInsensitiveMap() + + objects.fileTree().from(buildApiDir).visit { file -> + apiBuildDirFiles[file.name] = file.relativePath + } + objects.fileTree().from(projectApiDir).visit { file -> + expectedApiFiles[file.name] = file.relativePath + } + + if (!expectedApiFiles.containsKey(projectApiFile.get().asFile.name)) { + error("File ${projectApiFile.get().asFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + + ":$subject:apiDump task to generate one") + } + if (!apiBuildDirFiles.containsKey(generatedApiFile.get().asFile.name)) { + error("File ${generatedApiFile.get().asFile.name} is missing from dump results.") + } + + // Normalize case-sensitivity + val expectedApiDeclaration = expectedApiFiles.getValue(projectApiFile.get().asFile.name) + val actualApiDeclaration = apiBuildDirFiles.getValue(generatedApiFile.get().asFile.name) + val diffSet = mutableSetOf() + val expectedFile = expectedApiDeclaration.getFile(projectApiDir) + val actualFile = actualApiDeclaration.getFile(buildApiDir) + val diff = compareFiles(expectedFile, actualFile) + if (diff != null) diffSet.add(diff) + if (diffSet.isNotEmpty()) { + val diffText = diffSet.joinToString("\n\n") + error("API check failed for project $subject.\n$diffText\n\n You can run :$subject:apiDump task to overwrite API declarations") + } + } + + private fun File.relativeDirPath(): String { + return toRelativeString(rootDir) + File.separator + } + + private fun compareFiles(checkFile: File, builtFile: File): String? { + val checkText = checkFile.readText() + val builtText = builtFile.readText() + + // We don't compare full text because newlines on Windows & Linux/macOS are different + val checkLines = checkText.lines() + val builtLines = builtText.lines() + if (checkLines == builtLines) + return null + + val patch = DiffUtils.diff(checkLines, builtLines) + val diff = UnifiedDiffUtils.generateUnifiedDiff(checkFile.toString(), builtFile.toString(), checkLines, patch, 3) + return diff.joinToString("\n") + } +} diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index 9546ee54..1a115118 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -12,6 +12,7 @@ import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.api.tasks.TaskAction /** @@ -23,6 +24,7 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { * Collection consisting of a single path to a compiled klib (either file, or directory). */ @get:InputFiles + @get:SkipWhenEmpty public abstract val klibFile: ConfigurableFileCollection /** diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index ce49d3db..1c9d732f 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -24,7 +24,7 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. */ - @get:InputFile + @get:InputFiles public abstract val inputAbiFile: RegularFileProperty /** @@ -49,6 +49,7 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { @TaskAction internal fun generate() { val inputFile = inputAbiFile.asFile.get() + if (!inputFile.exists()) return if (inputFile.length() == 0L) { error("Project ABI file $inputAbiFile is empty.") } diff --git a/src/main/kotlin/CopyFile.kt b/src/main/kotlin/SyncFile.kt similarity index 69% rename from src/main/kotlin/CopyFile.kt rename to src/main/kotlin/SyncFile.kt index 645d7149..99f7175a 100644 --- a/src/main/kotlin/CopyFile.kt +++ b/src/main/kotlin/SyncFile.kt @@ -7,11 +7,10 @@ package kotlinx.validation import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.TaskAction -import java.io.File import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -19,8 +18,8 @@ import java.nio.file.StandardCopyOption // and registers it as an output dependency. If there's another task reading from that particular // directory or writing into it, their input/output dependencies would clash and as long as // there will be no explicit ordering or dependencies between these tasks, Gradle would be unhappy. -internal abstract class CopyFile : DefaultTask() { - @get:InputFile +internal abstract class SyncFile : DefaultTask() { + @get:InputFiles abstract val from: RegularFileProperty @get:OutputFile @@ -28,6 +27,13 @@ internal abstract class CopyFile : DefaultTask() { @TaskAction fun copy() { - Files.copy(from.asFile.get().toPath(), to.asFile.get().toPath(), StandardCopyOption.REPLACE_EXISTING) + val fromFile = from.asFile.get() + val toFile = to.asFile.get() + if (fromFile.exists()) { + toFile.parentFile.mkdirs() + Files.copy(fromFile.toPath(), toFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } else { + Files.deleteIfExists(toFile.toPath()) + } } } From e9a65fe41ca7732d928a68152cef9ce592d22467 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 5 Apr 2024 15:46:27 +0200 Subject: [PATCH 12/26] Use RegularFileProperties instead of File vars in the check task --- api/binary-compatibility-validator.api | 8 +-- .../BinaryCompatibilityValidatorPlugin.kt | 6 +- src/main/kotlin/KotlinApiCompareTask.kt | 55 ++++++++++--------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 4108f275..c62be4bf 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -82,13 +82,9 @@ public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTas } public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask { - public field generatedApiFile Ljava/io/File; - public field projectApiFile Ljava/io/File; public fun ()V - public final fun getGeneratedApiFile ()Ljava/io/File; - public final fun getProjectApiFile ()Ljava/io/File; - public final fun setGeneratedApiFile (Ljava/io/File;)V - public final fun setProjectApiFile (Ljava/io/File;)V + public final fun getGeneratedApiFile ()Lorg/gradle/api/file/RegularFileProperty; + public final fun getProjectApiFile ()Lorg/gradle/api/file/RegularFileProperty; } public abstract class kotlinx/validation/KotlinKlibAbiBuildTask : kotlinx/validation/BuildTaskBase { diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index ba536663..9b0ec8fd 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -298,8 +298,8 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" - projectApiFile = apiCheckDir.get().resolve(jvmDumpFileName) - generatedApiFile = apiBuildDir.get().resolve(jvmDumpFileName) + projectApiFile.set(apiCheckDir.get().resolve(jvmDumpFileName)) + generatedApiFile.set(apiBuildDir.get().resolve(jvmDumpFileName)) dependsOn(apiBuild) } @@ -403,7 +403,7 @@ private class KlibValidationPipelineBuilder( } private fun Project.checkKlibsTask(klibDumpConfig: TargetConfig) - = project.task(klibDumpConfig.apiTaskName("Check")) { + = project.task(klibDumpConfig.apiTaskName("Check")) { isEnabled = klibAbiCheckEnabled(project.name, extension) group = "verification" description = "Checks signatures of a public KLib ABI against the golden value in ABI folder for ${project.name}" diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 87a650c3..2a268a54 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -10,16 +10,20 @@ import com.github.difflib.UnifiedDiffUtils import java.io.* import javax.inject.Inject import org.gradle.api.* +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* -public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { +public open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { - @InputFiles - @PathSensitive(PathSensitivity.RELATIVE) - public lateinit var projectApiFile: File + @get:InputFiles + @get:SkipWhenEmpty + @get:PathSensitive(PathSensitivity.RELATIVE) + public val projectApiFile: RegularFileProperty = objects.fileProperty() - @InputFiles - public lateinit var generatedApiFile: File + @get:InputFiles + @get:SkipWhenEmpty + public val generatedApiFile: RegularFileProperty = objects.fileProperty() private val projectName = project.name @@ -27,28 +31,28 @@ public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { @TaskAction internal fun verify() { - val projectApiDir = projectApiFile.parentFile + val projectApiDir = projectApiFile.get().asFile.parentFile if (!projectApiDir.exists()) { error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") } - val buildApiDir = generatedApiFile.parentFile + val buildApiDir = generatedApiFile.get().asFile.parentFile if (!buildApiDir.exists()) { error("Expected folder with generate API declarations '$buildApiDir' does not exist.") } val subject = projectName - if (!projectApiFile.exists()) { - error("File ${projectApiFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + + if (!projectApiFile.get().asFile.exists()) { + error("File ${projectApiFile.get().asFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + ":$subject:apiDump task to generate one") } - if (!generatedApiFile.exists()) { - error("File ${generatedApiFile.name} is missing from dump results.") + if (!generatedApiFile.get().asFile.exists()) { + error("File ${generatedApiFile.get().asFile.name} is missing from dump results.") } // Normalize case-sensitivity val diffSet = mutableSetOf() - val diff = compareFiles(projectApiFile, generatedApiFile) + val diff = compareFiles(projectApiFile.get().asFile, generatedApiFile.get().asFile) if (diff != null) diffSet.add(diff) if (diffSet.isNotEmpty()) { val diffText = diffSet.joinToString("\n\n") @@ -80,13 +84,12 @@ public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { internal abstract class KotlinApiCompareLazyTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { @get:SkipWhenEmpty - @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - abstract val projectApiFile: RegularFileProperty + public val projectApiFile: RegularFileProperty = objects.fileProperty() - @get:SkipWhenEmpty @get:InputFiles - abstract val generatedApiFile: RegularFileProperty + @get:SkipWhenEmpty + public val generatedApiFile: RegularFileProperty = objects.fileProperty() private val projectName = project.name @@ -94,12 +97,14 @@ internal abstract class KotlinApiCompareLazyTask @Inject constructor(private val @TaskAction internal fun verify() { - val projectApiDir = projectApiFile.get().asFile.parentFile + val projectApiFile_ = projectApiFile.get().asFile + val projectApiDir = projectApiFile_.parentFile if (!projectApiDir.exists()) { error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") } - val buildApiDir = generatedApiFile.get().asFile.parentFile + val generatedApiFile_ = generatedApiFile.get().asFile + val buildApiDir = generatedApiFile_.parentFile if (!buildApiDir.exists()) { error("Expected folder with generate API declarations '$buildApiDir' does not exist.") } @@ -127,17 +132,17 @@ internal abstract class KotlinApiCompareLazyTask @Inject constructor(private val expectedApiFiles[file.name] = file.relativePath } - if (!expectedApiFiles.containsKey(projectApiFile.get().asFile.name)) { - error("File ${projectApiFile.get().asFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + + if (!expectedApiFiles.containsKey(projectApiFile_.name)) { + error("File ${projectApiFile_.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + ":$subject:apiDump task to generate one") } - if (!apiBuildDirFiles.containsKey(generatedApiFile.get().asFile.name)) { - error("File ${generatedApiFile.get().asFile.name} is missing from dump results.") + if (!apiBuildDirFiles.containsKey(generatedApiFile_.name)) { + error("File ${generatedApiFile_.name} is missing from dump results.") } // Normalize case-sensitivity - val expectedApiDeclaration = expectedApiFiles.getValue(projectApiFile.get().asFile.name) - val actualApiDeclaration = apiBuildDirFiles.getValue(generatedApiFile.get().asFile.name) + val expectedApiDeclaration = expectedApiFiles.getValue(projectApiFile_.name) + val actualApiDeclaration = apiBuildDirFiles.getValue(generatedApiFile_.name) val diffSet = mutableSetOf() val expectedFile = expectedApiDeclaration.getFile(projectApiDir) val actualFile = actualApiDeclaration.getFile(buildApiDir) From d91fa97e1ea590d760e71724ca88a828e70bafc5 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 5 Apr 2024 15:50:52 +0200 Subject: [PATCH 13/26] Cleanup --- .../BinaryCompatibilityValidatorPlugin.kt | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 9b0ec8fd..4297e62c 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -556,15 +556,6 @@ private class KlibValidationPipelineBuilder( } } - // Returns a predicate that checks if there are any compilable targets - private fun Project.hasCompilableTargetsPredicate(): Provider { - return project.provider { - project.kotlinMultiplatform.targets.matching { it.emitsKlib } - .asSequence() - .any { it.mainCompilationOrNull?.hasAnySources() == true } - } - } - private fun Project.configureKlibCompilation( compilation: KotlinCompilation, extension: ApiValidationExtension, @@ -601,7 +592,7 @@ private class KlibValidationPipelineBuilder( } private fun Project.unsupportedTargetDumpProxy( - compilation: KotlinCompilation, + @Suppress("UNUSED_PARAMETER") compilation: KotlinCompilation, klibApiDir: Provider, targetConfig: TargetConfig, unsupportedTarget: KlibTarget, @@ -610,8 +601,6 @@ private class KlibValidationPipelineBuilder( val targetName = targetConfig.targetName!! return project.task(targetConfig.apiTaskName("Infer")) { isEnabled = klibAbiCheckEnabled(project.name, extension) - val hasSourcesPredicate = compilation.hasAnySourcesPredicate() - onlyIf { hasSourcesPredicate.get() } description = "Try to infer the dump for unsupported target $targetName using dumps " + "generated for supported targets." group = "other" @@ -650,7 +639,3 @@ private val Project.klibDumpFileName: String private fun KotlinCompilation.hasAnySources(): Boolean = allKotlinSourceSets.any { it.kotlin.srcDirs.any(File::exists) } - -private fun KotlinCompilation.hasAnySourcesPredicate(): Provider = project.provider { - this.hasAnySources() -} From 1d15504cc360bd6b695ea5421832a533d5e97785 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 5 Apr 2024 16:08:03 +0200 Subject: [PATCH 14/26] Enable conf cache for JVM tests --- .../kotlin/kotlinx/validation/test/JvmProjectTests.kt | 6 ++---- .../validation/test/MultiPlatformSingleJvmTargetTest.kt | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt index 026f3e59..1f9ef007 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/JvmProjectTests.kt @@ -19,8 +19,7 @@ class JvmProjectTests : BaseKotlinGradleTest() { resolve("/examples/gradle/base/withPlugin.gradle.kts") resolve("/examples/gradle/configuration/generatedSources/generatedJvmSources.gradle.kts") } - // TODO: enable configuration cache back when we start skipping tasks correctly - runner(withConfigurationCache = false) { + runner { arguments.add(":apiDump") } } @@ -42,8 +41,7 @@ class JvmProjectTests : BaseKotlinGradleTest() { apiFile(projectName = rootProjectDir.name) { resolve("/examples/classes/GeneratedSources.dump") } - // TODO: enable configuration cache back when we start skipping tasks correctly - runner(withConfigurationCache = false) { + runner { arguments.add(":apiCheck") } } diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt b/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt index 0e32ce33..94a2cac4 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/MultiPlatformSingleJvmTargetTest.kt @@ -7,7 +7,6 @@ package kotlinx.validation.test import kotlinx.validation.api.* import org.assertj.core.api.Assertions.assertThat -import org.gradle.testkit.runner.TaskOutcome import org.junit.Test import java.io.File From b1df69a0ce4c31745cc2b93c8691ac0c0b4807cb Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 13 May 2024 18:10:51 +0200 Subject: [PATCH 15/26] Added missing annotations, cleaned up the code --- api/binary-compatibility-validator.api | 4 ++-- src/main/kotlin/-Utils.kt | 11 ++++++++--- src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt | 8 +++----- src/main/kotlin/KotlinApiCompareTask.kt | 4 +++- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 8 +++----- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 2 ++ src/main/kotlin/KotlinKlibInferAbiTask.kt | 5 ++++- src/main/kotlin/KotlinKlibMergeAbiTask.kt | 4 +++- src/main/kotlin/SyncFile.kt | 10 ++++------ 9 files changed, 32 insertions(+), 24 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index c62be4bf..2864e5de 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -105,7 +105,7 @@ public abstract class kotlinx/validation/KotlinKlibExtractAbiTask : org/gradle/a public abstract class kotlinx/validation/KotlinKlibInferAbiTask : org/gradle/api/DefaultTask { public fun ()V - public abstract fun getInputDumps ()Lorg/gradle/api/provider/ListProperty; + 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; @@ -113,7 +113,7 @@ public abstract class kotlinx/validation/KotlinKlibInferAbiTask : org/gradle/api public abstract class kotlinx/validation/KotlinKlibMergeAbiTask : org/gradle/api/DefaultTask { public fun ()V - public abstract fun getDumps ()Lorg/gradle/api/provider/ListProperty; + public abstract fun getDumps ()Lorg/gradle/api/provider/SetProperty; public abstract fun getMergedApiFile ()Lorg/gradle/api/file/RegularFileProperty; } diff --git a/src/main/kotlin/-Utils.kt b/src/main/kotlin/-Utils.kt index dbeff2ed..e6e0049c 100644 --- a/src/main/kotlin/-Utils.kt +++ b/src/main/kotlin/-Utils.kt @@ -8,9 +8,7 @@ package kotlinx.validation import kotlinx.validation.api.klib.KlibTarget import kotlinx.validation.api.klib.konanTargetNameMapping import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.* import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget @@ -50,8 +48,15 @@ public class KlibDumpMetadata( /** * Path to a resulting dump file. + * + * If a dump file was not generated for a particular [target] (which may + * happen for an empty project, a project having only test targets, + * or a project that has no sources for a particular target), + * [KlibDumpMetadata] will not force an error. + * Instead, a dependent task will be skipped. */ @get:InputFiles @get:SkipWhenEmpty + @get:PathSensitive(PathSensitivity.RELATIVE) public val dumpFile: RegularFileProperty ) : Serializable diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 4297e62c..4ff01b2b 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -433,7 +433,7 @@ private class KlibValidationPipelineBuilder( group = "other" strictValidation.set(extension.klib.strictValidation) requiredTargets.addAll(supportedTargets()) - inputAbiFile.set(klibApiDir.get().resolve(klibDumpFileName)) + inputAbiFile.fileProvider(klibApiDir.map { it.resolve(klibDumpFileName) }) outputAbiFile.set(klibOutputDir.resolve(klibDumpFileName)) } @@ -516,7 +516,6 @@ private class KlibValidationPipelineBuilder( // 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. val proxy = unsupportedTargetDumpProxy( - mainCompilation, klibApiDir, targetConfig, currentTarget.toKlibTarget(), apiBuildDir @@ -571,7 +570,7 @@ private class KlibValidationPipelineBuilder( description = "Builds Kotlin KLib ABI dump for 'main' compilations of $projectName. " + "Complementary task and shouldn't be called manually" this.target.set(target) - klibFile.from(project.provider { compilation.output.classesDirs }) + klibFile.from(compilation.output.classesDirs) signatureVersion.set(extension.klib.signatureVersion) outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) } @@ -592,7 +591,6 @@ private class KlibValidationPipelineBuilder( } private fun Project.unsupportedTargetDumpProxy( - @Suppress("UNUSED_PARAMETER") compilation: KotlinCompilation, klibApiDir: Provider, targetConfig: TargetConfig, unsupportedTarget: KlibTarget, @@ -605,7 +603,7 @@ private class KlibValidationPipelineBuilder( "generated for supported targets." group = "other" target.set(unsupportedTarget) - oldMergedKlibDump.set(klibApiDir.get().resolve(klibDumpFileName)) + oldMergedKlibDump.fileProvider(klibApiDir.map { it.resolve(klibDumpFileName) }) outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) } } diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 2a268a54..c931878e 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -14,6 +14,7 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* +@CacheableTask public open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { @get:InputFiles @@ -23,11 +24,12 @@ public open class KotlinApiCompareTask @Inject constructor(private val objects: @get:InputFiles @get:SkipWhenEmpty + @get:PathSensitive(PathSensitivity.RELATIVE) public val generatedApiFile: RegularFileProperty = objects.fileProperty() private val projectName = project.name - private val rootDir = project.rootProject.rootDir + private val rootDir = project.rootDir @TaskAction internal fun verify() { diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index 1a115118..8eeb6625 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -9,15 +9,12 @@ import kotlinx.validation.api.klib.* import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.SkipWhenEmpty -import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.* /** * Generates a text file with a KLib ABI dump for a single klib. */ +@CacheableTask public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { /** @@ -25,6 +22,7 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { */ @get:InputFiles @get:SkipWhenEmpty + @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val klibFile: ConfigurableFileCollection /** diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index 1c9d732f..ea0754e8 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -20,11 +20,13 @@ import org.gradle.api.tasks.* * If some targets the dump stored in a project directory was generated for are not supported by the host compiler, * only supported tasks could be extracted for further validation. */ +@CacheableTask public abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. */ @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val inputAbiFile: RegularFileProperty /** diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index f56a50ea..97c94992 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -11,6 +11,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* /** @@ -23,6 +24,7 @@ import org.gradle.api.tasks.* * from it and merged into the common ABI extracted previously. * The resulting dump is then used as an inferred dump for the unsupported target. */ +@CacheableTask public abstract class KotlinKlibInferAbiTask : DefaultTask() { /** * The name of a target to infer a dump for. @@ -34,12 +36,13 @@ public abstract class KotlinKlibInferAbiTask : DefaultTask() { * Newly created dumps that will be used for ABI inference. */ @get:Nested - public abstract val inputDumps: ListProperty + public abstract val inputDumps: SetProperty /** * Previously generated merged ABI dump file, the golden image every dump should be verified against. */ @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val oldMergedKlibDump: RegularFileProperty /** diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index 0dd5f3a1..bede44ee 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -10,11 +10,13 @@ import kotlinx.validation.api.klib.saveTo import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* /** * Merges multiple individual KLib ABI dumps into a single merged dump. */ +@CacheableTask public abstract class KotlinKlibMergeAbiTask : DefaultTask() { /** * Dumps to merge. @@ -23,7 +25,7 @@ public abstract class KotlinKlibMergeAbiTask : DefaultTask() { * target will not be mentioned in the resulting merged dump. */ @get:Nested - public abstract val dumps: ListProperty + public abstract val dumps: SetProperty /** * A path to a resulting merged dump file. diff --git a/src/main/kotlin/SyncFile.kt b/src/main/kotlin/SyncFile.kt index 99f7175a..e5ea1d37 100644 --- a/src/main/kotlin/SyncFile.kt +++ b/src/main/kotlin/SyncFile.kt @@ -7,10 +7,7 @@ package kotlinx.validation import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.OutputFiles -import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.* import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -18,8 +15,10 @@ import java.nio.file.StandardCopyOption // and registers it as an output dependency. If there's another task reading from that particular // directory or writing into it, their input/output dependencies would clash and as long as // there will be no explicit ordering or dependencies between these tasks, Gradle would be unhappy. +@CacheableTask internal abstract class SyncFile : DefaultTask() { @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) abstract val from: RegularFileProperty @get:OutputFile @@ -30,8 +29,7 @@ internal abstract class SyncFile : DefaultTask() { val fromFile = from.asFile.get() val toFile = to.asFile.get() if (fromFile.exists()) { - toFile.parentFile.mkdirs() - Files.copy(fromFile.toPath(), toFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + fromFile.copyTo(toFile, overwrite = true) } else { Files.deleteIfExists(toFile.toPath()) } From ca37fa6475123ff5ebb2203ec9d51fed55570d43 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 13 May 2024 18:18:11 +0200 Subject: [PATCH 16/26] Update KDoc --- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index 8eeb6625..febb907e 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -19,6 +19,12 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { /** * Collection consisting of a single path to a compiled klib (either file, or directory). + * + * By the end of the compilation process, there might be no klib file emitted, + * for example, when there are no sources in a project in general, + * or for a target in particular. + * The lack of a compiled klib file is not considered as an error, and + * instead causes the ask being skipped. */ @get:InputFiles @get:SkipWhenEmpty From 792a40921ba0ac5490843130f2764de82be3c06e Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Mon, 13 May 2024 18:22:18 +0200 Subject: [PATCH 17/26] Replaced ListProperties with SetProperties --- api/binary-compatibility-validator.api | 2 +- src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt | 4 ++-- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 4 ++-- src/main/kotlin/KotlinKlibInferAbiTask.kt | 1 - src/main/kotlin/KotlinKlibMergeAbiTask.kt | 1 - 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 2864e5de..ec29976d 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -99,7 +99,7 @@ public abstract class kotlinx/validation/KotlinKlibExtractAbiTask : org/gradle/a public fun ()V public abstract fun getInputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; public abstract fun getOutputAbiFile ()Lorg/gradle/api/file/RegularFileProperty; - public abstract fun getRequiredTargets ()Lorg/gradle/api/provider/ListProperty; + public abstract fun getRequiredTargets ()Lorg/gradle/api/provider/SetProperty; public final fun getStrictValidation ()Lorg/gradle/api/provider/Property; } diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 4ff01b2b..1f5b3687 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -481,8 +481,8 @@ private class KlibValidationPipelineBuilder( ) { val kotlin = project.kotlinMultiplatform - val generatedDumps = objects.listProperty(KlibDumpMetadata::class.java) - val inferredDumps = objects.listProperty(KlibDumpMetadata::class.java) + val generatedDumps = objects.setProperty(KlibDumpMetadata::class.java) + val inferredDumps = objects.setProperty(KlibDumpMetadata::class.java) mergeTask.configure { it.dumps.addAll(generatedDumps) } diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index ea0754e8..d2f755a5 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -10,8 +10,8 @@ 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.ListProperty import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* /** @@ -33,7 +33,7 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { * List of the targets that the resulting dump should contain. */ @get:Input - public abstract val requiredTargets: ListProperty + public abstract val requiredTargets: SetProperty /** * Refer to [KlibValidationSettings.strictValidation] for details. diff --git a/src/main/kotlin/KotlinKlibInferAbiTask.kt b/src/main/kotlin/KotlinKlibInferAbiTask.kt index 97c94992..889b33ec 100644 --- a/src/main/kotlin/KotlinKlibInferAbiTask.kt +++ b/src/main/kotlin/KotlinKlibInferAbiTask.kt @@ -9,7 +9,6 @@ import kotlinx.validation.api.klib.* import kotlinx.validation.api.klib.TargetHierarchy import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* diff --git a/src/main/kotlin/KotlinKlibMergeAbiTask.kt b/src/main/kotlin/KotlinKlibMergeAbiTask.kt index bede44ee..8596ba69 100644 --- a/src/main/kotlin/KotlinKlibMergeAbiTask.kt +++ b/src/main/kotlin/KotlinKlibMergeAbiTask.kt @@ -9,7 +9,6 @@ import kotlinx.validation.api.klib.KlibDump import kotlinx.validation.api.klib.saveTo import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.ListProperty import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.* From 2a30a463762405e92423eb3524c0f8e45ad14487 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 6 Jun 2024 11:26:05 +0200 Subject: [PATCH 18/26] Update compare task after rebase on dev branch --- api/binary-compatibility-validator.api | 2 +- src/main/kotlin/KotlinApiCompareTask.kt | 111 +++--------------------- 2 files changed, 11 insertions(+), 102 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index ec29976d..7749e82c 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -82,7 +82,7 @@ public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTas } public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask { - public fun ()V + public fun (Lorg/gradle/api/model/ObjectFactory;)V public final fun getGeneratedApiFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getProjectApiFile ()Lorg/gradle/api/file/RegularFileProperty; } diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index c931878e..4a809a54 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -15,7 +15,7 @@ import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* @CacheableTask -public open class KotlinApiCompareTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { +public open class KotlinApiCompareTask @Inject constructor(objects: ObjectFactory): DefaultTask() { @get:InputFiles @get:SkipWhenEmpty @@ -33,122 +33,31 @@ public open class KotlinApiCompareTask @Inject constructor(private val objects: @TaskAction internal fun verify() { - val projectApiDir = projectApiFile.get().asFile.parentFile - if (!projectApiDir.exists()) { - error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + - "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") - } - val buildApiDir = generatedApiFile.get().asFile.parentFile - if (!buildApiDir.exists()) { - error("Expected folder with generate API declarations '$buildApiDir' does not exist.") - } - val subject = projectName + val projectApiFile = projectApiFile.get().asFile + val generatedApiFile = generatedApiFile.get().asFile - if (!projectApiFile.get().asFile.exists()) { - error("File ${projectApiFile.get().asFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + - ":$subject:apiDump task to generate one") - } - if (!generatedApiFile.get().asFile.exists()) { - error("File ${generatedApiFile.get().asFile.name} is missing from dump results.") - } - - // Normalize case-sensitivity - val diffSet = mutableSetOf() - val diff = compareFiles(projectApiFile.get().asFile, generatedApiFile.get().asFile) - if (diff != null) diffSet.add(diff) - if (diffSet.isNotEmpty()) { - val diffText = diffSet.joinToString("\n\n") - error("API check failed for project $subject.\n$diffText\n\n You can run :$subject:apiDump task to overwrite API declarations") - } - } - - private fun File.relativeDirPath(): String { - return toRelativeString(rootDir) + File.separator - } - - private fun compareFiles(checkFile: File, builtFile: File): String? { - val checkText = checkFile.readText() - val builtText = builtFile.readText() - - // We don't compare full text because newlines on Windows & Linux/macOS are different - val checkLines = checkText.lines() - val builtLines = builtText.lines() - if (checkLines == builtLines) - return null - - val patch = DiffUtils.diff(checkLines, builtLines) - val diff = UnifiedDiffUtils.generateUnifiedDiff(checkFile.toString(), builtFile.toString(), checkLines, patch, 3) - return diff.joinToString("\n") - } -} - -// TODO: decide what to do with to old compare task -internal abstract class KotlinApiCompareLazyTask @Inject constructor(private val objects: ObjectFactory): DefaultTask() { - - @get:SkipWhenEmpty - @get:PathSensitive(PathSensitivity.RELATIVE) - public val projectApiFile: RegularFileProperty = objects.fileProperty() - - @get:InputFiles - @get:SkipWhenEmpty - public val generatedApiFile: RegularFileProperty = objects.fileProperty() - - private val projectName = project.name - - private val rootDir = project.rootProject.rootDir - - @TaskAction - internal fun verify() { - val projectApiFile_ = projectApiFile.get().asFile - val projectApiDir = projectApiFile_.parentFile + val projectApiDir = projectApiFile.parentFile if (!projectApiDir.exists()) { error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") } - val generatedApiFile_ = generatedApiFile.get().asFile - val buildApiDir = generatedApiFile_.parentFile + val buildApiDir = generatedApiFile.parentFile if (!buildApiDir.exists()) { error("Expected folder with generate API declarations '$buildApiDir' does not exist.") } val subject = projectName - /* - * 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 sensitvity. - * To workaround that, we replace paths we are looking for the same paths that - * actually exist on FS. - */ - fun caseInsensitiveMap() = TreeMap { rp, rp2 -> - rp.compareTo(rp2, true) - } - - val apiBuildDirFiles = caseInsensitiveMap() - val expectedApiFiles = caseInsensitiveMap() - - objects.fileTree().from(buildApiDir).visit { file -> - apiBuildDirFiles[file.name] = file.relativePath - } - objects.fileTree().from(projectApiDir).visit { file -> - expectedApiFiles[file.name] = file.relativePath - } - - if (!expectedApiFiles.containsKey(projectApiFile_.name)) { - error("File ${projectApiFile_.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + + if (!projectApiFile.exists()) { + error("File ${projectApiFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + ":$subject:apiDump task to generate one") } - if (!apiBuildDirFiles.containsKey(generatedApiFile_.name)) { - error("File ${generatedApiFile_.name} is missing from dump results.") + if (!generatedApiFile.exists()) { + error("File ${generatedApiFile.name} is missing from dump results.") } // Normalize case-sensitivity - val expectedApiDeclaration = expectedApiFiles.getValue(projectApiFile_.name) - val actualApiDeclaration = apiBuildDirFiles.getValue(generatedApiFile_.name) val diffSet = mutableSetOf() - val expectedFile = expectedApiDeclaration.getFile(projectApiDir) - val actualFile = actualApiDeclaration.getFile(buildApiDir) - val diff = compareFiles(expectedFile, actualFile) + val diff = compareFiles(projectApiFile, generatedApiFile) if (diff != null) diffSet.add(diff) if (diffSet.isNotEmpty()) { val diffText = diffSet.joinToString("\n\n") From 8420f77bcc808256ec2b425debdad8c71edfc27f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 6 Jun 2024 11:56:02 +0200 Subject: [PATCH 19/26] Remove some explicit dependencies between tasks --- .../kotlin/BinaryCompatibilityValidatorPlugin.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 1f5b3687..30bae713 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -234,7 +234,7 @@ private fun Project.configureKotlinCompilation( } outputApiFile = apiBuildDir.get().resolve(dumpFileName) } - configureCheckTasks(apiBuildDir, apiBuild, extension, targetConfig, commonApiDump, commonApiCheck) + configureCheckTasks(apiBuild, extension, targetConfig, commonApiDump, commonApiCheck) } internal val Project.sourceSets: SourceSetContainer @@ -277,12 +277,11 @@ private fun Project.configureApiTasks( outputApiFile = apiBuildDir.get().resolve(dumpFileName) } - configureCheckTasks(apiBuildDir, apiBuild, extension, targetConfig) + configureCheckTasks(apiBuild, extension, targetConfig) } private fun Project.configureCheckTasks( - apiBuildDir: Provider, - apiBuild: TaskProvider<*>, + apiBuild: TaskProvider, extension: ApiValidationExtension, targetConfig: TargetConfig, commonApiDump: TaskProvider? = null, @@ -299,8 +298,7 @@ private fun Project.configureCheckTasks( group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" projectApiFile.set(apiCheckDir.get().resolve(jvmDumpFileName)) - generatedApiFile.set(apiBuildDir.get().resolve(jvmDumpFileName)) - dependsOn(apiBuild) + generatedApiFile.fileProvider(apiBuild.map { it.outputApiFile }) } val dumpFileName = project.jvmDumpFileName @@ -308,9 +306,8 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "other" description = "Syncs the API file for $projectName" - from.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) + from.fileProvider(apiBuild.map { it.outputApiFile }) to.fileProvider(apiCheckDir.map { it.resolve(dumpFileName) }) - dependsOn(apiBuild) } commonApiDump?.configure { it.dependsOn(apiDump) } From 1d40fa285d0330b1cff3310b89137b9e9fd54573 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 6 Jun 2024 12:18:32 +0200 Subject: [PATCH 20/26] Rely on file providers as much as possible --- .../BinaryCompatibilityValidatorPlugin.kt | 93 ++++++++++--------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 30bae713..33813901 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -212,7 +212,7 @@ private fun Project.configureKotlinCompilation( val projectName = project.name val dumpFileName = project.jvmDumpFileName val apiDirProvider = targetConfig.apiDir - val apiBuildDir = apiDirProvider.map { layout.buildDirectory.asFile.get().resolve(it) } + val apiBuildDir = apiDirProvider.flatMap { f -> layout.buildDirectory.asFile.map { it.resolve(f) } } val apiBuild = task(targetConfig.apiTaskName("Build")) { // Do not enable task for empty umbrella modules @@ -259,7 +259,7 @@ private fun Project.configureApiTasks( ) { val projectName = project.name val dumpFileName = project.jvmDumpFileName - val apiBuildDir = targetConfig.apiDir.map { layout.buildDirectory.asFile.get().resolve(it) } + val apiBuildDir = targetConfig.apiDir.flatMap { f -> layout.buildDirectory.asFile.map { it.resolve(f) } } val sourceSetsOutputsProvider = project.provider { sourceSets .filter { it.name == SourceSet.MAIN_SOURCE_SET_NAME || it.name in extension.additionalSourceSets } @@ -297,7 +297,7 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" - projectApiFile.set(apiCheckDir.get().resolve(jvmDumpFileName)) + projectApiFile.fileProvider(apiCheckDir.map { it.resolve(jvmDumpFileName) }) generatedApiFile.fileProvider(apiBuild.map { it.outputApiFile }) } @@ -372,13 +372,13 @@ private class KlibValidationPipelineBuilder( TargetConfig(project, extension, KLIB_INFERRED_DUMPS_DIRECTORY, intermediateFilesConfig) val projectDir = project.projectDir - val klibApiDir = klibApiDirConfig?.map { - projectDir.resolve(it.apiDir.get()) + val klibApiDir = klibApiDirConfig?.flatMap { + it.apiDir.map { projectDir.resolve(it) } }!! - val projectBuildDir = project.layout.buildDirectory.asFile.get() - val klibMergeDir = projectBuildDir.resolve(klibDumpConfig.apiDir.get()) - val klibMergeInferredDir = projectBuildDir.resolve(klibInferDumpConfig.apiDir.get()) - val klibExtractedFileDir = klibMergeInferredDir.resolve("extracted") + val projectBuildDir = project.layout.buildDirectory.asFile + val klibMergeDir = projectBuildDir.flatMap { pd -> klibDumpConfig.apiDir.map { pd.resolve(it) } } + val klibMergeInferredDir = projectBuildDir.flatMap { pd -> klibInferDumpConfig.apiDir.map { pd.resolve(it) } } + val klibExtractedFileDir = klibMergeInferredDir.map { it.resolve("extracted") } val klibMerge = project.mergeKlibsUmbrellaTask(klibDumpConfig, klibMergeDir) val klibMergeInferred = project.mergeInferredKlibsUmbrellaTask(klibDumpConfig, klibMergeInferredDir) @@ -399,27 +399,29 @@ private class KlibValidationPipelineBuilder( project.configureTargets(klibApiDir, klibMerge, klibMergeInferred) } - private fun Project.checkKlibsTask(klibDumpConfig: TargetConfig) - = project.task(klibDumpConfig.apiTaskName("Check")) { - isEnabled = klibAbiCheckEnabled(project.name, extension) - group = "verification" - description = "Checks signatures of a public KLib ABI against the golden value in ABI folder for ${project.name}" - } + private fun Project.checkKlibsTask(klibDumpConfig: TargetConfig) = + project.task(klibDumpConfig.apiTaskName("Check")) { + isEnabled = klibAbiCheckEnabled(project.name, extension) + group = "verification" + description = + "Checks signatures of a public KLib ABI against the golden value in ABI folder for ${project.name}" + } - private fun Project.dumpKlibsTask(klibDumpConfig: TargetConfig) = project.task(klibDumpConfig.apiTaskName("Dump")) { - isEnabled = klibAbiCheckEnabled(project.name, extension) - description = "Syncs the KLib ABI file for ${project.name}" - group = "other" - onlyIf { - it as SyncFile - it.to.get().asFile.exists() || it.from.get().asFile.exists() + private fun Project.dumpKlibsTask(klibDumpConfig: TargetConfig) = + project.task(klibDumpConfig.apiTaskName("Dump")) { + isEnabled = klibAbiCheckEnabled(project.name, extension) + description = "Syncs the KLib ABI file for ${project.name}" + group = "other" + onlyIf { + it as SyncFile + it.to.get().asFile.exists() || it.from.get().asFile.exists() + } } - } private fun Project.extractAbi( klibDumpConfig: TargetConfig, klibApiDir: Provider, - klibOutputDir: File + klibOutputDir: Provider ) = project.task( klibDumpConfig.apiTaskName("ExtractForValidation") ) @@ -431,12 +433,12 @@ private class KlibValidationPipelineBuilder( strictValidation.set(extension.klib.strictValidation) requiredTargets.addAll(supportedTargets()) inputAbiFile.fileProvider(klibApiDir.map { it.resolve(klibDumpFileName) }) - outputAbiFile.set(klibOutputDir.resolve(klibDumpFileName)) + outputAbiFile.fileProvider(klibOutputDir.map { it.resolve(klibDumpFileName) }) } private fun Project.mergeInferredKlibsUmbrellaTask( klibDumpConfig: TargetConfig, - klibMergeDir: File, + klibMergeDir: Provider, ) = project.task( klibDumpConfig.apiTaskName("MergeInferred") ) @@ -445,17 +447,17 @@ private class KlibValidationPipelineBuilder( description = "Merges multiple KLib ABI dump files generated for " + "different targets (including inferred dumps for unsupported targets) " + "into a single merged KLib ABI dump" - mergedApiFile.set(klibMergeDir.resolve(klibDumpFileName)) + mergedApiFile.fileProvider(klibMergeDir.map { it.resolve(klibDumpFileName) }) } private fun Project.mergeKlibsUmbrellaTask( klibDumpConfig: TargetConfig, - klibMergeDir: File + klibMergeDir: Provider ) = 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.set(klibMergeDir.resolve(klibDumpFileName)) + mergedApiFile.fileProvider(klibMergeDir.map { it.resolve(klibDumpFileName) }) } fun Project.bannedTargets(): Set { @@ -494,14 +496,19 @@ private class KlibValidationPipelineBuilder( val target = currentTarget.toKlibTarget() val targetName = currentTarget.targetName val targetConfig = TargetConfig(project, extension, targetName, intermediateFilesConfig) - val apiBuildDir = targetConfig.apiDir.map { project.layout.buildDirectory.asFile.get().resolve(it) }.get() + val apiBuildDir = + targetConfig.apiDir.flatMap { f -> project.layout.buildDirectory.asFile.map { it.resolve(f) } } val targetSupported = targetIsSupported(currentTarget) // 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) - generatedDumps.add(KlibDumpMetadata(target, - objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) })) + val buildTargetAbi = configureKlibCompilation( + mainCompilation, extension, targetConfig, + target, apiBuildDir + ) + generatedDumps.add( + KlibDumpMetadata(target, + objects.fileProperty().also { it.set(buildTargetAbi.flatMap { it.outputAbiFile }) }) + ) return@configureEach } // If the target is unsupported, the regular merge task will only depend on a task complaining about @@ -518,10 +525,12 @@ private class KlibValidationPipelineBuilder( apiBuildDir ) proxy.configure { it.inputDumps.addAll(generatedDumps) } - inferredDumps.add(KlibDumpMetadata(currentTarget.toKlibTarget(), - objects.fileProperty().also { - it.set(proxy.flatMap { it.outputAbiFile }) - })) + inferredDumps.add( + KlibDumpMetadata(currentTarget.toKlibTarget(), + objects.fileProperty().also { + it.set(proxy.flatMap { it.outputAbiFile }) + }) + ) } } @@ -557,7 +566,7 @@ private class KlibValidationPipelineBuilder( extension: ApiValidationExtension, targetConfig: TargetConfig, target: KlibTarget, - apiBuildDir: File + apiBuildDir: Provider ): TaskProvider { val projectName = project.name val buildTask = project.task(targetConfig.apiTaskName("Build")) { @@ -569,7 +578,7 @@ private class KlibValidationPipelineBuilder( this.target.set(target) klibFile.from(compilation.output.classesDirs) signatureVersion.set(extension.klib.signatureVersion) - outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) + outputAbiFile.fileProvider(apiBuildDir.map { it.resolve(klibDumpFileName) }) } return buildTask } @@ -591,7 +600,7 @@ private class KlibValidationPipelineBuilder( klibApiDir: Provider, targetConfig: TargetConfig, unsupportedTarget: KlibTarget, - apiBuildDir: File + apiBuildDir: Provider ): TaskProvider { val targetName = targetConfig.targetName!! return project.task(targetConfig.apiTaskName("Infer")) { @@ -601,7 +610,7 @@ private class KlibValidationPipelineBuilder( group = "other" target.set(unsupportedTarget) oldMergedKlibDump.fileProvider(klibApiDir.map { it.resolve(klibDumpFileName) }) - outputAbiFile.set(apiBuildDir.resolve(klibDumpFileName)) + outputAbiFile.fileProvider(apiBuildDir.map { it.resolve(klibDumpFileName) }) } } } From 69f94f3095192d6d9db29800663a7d50aca81064 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 6 Jun 2024 15:09:13 +0200 Subject: [PATCH 21/26] Get rid of object factory in compare-task's ctor --- api/binary-compatibility-validator.api | 2 +- src/main/kotlin/KotlinApiCompareTask.kt | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 7749e82c..ec29976d 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -82,7 +82,7 @@ public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTas } public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask { - public fun (Lorg/gradle/api/model/ObjectFactory;)V + public fun ()V public final fun getGeneratedApiFile ()Lorg/gradle/api/file/RegularFileProperty; public final fun getProjectApiFile ()Lorg/gradle/api/file/RegularFileProperty; } diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 4a809a54..07c678af 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -11,21 +11,20 @@ import java.io.* import javax.inject.Inject import org.gradle.api.* import org.gradle.api.file.RegularFileProperty -import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.* @CacheableTask -public open class KotlinApiCompareTask @Inject constructor(objects: ObjectFactory): DefaultTask() { +public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { @get:InputFiles @get:SkipWhenEmpty @get:PathSensitive(PathSensitivity.RELATIVE) - public val projectApiFile: RegularFileProperty = objects.fileProperty() + public val projectApiFile: RegularFileProperty = project.objects.fileProperty() @get:InputFiles @get:SkipWhenEmpty @get:PathSensitive(PathSensitivity.RELATIVE) - public val generatedApiFile: RegularFileProperty = objects.fileProperty() + public val generatedApiFile: RegularFileProperty = project.objects.fileProperty() private val projectName = project.name From cd2a3201bce4e9b13e0cb7d8ab640c252cfbfd25 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Thu, 6 Jun 2024 15:47:35 +0200 Subject: [PATCH 22/26] Align JVM- and KLib-validation behavior for empty projects Also, reworked error reporting for the compare task. --- .../validation/test/DefaultConfigTests.kt | 7 ++- .../validation/test/KlibVerificationTests.kt | 33 +++++++++---- src/main/kotlin/KotlinApiCompareTask.kt | 47 ++++++++----------- src/main/kotlin/KotlinKlibExtractAbiTask.kt | 11 +++-- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt index 3879d9f4..490632c7 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/DefaultConfigTests.kt @@ -26,8 +26,13 @@ internal class DefaultConfigTests : BaseKotlinGradleTest() { } } + val projectName = rootProjectDir.name runner.buildAndFail().apply { - assertTrue { output.contains("Please ensure that ':apiDump' was executed") } + Assertions.assertThat(output).contains( + "Expected file with API declarations 'api/$projectName.api' does not exist." + ).contains( + "Please ensure that ':apiDump' was executed in order to get an API dump to compare the build against" + ) assertTaskFailure(":apiCheck") } } diff --git a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt index fd0d5e8a..372f9a60 100644 --- a/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt +++ b/src/functionalTest/kotlin/kotlinx/validation/test/KlibVerificationTests.kt @@ -14,6 +14,7 @@ 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.junit.Assume import org.junit.Test import java.io.File @@ -49,27 +50,32 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { 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 = Paths.get(pathTestFile).fileName.toString() kotlin(fileName, sourceSet) { resolve(pathTestFile) } } + private fun BaseKotlinScope.runApiCheck() { runner { arguments.add(":apiCheck") } } + private fun BaseKotlinScope.runApiDump() { runner { arguments.add(":apiDump") } } + private fun assertApiCheckPassed(buildResult: BuildResult) { buildResult.assertTaskSuccess(":apiCheck") } @@ -603,8 +609,10 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { checkKlibDump(runner.build(), "/examples/classes/AnotherBuildConfig.klib.dump") // Update the source file by adding a declaration - val updatedSourceFile = File(this::class.java.getResource( - "/examples/classes/AnotherBuildConfigModified.kt")!!.toURI() + val updatedSourceFile = File( + this::class.java.getResource( + "/examples/classes/AnotherBuildConfigModified.kt" + )!!.toURI() ) val existingSource = runner.projectDir.resolve( "src/commonMain/kotlin/AnotherBuildConfig.kt" @@ -624,8 +632,10 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { checkKlibDump(runner.build(), "/examples/classes/AnotherBuildConfig.klib.dump") // Update the source file by adding a declaration - val updatedSourceFile = File(this::class.java.getResource( - "/examples/classes/AnotherBuildConfigLinuxArm64.kt")!!.toURI() + val updatedSourceFile = File( + this::class.java.getResource( + "/examples/classes/AnotherBuildConfigLinuxArm64.kt" + )!!.toURI() ) val existingSource = runner.projectDir.resolve( "src/linuxArm64Main/kotlin/AnotherBuildConfigLinuxArm64.kt" @@ -649,8 +659,10 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { assertApiCheckPassed(runner.build()) // Update the source file by adding a declaration - val updatedSourceFile = File(this::class.java.getResource( - "/examples/classes/AnotherBuildConfigModified.kt")!!.toURI() + val updatedSourceFile = File( + this::class.java.getResource( + "/examples/classes/AnotherBuildConfigModified.kt" + )!!.toURI() ) val existingSource = runner.projectDir.resolve( "src/commonMain/kotlin/AnotherBuildConfig.kt" @@ -723,13 +735,18 @@ internal class KlibVerificationTests : BaseKotlinGradleTest() { } @Test - fun `apiCheck should not fail for empty project`() { + fun `apiCheck should fail for empty project`() { val runner = test { baseProjectSetting() addToSrcSet("/examples/classes/AnotherBuildConfig.kt", sourceSet = "commonTest") runApiCheck() } - runner.build() + runner.buildAndFail().apply { + assertTaskFailure(":klibApiExtractForValidation") + Assertions.assertThat(output).contains( + "File with project's API declarations 'api/testproject.klib.api' does not exist." + ) + } } @Test diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index 07c678af..f7eec205 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -14,15 +14,13 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* @CacheableTask -public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { +public open class KotlinApiCompareTask @Inject constructor() : DefaultTask() { - @get:InputFiles - @get:SkipWhenEmpty + @get:InputFiles // don't fail the task if file does not exist, instead print custom error message from verify() @get:PathSensitive(PathSensitivity.RELATIVE) public val projectApiFile: RegularFileProperty = project.objects.fileProperty() - @get:InputFiles - @get:SkipWhenEmpty + @get:InputFiles // don't fail the task if file does not exist, instead print custom error message from verify() @get:PathSensitive(PathSensitivity.RELATIVE) public val generatedApiFile: RegularFileProperty = project.objects.fileProperty() @@ -35,51 +33,46 @@ public open class KotlinApiCompareTask @Inject constructor(): DefaultTask() { val projectApiFile = projectApiFile.get().asFile val generatedApiFile = generatedApiFile.get().asFile - val projectApiDir = projectApiFile.parentFile - if (!projectApiDir.exists()) { - error("Expected folder with API declarations '$projectApiDir' does not exist.\n" + - "Please ensure that ':apiDump' was executed in order to get API dump to compare the build against") - } - val buildApiDir = generatedApiFile.parentFile - if (!buildApiDir.exists()) { - error("Expected folder with generate API declarations '$buildApiDir' does not exist.") - } - val subject = projectName - if (!projectApiFile.exists()) { - error("File ${projectApiFile.name} is missing from ${projectApiDir.relativeDirPath()}, please run " + - ":$subject:apiDump task to generate one") + error( + "Expected file with API declarations '${projectApiFile.relativeTo(rootDir)}' does not exist.\n" + + "Please ensure that ':apiDump' was executed in order to get " + + "an API dump to compare the build against" + ) } if (!generatedApiFile.exists()) { - error("File ${generatedApiFile.name} is missing from dump results.") + error( + "Expected file with generated API declarations '${generatedApiFile.relativeTo(rootDir)}'" + + " does not exist." + ) } - // Normalize case-sensitivity val diffSet = mutableSetOf() val diff = compareFiles(projectApiFile, generatedApiFile) if (diff != null) diffSet.add(diff) if (diffSet.isNotEmpty()) { val diffText = diffSet.joinToString("\n\n") - error("API check failed for project $subject.\n$diffText\n\n You can run :$subject:apiDump task to overwrite API declarations") + val subject = projectName + error( + "API check failed for project $subject.\n$diffText\n\n" + + "You can run :$subject:apiDump task to overwrite API declarations" + ) } } - private fun File.relativeDirPath(): String { - return toRelativeString(rootDir) + File.separator - } - private fun compareFiles(checkFile: File, builtFile: File): String? { val checkText = checkFile.readText() val builtText = builtFile.readText() - // We don't compare full text because newlines on Windows & Linux/macOS are different + // We don't compare a full text because newlines on Windows & Linux/macOS are different val checkLines = checkText.lines() val builtLines = builtText.lines() if (checkLines == builtLines) return null val patch = DiffUtils.diff(checkLines, builtLines) - val diff = UnifiedDiffUtils.generateUnifiedDiff(checkFile.toString(), builtFile.toString(), checkLines, patch, 3) + val diff = + UnifiedDiffUtils.generateUnifiedDiff(checkFile.toString(), builtFile.toString(), checkLines, patch, 3) return diff.joinToString("\n") } } diff --git a/src/main/kotlin/KotlinKlibExtractAbiTask.kt b/src/main/kotlin/KotlinKlibExtractAbiTask.kt index d2f755a5..2722f7a6 100644 --- a/src/main/kotlin/KotlinKlibExtractAbiTask.kt +++ b/src/main/kotlin/KotlinKlibExtractAbiTask.kt @@ -25,7 +25,7 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { /** * Merged KLib dump that should be filtered by this task. */ - @get:InputFiles + @get:InputFiles // don't fail the task if file does not exist, instead print custom error message from generate() @get:PathSensitive(PathSensitivity.RELATIVE) public abstract val inputAbiFile: RegularFileProperty @@ -47,13 +47,18 @@ public abstract class KotlinKlibExtractAbiTask : DefaultTask() { @get:OutputFile public abstract val outputAbiFile: RegularFileProperty + private val rootDir = project.rootDir + @OptIn(ExperimentalBCVApi::class) @TaskAction internal fun generate() { val inputFile = inputAbiFile.asFile.get() - if (!inputFile.exists()) return + 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") + } if (inputFile.length() == 0L) { - error("Project ABI file $inputAbiFile is empty.") + error("Project ABI file ${inputFile.relativeTo(rootDir)} is empty.") } val dump = KlibDump.from(inputFile) val enabledTargets = requiredTargets.get().map(KlibTarget::targetName).toSet() From d8271ab20c875dece655df1f22c32c7b6c71c13c Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 21 Jun 2024 13:16:32 +0200 Subject: [PATCH 23/26] Update tasks - SyncFile is no longer cachable - removed KotlinApiCompareTask's ctor --- src/main/kotlin/KotlinApiCompareTask.kt | 2 +- src/main/kotlin/SyncFile.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index f7eec205..eb079243 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -14,7 +14,7 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* @CacheableTask -public open class KotlinApiCompareTask @Inject constructor() : DefaultTask() { +public open class KotlinApiCompareTask : DefaultTask() { @get:InputFiles // don't fail the task if file does not exist, instead print custom error message from verify() @get:PathSensitive(PathSensitivity.RELATIVE) diff --git a/src/main/kotlin/SyncFile.kt b/src/main/kotlin/SyncFile.kt index e5ea1d37..fba06d10 100644 --- a/src/main/kotlin/SyncFile.kt +++ b/src/main/kotlin/SyncFile.kt @@ -8,6 +8,7 @@ package kotlinx.validation import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* +import org.gradle.work.DisableCachingByDefault import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -15,7 +16,7 @@ import java.nio.file.StandardCopyOption // and registers it as an output dependency. If there's another task reading from that particular // directory or writing into it, their input/output dependencies would clash and as long as // there will be no explicit ordering or dependencies between these tasks, Gradle would be unhappy. -@CacheableTask +@DisableCachingByDefault(because = "No computations, only copying files") internal abstract class SyncFile : DefaultTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) From 84112464ad59c2373d14a23fc161e513450c5640 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 21 Jun 2024 15:19:37 +0200 Subject: [PATCH 24/26] Use Gradle properties for all task settings --- api/binary-compatibility-validator.api | 37 ++++++------- .../BinaryCompatibilityValidatorPlugin.kt | 24 +++------ src/main/kotlin/BuildTaskBase.kt | 44 +++++++-------- src/main/kotlin/KotlinApiBuildTask.kt | 54 ++++++++++--------- src/main/kotlin/KotlinKlibAbiBuildTask.kt | 6 +-- 5 files changed, 74 insertions(+), 91 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index ec29976d..5bc7f3d5 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -32,18 +32,16 @@ public final class kotlinx/validation/BinaryCompatibilityValidatorPlugin : org/g public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultTask { public fun ()V - public final fun getIgnoredClasses ()Ljava/util/Set; - public final fun getIgnoredPackages ()Ljava/util/Set; - public final fun getNonPublicMarkers ()Ljava/util/Set; - public final fun getPublicClasses ()Ljava/util/Set; - public final fun getPublicMarkers ()Ljava/util/Set; - public final fun getPublicPackages ()Ljava/util/Set; - public final fun setIgnoredClasses (Ljava/util/Set;)V - public final fun setIgnoredPackages (Ljava/util/Set;)V - public final fun setNonPublicMarkers (Ljava/util/Set;)V - public final fun setPublicClasses (Ljava/util/Set;)V - public final fun setPublicMarkers (Ljava/util/Set;)V - public final fun setPublicPackages (Ljava/util/Set;)V + public final fun getIgnoredClasses ()Lorg/gradle/api/provider/SetProperty; + public final fun getIgnoredPackages ()Lorg/gradle/api/provider/SetProperty; + public final fun getNonPublicMarkers ()Lorg/gradle/api/provider/SetProperty; + public final fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; + public final fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; + public final fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; + public final fun setIgnoredClasses (Lorg/gradle/api/provider/SetProperty;)V + public final fun setPublicClasses (Lorg/gradle/api/provider/SetProperty;)V + public final fun setPublicMarkers (Lorg/gradle/api/provider/SetProperty;)V + public final fun setPublicPackages (Lorg/gradle/api/provider/SetProperty;)V } public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi : java/lang/annotation/Annotation { @@ -68,17 +66,12 @@ public class kotlinx/validation/KlibValidationSettings { public final fun setStrictValidation (Z)V } -public class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase { - public field inputDependencies Lorg/gradle/api/file/FileCollection; - public field outputApiFile Ljava/io/File; +public abstract class kotlinx/validation/KotlinApiBuildTask : kotlinx/validation/BuildTaskBase { public fun ()V - public final fun getInputClassesDirs ()Lorg/gradle/api/file/FileCollection; - public final fun getInputDependencies ()Lorg/gradle/api/file/FileCollection; - public final fun getInputJar ()Lorg/gradle/api/file/RegularFileProperty; - public final fun getOutputApiFile ()Ljava/io/File; - public final fun setInputClassesDirs (Lorg/gradle/api/file/FileCollection;)V - public final fun setInputDependencies (Lorg/gradle/api/file/FileCollection;)V - public final fun setOutputApiFile (Ljava/io/File;)V + 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; + public abstract fun getOutputApiFile ()Lorg/gradle/api/file/RegularFileProperty; } public class kotlinx/validation/KotlinApiCompareTask : org/gradle/api/DefaultTask { diff --git a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt index 33813901..c498aa3c 100644 --- a/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt +++ b/src/main/kotlin/BinaryCompatibilityValidatorPlugin.kt @@ -50,7 +50,6 @@ public class BinaryCompatibilityValidatorPlugin : Plugin { } } - @OptIn(ExperimentalBCVApi::class) private fun configureProject(project: Project, extension: ApiValidationExtension) { configureKotlinPlugin(project, extension) configureAndroidPlugin(project, extension) @@ -222,17 +221,12 @@ private fun Project.configureKotlinCompilation( "Builds Kotlin API for 'main' compilations of $projectName. Complementary task and shouldn't be called manually" if (useOutput) { // Workaround for #4 - inputClassesDirs = - files(provider { if (isEnabled) compilation.output.classesDirs else emptyList() }) - inputDependencies = - files(provider { if (isEnabled) compilation.output.classesDirs else emptyList() }) + inputClassesDirs.from(compilation.output.classesDirs) } else { - inputClassesDirs = - files(provider { if (isEnabled) compilation.output.classesDirs else emptyList() }) - inputDependencies = - files(provider { if (isEnabled) compilation.compileDependencyFiles else emptyList() }) + inputClassesDirs.from(compilation.output.classesDirs) + inputDependencies.from(compilation.compileDependencyFiles) } - outputApiFile = apiBuildDir.get().resolve(dumpFileName) + outputApiFile.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) } configureCheckTasks(apiBuild, extension, targetConfig, commonApiDump, commonApiCheck) } @@ -271,10 +265,8 @@ private fun Project.configureApiTasks( // 'group' is not specified deliberately, so it will be hidden from ./gradlew tasks description = "Builds Kotlin API for 'main' compilations of $projectName. Complementary task and shouldn't be called manually" - inputClassesDirs = files(provider { if (isEnabled) sourceSetsOutputsProvider.get() else emptyList() }) - inputDependencies = - files(provider { if (isEnabled) sourceSetsOutputsProvider.get() else emptyList() }) - outputApiFile = apiBuildDir.get().resolve(dumpFileName) + inputClassesDirs.from(sourceSetsOutputsProvider) + outputApiFile.fileProvider(apiBuildDir.map { it.resolve(dumpFileName) }) } configureCheckTasks(apiBuild, extension, targetConfig) @@ -298,7 +290,7 @@ private fun Project.configureCheckTasks( group = "verification" description = "Checks signatures of public API against the golden value in API folder for $projectName" projectApiFile.fileProvider(apiCheckDir.map { it.resolve(jvmDumpFileName) }) - generatedApiFile.fileProvider(apiBuild.map { it.outputApiFile }) + generatedApiFile.set(apiBuild.flatMap { it.outputApiFile }) } val dumpFileName = project.jvmDumpFileName @@ -306,7 +298,7 @@ private fun Project.configureCheckTasks( isEnabled = apiCheckEnabled(projectName, extension) && apiBuild.map { it.enabled }.getOrElse(true) group = "other" description = "Syncs the API file for $projectName" - from.fileProvider(apiBuild.map { it.outputApiFile }) + from.set(apiBuild.flatMap { it.outputApiFile }) to.fileProvider(apiCheckDir.map { it.resolve(dumpFileName) }) } diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index a5861c88..84ffb188 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -6,47 +6,43 @@ 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 + public abstract class BuildTaskBase : DefaultTask() { private val extension = project.apiValidationExtensionOrNull - private var _ignoredPackages: Set? = null + private fun stringSetProperty(provider: ApiValidationExtension.() -> Set): SetProperty { + return project.objects.setProperty(String::class.java).convention( + project.provider { + if (extension == null) { + emptySet() + } else { + provider(extension) + } + } + ) + } + @get:Input - public var ignoredPackages : Set - get() = _ignoredPackages ?: extension?.ignoredPackages ?: emptySet() - set(value) { _ignoredPackages = value } + public val ignoredPackages: SetProperty = stringSetProperty { ignoredPackages } - private var _nonPublicMarkes: Set? = null @get:Input - public var nonPublicMarkers : Set - get() = _nonPublicMarkes ?: extension?.nonPublicMarkers ?: emptySet() - set(value) { _nonPublicMarkes = value } + public val nonPublicMarkers: SetProperty = stringSetProperty { nonPublicMarkers } - private var _ignoredClasses: Set? = null @get:Input - public var ignoredClasses : Set - get() = _ignoredClasses ?: extension?.ignoredClasses ?: emptySet() - set(value) { _ignoredClasses = value } + public var ignoredClasses: SetProperty = stringSetProperty { ignoredClasses } - private var _publicPackages: Set? = null @get:Input - public var publicPackages: Set - get() = _publicPackages ?: extension?.publicPackages ?: emptySet() - set(value) { _publicPackages = value } + public var publicPackages: SetProperty = stringSetProperty { publicPackages } - private var _publicMarkers: Set? = null @get:Input - public var publicMarkers: Set - get() = _publicMarkers ?: extension?.publicMarkers ?: emptySet() - set(value) { _publicMarkers = value} + public var publicMarkers: SetProperty = stringSetProperty { publicMarkers } - private var _publicClasses: Set? = null @get:Input - public var publicClasses: Set - get() = _publicClasses ?: extension?.publicClasses ?: emptySet() - set(value) { _publicClasses = value } + public var publicClasses: SetProperty = stringSetProperty { publicClasses } @get:Internal internal val projectName = project.name diff --git a/src/main/kotlin/KotlinApiBuildTask.kt b/src/main/kotlin/KotlinApiBuildTask.kt index 8f0a6a20..7e3c1a7a 100644 --- a/src/main/kotlin/KotlinApiBuildTask.kt +++ b/src/main/kotlin/KotlinApiBuildTask.kt @@ -9,60 +9,62 @@ import kotlinx.validation.api.* import org.gradle.api.* import org.gradle.api.file.* import org.gradle.api.tasks.* -import java.io.File import java.util.jar.JarFile import javax.inject.Inject -public open class KotlinApiBuildTask @Inject constructor( +public abstract class KotlinApiBuildTask @Inject constructor( ) : BuildTaskBase() { - @OutputFile - public lateinit var outputApiFile: File + @get:OutputFile + public abstract val outputApiFile: RegularFileProperty - @InputFiles - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - public var inputClassesDirs: FileCollection? = null + @get:InputFiles + @get:Optional + @get:PathSensitive(PathSensitivity.RELATIVE) + public abstract val inputClassesDirs: ConfigurableFileCollection - @InputFile - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - public val inputJar: RegularFileProperty = this.project.objects.fileProperty() + @get:InputFile + @get:Optional + @get:PathSensitive(PathSensitivity.RELATIVE) + public abstract val inputJar: RegularFileProperty - @InputFiles - @PathSensitive(PathSensitivity.RELATIVE) - public lateinit var inputDependencies: FileCollection + @get:InputFiles + @get:Optional + @get:PathSensitive(PathSensitivity.RELATIVE) + public abstract val inputDependencies: ConfigurableFileCollection @TaskAction internal fun generate() { - outputApiFile.delete() - outputApiFile.parentFile.mkdirs() - val inputClassesDirs = inputClassesDirs val signatures = when { // inputJar takes precedence if specified inputJar.isPresent -> JarFile(inputJar.get().asFile).use { it.loadApiFromJvmClasses() } - inputClassesDirs != null -> + + inputClassesDirs.any() -> inputClassesDirs.asFileTree.asSequence() .filter { !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/") } .map { it.inputStream() } .loadApiFromJvmClasses() + else -> throw GradleException("KotlinApiBuildTask should have either inputClassesDirs, or inputJar property set") } - val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.map(::replaceDots).toSet()) - val ignoredPackagesNames = signatures.extractAnnotatedPackages(nonPublicMarkers.map(::replaceDots).toSet()) + val publicPackagesNames = signatures.extractAnnotatedPackages(publicMarkers.get().map(::replaceDots).toSet()) + val ignoredPackagesNames = + signatures.extractAnnotatedPackages(nonPublicMarkers.get().map(::replaceDots).toSet()) val filteredSignatures = signatures - .retainExplicitlyIncludedIfDeclared(publicPackages + publicPackagesNames, - publicClasses, publicMarkers) - .filterOutNonPublic(ignoredPackages + ignoredPackagesNames, ignoredClasses) - .filterOutAnnotated(nonPublicMarkers.map(::replaceDots).toSet()) + .retainExplicitlyIncludedIfDeclared( + publicPackages.get() + publicPackagesNames, + publicClasses.get(), publicMarkers.get() + ) + .filterOutNonPublic(ignoredPackages.get() + ignoredPackagesNames, ignoredClasses.get()) + .filterOutAnnotated(nonPublicMarkers.get().map(::replaceDots).toSet()) - outputApiFile.bufferedWriter().use { writer -> + outputApiFile.asFile.get().bufferedWriter().use { writer -> filteredSignatures.dump(writer) } } diff --git a/src/main/kotlin/KotlinKlibAbiBuildTask.kt b/src/main/kotlin/KotlinKlibAbiBuildTask.kt index febb907e..29552beb 100644 --- a/src/main/kotlin/KotlinKlibAbiBuildTask.kt +++ b/src/main/kotlin/KotlinKlibAbiBuildTask.kt @@ -59,9 +59,9 @@ public abstract class KotlinKlibAbiBuildTask : BuildTaskBase() { outputFile.parentFile.mkdirs() val dump = KlibDump.fromKlib(klibFile.singleFile, target.get().configurableName, KLibDumpFilters { - ignoredClasses.addAll(this@KotlinKlibAbiBuildTask.ignoredClasses) - ignoredPackages.addAll(this@KotlinKlibAbiBuildTask.ignoredPackages) - nonPublicMarkers.addAll(this@KotlinKlibAbiBuildTask.nonPublicMarkers) + ignoredClasses.addAll(this@KotlinKlibAbiBuildTask.ignoredClasses.get()) + ignoredPackages.addAll(this@KotlinKlibAbiBuildTask.ignoredPackages.get()) + nonPublicMarkers.addAll(this@KotlinKlibAbiBuildTask.nonPublicMarkers.get()) signatureVersion = this@KotlinKlibAbiBuildTask.signatureVersion.get() }) From e306d7311c9542f836a2cf2700ef0cde0d794a6f Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 21 Jun 2024 15:53:33 +0200 Subject: [PATCH 25/26] Make KotlinApiCompareTask non cachable --- src/main/kotlin/KotlinApiCompareTask.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/KotlinApiCompareTask.kt b/src/main/kotlin/KotlinApiCompareTask.kt index eb079243..37845f4b 100644 --- a/src/main/kotlin/KotlinApiCompareTask.kt +++ b/src/main/kotlin/KotlinApiCompareTask.kt @@ -13,7 +13,6 @@ import org.gradle.api.* import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* -@CacheableTask public open class KotlinApiCompareTask : DefaultTask() { @get:InputFiles // don't fail the task if file does not exist, instead print custom error message from verify() From df5c4f9106173d3d1aa72038575d5978ab8bb070 Mon Sep 17 00:00:00 2001 From: Filipp Zhinkin Date: Fri, 21 Jun 2024 18:07:57 +0200 Subject: [PATCH 26/26] Fixed property kind --- api/binary-compatibility-validator.api | 4 ---- src/main/kotlin/BuildTaskBase.kt | 9 ++++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/api/binary-compatibility-validator.api b/api/binary-compatibility-validator.api index 5bc7f3d5..ad8325d0 100644 --- a/api/binary-compatibility-validator.api +++ b/api/binary-compatibility-validator.api @@ -38,10 +38,6 @@ public abstract class kotlinx/validation/BuildTaskBase : org/gradle/api/DefaultT public final fun getPublicClasses ()Lorg/gradle/api/provider/SetProperty; public final fun getPublicMarkers ()Lorg/gradle/api/provider/SetProperty; public final fun getPublicPackages ()Lorg/gradle/api/provider/SetProperty; - public final fun setIgnoredClasses (Lorg/gradle/api/provider/SetProperty;)V - public final fun setPublicClasses (Lorg/gradle/api/provider/SetProperty;)V - public final fun setPublicMarkers (Lorg/gradle/api/provider/SetProperty;)V - public final fun setPublicPackages (Lorg/gradle/api/provider/SetProperty;)V } public abstract interface annotation class kotlinx/validation/ExperimentalBCVApi : java/lang/annotation/Annotation { diff --git a/src/main/kotlin/BuildTaskBase.kt b/src/main/kotlin/BuildTaskBase.kt index 84ffb188..e4dd551c 100644 --- a/src/main/kotlin/BuildTaskBase.kt +++ b/src/main/kotlin/BuildTaskBase.kt @@ -10,7 +10,6 @@ import org.gradle.api.provider.SetProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal - public abstract class BuildTaskBase : DefaultTask() { private val extension = project.apiValidationExtensionOrNull @@ -33,16 +32,16 @@ public abstract class BuildTaskBase : DefaultTask() { public val nonPublicMarkers: SetProperty = stringSetProperty { nonPublicMarkers } @get:Input - public var ignoredClasses: SetProperty = stringSetProperty { ignoredClasses } + public val ignoredClasses: SetProperty = stringSetProperty { ignoredClasses } @get:Input - public var publicPackages: SetProperty = stringSetProperty { publicPackages } + public val publicPackages: SetProperty = stringSetProperty { publicPackages } @get:Input - public var publicMarkers: SetProperty = stringSetProperty { publicMarkers } + public val publicMarkers: SetProperty = stringSetProperty { publicMarkers } @get:Input - public var publicClasses: SetProperty = stringSetProperty { publicClasses } + public val publicClasses: SetProperty = stringSetProperty { publicClasses } @get:Internal internal val projectName = project.name