Skip to content

Commit 2e829ed

Browse files
TapchicomaSpace Team
authored and
Space Team
committed
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)