Implement kotlin-tooling-metadata.json artifact
- Introduce new :kotlin-tooling-metadata project - Create new buildKotlinToolingMetadata task by default - Add `kotlin-tooling-metadata.json` to root mpp publications - Add kotlin.mpp.enableKotlinToolingMetadataArtifact flag to disable kotlin-tooling-metadata.json artifact ^KT-44584 Verification Pending
This commit is contained in:
committed by
TeamCityServer
parent
88058ca2c8
commit
ac8b4c1b79
@@ -0,0 +1,15 @@
|
||||
plugins {
|
||||
java
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
publish()
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
|
||||
dependencies {
|
||||
implementation(kotlinStdlib())
|
||||
implementation("com.google.code.gson:gson:${rootProject.extra["versions.jar.gson"]}")
|
||||
testImplementation(project(":kotlin-test:kotlin-test-junit"))
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.tooling
|
||||
|
||||
import com.google.gson.*
|
||||
import org.jetbrains.kotlin.tooling.KotlinToolingMetadataParsingResult.Failure
|
||||
import org.jetbrains.kotlin.tooling.KotlinToolingMetadataParsingResult.Success
|
||||
|
||||
fun KotlinToolingMetadata.toJsonString(): String {
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
return gson.toJson(toJsonObject())
|
||||
}
|
||||
|
||||
internal fun KotlinToolingMetadata.toJsonObject(): JsonObject {
|
||||
return JsonObject().apply {
|
||||
addProperty("buildSystem", buildSystem)
|
||||
addProperty("buildSystemVersion", buildSystemVersion)
|
||||
addProperty("buildPlugin", buildPlugin)
|
||||
addProperty("buildPluginVersion", buildPluginVersion)
|
||||
add("projectSettings", projectSettings.toJsonObject())
|
||||
add("projectTargets", projectTargets.toJsonArray())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KotlinToolingMetadata.ProjectSettings.toJsonObject(): JsonObject {
|
||||
return JsonObject().apply {
|
||||
addProperty("isHmppEnabled", isHmppEnabled)
|
||||
addProperty("isCompatibilityMetadataVariantEnabled", isCompatibilityMetadataVariantEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<KotlinToolingMetadata.ProjectTargetMetadata>.toJsonArray(): JsonArray {
|
||||
return JsonArray().apply {
|
||||
this@toJsonArray.forEach { targetMetadata ->
|
||||
add(targetMetadata.toJsonObject())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun KotlinToolingMetadata.ProjectTargetMetadata.toJsonObject(): JsonObject {
|
||||
return JsonObject().apply {
|
||||
addProperty("target", target)
|
||||
addProperty("platformType", platformType)
|
||||
if (extras.isNotEmpty()) {
|
||||
add("extras", JsonObject().apply {
|
||||
for (extra in extras) {
|
||||
addProperty(extra.key, extra.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class KotlinToolingMetadataParsingResult {
|
||||
data class Success(val value: KotlinToolingMetadata) : KotlinToolingMetadataParsingResult()
|
||||
data class Failure(val reason: String) : KotlinToolingMetadataParsingResult()
|
||||
}
|
||||
|
||||
fun KotlinToolingMetadata.Companion.parseJson(json: String): KotlinToolingMetadataParsingResult {
|
||||
val jsonElement = try {
|
||||
JsonParser.parseString(json)
|
||||
} catch (e: JsonParseException) {
|
||||
return Failure("Invalid json: ${e.message}")
|
||||
}
|
||||
if (jsonElement !is JsonObject) {
|
||||
return Failure("Expected JsonObject. Found ${json::class.java.canonicalName}")
|
||||
}
|
||||
return runCatching { jsonElement.toKotlinToolingMetadataOrThrow() }
|
||||
.fold(
|
||||
onSuccess = { Success(it) },
|
||||
onFailure = { Failure("Failed parsing JsonObject: ${it.message}") }
|
||||
)
|
||||
}
|
||||
|
||||
fun KotlinToolingMetadata.Companion.parseJsonOrThrow(value: String): KotlinToolingMetadata {
|
||||
return when (val result = parseJson(value)) {
|
||||
is Success -> result.value
|
||||
is Failure -> throw IllegalArgumentException(result.reason)
|
||||
}
|
||||
}
|
||||
|
||||
private fun JsonObject.toKotlinToolingMetadataOrThrow(): KotlinToolingMetadata {
|
||||
return KotlinToolingMetadata(
|
||||
buildSystem = getOrThrow("buildSystem").asString,
|
||||
buildSystemVersion = getOrThrow("buildSystemVersion").asString,
|
||||
buildPlugin = getOrThrow("buildPlugin").asString,
|
||||
buildPluginVersion = getOrThrow("buildPluginVersion").asString,
|
||||
projectSettings = getOrThrow("projectSettings").asJsonObject.toProjectSettingsOrThrow(),
|
||||
projectTargets = getOrThrow("projectTargets").asJsonArray.map { it.asJsonObject.toTargetMetadataOrThrow() }
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.toProjectSettingsOrThrow(): KotlinToolingMetadata.ProjectSettings {
|
||||
return KotlinToolingMetadata.ProjectSettings(
|
||||
isHmppEnabled = getOrThrow("isHmppEnabled").asBoolean,
|
||||
isCompatibilityMetadataVariantEnabled = getOrThrow("isCompatibilityMetadataVariantEnabled").asBoolean
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.toTargetMetadataOrThrow(): KotlinToolingMetadata.ProjectTargetMetadata {
|
||||
return KotlinToolingMetadata.ProjectTargetMetadata(
|
||||
target = getOrThrow("target").asString,
|
||||
platformType = getOrThrow("platformType").asString,
|
||||
extras = (get("extras") as? JsonObject)?.toTargetMetadataExtrasOrThrow() ?: emptyMap()
|
||||
)
|
||||
}
|
||||
|
||||
private fun JsonObject.toTargetMetadataExtrasOrThrow(): Map<String, String> {
|
||||
val map = mutableMapOf<String, String>()
|
||||
this.keySet().forEach { key ->
|
||||
val primitive = this[key] as? JsonPrimitive
|
||||
if (primitive != null) {
|
||||
map[key] = primitive.asString
|
||||
}
|
||||
}
|
||||
return map.toMap()
|
||||
}
|
||||
|
||||
private fun JsonObject.getOrThrow(key: String): JsonElement {
|
||||
return get(key) ?: throw IllegalArgumentException("Missing key: $key")
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.tooling
|
||||
|
||||
|
||||
data class KotlinToolingMetadata(
|
||||
/**
|
||||
* Build System used (e.g. Gradle, Maven, ...)
|
||||
*/
|
||||
val buildSystem: String,
|
||||
val buildSystemVersion: String,
|
||||
|
||||
/**
|
||||
* Plugin used to build (e.g.
|
||||
* - org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin
|
||||
* - org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin
|
||||
* - ...
|
||||
*/
|
||||
val buildPlugin: String,
|
||||
val buildPluginVersion: String,
|
||||
|
||||
val projectSettings: ProjectSettings,
|
||||
val projectTargets: List<ProjectTargetMetadata>,
|
||||
) {
|
||||
|
||||
data class ProjectSettings(
|
||||
val isHmppEnabled: Boolean,
|
||||
val isCompatibilityMetadataVariantEnabled: Boolean,
|
||||
)
|
||||
|
||||
data class ProjectTargetMetadata(
|
||||
val target: String,
|
||||
val platformType: String,
|
||||
val extras: Map<String, String>
|
||||
)
|
||||
|
||||
companion object
|
||||
}
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.tooling
|
||||
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
|
||||
class DeserializationFailureTest {
|
||||
@Test
|
||||
fun sample1() {
|
||||
val result = KotlinToolingMetadata.parseJson("")
|
||||
assertTrue(
|
||||
result is KotlinToolingMetadataParsingResult.Failure,
|
||||
"Expected empty String to produce Failure. Actual: $result"
|
||||
)
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { KotlinToolingMetadata.parseJsonOrThrow("") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sample2 missing build pluginVersion`() {
|
||||
@Language("JSON") val json =
|
||||
"""
|
||||
{
|
||||
"buildSystem": "Gradle",
|
||||
"buildSystemVersion": "6.7",
|
||||
"buildPlugin": "org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper",
|
||||
"projectSettings": {
|
||||
"isHmppEnabled": false,
|
||||
"isCompatibilityMetadataVariantEnabled": true
|
||||
},
|
||||
"projectTargets": []
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val result = KotlinToolingMetadata.parseJson(json)
|
||||
assertTrue(
|
||||
result is KotlinToolingMetadataParsingResult.Failure,
|
||||
"Expected parsing failure, because of missing pluginVersion. Actual: $result"
|
||||
)
|
||||
|
||||
assertFailsWith<IllegalArgumentException> { KotlinToolingMetadata.parseJsonOrThrow(json) }
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.tooling
|
||||
|
||||
import junit.framework.Assert.assertFalse
|
||||
import junit.framework.Assert.assertTrue
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/*
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
class DeserializeStringTest {
|
||||
|
||||
@Test
|
||||
fun sample1() {
|
||||
@Language("JSON") val json =
|
||||
"""
|
||||
{
|
||||
"buildSystem": "Gradle",
|
||||
"buildSystemVersion": "6.7",
|
||||
"buildPlugin": "org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper",
|
||||
"buildPluginVersion": "1.5.255-SNAPSHOT",
|
||||
"projectSettings": {
|
||||
"isHmppEnabled": false,
|
||||
"isCompatibilityMetadataVariantEnabled": true
|
||||
},
|
||||
"projectTargets": [
|
||||
{
|
||||
"target": "org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget",
|
||||
"platformType": "androidJvm",
|
||||
"extras": {
|
||||
"sourceCompatibility": "1.7",
|
||||
"targetCompatibility": "1.7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target": "org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget_Decorated",
|
||||
"platformType": "js",
|
||||
"extras": {
|
||||
"isBrowserConfigured": "true",
|
||||
"isNodejsConfigured": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target": "org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget_Decorated",
|
||||
"platformType": "jvm",
|
||||
"extras": {
|
||||
"withJavaEnabled": "false",
|
||||
"jvmTarget": "1.8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"target": "org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget_Decorated",
|
||||
"platformType": "common"
|
||||
}
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val metadata = KotlinToolingMetadata.parseJsonOrThrow(json)
|
||||
assertEquals("Gradle", metadata.buildSystem)
|
||||
assertEquals("6.7", metadata.buildSystemVersion)
|
||||
assertEquals("org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper", metadata.buildPlugin)
|
||||
assertEquals("1.5.255-SNAPSHOT", metadata.buildPluginVersion)
|
||||
assertFalse(metadata.projectSettings.isHmppEnabled)
|
||||
assertTrue(metadata.projectSettings.isCompatibilityMetadataVariantEnabled)
|
||||
assertEquals(4, metadata.projectTargets.size, "Expected exactly 4 targets")
|
||||
|
||||
val androidJvmTarget = metadata.projectTargets.single { it.platformType == "androidJvm" }
|
||||
assertEquals("org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget", androidJvmTarget.target)
|
||||
assertEquals(2, androidJvmTarget.extras.size, "Expected exactly two extras")
|
||||
assertEquals("1.7", androidJvmTarget.extras["sourceCompatibility"])
|
||||
assertEquals("1.7", androidJvmTarget.extras["targetCompatibility"])
|
||||
|
||||
val jsTarget = metadata.projectTargets.single { it.platformType == "js" }
|
||||
assertEquals("org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget_Decorated", jsTarget.target)
|
||||
assertEquals(2, jsTarget.extras.size, "Expected exactly two extras")
|
||||
assertEquals("true", jsTarget.extras["isBrowserConfigured"])
|
||||
assertEquals("true", jsTarget.extras["isNodejsConfigured"])
|
||||
|
||||
val jvmTarget = metadata.projectTargets.single { it.platformType == "jvm" }
|
||||
assertEquals("org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget_Decorated", jvmTarget.target)
|
||||
assertEquals(2, jvmTarget.extras.size, "Expected exactly two extras")
|
||||
assertEquals("false", jvmTarget.extras["withJavaEnabled"])
|
||||
assertEquals("1.8", jvmTarget.extras["jvmTarget"])
|
||||
|
||||
val commonTarget = metadata.projectTargets.single { it.platformType == "common" }
|
||||
assertEquals("org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget_Decorated", commonTarget.target)
|
||||
assertEquals(0, commonTarget.extras.size, "Expected zero extras")
|
||||
}
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.tooling
|
||||
|
||||
import org.jetbrains.kotlin.tooling.KotlinToolingMetadata.ProjectTargetMetadata
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/*
|
||||
* Copyright 2010-2021 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.
|
||||
*/
|
||||
|
||||
class SerializeAndDeserializeTest {
|
||||
|
||||
@Test
|
||||
fun sample1() = assertDeserializedMatchesOrigin(
|
||||
defaultKotlinToolingMetadata()
|
||||
)
|
||||
|
||||
@Test
|
||||
fun sample2() = assertDeserializedMatchesOrigin(
|
||||
defaultKotlinToolingMetadata().copy(
|
||||
projectTargets = listOf(
|
||||
ProjectTargetMetadata(
|
||||
target = "generic target",
|
||||
platformType = "generic platform type",
|
||||
extras = mapOf(
|
||||
"extra0" to "extra value0",
|
||||
"extra1" to "extra value1"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun sample3() = assertDeserializedMatchesOrigin(
|
||||
defaultKotlinToolingMetadata().copy(
|
||||
projectTargets = listOf(
|
||||
ProjectTargetMetadata(
|
||||
target = "generic target",
|
||||
platformType = "generic platform type",
|
||||
extras = mapOf(
|
||||
"extra0" to "{ some extra value %\" with, chars to escape",
|
||||
"extra1" to "extra value1"
|
||||
)
|
||||
),
|
||||
ProjectTargetMetadata(
|
||||
target = "generic target 2 (with no extras)",
|
||||
platformType = "generic platform type 2",
|
||||
extras = emptyMap()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertDeserializedMatchesOrigin(origin: KotlinToolingMetadata) {
|
||||
val json = origin.toJsonString()
|
||||
val deserialized = KotlinToolingMetadata.parseJsonOrThrow(json)
|
||||
assertEquals(origin, deserialized)
|
||||
}
|
||||
|
||||
private fun defaultKotlinToolingMetadata(): KotlinToolingMetadata {
|
||||
return KotlinToolingMetadata(
|
||||
buildSystem = "generic build system",
|
||||
buildSystemVersion = "v1.0 (build system)",
|
||||
buildPlugin = "generic build plugin",
|
||||
buildPluginVersion = "v1.0 (build plugin)",
|
||||
projectSettings = KotlinToolingMetadata.ProjectSettings(
|
||||
isHmppEnabled = false,
|
||||
isCompatibilityMetadataVariantEnabled = true
|
||||
),
|
||||
projectTargets = emptyList()
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user