diff --git a/buildSrc/src/main/kotlin/tasks.kt b/buildSrc/src/main/kotlin/tasks.kt index ce08cbde17c..65c12117f2c 100644 --- a/buildSrc/src/main/kotlin/tasks.kt +++ b/buildSrc/src/main/kotlin/tasks.kt @@ -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() { diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 7e629b7e649..1f517bd4039 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -19,6 +19,7 @@ + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts index 833b662804c..d17261454af 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts @@ -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")) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingMetadataIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingMetadataIT.kt new file mode 100644 index 00000000000..6dce3d1a159 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingMetadataIT.kt @@ -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}") + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/build.gradle.kts new file mode 100644 index 00000000000..b9e7e30a0c4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/build.gradle.kts @@ -0,0 +1,94 @@ +plugins { + kotlin("multiplatform").version("") + `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") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/gradle.properties new file mode 100644 index 00000000000..0ae1e640ff1 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/gradle.properties @@ -0,0 +1,2 @@ +kotlin.mpp.enableGranularSourceSetsMetadata=true +kotlin.mpp.enableCompatibilityMetadataVariant=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/settings.gradle.kts new file mode 100644 index 00000000000..17a7c7b3b1d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/settings.gradle.kts @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +rootProject.name = "my-lib-bar" + +enableFeaturePreview("GRADLE_METADATA") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonMain/kotlin/Bar.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonMain/kotlin/Bar.kt new file mode 100644 index 00000000000..91879d1ab0b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonMain/kotlin/Bar.kt @@ -0,0 +1,3 @@ +package com.example.bar + +fun stub() = "" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonTest/kotlin/BarTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonTest/kotlin/BarTest.kt new file mode 100644 index 00000000000..c30bf1a51d2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/commonTest/kotlin/BarTest.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import kotlin.test.Test + +class BarTest { + @Test + fun testBar() { + + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt new file mode 100644 index 00000000000..d3164ac157f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsMain/kotlin/BarJvmAndJs.kt @@ -0,0 +1,3 @@ +package com.example.bar + +fun jvmAndJsStub() = "" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt new file mode 100644 index 00000000000..3ad9f4da706 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/jvmAndJsTest/kotlin/BarJvmAndJsTest.kt @@ -0,0 +1,15 @@ +package com.example.bar + +import kotlin.test.Test + +class BarJvmAndJsTest { + @Test + fun testBarJvmAndJs() { + + } + + @Test + fun testThirdParty() { + + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt new file mode 100644 index 00000000000..258106a65e3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsMain/kotlin/BarLinuxAndJs.kt @@ -0,0 +1,3 @@ +package com.example.bar + +fun linuxAndJsStub() = "" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt new file mode 100644 index 00000000000..5befd85dac4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxAndJsTest/kotlin/BarLinuxAndJsTest.kt @@ -0,0 +1,10 @@ +package com.example.bar + +import kotlin.test.Test + +class BarLinuxAndJsTest { + @Test + fun testBarLinuxAndJs() { + + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxX64Main/kotlin/BarLinux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxX64Main/kotlin/BarLinux.kt new file mode 100644 index 00000000000..4b0a2158778 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-published/src/linuxX64Main/kotlin/BarLinux.kt @@ -0,0 +1,3 @@ +package com.example.bar + +fun linuxStub() ="" diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 7d02a807506..ba9e92f08b9 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -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")) 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 new file mode 100644 index 00000000000..cafaf12dfed --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/BuildKotlinToolingMetadataTest.kt @@ -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() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 294a8ba0796..89e7fe99d44 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -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" diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 065af6fe07f..cd0ad69148d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -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.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 = - KotlinMultiplatformPlugin( - kotlinPluginVersion, - featurePreviews - ) + KotlinMultiplatformPlugin(kotlinPluginVersion) override val projectExtensionClass: KClass get() = KotlinMultiplatformExtension::class diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index d24d378bbc1..bc561074fe0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -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) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index de8372e63ff..e0d1fa365bf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -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 { private class TargetFromPresetExtension(val targetsContainer: KotlinTargetsContainerWithPresets) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt index 4b6556bb3e8..781de3a1912 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/Publishing.kt @@ -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) } } 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 new file mode 100644 index 00000000000..509ba1a9249 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tooling/BuildKotlinToolingMetadataTask.kt @@ -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 = 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 { + 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 { + val extras = mutableMapOf() + 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() +} diff --git a/libraries/tools/kotlin-tooling-metadata/build.gradle.kts b/libraries/tools/kotlin-tooling-metadata/build.gradle.kts new file mode 100644 index 00000000000..f4e9a0df2d9 --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/build.gradle.kts @@ -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")) +} diff --git a/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/JsonConversion.kt b/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/JsonConversion.kt new file mode 100644 index 00000000000..2fe74043106 --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/JsonConversion.kt @@ -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.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 { + val map = mutableMapOf() + 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") +} diff --git a/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/KotlinToolingMetadata.kt b/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/KotlinToolingMetadata.kt new file mode 100644 index 00000000000..e55233daf9c --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/src/main/kotlin/org/jetbrains/kotlin/tooling/KotlinToolingMetadata.kt @@ -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, +) { + + data class ProjectSettings( + val isHmppEnabled: Boolean, + val isCompatibilityMetadataVariantEnabled: Boolean, + ) + + data class ProjectTargetMetadata( + val target: String, + val platformType: String, + val extras: Map + ) + + companion object +} + + diff --git a/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializationFailureTest.kt b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializationFailureTest.kt new file mode 100644 index 00000000000..c58e3fac7dc --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializationFailureTest.kt @@ -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 { 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 { KotlinToolingMetadata.parseJsonOrThrow(json) } + } +} diff --git a/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializeStringTest.kt b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializeStringTest.kt new file mode 100644 index 00000000000..b6340468e6c --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/DeserializeStringTest.kt @@ -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") + } +} + diff --git a/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/SerializeAndDeserializeTest.kt b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/SerializeAndDeserializeTest.kt new file mode 100644 index 00000000000..e8dc9d7e587 --- /dev/null +++ b/libraries/tools/kotlin-tooling-metadata/src/test/kotlin/org/jetbrains/kotlin/tooling/SerializeAndDeserializeTest.kt @@ -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() + ) +} diff --git a/settings.gradle b/settings.gradle index 0065ce39b96..2b2e6ff975c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -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