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:
sebastian.sellmair
2021-02-02 14:04:05 +01:00
committed by TeamCityServer
parent 88058ca2c8
commit ac8b4c1b79
29 changed files with 975 additions and 9 deletions
+3 -1
View File
@@ -41,6 +41,7 @@ val kotlinGradlePluginAndItsRequired = arrayOf(
":kotlin-gradle-plugin-api",
":kotlin-gradle-plugin",
":kotlin-gradle-plugin-model",
":kotlin-tooling-metadata",
":kotlin-reflect",
":kotlin-annotation-processing-gradle",
":kotlin-test",
@@ -59,7 +60,8 @@ val kotlinGradlePluginAndItsRequired = arrayOf(
":kotlin-serialization",
":kotlin-test-js-runner",
":native:kotlin-klib-commonizer-embeddable",
":native:kotlin-klib-commonizer-api"
":native:kotlin-klib-commonizer-api",
":native:kotlin-native-utils"
)
fun Task.dependsOnKotlinGradlePluginInstall() {
+1
View File
@@ -19,6 +19,7 @@
<trust group="org.jetbrains.kotlin" name="kotlin-gradle-plugin-model"/>
<trust group="org.jetbrains.kotlin" name="kotlin-klib-commonizer-api"/>
<trust group="org.jetbrains.kotlin" name="kotlin-klib-commonizer-embeddable"/>
<trust group="org.jetbrains.kotlin" name="kotlin-tooling-metadata"/>
<trust group="org.jetbrains.kotlin" name="kotlin-native-utils"/>
<trust group="org.jetbrains.kotlin" name="kotlin-reflect"/>
<trust group="org.jetbrains.kotlin" name="kotlin-sam-with-receiver"/>
@@ -15,6 +15,7 @@ val kotlinGradlePluginTest = project(":kotlin-gradle-plugin").sourceSets.named("
dependencies {
testImplementation(project(":kotlin-gradle-plugin"))
testImplementation(project(":kotlin-tooling-metadata"))
testImplementation(kotlinGradlePluginTest)
testImplementation(project(":kotlin-gradle-subplugin-example"))
testImplementation(project(":kotlin-allopen"))
@@ -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.gradle
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.tooling.KotlinToolingMetadata
import org.jetbrains.kotlin.tooling.parseJsonOrThrow
import org.junit.Test
import kotlin.test.assertEquals
class KotlinToolingMetadataIT : BaseGradleIT() {
override val defaultGradleVersion: GradleVersionRequired = GradleVersionRequired.FOR_MPP_SUPPORT
private val defaultKotlinToolingMetadataJsonPath = "build/kotlinToolingMetadata/kotlin-tooling-metadata.json"
@Test
fun `new-mpp-published`() = with(transformProjectWithPluginsDsl("new-mpp-published")) {
build("publish") {
assertSuccessful()
assertTasksExecuted(
":${BuildKotlinToolingMetadataTask.defaultTaskName}"
)
/* Check metadata file in build dir */
assertFileExists(defaultKotlinToolingMetadataJsonPath)
val metadataJson = projectDir.resolve(defaultKotlinToolingMetadataJsonPath).readText()
val metadata = KotlinToolingMetadata.parseJsonOrThrow(metadataJson)
assertEquals(
listOf(
KotlinPlatformType.common.name, KotlinPlatformType.jvm.name,
KotlinPlatformType.js.name, KotlinPlatformType.native.name
).sorted(),
metadata.projectTargets.map { it.platformType }.sorted()
)
/* Check metadata file in published repository */
val publishedMetadataJson = projectDir.parentFile.resolve(
"repo/com/example/bar/my-lib-bar/1.0/my-lib-bar-1.0-kotlin-tooling-metadata.json"
).readText()
assertEquals(
metadataJson, publishedMetadataJson,
"Expected published kotlin-tooling-metadata.json to contain same content as in buildDir"
)
}
/* Checking UP-TO-DATE behaviour */
build("publish") {
assertSuccessful()
// Nothing changed. Should be up-to-date
assertTasksUpToDate(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
}
// Adding macos target
val buildFile = projectDir.resolve("build.gradle.kts")
buildFile.writeText(
buildFile.readText().replace(
"linuxX64()", "linuxX64()\nmacosX64()"
)
)
build("publish") {
assertSuccessful()
assertTasksExecuted(":${BuildKotlinToolingMetadataTask.defaultTaskName}")
val metadata = KotlinToolingMetadata.parseJsonOrThrow(projectDir.resolve(defaultKotlinToolingMetadataJsonPath).readText())
assertEquals(
listOf(KonanTarget.LINUX_X64.name, KonanTarget.MACOS_X64.name).sorted(),
metadata.projectTargets.mapNotNull { it.extras["konanTarget"] }.sorted()
)
}
}
@Test
fun `new-mpp-published with KotlinToolingMetadataArtifact disabled`() = with(transformProjectWithPluginsDsl("new-mpp-published")) {
projectDir.resolve("gradle.properties").appendText("\nkotlin.mpp.enableKotlinToolingMetadataArtifact=false")
build("publish") {
assertSuccessful()
assertNoSuchFile(defaultKotlinToolingMetadataJsonPath)
assertTasksNotRealized(
":${BuildKotlinToolingMetadataTask.defaultTaskName}"
)
}
}
@Test
fun `kotlin-js-browser-project`() = with(transformProjectWithPluginsDsl("kotlin-js-browser-project")) {
build(BuildKotlinToolingMetadataTask.defaultTaskName) {
assertSuccessful()
assertTasksExecuted(":app:${BuildKotlinToolingMetadataTask.defaultTaskName}")
assertTasksExecuted(":base:${BuildKotlinToolingMetadataTask.defaultTaskName}")
assertTasksExecuted(":lib:${BuildKotlinToolingMetadataTask.defaultTaskName}")
}
}
}
@@ -0,0 +1,94 @@
plugins {
kotlin("multiplatform").version("<pluginMarkerVersion>")
`maven-publish`
}
repositories {
mavenLocal()
maven("../repo")
jcenter()
}
group = "com.example.bar"
version = "1.0"
kotlin {
jvm()
js()
linuxX64()
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmAndJsMain by creating {
dependsOn(commonMain)
}
val jvmAndJsTest by creating {
dependsOn(commonTest)
}
val linuxAndJsMain by creating {
dependsOn(commonMain)
}
val linuxAndJsTest by creating {
dependsOn(commonTest)
}
jvm().compilations["main"].defaultSourceSet {
dependsOn(jvmAndJsMain)
dependencies {
implementation(kotlin("stdlib"))
}
}
jvm().compilations["test"].defaultSourceSet {
dependsOn(jvmAndJsTest)
dependencies {
implementation(kotlin("test-junit"))
}
}
js().compilations["main"].defaultSourceSet {
dependsOn(jvmAndJsMain)
dependsOn(linuxAndJsMain)
dependencies {
implementation(kotlin("stdlib-js"))
}
}
js().compilations["test"].defaultSourceSet {
dependsOn(jvmAndJsTest)
dependsOn(linuxAndJsTest)
dependencies {
implementation(kotlin("test-js"))
}
}
linuxX64().compilations["main"].defaultSourceSet {
dependsOn(linuxAndJsMain)
}
linuxX64().compilations["test"].defaultSourceSet {
dependsOn(linuxAndJsTest)
}
}
}
publishing {
repositories {
maven("../repo")
}
}
@@ -0,0 +1,2 @@
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.mpp.enableCompatibilityMetadataVariant=true
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
rootProject.name = "my-lib-bar"
enableFeaturePreview("GRADLE_METADATA")
@@ -0,0 +1,10 @@
package com.example.bar
import kotlin.test.Test
class BarTest {
@Test
fun testBar() {
}
}
@@ -0,0 +1,15 @@
package com.example.bar
import kotlin.test.Test
class BarJvmAndJsTest {
@Test
fun testBarJvmAndJs() {
}
@Test
fun testThirdParty() {
}
}
@@ -0,0 +1,10 @@
package com.example.bar
import kotlin.test.Test
class BarLinuxAndJsTest {
@Test
fun testBarLinuxAndJs() {
}
}
@@ -43,6 +43,8 @@ dependencies {
implementation(kotlinStdlib())
implementation(project(":kotlin-util-klib"))
implementation(project(":native:kotlin-klib-commonizer-api"))
implementation(project(":kotlin-tooling-metadata"))
compileOnly(project(":native:kotlin-native-utils"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(project(":kotlin-android-extensions"))
compileOnly(project(":kotlin-build-common"))
@@ -0,0 +1,154 @@
/*
* 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.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle
import com.android.build.gradle.BaseExtension
import org.gradle.api.JavaVersion
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.dsl.KotlinJsProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.tooling.KotlinToolingMetadata
import org.jetbrains.kotlin.tooling.toJsonString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class BuildKotlinToolingMetadataTest {
private val project = ProjectBuilder.builder().build() as ProjectInternal
private val multiplatform get() = project.extensions.getByType(KotlinMultiplatformExtension::class.java)
private val js get() = project.extensions.getByType(KotlinJsProjectExtension::class.java)
@Test
fun `multiplatform empty setup`() {
project.plugins.apply("kotlin-multiplatform")
val metadata = getKotlinToolingMetadata()
assertEquals("Gradle", metadata.buildSystem)
assertEquals(project.gradle.gradleVersion, metadata.buildSystemVersion)
assertEquals(KotlinMultiplatformPluginWrapper::class.java.canonicalName, metadata.buildPlugin)
assertEquals(project.getKotlinPluginVersion().toString(), 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(
KotlinPlatformType.common.name, metadata.projectTargets.single().platformType
)
assertTrue(metadata.toJsonString().isNotBlank(), "Expected non blank json representation")
}
@Test
fun `multiplatform JS JVM Android setup`() {
project.plugins.apply("com.android.application")
project.plugins.apply("kotlin-multiplatform")
val android = project.extensions.getByType(BaseExtension::class.java)
val kotlin = multiplatform
android.compileSdkVersion(28)
kotlin.android()
kotlin.jvm()
kotlin.js {
nodejs()
browser()
}
project.evaluate()
val metadata = getKotlinToolingMetadata()
assertEquals(KotlinMultiplatformPluginWrapper::class.java.canonicalName, metadata.buildPlugin)
assertEquals(
listOf(KotlinPlatformType.common, KotlinPlatformType.androidJvm, KotlinPlatformType.jvm, KotlinPlatformType.js)
.map { it.name }.sorted(),
metadata.projectTargets.map { it.platformType }.sorted()
)
}
@Test
fun `multiplatform Android target with different source and target compatibility`() {
project.plugins.apply("kotlin-multiplatform")
project.plugins.apply("com.android.application")
val android = project.extensions.getByType(BaseExtension::class.java)
val kotlin = multiplatform
android.compileSdkVersion(28)
kotlin.android()
android.compileOptions.setSourceCompatibility(JavaVersion.VERSION_1_6)
android.compileOptions.setTargetCompatibility(JavaVersion.VERSION_1_8)
project.evaluate()
val androidTargetMetadata = getKotlinToolingMetadata()
.projectTargets.single { it.platformType == KotlinPlatformType.androidJvm.name }
assertEquals("1.6", androidTargetMetadata.extras["sourceCompatibility"])
assertEquals("1.8", androidTargetMetadata.extras["targetCompatibility"])
}
@Test
fun `multiplatform JVM with different targets`() {
project.plugins.apply("kotlin-multiplatform")
val kotlin = multiplatform
val jvm = kotlin.jvm()
jvm.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME).kotlinOptions.jvmTarget = "12"
jvm.compilations.getByName(KotlinCompilation.TEST_COMPILATION_NAME).kotlinOptions.jvmTarget = "10"
assertEquals(
"12", getKotlinToolingMetadata().projectTargets
.single { it.platformType == KotlinPlatformType.jvm.name }.extras["jvmTarget"],
"Expected jvmTarget of main compilation"
)
}
@Test
fun `multiplatform with native target`() {
project.plugins.apply("kotlin-multiplatform")
val kotlin = multiplatform
kotlin.linuxX64()
val metadata = getKotlinToolingMetadata()
val linuxTarget = metadata.projectTargets.single { it.platformType == KotlinPlatformType.native.name }
assertEquals(KonanTarget.LINUX_X64.name, linuxTarget.extras["konanTarget"])
}
@Test
fun js() {
project.plugins.apply("org.jetbrains.kotlin.js")
val kotlin = js
kotlin.js { nodejs() }
val metadata = getKotlinToolingMetadata()
assertEquals(org.jetbrains.kotlin.gradle.plugin.KotlinJsPluginWrapper::class.java.canonicalName, metadata.buildPlugin)
val jsTarget = metadata.projectTargets.single { it.platformType == KotlinPlatformType.js.name }
assertEquals("true", jsTarget.extras["isNodejsConfigured"])
assertEquals("false", jsTarget.extras["isBrowserConfigured"])
}
@Test
fun jvm() {
project.plugins.apply("org.jetbrains.kotlin.jvm")
val metadata = getKotlinToolingMetadata()
assertEquals(org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper::class.java.canonicalName, metadata.buildPlugin)
}
private fun getKotlinToolingMetadata(): KotlinToolingMetadata {
val task = project.tasks.named(BuildKotlinToolingMetadataTask.defaultTaskName, BuildKotlinToolingMetadataTask::class.java).get()
return task.getKotlinToolingMetadata()
}
}
@@ -24,7 +24,6 @@ import org.gradle.api.plugins.MavenPluginConvention
import org.gradle.api.provider.Provider
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.artifacts.maven.MavenPom as OldMavenPom
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.AbstractCompile
@@ -49,6 +48,7 @@ import java.io.File
import java.net.URL
import java.util.concurrent.Callable
import java.util.jar.Manifest
import org.gradle.api.artifacts.maven.MavenPom as OldMavenPom
const val PLUGIN_CLASSPATH_CONFIGURATION_NAME = "kotlinCompilerPluginClasspath"
const val NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME = "kotlinNativeCompilerPluginClasspath"
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.gradle.tasks.KOTLIN_COMPILER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_KLIB_COMMONIZER_EMBEDDABLE
import org.jetbrains.kotlin.gradle.tasks.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility
import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
@@ -90,6 +91,7 @@ abstract class KotlinBasePluginWrapper : Plugin<Project> {
}
project.extensions.add(KotlinTestsRegistry.PROJECT_EXTENSION_NAME, createTestRegistry(project))
project.tasks.register(BuildKotlinToolingMetadataTask.defaultTaskName, BuildKotlinToolingMetadataTask::class.java)
val plugin = getPlugin(project, kotlinGradleBuildServices)
@@ -210,13 +212,9 @@ open class KotlinJsPluginWrapper @Inject constructor(
}
open class KotlinMultiplatformPluginWrapper @Inject constructor(
private val featurePreviews: FeaturePreviews
) : KotlinBasePluginWrapper() {
override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin<Project> =
KotlinMultiplatformPlugin(
kotlinPluginVersion,
featurePreviews
)
KotlinMultiplatformPlugin(kotlinPluginVersion)
override val projectExtensionClass: KClass<out KotlinMultiplatformExtension>
get() = KotlinMultiplatformExtension::class
@@ -126,6 +126,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val enableCompatibilityMetadataVariant: Boolean
get() = booleanProperty("kotlin.mpp.enableCompatibilityMetadataVariant") ?: true
val enableKotlinToolingMetadataArtifact: Boolean
get() = booleanProperty("kotlin.mpp.enableKotlinToolingMetadataArtifact") ?: true
val mppStabilityNoWarn: Boolean?
get() = booleanProperty(KotlinMultiplatformPlugin.STABILITY_NOWARN_FLAG)
@@ -44,8 +44,7 @@ import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
class KotlinMultiplatformPlugin(
private val kotlinPluginVersion: String,
private val featurePreviews: FeaturePreviews // TODO get rid of this internal API usage once we don't need it
private val kotlinPluginVersion: String
) : Plugin<Project> {
private class TargetFromPresetExtension(val targetsContainer: KotlinTargetsContainerWithPresets) {
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
import org.jetbrains.kotlin.gradle.plugin.sources.sourceSetDependencyConfigurationByScope
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.tooling.BuildKotlinToolingMetadataTask
internal fun configurePublishingWithMavenPublish(project: Project) = project.pluginManager.withPlugin("maven-publish") {
project.extensions.configure(PublishingExtension::class.java) { publishing ->
@@ -43,6 +44,20 @@ private fun createRootPublication(project: Project, publishing: PublishingExtens
kotlinSoftwareComponent.sourcesArtifacts.forEach { sourceArtifact ->
artifact(sourceArtifact)
}
addKotlinToolingMetadataArtifactIfNeeded(project)
}
}
private fun MavenPublication.addKotlinToolingMetadataArtifactIfNeeded(project: Project) {
if (!PropertiesProvider(project).enableKotlinToolingMetadataArtifact) return
val buildKotlinToolingMetadataTask = project.tasks.withType(BuildKotlinToolingMetadataTask::class.java)
.named(BuildKotlinToolingMetadataTask.defaultTaskName) ?: return
artifact(buildKotlinToolingMetadataTask.flatMap { it.outputFile }) { artifact ->
artifact.classifier = "kotlin-tooling-metadata"
artifact.builtBy(buildKotlinToolingMetadataTask)
}
}
@@ -0,0 +1,126 @@
/*
* 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.gradle.tooling
import com.android.build.gradle.BaseExtension
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
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
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.tooling.KotlinToolingMetadata
import org.jetbrains.kotlin.tooling.toJsonString
import java.io.File
open class BuildKotlinToolingMetadataTask : DefaultTask() {
companion object {
const val defaultTaskName: String = "buildKotlinToolingMetadata"
}
init {
group = "build"
description = "Build metadata json file containing information about the used Kotlin tooling"
}
@get:OutputFile
val outputFile: Property<File> = project.objects.property(File::class.java)
.convention(project.buildDir.resolve("kotlinToolingMetadata").resolve("kotlin-tooling-metadata.json"))
@Internal
fun getKotlinToolingMetadata(): KotlinToolingMetadata {
return project.kotlinExtension.getKotlinToolingMetadata()
}
@Input
internal fun getKotlinToolingMetadataJson(): String = getKotlinToolingMetadata().toJsonString()
@TaskAction
internal fun createToolingMetadataFile() {
val outputFile = outputFile.orNull ?: return
outputFile.parentFile.mkdirs()
outputFile.writeText(getKotlinToolingMetadataJson())
}
}
private fun KotlinProjectExtension.getKotlinToolingMetadata(): KotlinToolingMetadata {
return KotlinToolingMetadata(
buildSystem = "Gradle",
buildSystemVersion = project.gradle.gradleVersion,
buildPlugin = project.plugins.withType(KotlinBasePluginWrapper::class.java).joinToString(";") { it.javaClass.canonicalName },
buildPluginVersion = project.getKotlinPluginVersion() ?: throw IllegalStateException(
"Failed to infer Kotlin Plugin version"
),
projectSettings = buildProjectSettings(),
projectTargets = buildProjectTargets()
)
}
private fun KotlinProjectExtension.buildProjectSettings(): KotlinToolingMetadata.ProjectSettings {
return KotlinToolingMetadata.ProjectSettings(
isHmppEnabled = project.isKotlinGranularMetadataEnabled,
isCompatibilityMetadataVariantEnabled = project.isCompatibilityMetadataVariantEnabled
)
}
private fun KotlinProjectExtension.buildProjectTargets(): List<KotlinToolingMetadata.ProjectTargetMetadata> {
val targets = when (this) {
is KotlinMultiplatformExtension -> this.targets.toSet()
is KotlinSingleTargetExtension -> setOf(this.target)
else -> emptySet()
}
return targets.map { target -> buildTargetMetadata(target) }
}
private fun buildTargetMetadata(target: KotlinTarget): KotlinToolingMetadata.ProjectTargetMetadata {
return KotlinToolingMetadata.ProjectTargetMetadata(
target = target.javaClass.canonicalName,
platformType = target.platformType.name,
extras = buildTargetMetadataExtras(target)
)
}
private fun buildTargetMetadataExtras(target: KotlinTarget): Map<String, String> {
val extras = mutableMapOf<String, String>()
when (target) {
is KotlinJvmTarget -> {
extras["withJavaEnabled"] = target.withJavaEnabled.toString()
target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.let { mainCompilation ->
extras["jvmTarget"] = mainCompilation.kotlinOptions.jvmTarget
}
}
is KotlinAndroidTarget -> {
val androidExtension = target.project.extensions.findByType(BaseExtension::class.java)
extras["sourceCompatibility"] = androidExtension?.compileOptions?.sourceCompatibility.toString()
extras["targetCompatibility"] = androidExtension?.compileOptions?.targetCompatibility.toString()
}
is KotlinJsSubTargetContainerDsl -> {
extras["isBrowserConfigured"] = target.isBrowserConfigured.toString()
extras["isNodejsConfigured"] = target.isNodejsConfigured.toString()
}
is KotlinNativeTarget -> {
extras["konanTarget"] = target.konanTarget.name
}
}
return extras.toMap()
}
@@ -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"))
}
@@ -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")
}
@@ -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
}
@@ -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) }
}
}
@@ -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")
}
}
@@ -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()
)
}
+2
View File
@@ -253,6 +253,7 @@ include ":benchmarks",
":kotlin-gradle-plugin-model",
":kotlin-gradle-plugin-test-utils-embeddable",
":kotlin-gradle-plugin-integration-tests",
":kotlin-tooling-metadata",
":kotlin-allopen",
":kotlin-noarg",
":kotlin-sam-with-receiver",
@@ -520,6 +521,7 @@ project(':kotlin-gradle-plugin').projectDir = "$rootDir/libraries/tools/kotlin-g
project(':kotlin-gradle-plugin-model').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-model" as File
project(':kotlin-gradle-plugin-test-utils-embeddable').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-test-utils-embeddable" as File
project(':kotlin-gradle-plugin-integration-tests').projectDir = "$rootDir/libraries/tools/kotlin-gradle-plugin-integration-tests" as File
project(':kotlin-tooling-metadata').projectDir = "$rootDir/libraries/tools/kotlin-tooling-metadata" as File
project(':kotlin-allopen').projectDir = "$rootDir/libraries/tools/kotlin-allopen" as File
project(':kotlin-noarg').projectDir = "$rootDir/libraries/tools/kotlin-noarg" as File
project(':kotlin-sam-with-receiver').projectDir = "$rootDir/libraries/tools/kotlin-sam-with-receiver" as File