From 8868738ac84d82bf368c2a86746698b2eb1c00e4 Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Wed, 20 Oct 2021 23:32:52 +0400 Subject: [PATCH] KT-48709: Fix CInterop configurations ambiguity with user variants * Introduce a new org.gradle.usage value: kotlin-cinterop * Add compatibility+disambiguation rules to ensure that kotlin-cinterop consumers can also fall back to ordinary (kotlin-api) published variants, but not vice versa * This ensures that ordinary kotlin-api consumers don't encounter equally-compatible candidates where one is the normal API elements and the other is C interop API elements * Set org.gradle.usage = kotlin-cinterop in the C interop API elements configurations and dependency-consuming configurations Issue #KT-48709 --- .../kotlin/gradle/native/GeneralNativeIT.kt | 47 +++++++++++++++++++ .../kotlin/gradle/plugin/mpp/KotlinUsages.kt | 33 ++++++++++++- .../native/internal/CInteropConfigurations.kt | 4 +- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt index 4a081e62f34..6b023e8deb5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt @@ -26,7 +26,9 @@ import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.presetName import org.junit.Assume import org.junit.Ignore +import org.junit.Rule import org.junit.Test +import org.junit.rules.ErrorCollector import java.io.File import java.util.* import kotlin.test.assertEquals @@ -1034,6 +1036,51 @@ class GeneralNativeIT : BaseGradleIT() { } } + @Test + fun testCinteropConfigurationsVariantAwareResolution() = with(transformNativeTestProjectWithPluginDsl("native-cinterop")) { + build(":publishedLibrary:publish") { + assertSuccessful() + } + + fun CompiledProject.assertVariantInDependencyInsight(variantName: String) { + try { + assertContains("variant \"$variantName\" [") + } catch (originalError: AssertionError) { + val matchedVariants = Regex("variant \"(.*?)\" \\[").findAll(output).toList() + throw AssertionError( + "Expected variant $variantName. " + + if (matchedVariants.isNotEmpty()) + "Matched instead: " + matchedVariants.joinToString { it.groupValues[1] } + else "No match.", + originalError + ) + } + } + + build(":dependencyInsight", "--configuration", "hostTestTestNumberCInterop", "--dependency", "org.example:publishedLibrary") { + assertSuccessful() + assertVariantInDependencyInsight("hostApiElements-published") + } + + gradleBuildScript("projectLibrary").appendText( + "\n" + """ + configurations.create("ktlint") { + def bundlingAttribute = Attribute.of("org.gradle.dependency.bundling", String) + attributes.attribute(bundlingAttribute, "external") + } + """.trimIndent() + ) + + build(":dependencyInsight", "--configuration", "hostTestTestNumberCInterop", "--dependency", ":projectLibrary") { + assertSuccessful() + assertVariantInDependencyInsight("hostCInteropApiElements") + } + build(":dependencyInsight", "--configuration", "hostCompileKlibraries", "--dependency", ":projectLibrary") { + assertSuccessful() + assertVariantInDependencyInsight("hostApiElements") + } + } + companion object { fun List.containsSequentially(vararg elements: String): Boolean { check(elements.isNotEmpty()) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt index 75e00ec7659..ef87b5ae7f5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinUsages.kt @@ -19,6 +19,7 @@ object KotlinUsages { const val KOTLIN_API = "kotlin-api" const val KOTLIN_RUNTIME = "kotlin-runtime" const val KOTLIN_METADATA = "kotlin-metadata" + const val KOTLIN_CINTEROP = "kotlin-cinterop" const val KOTLIN_SOURCES = "kotlin-sources" val values = setOf(KOTLIN_API, KOTLIN_RUNTIME) @@ -90,6 +91,29 @@ object KotlinUsages { } } + private class KotlinCinteropCompatibility : AttributeCompatibilityRule { + private val compatibleProducerValues = setOf(KOTLIN_API, JAVA_API, JAVA_RUNTIME) + override fun execute(details: CompatibilityCheckDetails) = with(details) { + if (consumerValue?.name == KOTLIN_CINTEROP && producerValue?.name in compatibleProducerValues) { + compatible() + } + } + } + + private class KotlinCinteropDisambiguation : AttributeDisambiguationRule { + override fun execute(details: MultipleCandidatesDetails) = details.run { + if (consumerValue?.name == KOTLIN_CINTEROP) { + val candidateNames = candidateValues.map { it?.name } + when { + KOTLIN_CINTEROP in candidateNames -> chooseCandidateByName(KOTLIN_CINTEROP) + KOTLIN_API in candidateNames -> chooseCandidateByName(KOTLIN_API) + JAVA_API in candidateNames -> chooseCandidateByName(JAVA_API) + else -> Unit + } + } + } + } + private class KotlinMetadataDisambiguation : AttributeDisambiguationRule { override fun execute(details: MultipleCandidatesDetails) = details.run { if (consumerValue?.name == KOTLIN_METADATA) { @@ -106,8 +130,6 @@ object KotlinUsages { override fun execute(details: MultipleCandidatesDetails) = with(details) { val candidateNames = candidateValues.map { it?.name }.toSet() - fun chooseCandidateByName(name: String?): Unit = closestMatch(candidateValues.single { it?.name == name }!!) - // if both API and runtime artifacts are chosen according to the compatibility rules, then // the consumer requested nothing specific, so provide them with the runtime variant, which is more complete: if (candidateNames.filterNotNull().toSet() == setOf(KOTLIN_RUNTIME, KOTLIN_API)) { @@ -135,11 +157,18 @@ object KotlinUsages { } } + private fun MultipleCandidatesDetails.chooseCandidateByName(name: String?): Unit { + closestMatch(candidateValues.single { it?.name == name }!!) + } + internal fun setupAttributesMatchingStrategy(project: Project, attributesSchema: AttributesSchema) { attributesSchema.attribute(USAGE_ATTRIBUTE) { strategy -> strategy.compatibilityRules.add(KotlinJavaRuntimeJarsCompatibility::class.java) strategy.disambiguationRules.add(KotlinUsagesDisambiguation::class.java) + strategy.compatibilityRules.add(KotlinCinteropCompatibility::class.java) + strategy.disambiguationRules.add(KotlinCinteropDisambiguation::class.java) + if (project.isKotlinGranularMetadataEnabled) { strategy.compatibilityRules.add(KotlinMetadataCompatibility::class.java) strategy.disambiguationRules.add(KotlinMetadataDisambiguation::class.java) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt index 7475d22369f..651c1c03aa7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt @@ -49,7 +49,7 @@ internal fun Project.locateOrCreateCInteropDependencyConfiguration( usesPlatformOf(target) attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, cinteropKlibLibraryElements()) - attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_API)) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_CINTEROP)) attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY)) description = "Dependencies for cinterop '${cinterop.name}' (compilation '${compilation.name}')." } @@ -65,7 +65,7 @@ internal fun Project.locateOrCreateCInteropApiElementsConfiguration(target: Kotl usesPlatformOf(target) attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, cinteropKlibLibraryElements()) - attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_API)) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_CINTEROP)) attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY)) attributes.attribute(ArtifactAttributes.ARTIFACT_FORMAT, NativeArtifactFormat.KLIB) }