diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index b65e99f918b..2ad07cedb50 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -24,12 +24,15 @@ kotlin.sourceSets.all { languageSettings.optIn("kotlin.RequiresOptIn") languageSettings.optIn("org.jetbrains.kotlin.gradle.plugin.mpp.pm20.AdvancedKotlinGradlePluginApi") languageSettings.optIn("org.jetbrains.kotlin.gradle.kpm.idea.InternalKotlinGradlePluginApi") + languageSettings.optIn("org.jetbrains.kotlin.gradle.plugin.ExperimentalKotlinGradlePluginApi") } dependencies { compileOnly(gradleKotlinDsl()) api(project(":kotlin-gradle-plugin-api")) api(project(":kotlin-gradle-plugin-model")) + api(project(":kotlin-tooling-core")) + implementation(project(":kotlin-gradle-plugin-idea")) compileOnly(project(":compiler")) compileOnly(project(":compiler:incremental-compilation-impl")) @@ -37,7 +40,6 @@ dependencies { implementation(project(":kotlin-util-klib")) implementation(project(":native:kotlin-klib-commonizer-api")) - implementation(project(":kotlin-tooling-core")) implementation(project(":kotlin-tooling-metadata")) implementation(project(":kotlin-project-model")) compileOnly(project(":native:kotlin-native-utils")) diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingVersionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingVersionTest.kt new file mode 100644 index 00000000000..92e2c7e159f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/KotlinToolingVersionTest.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2022 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 org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion +import org.jetbrains.kotlin.gradle.plugin.kotlinToolingVersion +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame + +class KotlinToolingVersionTest { + + @Test + fun `test - kotlinToolingVersion is cached in project`() { + val project = ProjectBuilder.builder().build() + assertSame(project.kotlinToolingVersion, project.kotlinToolingVersion) + } + + @Test + fun `test - kotlinToolingVersion matches kotlinPluginVersion string`() { + val project = ProjectBuilder.builder().build() + assertEquals(project.getKotlinPluginVersion(), project.kotlinToolingVersion.toString()) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ExperimentalKotlinGradlePluginApi.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ExperimentalKotlinGradlePluginApi.kt new file mode 100644 index 00000000000..02701787b2e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/ExperimentalKotlinGradlePluginApi.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 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.plugin + +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +annotation class ExperimentalKotlinGradlePluginApi() 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 acba1419c5f..c390eb394c0 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 @@ -20,7 +20,6 @@ import org.gradle.api.GradleException import org.gradle.api.NamedDomainObjectFactory import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.compilerRunner.registerCommonizerClasspathConfigurationIfNecessary @@ -45,9 +44,11 @@ import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile 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.getOrPut import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck import org.jetbrains.kotlin.statistics.metrics.StringMetrics +import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion import javax.inject.Inject import kotlin.reflect.KClass @@ -84,7 +85,8 @@ abstract class KotlinBasePluginWrapper : Plugin { KotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion) - val buildMetricReporter = BuildMetricsReporterService.registerIfAbsent(project, BuildMetricsReporterService.getStartParameters(project)) + val buildMetricReporter = + BuildMetricsReporterService.registerIfAbsent(project, BuildMetricsReporterService.getStartParameters(project)) buildMetricReporter?.also { BuildEventsListenerRegistryHolder.getInstance(project).listenerRegistry.onTaskCompletion(it) } @@ -262,6 +264,13 @@ fun Project.getKotlinPluginVersion(): String { return kotlinPluginVersionFromResources.value } +@ExperimentalKotlinGradlePluginApi +val Project.kotlinToolingVersion: KotlinToolingVersion + get() = extensions.extraProperties.getOrPut("kotlinToolingVersion") { + KotlinToolingVersion(getKotlinPluginVersion()) + } + + private fun loadKotlinPluginVersionFromResourcesOf(any: Any) = any.loadPropertyFromResources("project.properties", "project.version") diff --git a/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersion.kt b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersion.kt new file mode 100644 index 00000000000..a67863027c2 --- /dev/null +++ b/libraries/tools/kotlin-tooling-core/src/main/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersion.kt @@ -0,0 +1,209 @@ +/* + * Copyright 2010-2022 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.core + +import java.io.Serializable +import java.util.* + +fun KotlinToolingVersion(kotlinVersionString: String): KotlinToolingVersion { + fun throwInvalid(): Nothing { + throw IllegalArgumentException("Invalid Kotlin version: $kotlinVersionString") + } + + val baseVersion = kotlinVersionString.split("-", limit = 2)[0] + val classifier = kotlinVersionString.split("-", limit = 2).getOrNull(1) + + val baseVersionSplit = baseVersion.split(".") + if (!(baseVersionSplit.size == 2 || baseVersionSplit.size == 3)) throwInvalid() + + return KotlinToolingVersion( + major = baseVersionSplit[0].toIntOrNull() ?: throwInvalid(), + minor = baseVersionSplit[1].toIntOrNull() ?: throwInvalid(), + patch = baseVersionSplit.getOrNull(2)?.let { it.toIntOrNull() ?: throwInvalid() } ?: 0, + classifier = classifier + ) +} + +fun KotlinToolingVersion(kotlinVersion: KotlinVersion, classifier: String? = null): KotlinToolingVersion { + return KotlinToolingVersion(kotlinVersion.major, kotlinVersion.minor, kotlinVersion.patch, classifier) +} + +class KotlinToolingVersion( + val major: Int, + val minor: Int, + val patch: Int, + val classifier: String? +) : Comparable, Serializable { + + enum class Maturity { + SNAPSHOT, DEV, MILESTONE, ALPHA, BETA, RC, STABLE + } + + val maturity: Maturity + get() { + val classifier = this.classifier?.toLowerCase(Locale.ROOT) + return when { + classifier == null || classifier.matches(Regex("""(release-)?\d+""")) -> Maturity.STABLE + classifier.matches(Regex("""(rc)(\d*)?-?\d*""")) -> Maturity.RC + classifier.matches(Regex("""beta(\d*)?-?\d*""")) -> Maturity.BETA + classifier.matches(Regex("""alpha(\d*)?-?\d*""")) -> Maturity.ALPHA + classifier.matches(Regex("""m\d+(-\d*)?""")) -> Maturity.MILESTONE + classifier.matches(Regex("""dev-?\d*""")) -> Maturity.DEV + classifier == "snapshot" -> Maturity.SNAPSHOT + else -> throw IllegalArgumentException("Can't infer maturity of KotlinVersion $this") + } + } + + override fun compareTo(other: KotlinToolingVersion): Int { + if (this == other) return 0 + (this.major - other.major).takeIf { it != 0 }?.let { return it } + (this.minor - other.minor).takeIf { it != 0 }?.let { return it } + (this.patch - other.patch).takeIf { it != 0 }?.let { return it } + (this.maturity.ordinal - other.maturity.ordinal).takeIf { it != 0 }?.let { return it } + + if (this.classifier == null && other.classifier != null) { + /* eg. 1.6.20 > 1.6.20-200 */ + return 1 + } + + if (this.classifier != null && other.classifier == null) { + /* e.g. 1.6.20-200 < 1.6.20 */ + return -1 + } + + val thisClassifierNumber = this.classifierNumber + val otherClassifierNumber = other.classifierNumber + if (thisClassifierNumber != null && otherClassifierNumber != null) { + (thisClassifierNumber - otherClassifierNumber).takeIf { it != 0 }?.let { return it } + } + + if (thisClassifierNumber != null && otherClassifierNumber == null) { + /* e.g. 1.6.20-rc1 > 1.6.20-rc */ + return 1 + } + + if (thisClassifierNumber == null && otherClassifierNumber != null) { + /* e.g. 1.6.20-rc < 1.6.20-rc1 */ + return -1 + } + + val thisBuildNumber = this.buildNumber + val otherBuildNumber = other.buildNumber + if (thisBuildNumber != null && otherBuildNumber != null) { + (thisBuildNumber - otherBuildNumber).takeIf { it != 0 }?.let { return it } + } + + if (thisBuildNumber == null && otherBuildNumber != null) { + /* e.g. 1.6.20-M1 > 1.6.20-M1-200 */ + return 1 + } + + if (thisBuildNumber != null && otherBuildNumber == null) { + /* e.g. 1.6.20-M1-200 < 1.6.20-M1 */ + return -1 + } + + return 0 + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is KotlinToolingVersion) return false + if (this.major != other.major) return false + if (this.minor != other.minor) return false + if (this.patch != other.patch) return false + if (this.classifier?.toLowerCase(Locale.ROOT) != other.classifier?.toLowerCase(Locale.ROOT)) return false + return true + } + + override fun hashCode(): Int { + var result = major + result = 31 * result + minor + result = 31 * result + patch + result = 31 * result + (classifier?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "$major.$minor.$patch" + if (classifier != null) "-$classifier" else "" + } +} + +fun KotlinToolingVersion.toKotlinVersion(): KotlinVersion = + KotlinVersion(major, minor, patch) + +fun KotlinVersion.toKotlinToolingVersion(classifier: String? = null): KotlinToolingVersion = + KotlinToolingVersion(this, classifier) + +val KotlinToolingVersion.isSnapshot: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.SNAPSHOT + +val KotlinToolingVersion.isDev: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.DEV + +val KotlinToolingVersion.isMilestone: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.MILESTONE + +val KotlinToolingVersion.isAlpha: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.ALPHA + +val KotlinToolingVersion.isBeta: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.BETA + +val KotlinToolingVersion.isRC: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.RC + +val KotlinToolingVersion.isStable: Boolean + get() = this.maturity == KotlinToolingVersion.Maturity.STABLE + +val KotlinToolingVersion.isPreRelease: Boolean get() = !isStable + +val KotlinToolingVersion.buildNumber: Int? + get() { + if (classifier == null) return null + + /* + Handle classifiers that only consist of version + build number. This is used for stable releases + like: + 1.6.20-1 + 1.6.20-22 + 1.6. + */ + val buildNumberOnlyClassifierRegex = Regex("\\d+") + if (buildNumberOnlyClassifierRegex.matches(classifier)) { + return classifier.toIntOrNull() + } + + val classifierRegex = Regex("""(.+?)(\d*)?-?(\d*)?""") + val classifierMatch = classifierRegex.matchEntire(classifier) ?: return null + return classifierMatch.groupValues.getOrNull(3)?.toIntOrNull() + } + +val KotlinToolingVersion.classifierNumber: Int? + get() { + if (classifier == null) return null + + /* + Classifiers with only a buildNumber assigned + */ + val buildNumberOnlyClassifierRegex = Regex("\\d+") + if (buildNumberOnlyClassifierRegex.matches(classifier)) { + return null + } + + + val classifierRegex = Regex("""(.+?)(\d*)?-?(\d*)?""") + val classifierMatch = classifierRegex.matchEntire(classifier) ?: return null + return classifierMatch.groupValues.getOrNull(2)?.toIntOrNull() + } + +operator fun String.compareTo(version: KotlinToolingVersion): Int { + return KotlinToolingVersion(this).compareTo(version) +} + +operator fun KotlinToolingVersion.compareTo(kotlinVersionString: String): Int { + return this.compareTo(KotlinToolingVersion(kotlinVersionString)) +} diff --git a/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersionTest.kt b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersionTest.kt new file mode 100644 index 00000000000..bbfe1ad3bd9 --- /dev/null +++ b/libraries/tools/kotlin-tooling-core/src/test/kotlin/org/jetbrains/kotlin/tooling/core/KotlinToolingVersionTest.kt @@ -0,0 +1,265 @@ +/* + * Copyright 2010-2022 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.core + +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class KotlinToolingVersionTest { + + @Test + fun compareMajorVersions() { + assertTrue( + KotlinToolingVersion("1.6.0") < KotlinToolingVersion("2.0.0") + ) + + assertTrue( + KotlinToolingVersion("2.0.0") > KotlinToolingVersion("1.0.0") + ) + + assertEquals(0, KotlinToolingVersion("2.0.0").compareTo(KotlinToolingVersion("2.0.0"))) + } + + @Test + fun compareMinorVersions() { + assertTrue( + KotlinToolingVersion("1.6.20") > KotlinToolingVersion("1.5.30") + ) + + assertTrue( + KotlinToolingVersion("1.5.30") < KotlinToolingVersion("1.6.20") + ) + + assertTrue( + KotlinToolingVersion("1.7.20-dev-100") > KotlinToolingVersion("1.6.0") + ) + + assertTrue( + KotlinToolingVersion("1.7.20-dev-100") > KotlinToolingVersion("1.6") + ) + } + + @Test + fun comparePatchVersions() { + assertTrue( + KotlinToolingVersion("1.7.20") > KotlinToolingVersion("1.7.10") + ) + + assertTrue( + KotlinToolingVersion("1.7.10") < KotlinToolingVersion("1.7.20") + ) + + assertTrue( + KotlinToolingVersion("1.7.10-beta2-200") > KotlinToolingVersion("1.7.0") + ) + } + + @Test + fun compareMaturity() { + assertTrue( + KotlinToolingVersion("1.7.0") > KotlinToolingVersion("1.7.0-rc") + ) + + assertTrue( + KotlinToolingVersion("1.7.0-rc") > KotlinToolingVersion("1.7.0-beta") + ) + + assertTrue( + KotlinToolingVersion("1.7.0-beta") > KotlinToolingVersion("1.7.0-alpha") + ) + + assertTrue( + KotlinToolingVersion("1.7.0-alpha") > KotlinToolingVersion("1.7.0-m1") + ) + + assertTrue( + KotlinToolingVersion("1.7.0-m1") > KotlinToolingVersion("1.7.0-dev") + ) + + assertTrue( + KotlinToolingVersion("1.7.0-dev") > KotlinToolingVersion("1.7.0-snapshot") + ) + } + + @Test + fun compareClassifierNumberAndBuildNumber() { + assertTrue( + KotlinToolingVersion("1.6.20-M1") < KotlinToolingVersion("1.6.20") + ) + + assertTrue( + KotlinToolingVersion("1.6.20") > KotlinToolingVersion("1.6.20-1") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-1") < KotlinToolingVersion("1.6.20-2") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-M1") < KotlinToolingVersion("1.6.20-M2") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-M1-2") > KotlinToolingVersion("1.6.20-M1-1") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-M1-2") < KotlinToolingVersion("1.6.20-M2-1") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-M1-2") < KotlinToolingVersion("1.6.20-M2") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-beta1") > KotlinToolingVersion("1.6.20-beta") + ) + + assertTrue( + KotlinToolingVersion("1.6.20-M1") > KotlinToolingVersion("1.6.20-M1-1") + ) + } + + @Test + fun maturityWithClassifierNumberAndBuildNumber() { + assertEquals( + KotlinToolingVersion.Maturity.STABLE, + KotlinToolingVersion("1.6.20").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.STABLE, + KotlinToolingVersion("1.6.20-999").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.STABLE, + KotlinToolingVersion("1.6.20-release-999").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.STABLE, + KotlinToolingVersion("1.6.20-rElEaSe-999").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.RC, + KotlinToolingVersion("1.6.20-rc2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.RC, + KotlinToolingVersion("1.6.20-RC2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.BETA, + KotlinToolingVersion("1.6.20-beta2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.BETA, + KotlinToolingVersion("1.6.20-bEtA2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.ALPHA, + KotlinToolingVersion("1.6.20-alpha2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.ALPHA, + KotlinToolingVersion("1.6.20-aLpHa2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.MILESTONE, + KotlinToolingVersion("1.6.20-m2411-1901").maturity + ) + + assertEquals( + KotlinToolingVersion.Maturity.MILESTONE, + KotlinToolingVersion("1.6.20-M2411-1901").maturity + ) + } + + @Test + fun invalidMilestoneVersion() { + val exception = assertFailsWith { KotlinToolingVersion("1.6.20-M") } + assertTrue("maturity" in exception.message.orEmpty().toLowerCase(), "Expected 'maturity' issue mentioned in error message") + } + + @Test + fun buildNumber() { + assertEquals(510, KotlinToolingVersion("1.6.20-510").buildNumber) + assertEquals(510, KotlinToolingVersion("1.6.20-release-510").buildNumber) + assertEquals(510, KotlinToolingVersion("1.6.20-rc1-510").buildNumber) + assertEquals(510, KotlinToolingVersion("1.6.20-beta1-510").buildNumber) + assertEquals(510, KotlinToolingVersion("1.6.20-alpha1-510").buildNumber) + assertEquals(510, KotlinToolingVersion("1.6.20-m1-510").buildNumber) + } + + @Test + fun classifierNumber() { + assertEquals(2, KotlinToolingVersion("1.6.20-rc2-510").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-beta2-510").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-alpha2-510").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-m2-510").classifierNumber) + + assertEquals(2, KotlinToolingVersion("1.6.20-rc2").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-beta2").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-alpha2").classifierNumber) + assertEquals(2, KotlinToolingVersion("1.6.20-m2").classifierNumber) + } + + @Test + fun toKotlinVersion() { + assertEquals(KotlinVersion(1, 7, 20), KotlinToolingVersion("1.7.20-rc2-202").toKotlinVersion()) + } + + @Test + fun toKotlinToolingVersion() { + assertEquals(KotlinToolingVersion("1.7"), KotlinVersion(1, 7).toKotlinToolingVersion()) + } + + @Test + fun isMaturity() { + assertTrue( + KotlinToolingVersion("1.7").isStable + ) + + assertFalse( + KotlinToolingVersion("1.7").isPreRelease + ) + + assertTrue( + KotlinToolingVersion("1.7.0-rc").isRC + ) + + assertTrue( + KotlinToolingVersion("1.7.0-beta").isBeta + ) + + assertTrue( + KotlinToolingVersion("1.7.0-alpha").isAlpha + ) + + assertTrue( + KotlinToolingVersion("1.7.0-m1").isMilestone + ) + + assertTrue( + KotlinToolingVersion("1.7.0-dev").isDev + ) + + assertTrue( + KotlinToolingVersion("1.7.0-snapshot").isSnapshot + ) + } +}