Skip to content

Commit d35938b

Browse files
committed
refactor(model): Simplify the code to get version ranges
The check against `IVY_VERSION_RANGE_INDICATORS` (which did not actually contain only *Ivy* indicators) was only needed because the Semver4j library by default parses single versions into ranges (like "2" becomes ">= 2.0.0, < 3.0.0"), which made it unusable for checking for version ranges by simply testing whether range parsing fails. However, that behavior came from the `XRangeProcessor` being applied by default, which is a syntax that ORT never officially supported for package curations / configurations. Instead, ORT only ever supported Ivy version ranges officially. So simplify the code by explicitly only using `IvyProcessor` and working on `String`s internally. Signed-off-by: Sebastian Schuberth <[email protected]>
1 parent 93c310a commit d35938b

File tree

2 files changed

+76
-21
lines changed

2 files changed

+76
-21
lines changed

model/src/main/kotlin/utils/VersionUtils.kt

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,14 @@ import org.ossreviewtoolkit.utils.common.withoutSuffix
2626
import org.ossreviewtoolkit.utils.ort.showStackTrace
2727

2828
import org.semver4j.Semver
29+
import org.semver4j.processor.IvyProcessor
2930
import org.semver4j.range.RangeList
3031
import org.semver4j.range.RangeListFactory
3132

32-
/**
33-
* A list of Strings that are used by Ivy-style version ranges.
34-
*/
35-
private val IVY_VERSION_RANGE_INDICATORS = listOf(",", "~", "*", "+", ">", "<", "=", " - ", "^", ".x", "||")
36-
3733
/**
3834
* Return true if the version of this [Identifier] is an Ivy version range.
3935
*/
40-
fun Identifier.isVersionRange(): Boolean {
41-
val ranges = getVersionRanges()?.get()?.flatten() ?: return false
42-
val rangeVersions = ranges.mapTo(mutableSetOf()) { it.rangeVersion }
43-
val isSingleVersion = rangeVersions.size <= 1 && ranges.all { range ->
44-
// Determine whether the non-accessible `Range.rangeOperator` is `RangeOperator.EQUALS`.
45-
range.toString().startsWith("=")
46-
}
47-
48-
return !isSingleVersion
49-
}
36+
fun Identifier.isVersionRange(): Boolean = version.getIvyVersionRanges().get().isNotEmpty()
5037

5138
/**
5239
* Return true if the version of this [Identifier] interpreted as an Ivy version matcher is applicable to the
@@ -62,7 +49,7 @@ internal fun Identifier.isApplicableIvyVersion(pkgId: Identifier): Boolean =
6249

6350
// `Semver.satisfies(String)` requires a valid version range to work as expected, see:
6451
// https://github.com/semver4j/semver4j/issues/132.
65-
val ranges = getVersionRanges() ?: return false
52+
val ranges = version.getIvyVersionRanges()
6653

6754
return Semver.coerce(pkgId.version)?.satisfies(ranges) == true
6855
}.onFailure {
@@ -74,10 +61,17 @@ internal fun Identifier.isApplicableIvyVersion(pkgId: Identifier): Boolean =
7461
it.showStackTrace()
7562
}.getOrDefault(false)
7663

77-
private fun Identifier.getVersionRanges(): RangeList? {
78-
if (IVY_VERSION_RANGE_INDICATORS.none { version.contains(it, ignoreCase = true) }) return null
64+
/**
65+
* Get the version ranges contained in this string. Return an empty list if no (non-pathological) ranges are contained.
66+
*/
67+
internal fun String.getIvyVersionRanges(): RangeList {
68+
if (isBlank()) return RangeList(/* includePreRelease = */ false)
69+
70+
val ranges = RangeListFactory.create(this, IvyProcessor())
7971

80-
return runCatching {
81-
RangeListFactory.create(version).takeUnless { it.get().isEmpty() }
82-
}.getOrNull()
72+
val isSingleVersion = ranges.get().flatten().singleOrNull { it.toString().startsWith("=") } != null
73+
if (isSingleVersion) return RangeList(/* includePreRelease = */ false)
74+
75+
return ranges
8376
}
77+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (C) 2026 The ORT Project Copyright Holders <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
* License-Filename: LICENSE
18+
*/
19+
20+
package org.ossreviewtoolkit.model.utils
21+
22+
import io.kotest.core.spec.style.WordSpec
23+
import io.kotest.matchers.collections.beEmpty
24+
import io.kotest.matchers.should
25+
import io.kotest.matchers.shouldBe
26+
27+
import org.semver4j.range.Range
28+
29+
class VersionUtilsTest : WordSpec({
30+
"getIvyVersionRanges()" should {
31+
"return an empty range list for pathological cases" {
32+
"".getIvyVersionRanges().get() should beEmpty()
33+
" ".getIvyVersionRanges().get() should beEmpty()
34+
"foo".getIvyVersionRanges().get() should beEmpty()
35+
"null".getIvyVersionRanges().get() should beEmpty()
36+
}
37+
38+
"return an empty range list for invalid ranges" {
39+
"[a,b]".getIvyVersionRanges().get() should beEmpty()
40+
"[1,2))".getIvyVersionRanges().get() should beEmpty()
41+
"[1.2.3,4.5.6".getIvyVersionRanges().get() should beEmpty()
42+
"[1.2.3.4,5.6.7.8]".getIvyVersionRanges().get() should beEmpty()
43+
}
44+
45+
"return an empty list for single versions" {
46+
"2".getIvyVersionRanges().get() shouldBe listOf()
47+
"2.0".getIvyVersionRanges().get() shouldBe listOf()
48+
"2.0.0".getIvyVersionRanges().get() shouldBe listOf()
49+
}
50+
51+
"return the correct range list for Ivy version ranges" {
52+
"[1.2.3,4.5.6]".getIvyVersionRanges().get() shouldBe listOf(
53+
listOf(Range("1.2.3", Range.RangeOperator.GTE), Range("4.5.6", Range.RangeOperator.LTE))
54+
)
55+
56+
"[3.3.0,)".getIvyVersionRanges().get() shouldBe listOf(
57+
listOf(Range("3.3.0", Range.RangeOperator.GTE))
58+
)
59+
}
60+
}
61+
})

0 commit comments

Comments
 (0)