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
This commit is contained in:
+47
@@ -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<String>.containsSequentially(vararg elements: String): Boolean {
|
||||
check(elements.isNotEmpty())
|
||||
|
||||
+31
-2
@@ -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<Usage> {
|
||||
private val compatibleProducerValues = setOf(KOTLIN_API, JAVA_API, JAVA_RUNTIME)
|
||||
override fun execute(details: CompatibilityCheckDetails<Usage>) = with(details) {
|
||||
if (consumerValue?.name == KOTLIN_CINTEROP && producerValue?.name in compatibleProducerValues) {
|
||||
compatible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class KotlinCinteropDisambiguation : AttributeDisambiguationRule<Usage> {
|
||||
override fun execute(details: MultipleCandidatesDetails<Usage?>) = 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<Usage> {
|
||||
override fun execute(details: MultipleCandidatesDetails<Usage>) = details.run {
|
||||
if (consumerValue?.name == KOTLIN_METADATA) {
|
||||
@@ -106,8 +130,6 @@ object KotlinUsages {
|
||||
override fun execute(details: MultipleCandidatesDetails<Usage?>) = 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<Usage?>.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)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user