[Gradle] Implement 'publishJvmEnvironmentAttribute' flag

When the flag is 'on', then publications will include
The 'org.gradle.jvm.environment' Gradle attribute.

We previously did not publish this attribute, as it lead
to problems for 'old' consumers.

We will roll out publications gradually with this feature flag.
The external Android target is expected to opt into this flag
as it is necessary for disambiguating Android from JVM targets
when the external Android target also uses KotlinPlatformType.jvm
This commit is contained in:
Sebastian Sellmair
2023-07-21 12:03:50 +02:00
committed by Space Team
parent 89c3781c8e
commit 96b33007e5
6 changed files with 188 additions and 2 deletions
@@ -789,6 +789,15 @@ public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotli
public static final fun ExternalKotlinCompilationDescriptor (Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor;
}
public abstract interface class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils {
public abstract fun enableKgpBasedDependencyResolution (Z)V
public abstract fun publishJvmEnvironmentAttribute (Z)V
}
public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtilsKt {
public static final fun getExternalKotlinTargetApiUtils (Lorg/gradle/api/Project;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils;
}
public abstract interface class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetConfigurationDescriptor {
public abstract fun getConfigure ()Lkotlin/jvm/functions/Function2;
}
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_SUPPORT
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_IMPORT_ENABLE_KGP_DEPENDENCY_RESOLUTION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_IMPORT_ENABLE_SLOW_SOURCES_JAR_RESOLVER
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_DEPENDENCY_PROPAGATION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_NATIVE_SUPPRESS_EXPERIMENTAL_ARTIFACTS_DSL_WARNING
@@ -271,6 +272,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val ignoreHmppDeprecationWarnings: Boolean?
get() = booleanProperty(KOTLIN_MPP_DEPRECATED_PROPERTIES_NO_WARN)
val publishJvmEnvironmentAttribute: Boolean
get() = booleanProperty(KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE) ?: false
/**
* Enables individual test task reporting for aggregated test tasks.
*
@@ -633,6 +637,7 @@ internal class PropertiesProvider private constructor(private val project: Proje
const val KOTLIN_CREATE_DEFAULT_MULTIPLATFORM_PUBLICATIONS = "kotlin.internal.mpp.createDefaultMultiplatformPublications"
const val KOTLIN_RUN_COMPILER_VIA_BUILD_TOOLS_API = "kotlin.compiler.runViaBuildToolsApi"
const val KOTLIN_MPP_ALLOW_LEGACY_DEPENDENCIES = "kotlin.mpp.allow.legacy.dependencies"
const val KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE = "kotlin.publishJvmEnvironmentAttribute"
const val KOTLIN_EXPERIMENTAL_TRY_K2 = "kotlin.experimental.tryK2"
const val KOTLIN_INTERNAL_VERBOSE_DIAGNOSTICS = "kotlin.internal.verboseDiagnostics"
const val KOTLIN_SUPPRESS_GRADLE_PLUGIN_WARNINGS = "kotlin.suppressGradlePluginWarnings"
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterFinaliseCompilations
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.utils.markResolvable
import org.jetbrains.kotlin.gradle.targets.metadata.*
import org.jetbrains.kotlin.gradle.targets.metadata.COMMON_MAIN_ELEMENTS_CONFIGURATION_NAME
@@ -35,7 +36,7 @@ import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
abstract class KotlinSoftwareComponent(
private val project: Project,
private val name: String,
protected val kotlinTargets: Iterable<KotlinTarget>
protected val kotlinTargets: Iterable<KotlinTarget>,
) : SoftwareComponentInternal, ComponentWithVariants {
override fun getName(): String = name
@@ -220,6 +221,8 @@ class DefaultKotlinUsageContext(
override fun getGlobalExcludes(): Set<ExcludeRule> = emptySet()
private val publishJvmEnvironmentAttribute get() = project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute
private fun filterOutNonPublishableAttributes(attributes: Set<Attribute<*>>): Set<Attribute<*>> =
attributes.filterTo(mutableSetOf()) {
it != ProjectLocalConfigurations.ATTRIBUTE &&
@@ -234,9 +237,17 @@ class DefaultKotlinUsageContext(
* 2. If this attribute is published, but not present on all the variants in a multiplatform library, and is also
* missing on the consumer side (like Gradle < 7.0, Kotlin 1.6.0), then there is a
* case when Gradle fails to choose a variant in a completely reasonable setup.
*
* UPD: 1.9.20:
* We should now be ready to publish the 'jvm environment' attribute.
* It will however be rolled out as 'opt-in' first (as safety measure).
* We expect that the 'external Android target' will opt-into publishing this attribute,
* as it will switch to KotlinPlatformType.jvm and requires this additional attribute to disambiguate
* Android from the JVM
*/
it.name != "org.gradle.jvm.environment"
(it.name != "org.gradle.jvm.environment" || publishJvmEnvironmentAttribute)
}
}
internal fun Iterable<DefaultKotlinUsageContext>.publishableUsages() = this
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.mpp.external
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_IMPORT_ENABLE_KGP_DEPENDENCY_RESOLUTION
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE
import org.jetbrains.kotlin.gradle.plugin.extraProperties
@ExternalKotlinTargetApi
interface ExternalKotlinTargetApiUtils {
fun enableKgpBasedDependencyResolution(enable: Boolean = true)
fun publishJvmEnvironmentAttribute(publish: Boolean = true)
}
@ExternalKotlinTargetApi
val Project.externalKotlinTargetApiUtils: ExternalKotlinTargetApiUtils
get() = object : ExternalKotlinTargetApiUtils {
override fun enableKgpBasedDependencyResolution(enable: Boolean) {
project.extraProperties.set(KOTLIN_MPP_IMPORT_ENABLE_KGP_DEPENDENCY_RESOLUTION, enable.toString())
}
override fun publishJvmEnvironmentAttribute(publish: Boolean) {
project.extraProperties.set(KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE, publish.toString())
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.external.externalKotlinTargetApiUtils
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import kotlin.test.*
class ExternalKotlinTargetApiUtilsTest {
@Test
fun `test - enableKgpBasedDependencyResolution`() {
val project = buildProjectWithMPP()
assertNull(project.kotlinPropertiesProvider.enableKgpDependencyResolution)
project.externalKotlinTargetApiUtils.enableKgpBasedDependencyResolution(true)
assertEquals(true, project.kotlinPropertiesProvider.enableKgpDependencyResolution)
project.externalKotlinTargetApiUtils.enableKgpBasedDependencyResolution(false)
assertEquals(false, project.kotlinPropertiesProvider.enableKgpDependencyResolution)
project.externalKotlinTargetApiUtils.enableKgpBasedDependencyResolution()
assertEquals(true, project.kotlinPropertiesProvider.enableKgpDependencyResolution)
}
@Test
fun `test - publishJvmEnvironmentAttribute`() {
val project = buildProjectWithMPP()
project.externalKotlinTargetApiUtils.publishJvmEnvironmentAttribute(true)
assertTrue(project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
project.externalKotlinTargetApiUtils.publishJvmEnvironmentAttribute(false)
assertFalse(project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
project.externalKotlinTargetApiUtils.publishJvmEnvironmentAttribute()
assertTrue(project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.unitTests
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.api.attributes.java.TargetJvmEnvironment.STANDARD_JVM
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE
import org.jetbrains.kotlin.gradle.plugin.configurationResult
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsageContext
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinVariantWithMetadataVariant
import org.jetbrains.kotlin.gradle.plugin.mpp.containsMultiplatformAttributes
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.propertiesExtension
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
import org.jetbrains.kotlin.gradle.utils.named
import kotlin.test.*
class PublishJvmEnvironmentAttributeTest {
@Test
fun `test - default value`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
configurationResult.await()
assertFalse(kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
assertNoJvmEnvironmentAttribute(kotlin.jvm())
}
@Test
fun `test - publishJvmEnvironmentAttribute disabled`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
project.propertiesExtension.set(KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE, "false")
configurationResult.await()
assertFalse(kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
assertNoJvmEnvironmentAttribute(kotlin.jvm())
}
@Test
fun `test - publishJvmEnvironmentAttribute enabled`() = buildProjectWithMPP().runLifecycleAwareTest {
val kotlin = multiplatformExtension
kotlin.jvm()
project.propertiesExtension.set(KOTLIN_PUBLISH_JVM_ENVIRONMENT_ATTRIBUTE, "true")
configurationResult.await()
assertTrue(kotlinPropertiesProvider.publishJvmEnvironmentAttribute)
assertJvmEnvironmentAttributeEquals(kotlin.jvm(), project.objects.named<TargetJvmEnvironment>(STANDARD_JVM))
}
private fun assertNoJvmEnvironmentAttribute(target: KotlinTarget) {
target.forEachUsage { usage ->
usage.attributes.containsMultiplatformAttributes
assertNull(
usage.attributes.getAttribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE),
"Expected no jvm environment attribute to be set on usage '$usage"
)
}
}
private fun assertJvmEnvironmentAttributeEquals(target: KotlinTarget, value: TargetJvmEnvironment) {
target.forEachUsage { usage ->
usage.attributes.containsMultiplatformAttributes
assertEquals(
value, usage.attributes.getAttribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE),
)
}
}
private fun KotlinTarget.forEachUsage(action: (usage: KotlinUsageContext) -> Unit) {
val component = internal.kotlinComponents.singleOrNull() ?: fail("Expected a single component. Found: ${components}")
component as KotlinVariantWithMetadataVariant
component.usages.ifEmpty { fail("Expected at least one 'usage'") }
component.usages.forEach(action)
}
}