From ac8e7606a2df000163c6314066f5663411c32196 Mon Sep 17 00:00:00 2001 From: Anton Lakotka Date: Fri, 25 Feb 2022 10:53:34 +0100 Subject: [PATCH] [Gradle] Map KPM model to Build Tooling Metadata v1.1.0 Update buildKotlinToolingMetadata gradle task KT-49704 --- .../gradle/BuildKotlinToolingMetadataTest.kt | 139 ++++++++++++++---- .../gradle/MultiplatformExtensionTest.kt | 5 + .../tooling/BuildKotlinToolingMetadataTask.kt | 92 +++++++++++- 3 files changed, 205 insertions(+), 31 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/BuildKotlinToolingMetadataTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/BuildKotlinToolingMetadataTest.kt index fe033e83cd9..eaa8905a013 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/BuildKotlinToolingMetadataTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/BuildKotlinToolingMetadataTest.kt @@ -23,7 +23,9 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.* import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.* import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask @@ -32,12 +34,22 @@ import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.library.KotlinAbiVersion import org.jetbrains.kotlin.tooling.KotlinToolingMetadata import org.jetbrains.kotlin.tooling.toJsonString +import org.junit.Assume +import org.junit.AssumptionViolatedException +import org.junit.Before +import org.junit.runner.RunWith +import org.junit.runners.Parameterized import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue +@RunWith(Parameterized::class) class BuildKotlinToolingMetadataTest { + @Parameterized.Parameter(0) + @JvmField + val kpmModelMappingEnabled: Boolean = false + private val project = ProjectBuilder.builder().build().also{ addBuildEventsListenerRegistryMock(it) } as ProjectInternal private val multiplatformExtension get() = project.extensions.getByType(KotlinMultiplatformExtension::class.java) private val jsExtension get() = project.extensions.getByType(KotlinJsProjectExtension::class.java) @@ -46,6 +58,11 @@ class BuildKotlinToolingMetadataTest { project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableKotlinToolingMetadataArtifact", "true") } + @Before + fun setup() { + project.enableKpmModelMapping(kpmModelMappingEnabled) + } + @Test fun `multiplatform empty setup`() { project.plugins.apply("kotlin-multiplatform") @@ -57,11 +74,19 @@ class BuildKotlinToolingMetadataTest { assertEquals(KotlinMultiplatformPluginWrapper::class.java.canonicalName, metadata.buildPlugin) assertEquals(project.getKotlinPluginVersion(), metadata.buildPluginVersion) assertEquals(1, metadata.projectTargets.size, "Expected one target (metadata)") - assertTrue( - KotlinMetadataTarget::class.java.isAssignableFrom(Class.forName(metadata.projectTargets.single().target)), - "Expect target to be implement ${KotlinMetadataTarget::class.simpleName}" - ) - assertEquals(common.name, metadata.projectTargets.single().platformType) + val targetMetadata = metadata.projectTargets.single() + if (kpmModelMappingEnabled) { + assertTrue( + KotlinGradleFragment::class.java.isAssignableFrom(Class.forName(targetMetadata.target)), + "Expect target to be implement ${KotlinGradleFragment::class.simpleName}" + ) + } else { + assertTrue( + KotlinMetadataTarget::class.java.isAssignableFrom(Class.forName(targetMetadata.target)), + "Expect target to be implement ${KotlinMetadataTarget::class.simpleName}" + ) + } + assertEquals(common.name, targetMetadata.platformType) assertTrue(metadata.toJsonString().isNotBlank(), "Expected non blank json representation") } @@ -87,35 +112,38 @@ class BuildKotlinToolingMetadataTest { val metadata = getKotlinToolingMetadata() assertEquals(KotlinMultiplatformPluginWrapper::class.java.canonicalName, metadata.buildPlugin) + val expectedTargets = if (kpmModelMappingEnabled) { + mapOf( + common to KotlinGradleFragmentInternal::class, + androidJvm to LegacyMappedVariantWithRuntime::class, + jvm to KotlinJvmVariant::class, + js to LegacyMappedVariantWithRuntime::class, + native to KotlinLinuxX64Variant::class + ) + } else { + mapOf( + common to KotlinMetadataTarget::class, + androidJvm to KotlinAndroidTarget::class, + jvm to KotlinJvmTarget::class, + js to KotlinJsTarget::class, + native to KotlinNativeTargetWithHostTests::class + ) + } + assertEquals( - listOf(common, androidJvm, jvm, js, native).map { it.name }.sorted(), + expectedTargets.keys.map { it.name }.sorted(), metadata.projectTargets.map { it.platformType }.sorted() ) - assertEquals( - KotlinMetadataTarget::class.java.canonicalName, - metadata.projectTargets.single { it.platformType == common.name }.target - ) + expectedTargets.forEach { (platformType, targetClass) -> + assertEquals( + targetClass.java.canonicalName, + metadata.projectTargets.single { it.platformType == platformType.name }.target, + "Platform '$platformType' has different target class" + ) + } - assertEquals( - KotlinAndroidTarget::class.java.canonicalName, - metadata.projectTargets.single { it.platformType == androidJvm.name }.target - ) - - assertEquals( - KotlinJvmTarget::class.java.canonicalName, - metadata.projectTargets.single { it.platformType == jvm.name }.target - ) - - assertEquals( - KotlinJsTarget::class.java.canonicalName, - metadata.projectTargets.single { it.platformType == js.name }.target - ) - - assertEquals( - KotlinNativeTargetWithHostTests::class.java.canonicalName, - metadata.projectTargets.single { it.platformType == native.name }.target - ) + assertEquals(kpmModelMappingEnabled, metadata.projectSettings.isKPMEnabled, "projectSettings.isKPMEnabled must be set") } @Test @@ -167,6 +195,7 @@ class BuildKotlinToolingMetadataTest { @Test fun js() { + Assume.assumeFalse("KPM model mapping is not yet supported in single-platform projects", kpmModelMappingEnabled) project.plugins.apply("org.jetbrains.kotlin.js") val kotlin = jsExtension kotlin.js { nodejs() } @@ -181,13 +210,65 @@ class BuildKotlinToolingMetadataTest { @Test fun jvm() { + Assume.assumeFalse("KPM model mapping is not yet supported in single-platform projects", kpmModelMappingEnabled) project.plugins.apply("org.jetbrains.kotlin.jvm") val metadata = getKotlinToolingMetadata() assertEquals(org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper::class.java.canonicalName, metadata.buildPlugin) } + @Test + fun `multiple native targets`() { + project.plugins.apply("kotlin-multiplatform") + val kotlin = multiplatformExtension + kotlin.linuxX64() + kotlin.linuxArm64() + + val metadata = getKotlinToolingMetadata() + val nativeTargets = metadata.projectTargets.filter { it.platformType == native.name }.sortedBy { it.extras.native?.konanTarget } + assertEquals(2, nativeTargets.size, "Expected only two native targets") + val (linuxArm64, linuxX64) = nativeTargets + + assertEquals( + "linux_arm64", + linuxArm64.extras.native?.konanTarget + ) + assertEquals( + "linux_x64", + linuxX64.extras.native?.konanTarget + ) + + if (kpmModelMappingEnabled) { + assertEquals( + KotlinLinuxArm64Variant::class.java.canonicalName, + linuxArm64.target + ) + assertEquals( + KotlinLinuxX64Variant::class.java.canonicalName, + linuxX64.target + ) + } else { + assertEquals( + KotlinNativeTarget::class.java.canonicalName, + linuxArm64.target + ) + assertEquals( + KotlinNativeTargetWithHostTests::class.java.canonicalName, + linuxX64.target + ) + } + } + private fun getKotlinToolingMetadata(): KotlinToolingMetadata { val task = project.buildKotlinToolingMetadataTask?.get() ?: error("No ${BuildKotlinToolingMetadataTask.defaultTaskName} task") return task.kotlinToolingMetadata } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "KPM-Model-Mapping:{0}") + fun parameters() = listOf( + arrayOf(false), + arrayOf(true), + ) + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt index 2e00f71bada..101db77e1b4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/MultiplatformExtensionTest.kt @@ -14,6 +14,7 @@ import org.gradle.api.plugins.ExtraPropertiesExtension import org.gradle.testfixtures.ProjectBuilder import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_KPM_EXPERIMENTAL_MODEL_MAPPING import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_STDLIB_DEFAULT_DEPENDENCY @@ -103,6 +104,10 @@ fun Project.enableHierarchicalStructureByDefault(enabled: Boolean = true) { propertiesExtension.set(KOTLIN_MPP_HIERARCHICAL_STRUCTURE_BY_DEFAULT, enabled.toString()) } +fun Project.enableKpmModelMapping(enabled: Boolean = true) { + propertiesExtension.set(KOTLIN_KPM_EXPERIMENTAL_MODEL_MAPPING, enabled.toString()) +} + fun Project.enableDefaultStdlibDependency(enabled: Boolean = true) { project.propertiesExtension.set(KOTLIN_STDLIB_DEFAULT_DEPENDENCY, enabled.toString()) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tooling/BuildKotlinToolingMetadataTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tooling/BuildKotlinToolingMetadataTask.kt index d24b90e70c6..96489ca35e9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tooling/BuildKotlinToolingMetadataTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tooling/BuildKotlinToolingMetadataTask.kt @@ -18,6 +18,8 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.* +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.hasKpmModel import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsSubTargetContainerDsl import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget import org.jetbrains.kotlin.gradle.targets.metadata.isCompatibilityMetadataVariantEnabled @@ -62,7 +64,11 @@ abstract class BuildKotlinToolingMetadataTask : DefaultTask() { @get:Internal internal val kotlinToolingMetadata by lazy { - project.kotlinExtension.getKotlinToolingMetadata() + if (project.hasKpmModel) { + project.kpmModelContainer.getKotlinToolingMetadata(project) + } else { + project.kotlinExtension.getKotlinToolingMetadata() + } } @get:Input @@ -79,6 +85,87 @@ abstract class BuildKotlinToolingMetadataTask : DefaultTask() { } } +private fun KpmGradleProjectModelContainer.getKotlinToolingMetadata(project: Project): KotlinToolingMetadata { + return KotlinToolingMetadata( + schemaVersion = KotlinToolingMetadata.currentSchemaVersion, + buildSystem = "Gradle", + buildSystemVersion = project.gradle.gradleVersion, + buildPlugin = project.plugins.withType(KotlinBasePluginWrapper::class.java).joinToString(";") { it.javaClass.canonicalName }, + buildPluginVersion = project.getKotlinPluginVersion(), + projectSettings = project.buildProjectSettings(), + projectTargets = buildProjectTargets() + ) +} + +private fun Project.buildProjectSettings(): KotlinToolingMetadata.ProjectSettings { + return KotlinToolingMetadata.ProjectSettings( + isHmppEnabled = project.isKotlinGranularMetadataEnabled, + isCompatibilityMetadataVariantEnabled = project.isCompatibilityMetadataVariantEnabled, + isKPMEnabled = project.hasKpmModel + ) +} + +private fun KpmGradleProjectModelContainer.buildProjectTargets(): List { + val result = mutableSetOf() + val mainModules = modules.filter { it.isMain } + + for (module in mainModules) { + result += KotlinToolingMetadata.ProjectTargetMetadata( + target = module.common.javaClass.canonicalName, + platformType = KotlinPlatformType.common.name, + extras = KotlinToolingMetadata.ProjectTargetMetadata.Extras() + ) + + for (variant in module.variants) { + result += KotlinToolingMetadata.ProjectTargetMetadata( + target = variant.javaClass.canonicalName, + platformType = variant.platformType.name, + extras = KotlinToolingMetadata.ProjectTargetMetadata.Extras( + jvm = variant.jvmExtrasOrNull(), + android = variant.androidExtrasOrNull(), + js = variant.jsExtrasOrNull(), + native = variant.nativeExtrasOrNull() + ) + ) + } + } + + return result.toList() +} + +private fun KotlinGradleVariant.jvmExtrasOrNull() = + when (this) { + is KotlinJvmVariant -> KotlinToolingMetadata.ProjectTargetMetadata.JvmExtras( + jvmTarget = compilationData.kotlinOptions.jvmTarget, + withJavaEnabled = false + ) + is LegacyMappedVariant -> buildJvmExtrasOrNull(compilation.target) + else -> null + } + +private fun KotlinGradleVariant.androidExtrasOrNull() = + when (this) { + is LegacyMappedVariant -> buildAndroidExtrasOrNull(compilation.target) + else -> null + } + +private fun KotlinGradleVariant.jsExtrasOrNull() = + when (this) { + is LegacyMappedVariant -> buildJsExtrasOrNull(compilation.target) + else -> null + } + +private fun KotlinGradleVariant.nativeExtrasOrNull() = + when (this) { + is KotlinNativeVariantInternal -> KotlinToolingMetadata.ProjectTargetMetadata.NativeExtras( + konanTarget = konanTarget.name, + konanVersion = project.konanVersion.toString(), + konanAbiVersion = KotlinAbiVersion.CURRENT.toString() + ) + is LegacyMappedVariant -> buildNativeExtrasOrNull(compilation.target) + else -> null + } + private fun KotlinProjectExtension.getKotlinToolingMetadata(): KotlinToolingMetadata { return KotlinToolingMetadata( schemaVersion = KotlinToolingMetadata.currentSchemaVersion, @@ -94,7 +181,8 @@ private fun KotlinProjectExtension.getKotlinToolingMetadata(): KotlinToolingMeta private fun KotlinProjectExtension.buildProjectSettings(): KotlinToolingMetadata.ProjectSettings { return KotlinToolingMetadata.ProjectSettings( isHmppEnabled = project.isKotlinGranularMetadataEnabled, - isCompatibilityMetadataVariantEnabled = project.isCompatibilityMetadataVariantEnabled + isCompatibilityMetadataVariantEnabled = project.isCompatibilityMetadataVariantEnabled, + isKPMEnabled = false ) }