[Gradle] Map KPM model to Build Tooling Metadata v1.1.0

Update buildKotlinToolingMetadata gradle task

KT-49704
This commit is contained in:
Anton Lakotka
2022-02-25 10:53:34 +01:00
parent 2dc774c1fe
commit ac8e7606a2
3 changed files with 205 additions and 31 deletions
@@ -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),
)
}
}
@@ -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())
}
@@ -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<KotlinToolingMetadata.ProjectTargetMetadata> {
val result = mutableSetOf<KotlinToolingMetadata.ProjectTargetMetadata>()
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
)
}