Skip to content

Commit 2e829ed

Browse files
TapchicomaSpace Team
authored andcommitted
Fix version parsing crash on Gradle rich version string
Gradle accepts different types of version string declaration: https://docs.gradle.org/current/userguide/single_versions.html. Now code should parse such declarations into semantic versioning without exception. ^KT-55255 Fixed
1 parent f603c0e commit 2e829ed

File tree

4 files changed

+128
-3
lines changed

4 files changed

+128
-3
lines changed

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/kotlinTestDependencyManagement.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ private val Dependency.isKotlinTestRootDependency: Boolean
4343

4444
private val kotlin150Version = SemVer(1.toBigInteger(), 5.toBigInteger(), 0.toBigInteger())
4545

46-
private fun isAtLeast1_5(version: String) = SemVer.from(version) >= kotlin150Version
46+
private fun isAtLeast1_5(version: String) = SemVer.fromGradleRichVersion(version) >= kotlin150Version
4747

4848
private val jvmPlatforms = setOf(KotlinPlatformType.jvm, KotlinPlatformType.androidJvm)
4949

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/stdlibDependencyManagement.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ internal fun ConfigurationContainer.configureStdlibVersionAlignment() = all { co
6767
if (dependency.group == KOTLIN_MODULE_GROUP &&
6868
(dependency.name == "kotlin-stdlib" || dependency.name == "kotlin-stdlib-jdk7") &&
6969
dependency.version != null &&
70-
SemVer.from(dependency.version!!) >= kotlin180Version
70+
SemVer.fromGradleRichVersion(dependency.version!!) >= kotlin180Version
7171
) {
7272
if (configuration.isCanBeResolved) configuration.alignStdlibJvmVariantVersions(dependency)
7373

libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/semver.kt

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,94 @@ data class SemVer(
8383
build.takeIf { it.isNotBlank() }
8484
)
8585
}
86+
87+
/**
88+
* Parses Gradle [rich versions](https://docs.gradle.org/current/userguide/single_versions.html) version string.
89+
* In case of ranges, version prefixes or latest status - returned version will be closest using [Int.MAX_VALUE] as highest possible
90+
* version number for major, minor or patch.
91+
*/
92+
fun fromGradleRichVersion(version: String): SemVer {
93+
return when {
94+
version == "+" || version.startsWith("latest.") ->
95+
SemVer(Int.MAX_VALUE.toBigInteger(), Int.MAX_VALUE.toBigInteger(), Int.MAX_VALUE.toBigInteger())
96+
version.matches(MAJOR_PREFIX_VERSION) ->
97+
from("${version.replaceFirst("+", Int.MAX_VALUE.toString())}.${Int.MAX_VALUE}", loose = true)
98+
version.matches(MINOR_PREFIX_VERSION) ->
99+
from(version.replaceFirst("+", Int.MAX_VALUE.toString()), loose = true)
100+
version.matches(FINITE_RANGE) -> {
101+
if (version.endsWith(CLOSE_INC)) {
102+
from(FINITE_RANGE.find(version)!!.groups[2]!!.value, loose = true)
103+
} else {
104+
from(FINITE_RANGE.find(version)!!.groups[2]!!.value, loose = true).decrement()
105+
}
106+
}
107+
version.matches(LOWER_INFINITE_RANGE) -> {
108+
if (version.endsWith(CLOSE_INC)) {
109+
from(LOWER_INFINITE_RANGE.find(version)!!.groups[1]!!.value, loose = true)
110+
} else {
111+
from(LOWER_INFINITE_RANGE.find(version)!!.groups[1]!!.value, loose = true).decrement()
112+
}
113+
}
114+
version.matches(UPPER_INFINITE_RANGE) -> {
115+
SemVer(Int.MAX_VALUE.toBigInteger(), Int.MAX_VALUE.toBigInteger(), Int.MAX_VALUE.toBigInteger())
116+
}
117+
version.matches(SINGLE_VALUE_RANGE) -> {
118+
from(SINGLE_VALUE_RANGE.find(version)!!.groups[1]!!.value, loose = true)
119+
}
120+
else -> from(version, loose = true)
121+
}
122+
}
123+
124+
private fun SemVer.decrement(): SemVer {
125+
return if (patch == 0.toBigInteger()) {
126+
if (minor == 0.toBigInteger()) {
127+
SemVer(major.dec(), Int.MAX_VALUE.toBigInteger(), Int.MAX_VALUE.toBigInteger())
128+
} else {
129+
SemVer(major, minor.dec(), Int.MAX_VALUE.toBigInteger())
130+
}
131+
} else {
132+
SemVer(major, minor, patch.dec())
133+
}
134+
}
135+
136+
private val MAJOR_PREFIX_VERSION = "^[0-9]+\\.\\+$".toRegex()
137+
private val MINOR_PREFIX_VERSION = "^[0-9]+\\.[0-9]+\\.\\+$".toRegex()
138+
139+
// Following constants and logic around was peeked from
140+
// https://github.com/gradle/gradle/blob/master/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/strategy/VersionRangeSelector.java
141+
private const val OPEN_INC = "["
142+
private const val OPEN_EXC = "]"
143+
private const val OPEN_EXC_MAVEN = "("
144+
private const val CLOSE_INC = "]"
145+
private const val CLOSE_EXC = "["
146+
private const val CLOSE_EXC_MAVEN = ")"
147+
private const val LOWER_INFINITE = "("
148+
private const val UPPER_INFINITE = ")"
149+
private const val SEPARATOR = ","
150+
151+
private const val OPEN_INC_PATTERN = "\\" + OPEN_INC
152+
private const val OPEN_EXC_PATTERN = "\\" + OPEN_EXC + "\\" + OPEN_EXC_MAVEN
153+
private const val CLOSE_INC_PATTERN = "\\" + CLOSE_INC
154+
private const val CLOSE_EXC_PATTERN = "\\" + CLOSE_EXC + "\\" + CLOSE_EXC_MAVEN
155+
private const val LI_PATTERN = "\\" + LOWER_INFINITE
156+
private const val UI_PATTERN = "\\" + UPPER_INFINITE
157+
private const val SEP_PATTERN = "\\s*\\$SEPARATOR\\s*"
158+
private const val OPEN_PATTERN = "[$OPEN_INC_PATTERN$OPEN_EXC_PATTERN]"
159+
private const val CLOSE_PATTERN = "[$CLOSE_INC_PATTERN$CLOSE_EXC_PATTERN]"
160+
private const val ANY_NON_SPECIAL_PATTERN = ("[^\\s" + SEPARATOR + OPEN_INC_PATTERN
161+
+ OPEN_EXC_PATTERN + CLOSE_INC_PATTERN + CLOSE_EXC_PATTERN + LI_PATTERN + UI_PATTERN
162+
+ "]")
163+
private const val FINITE_PATTERN = (OPEN_PATTERN + "\\s*(" + ANY_NON_SPECIAL_PATTERN
164+
+ "+)" + SEP_PATTERN + "(" + ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN)
165+
private const val LOWER_INFINITE_PATTERN = (LI_PATTERN + SEP_PATTERN + "("
166+
+ ANY_NON_SPECIAL_PATTERN + "+)\\s*" + CLOSE_PATTERN)
167+
private const val UPPER_INFINITE_PATTERN = (OPEN_PATTERN + "\\s*("
168+
+ ANY_NON_SPECIAL_PATTERN + "+)" + SEP_PATTERN + UI_PATTERN)
169+
private const val SINGLE_VALUE_PATTERN = "$OPEN_INC_PATTERN\\s*($ANY_NON_SPECIAL_PATTERN+)$CLOSE_INC_PATTERN"
170+
private val FINITE_RANGE = FINITE_PATTERN.toRegex()
171+
private val LOWER_INFINITE_RANGE = LOWER_INFINITE_PATTERN.toRegex()
172+
private val UPPER_INFINITE_RANGE = UPPER_INFINITE_PATTERN.toRegex()
173+
private val SINGLE_VALUE_RANGE = SINGLE_VALUE_PATTERN.toRegex()
86174
}
87175
}
88176

