kotlin-tooling-core: Implement KotlinToolingVersion API

This commit is contained in:
sebastian.sellmair
2022-03-05 14:34:32 +01:00
committed by Space
parent 14046b81ff
commit 93e04c0b33
6 changed files with 527 additions and 3 deletions
@@ -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"))
@@ -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())
}
}
@@ -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()
@@ -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<Project> {
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")
@@ -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<KotlinToolingVersion>, 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))
}
@@ -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<IllegalArgumentException> { 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
)
}
}