From 96b33007e5f33d3570551e5bf62d0da47f02ee25 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Fri, 21 Jul 2023 12:03:50 +0200 Subject: [PATCH] [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 --- .../api/kotlin-gradle-plugin.api | 9 ++ .../gradle/plugin/PropertiesProvider.kt | 5 ++ .../plugin/mpp/KotlinSoftwareComponent.kt | 15 +++- .../external/ExternalKotlinTargetApiUtils.kt | 31 +++++++ .../ExternalKotlinTargetApiUtilsTest.kt | 44 ++++++++++ .../PublishJvmEnvironmentAttributeTest.kt | 86 +++++++++++++++++++ 6 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiUtilsTest.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PublishJvmEnvironmentAttributeTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api b/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api index bfa58ed241a..0682a6cd9ea 100644 --- a/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api +++ b/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api @@ -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; } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index d17705fb054..90f7b7ff7f6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt index 93c78a5dffb..cfdb4cc56cc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinSoftwareComponent.kt @@ -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 + protected val kotlinTargets: Iterable, ) : SoftwareComponentInternal, ComponentWithVariants { override fun getName(): String = name @@ -220,6 +221,8 @@ class DefaultKotlinUsageContext( override fun getGlobalExcludes(): Set = emptySet() + private val publishJvmEnvironmentAttribute get() = project.kotlinPropertiesProvider.publishJvmEnvironmentAttribute + private fun filterOutNonPublishableAttributes(attributes: Set>): Set> = 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.publishableUsages() = this diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils.kt new file mode 100644 index 00000000000..b656eb9dd33 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetApiUtils.kt @@ -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()) + } + } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiUtilsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiUtilsTest.kt new file mode 100644 index 00000000000..aa4d8ae4c1a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiUtilsTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PublishJvmEnvironmentAttributeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PublishJvmEnvironmentAttributeTest.kt new file mode 100644 index 00000000000..be301dd967b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/PublishJvmEnvironmentAttributeTest.kt @@ -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(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) + } +}