Skip to content

Commit 0fdf659

Browse files
committed
feat(utils): Allow version range for package configuration
The version ranges are only taken in account if the package configuration contains the `id` and the `sourceCodeOrigin`, or just the `id`. The `revision` part of the `vcs` property is still optional to prevent a breaking change, see [2]. See [1] for the specification. Fixes #9918. [1]: #9918 (comment) [2]: #9918 (comment) Signed-off-by: Nicolas Nobelis <[email protected]>
1 parent 0c7c054 commit 0fdf659

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

model/src/main/kotlin/config/PackageConfiguration.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import org.ossreviewtoolkit.model.RepositoryProvenance
3030
import org.ossreviewtoolkit.model.SourceCodeOrigin
3131
import org.ossreviewtoolkit.model.VcsInfo
3232
import org.ossreviewtoolkit.model.VcsType
33+
import org.ossreviewtoolkit.model.utils.isApplicableIvyVersion
34+
import org.ossreviewtoolkit.model.utils.isVersionRange
3335
import org.ossreviewtoolkit.utils.common.replaceCredentialsInUri
3436

3537
/**
@@ -80,14 +82,20 @@ data class PackageConfiguration(
8082
) {
8183
"A package configuration must contain at most one of 'sourceArtifactUrl', 'vcs' or 'sourceCodeOrigin'."
8284
}
85+
86+
if (id.isVersionRange()) {
87+
require(vcs == null && sourceArtifactUrl == null) {
88+
"A package configuration cannot have a version range and a 'vcs' or 'sourceArtifactUrl'."
89+
}
90+
}
8391
}
8492

8593
fun matches(otherId: Identifier, provenance: Provenance): Boolean {
8694
@Suppress("ComplexCondition")
8795
if (!id.type.equals(otherId.type, ignoreCase = true) ||
8896
id.namespace != otherId.namespace ||
8997
id.name != otherId.name ||
90-
id.version != otherId.version
98+
!id.isApplicableIvyVersion(otherId)
9199
) {
92100
return false
93101
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,4 @@ internal fun Identifier.isApplicableIvyVersion(pkgId: Identifier) =
6363
it.showStackTrace()
6464
}.getOrDefault(false)
6565

66-
private fun Identifier.isVersionRange() = versionRangeIndicators.any { version.contains(it, ignoreCase = true) }
66+
internal fun Identifier.isVersionRange() = versionRangeIndicators.any { version.contains(it, ignoreCase = true) }

model/src/test/kotlin/config/PackageConfigurationTest.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.ossreviewtoolkit.model.config
2121

22+
import io.kotest.assertions.throwables.shouldThrow
2223
import io.kotest.core.spec.style.WordSpec
2324
import io.kotest.matchers.shouldBe
2425

@@ -159,6 +160,19 @@ class PackageConfigurationTest : WordSpec({
159160
) shouldBe true
160161
}
161162

163+
"return true if source code origin is equal and identifier matches with version is in range" {
164+
val config =
165+
PackageConfiguration(
166+
id = Identifier.EMPTY.copy(name = "some-name", version = "[51.0.0,60.0.0]"),
167+
sourceCodeOrigin = SourceCodeOrigin.ARTIFACT
168+
)
169+
170+
config.matches(
171+
config.id.copy(version = "55"),
172+
ARTIFACT_PROVENANCE
173+
) shouldBe true
174+
}
175+
162176
"return false if only source code origin is not equal" {
163177
val config =
164178
PackageConfiguration(
@@ -193,5 +207,53 @@ class PackageConfigurationTest : WordSpec({
193207
ARTIFACT_PROVENANCE
194208
) shouldBe false
195209
}
210+
211+
"return true if only matched by id with a matching version range" {
212+
val config =
213+
PackageConfiguration(
214+
id = Identifier.EMPTY.copy(name = "some-name", version = "[51.0.0,60.0.0]")
215+
)
216+
217+
config.matches(
218+
config.id.copy(version = "55"),
219+
ARTIFACT_PROVENANCE
220+
) shouldBe true
221+
}
222+
223+
"return false if only matched by id with a non-matching version range" {
224+
val config =
225+
PackageConfiguration(
226+
id = Identifier.EMPTY.copy(name = "some-name", version = "[51.0.0,60.0.0]")
227+
)
228+
229+
config.matches(
230+
config.id.copy(version = "6"),
231+
ARTIFACT_PROVENANCE
232+
) shouldBe false
233+
}
234+
}
235+
236+
"init()" should {
237+
"throw an exception if a version range is given while having a vcs" {
238+
val baseConfig = vcsPackageConfig(
239+
name = "some-name",
240+
revision = "12345678",
241+
url = "ssh://git@host/repo.git"
242+
)
243+
shouldThrow<IllegalArgumentException> {
244+
baseConfig.copy(
245+
id = baseConfig.id.copy(version = "[51.0.0,60.0.0]")
246+
)
247+
}
248+
}
249+
250+
"throw an exception if a version range is given while having a source artifact URL" {
251+
val baseConfig = sourceArtifactConfig(name = "some-name", url = "https://host/path/file.zip")
252+
shouldThrow<IllegalArgumentException> {
253+
baseConfig.copy(
254+
id = baseConfig.id.copy(version = "[51.0.0,60.0.0]")
255+
)
256+
}
257+
}
196258
}
197259
})

website/docs/configuration/package-configurations.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,38 @@ Use a package configuration file to:
1313

1414
## Package Configuration File Basics
1515

16-
Each package configuration applies exactly to one *package id* and *provenance* which must be specified.
17-
The *provenance* can be specified as either a *source artifact* or a *VCS location* with an optional revision.
16+
A package configuration applies to the packages it matches with.
17+
It contains the mandatory `id` matcher, for matching package IDs, which allows for using [Ivy-style version matchers](https://ant.apache.org/ivy/history/2.5.0/settings/version-matchers.html).
18+
In addition to the `id`, at most one of the matchers `vcs`, `sourceArtifactUrl` or `sourceCodeOrigin` may additionally
19+
be specified, which targets the repository provenance, the source artifact provenance or just the source code origin of
20+
the package's scan result(s).
1821

19-
Here is an example of a package configuration for `ansi-styles 4.2.1`, when the source artifact is (to be) scanned:
22+
The following example illustrates a package configuration for `ansi-styles 4.2.1`, utilizing the available options:
2023

2124
```yaml
25+
# Apply only specified source artifact by its URL.
2226
id: "NPM::ansi-styles:4.2.1"
2327
source_artifact_url: "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz"
24-
```
25-
26-
If the source repository is (to be) scanned, then use the package configuration below:
2728

28-
```yaml
29+
# Apply only to specific code repository URL with optionally a hash.
2930
id: "NPM::ansi-styles:4.2.1"
3031
vcs:
3132
type: "Git"
3233
url: "https://github.com/chalk/ansi-styles.git"
3334
revision: "74d421cf32342ac6ec7b507bd903a9e1105f74d7"
35+
36+
# Apply to all versions lower than 4.2.1 where a code repository was scanned.
37+
id: "NPM::ansi-styles:(,4.2.1]"
38+
source_code_origin: VCS
39+
40+
# Apply to versions all versions greater or equal to 4.0
41+
# and lower or equal to 4.2.1 where a source artifact was scanned.
42+
id: "NPM::ansi-styles:[4.0,4.2.1]"
43+
source_code_origin: SOURCE_ARTIFACT
44+
45+
# Apply only to version 4.2.1, regardless whether
46+
# code repository or source artifact was scanned.
47+
id: "NPM::ansi-styles:4.2.1"
3448
```
3549
3650
## Defining Path Excludes and License Finding Curations

0 commit comments

Comments
 (0)