Fix the localToProject attribute schema for Gradle 4.10.2+
In Gradle 4.10.2, a change was made in the attributes disambiguation process: if some of the candidates have a single attribute value and others don't have that attribute (i.e. have a null value), the disambiguation rule for that attribute doesn't get called. Effectively, null values are excluded from disambiguation, but still may cause ambiguity (sic!) See: https://github.com/gradle/gradle/issues/6747#issuecomment-445921139 This change affected our attribute `localToProject` that we use to disambiguate the deprecated but still consumable configurations like `compile`, `runtime`, `testCompile`, `testRuntime` from those configurations which should have priority during project dependency resolution: the `*Element` ones. Our scheme marked the former configurations with the attribute and the latter were not marked, with a disambiguation rule that preferred null values. To fix this logic with Gradle 4.10.2, we instead mark both kinds of configurations with the attribute, which now has a value 'public' that is preferred by the rule over the other values. We also make sure that the attribute doesn't leak into the published Gradle metadata, as it is only needed for project-to-project dependencies resolution. Issue #KT-28795 Fixed
This commit is contained in:
+4
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
package org.jetbrains.kotlin.gradle
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.UnusedSourceSetsChecker
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX
|
||||
@@ -75,6 +76,9 @@ class NewMultiplatformIT : BaseGradleIT() {
|
||||
Assert.assertTrue("$it should exist", groupDir.resolve(it).exists())
|
||||
}
|
||||
|
||||
val gradleMetadata = groupDir.resolve("sample-lib/1.0/sample-lib-1.0.module").readText()
|
||||
assertFalse(gradleMetadata.contains(ProjectLocalConfigurations.ATTRIBUTE.name))
|
||||
|
||||
listOf(jvmJarName, jsJarName, metadataJarName, wasmKlibName, nativeKlibName).forEach {
|
||||
val pom = groupDir.resolve(it.replaceAfterLast('.', "pom"))
|
||||
Assert.assertTrue(
|
||||
|
||||
+2
@@ -448,11 +448,13 @@ internal abstract class AbstractKotlinPlugin(
|
||||
if (kotlinTarget.platformType != KotlinPlatformType.common) {
|
||||
project.configurations.getByName(kotlinTarget.apiElementsConfigurationName).run {
|
||||
attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerApiUsage(kotlinTarget))
|
||||
setupAsPublicConfigurationIfSupported(kotlinTarget)
|
||||
usesPlatformOf(kotlinTarget)
|
||||
}
|
||||
|
||||
project.configurations.getByName(kotlinTarget.runtimeElementsConfigurationName).run {
|
||||
attributes.attribute(Usage.USAGE_ATTRIBUTE, KotlinUsages.producerRuntimeUsage(kotlinTarget))
|
||||
setupAsPublicConfigurationIfSupported(kotlinTarget)
|
||||
usesPlatformOf(kotlinTarget)
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -187,6 +187,7 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
|
||||
extendsFrom(runtimeConfiguration)
|
||||
}
|
||||
usesPlatformOf(target)
|
||||
setupAsPublicConfigurationIfSupported(target)
|
||||
}
|
||||
|
||||
if (mainCompilation is KotlinCompilationToRunnableFiles<*>) {
|
||||
@@ -199,6 +200,7 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
|
||||
val runtimeConfiguration = configurations.maybeCreate(mainCompilation.deprecatedRuntimeConfigurationName)
|
||||
extendsFrom(implementationConfiguration, runtimeOnlyConfiguration, runtimeConfiguration)
|
||||
usesPlatformOf(target)
|
||||
setupAsPublicConfigurationIfSupported(target)
|
||||
}
|
||||
defaultConfiguration.extendsFrom(runtimeElementsConfiguration)
|
||||
} else {
|
||||
|
||||
+16
-6
@@ -10,8 +10,11 @@ import org.gradle.api.attributes.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget
|
||||
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
|
||||
|
||||
internal object ProjectLocalConfigurations {
|
||||
val ATTRIBUTE = Attribute.of("org.jetbrains.kotlin.localToProject", String::class.java)
|
||||
object ProjectLocalConfigurations {
|
||||
val ATTRIBUTE: Attribute<String> = Attribute.of("org.jetbrains.kotlin.localToProject", String::class.java)
|
||||
|
||||
const val PUBLIC_VALUE = "public"
|
||||
const val LOCAL_TO_PROJECT_PREFIX = "local to "
|
||||
|
||||
fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) = with(attributesSchema) {
|
||||
attribute(ATTRIBUTE) {
|
||||
@@ -30,9 +33,8 @@ internal object ProjectLocalConfigurations {
|
||||
|
||||
class ProjectLocalDisambiguation : AttributeDisambiguationRule<String> {
|
||||
override fun execute(details: MultipleCandidatesDetails<String?>) = with(details) {
|
||||
if (candidateValues.contains(null)) {
|
||||
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||
closestMatch(null as String?)
|
||||
if (candidateValues.contains(PUBLIC_VALUE)) {
|
||||
closestMatch(PUBLIC_VALUE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +48,15 @@ internal fun Configuration.setupAsLocalTargetSpecificConfigurationIfSupported(ta
|
||||
(target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)
|
||||
) {
|
||||
usesPlatformOf(target)
|
||||
attributes.attribute(ProjectLocalConfigurations.ATTRIBUTE, target.project.path)
|
||||
attributes.attribute(ProjectLocalConfigurations.ATTRIBUTE, ProjectLocalConfigurations.LOCAL_TO_PROJECT_PREFIX + target.project.path)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Configuration.setupAsPublicConfigurationIfSupported(target: KotlinTarget) {
|
||||
if (gradleVersionSupportsAttributeRules &&
|
||||
(target !is KotlinWithJavaTarget<*> || target.platformType != KotlinPlatformType.common)
|
||||
) {
|
||||
attributes.attribute(ProjectLocalConfigurations.ATTRIBUTE, ProjectLocalConfigurations.PUBLIC_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-5
@@ -20,19 +20,25 @@ import org.gradle.api.attributes.AttributeContainer
|
||||
import java.util.*
|
||||
|
||||
// TODO better implementation: attribute invariants (no attrs with same name and different types allowed), thread safety?
|
||||
class HierarchyAttributeContainer(val parent: AttributeContainer?) : AttributeContainer {
|
||||
class HierarchyAttributeContainer(
|
||||
val parent: AttributeContainer?,
|
||||
val filterParentAttributes: (Attribute<*>) -> Boolean = { true }
|
||||
) : AttributeContainer {
|
||||
private val attributesMap = Collections.synchronizedMap(mutableMapOf<Attribute<*>, Any>())
|
||||
|
||||
private fun getFilteredParentAttribute(key: Attribute<*>) =
|
||||
if (parent != null && filterParentAttributes(key)) parent.getAttribute(key) else null
|
||||
|
||||
override fun contains(key: Attribute<*>): Boolean =
|
||||
attributesMap.contains(key) || parent?.contains(key) ?: false
|
||||
attributesMap.contains(key) || getFilteredParentAttribute(key) != null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any?> getAttribute(key: Attribute<T>?): T? =
|
||||
attributesMap.get(key as Attribute<*>) as T? ?: parent?.getAttribute(key)
|
||||
attributesMap.get(key as Attribute<*>) as T? ?: getFilteredParentAttribute(key) as T?
|
||||
|
||||
override fun isEmpty(): Boolean = attributesMap.isEmpty() && (parent?.isEmpty ?: true)
|
||||
override fun isEmpty(): Boolean = attributesMap.isEmpty() && (parent?.keySet().orEmpty().filter(filterParentAttributes).isEmpty())
|
||||
|
||||
override fun keySet(): Set<Attribute<*>> = attributesMap.keys + parent?.keySet().orEmpty()
|
||||
override fun keySet(): Set<Attribute<*>> = attributesMap.keys + parent?.keySet().orEmpty().filter(filterParentAttributes)
|
||||
|
||||
override fun <T : Any?> attribute(key: Attribute<T>?, value: T): AttributeContainer {
|
||||
val checkedValue = requireNotNull(value as Any?) { "null values for attributes are not supported" }
|
||||
|
||||
+2
-5
@@ -13,10 +13,7 @@ import org.gradle.api.capabilities.Capability
|
||||
import org.gradle.api.component.ComponentWithVariants
|
||||
import org.gradle.api.internal.component.SoftwareComponentInternal
|
||||
import org.gradle.api.internal.component.UsageContext
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetComponent
|
||||
import org.jetbrains.kotlin.gradle.plugin.*
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
class KotlinSoftwareComponent(
|
||||
@@ -72,7 +69,7 @@ internal class KotlinPlatformUsageContext(
|
||||
configuration.artifacts
|
||||
|
||||
override fun getAttributes(): AttributeContainer =
|
||||
configuration.outgoing.attributes
|
||||
HierarchyAttributeContainer(configuration.outgoing.attributes) { it != ProjectLocalConfigurations.ATTRIBUTE }
|
||||
|
||||
override fun getCapabilities(): Set<Capability> = emptySet()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user