@@ -195,4 +283,4 @@ fun min(a: SemVer, b: SemVer): SemVer =
195283
if (a < b) a else b
196284

197285
fun max(a: SemVer, b: SemVer): SemVer =
198-
if (a > b) a else b
286+
if (a > b) a else b

libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/SemVerTest.kt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,41 @@ class SemVerTest {
4444
).sorted().joinToString(", ")
4545
)
4646
}
47+
48+
@Test
49+
fun testParseGradleRichVersions() {
50+
val maxInt = Int.MAX_VALUE.toBigInteger()
51+
listOf(
52+
// Version prefix
53+
SemVer(maxInt, maxInt, maxInt) to SemVer.fromGradleRichVersion("+"),
54+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("1.+"),
55+
SemVer(1.toBigInteger(), 10.toBigInteger(), maxInt) to SemVer.fromGradleRichVersion("1.10.+"),
56+
SemVer(1.toBigInteger(), 10.toBigInteger(), 0.toBigInteger()) to SemVer.fromGradleRichVersion("1.10.0.+"),
57+
58+
// Version latest state
59+
SemVer(maxInt, maxInt, maxInt) to SemVer.fromGradleRichVersion("latest.release"),
60+
SemVer(maxInt, maxInt, maxInt) to SemVer.fromGradleRichVersion("latest.integration"),
61+
62+
// Ranges
63+
SemVer(1.toBigInteger(), 5.toBigInteger(), 0.toBigInteger()) to SemVer.fromGradleRichVersion("(1.2,1.5]"),
64+
SemVer(1.toBigInteger(), 5.toBigInteger(), 55.toBigInteger()) to SemVer.fromGradleRichVersion("[1.2,1.5.55]"),
65+
SemVer(1.toBigInteger(), 5.toBigInteger(), 54.toBigInteger()) to SemVer.fromGradleRichVersion("[1.2,1.5.55-SNAPSHOT["),
66+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("[1.1,2.0)"),
67+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("(1.1,2.0)"),
68+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("(1.1,2.0-SNAPSHOT)"),
69+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("]1.0, 2.0["),
70+
SemVer(1.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("[1.0, 2.0["),
71+
SemVer(1.toBigInteger(), 0.toBigInteger(), 0.toBigInteger()) to SemVer.fromGradleRichVersion("(,1.0]"),
72+
SemVer(1.toBigInteger(), 0.toBigInteger(), 0.toBigInteger(), "SNAPSHOT") to SemVer.fromGradleRichVersion("(,1.0-SNAPSHOT]"),
73+
SemVer(0.toBigInteger(), maxInt, maxInt) to SemVer.fromGradleRichVersion("(,1.0)"),
74+
SemVer(maxInt, maxInt, maxInt) to SemVer.fromGradleRichVersion("[1.0,)"),
75+
SemVer(10.toBigInteger(), 0.toBigInteger(), 20.toBigInteger()) to SemVer.fromGradleRichVersion("[10.0.20]"),
76+
77+
// Normal
78+
SemVer(10.toBigInteger(), 0.toBigInteger(), 20.toBigInteger()) to SemVer.fromGradleRichVersion("10.0.20"),
79+
SemVer(10.toBigInteger(), 0.toBigInteger(), 0.toBigInteger(), "SNAPSHOT") to SemVer.fromGradleRichVersion("10.0-SNAPSHOT"),
80+
).forEach { (expected, actual) ->
81+
assertEquals(expected, actual)
82+
}
83+
}
4784
}

0 commit comments

Comments
 (0)