Skip to content

Commit

Permalink
Support Ivy version matchers in curation data
Browse files Browse the repository at this point in the history
This allows to create a single curation that matches multiple versions
of the same package. For Ivy version specifiers see

http://ant.apache.org/ivy/history/2.4.0/settings/version-matchers.html

As part of the required refactoring Identifier.matches() was removed
incl. its belonging test as the matching algorithm was in fact very
curation-specific. The logic was therefore split into several helper
functions in the PackageCuration class.

Signed-off-by: Sebastian Schuberth <[email protected]>
  • Loading branch information
sschuberth committed Apr 12, 2019
1 parent 101848d commit cd8fc1b
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 71 deletions.
4 changes: 2 additions & 2 deletions analyzer/src/main/kotlin/PackageCurationProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import com.here.ort.model.PackageCuration
*/
interface PackageCurationProvider {
/**
* Get all available [PackageCuration]s for the provided [Identifier].
* Get all available [PackageCuration]s for the provided [pkgId].
*/
fun getCurationsFor(identifier: Identifier): List<PackageCuration>
fun getCurationsFor(pkgId: Identifier): List<PackageCuration>
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ class YamlFilePackageCurationProvider(
curationFile.readValue<List<PackageCuration>>()
}

override fun getCurationsFor(identifier: Identifier) = packageCurations.filter { it.id.matches(identifier) }
override fun getCurationsFor(pkgId: Identifier) = packageCurations.filter { it.isApplicable(pkgId) }
}
3 changes: 3 additions & 0 deletions analyzer/src/test/assets/package-curations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@
- id: "Maven:org.apache::"
curations:
homepage_url: "http://home.page"
- id: "NPM::ramda:[0.21.0,0.25.0]"
curations:
concluded_license: "MIT"
35 changes: 30 additions & 5 deletions analyzer/src/test/kotlin/YamlFilePackageCurationProviderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package com.here.ort.analyzer

import com.here.ort.model.Identifier

import io.kotlintest.matchers.beEmpty
import io.kotlintest.matchers.haveSize
import io.kotlintest.should
import io.kotlintest.shouldBe
Expand All @@ -35,22 +36,46 @@ class YamlFilePackageCurationProviderTest : StringSpec() {
"Provider can read YAML file" {
val provider = YamlFilePackageCurationProvider(curationsFile)

provider.packageCurations should haveSize(7)
provider.packageCurations should haveSize(8)
}

"Provider returns only matching curations" {
val identifier = Identifier("maven", "org.hamcrest", "hamcrest-core", "1.3")
"Provider returns only matching curations for a fixed version" {
val provider = YamlFilePackageCurationProvider(curationsFile)

val identifier = Identifier("maven", "org.hamcrest", "hamcrest-core", "1.3")
val curations = provider.getCurationsFor(identifier)

curations should haveSize(4)
curations.forEach {
it.id.matches(identifier) shouldBe true
it.isApplicable(identifier) shouldBe true
}
(provider.packageCurations - curations).forEach {
it.id.matches(identifier) shouldBe false
it.isApplicable(identifier) shouldBe false
}
}

"Provider returns only matching curations for a version range" {
val provider = YamlFilePackageCurationProvider(curationsFile)

val idMinVersion = Identifier("npm", "", "ramda", "0.21.0")
val idMaxVersion = Identifier("npm", "", "ramda", "0.25.0")
val idOutVersion = Identifier("npm", "", "ramda", "0.26.0")

val curationsMinVersion = provider.getCurationsFor(idMinVersion)
val curationsMaxVersion = provider.getCurationsFor(idMaxVersion)
val curationsOutVersion = provider.getCurationsFor(idOutVersion)

curationsMinVersion should haveSize(1)
(provider.packageCurations - curationsMinVersion).forEach {
it.isApplicable(idMinVersion) shouldBe false
}

curationsMaxVersion should haveSize(1)
(provider.packageCurations - curationsMaxVersion).forEach {
it.isApplicable(idMinVersion) shouldBe false
}

curationsOutVersion should beEmpty()
}
}
}
7 changes: 7 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@ location of source artifacts. The structure of the curations file is:
comment: "Revision found by comparing NPM packages with the sources from https://github.com/olov/ast-traverse."
vcs:
revision: "73f2b3c319af82fd8e490d40dd89a15951069b0d"
- id: "NPM::ramda:[0.21.0,0.25.0]" # Ivy-style version matchers are supported.
curations:
comment: "The package is licensed under MIT per `LICENSE` and `dist/ramda.js`. The project logo is CC-BY-NC-SA-3.0 \
but it is not part of the distributed .tar.gz package, see the `README.md` which says: \
Ramda logo artwork © 2014 J. C. Phillipps. Licensed Creative Commons CC BY-NC-SA 3.0"
concluded_license: "MIT"
```
To use the curations file pass it to the `--package-curations-file` option of the `analyzer`:
Expand Down
28 changes: 0 additions & 28 deletions model/src/main/kotlin/Identifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,34 +103,6 @@ data class Identifier(
}
}

/**
* Return true if this matches the other identifier. To match, both identifiers need to have the same [type] and
* [namespace], and the [name] and [version] must be either equal or empty for at least one of them.
*
* Examples for matching identifiers:
* * "maven:org.hamcrest:hamcrest-core:1.3" <-> "maven:org.hamcrest:hamcrest-core:"
* * "maven:org.hamcrest:hamcrest-core:1.3" <-> "maven:org.hamcrest::1.3"
* * "maven:org.hamcrest:hamcrest-core:1.3" <-> "maven:org.hamcrest::"
*
* Examples for not matching identifiers:
* * "maven:org.hamcrest:hamcrest-core:1.3" <-> "maven:org.hamcrest:hamcrest-core:1.2"
* * "maven:org.hamcrest:hamcrest-core:" <-> "maven:org.hamcrest:hamcrest-library:"
*/
fun matches(other: Identifier): Boolean {
if (!type.equals(other.type, true)) {
return false
}

if (namespace != other.namespace) {
return false
}

val nameMatches = name == other.name || name.isBlank() || other.name.isBlank()
val versionMatches = version == other.version || version.isBlank() || other.version.isBlank()

return nameMatches && versionMatches
}

/**
* Create Maven-like coordinates based on the properties of the [Identifier].
*/
Expand Down
40 changes: 39 additions & 1 deletion model/src/main/kotlin/PackageCuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ package com.here.ort.model

import com.fasterxml.jackson.annotation.JsonProperty

import com.vdurmont.semver4j.Semver
import com.vdurmont.semver4j.SemverException

/**
* Return true if this string equals the [other] string, or if either string is blank.
*/
private fun String.equalsOrIsBlank(other: String) = equals(other) || isBlank() || other.isBlank()

/**
* This class assigns a [PackageCurationData] object to a [Package] identified by the [id].
*/
Expand All @@ -36,13 +44,43 @@ data class PackageCuration(
@JsonProperty("curations")
val data: PackageCurationData
) {
/**
* Return true if this [PackageCuration] is applicable to the package with the given [identifier][pkgId],
* disregarding the version.
*/
private fun isApplicableDisregardingVersion(pkgId: Identifier) =
id.type.equals(pkgId.type, true)
&& id.namespace == pkgId.namespace
&& id.name.equalsOrIsBlank(pkgId.name)

/**
* Return true if the version of this [PackageCuration] interpreted as an Ivy version matcher is applicable to the
* package with the given [identifier][pkgId].
*/
private fun isApplicableIvyVersion(pkgId: Identifier) =
try {
val pkgIvyVersion = Semver(pkgId.version, Semver.SemverType.IVY)
pkgIvyVersion.satisfies(id.version)
} catch (e: SemverException) {
false
}

/**
* Return true if this [PackageCuration] is applicable to the package with the given [identifier][pkgId]. The
* curation's version may be an
* [Ivy version matcher](http://ant.apache.org/ivy/history/2.4.0/settings/version-matchers.html).
*/
fun isApplicable(pkgId: Identifier): Boolean =
isApplicableDisregardingVersion(pkgId)
&& (id.version.equalsOrIsBlank(pkgId.version) || isApplicableIvyVersion(pkgId))

/**
* Apply the curation [data] to the provided package.
*
* @see [PackageCurationData.apply]
*/
fun apply(curatedPackage: CuratedPackage): CuratedPackage {
if (!id.matches(curatedPackage.pkg.id)) {
if (!isApplicable(curatedPackage.pkg.id)) {
throw IllegalArgumentException(
"Package curation identifier '${id.toCoordinates()}' does not match " +
"package identifier '${curatedPackage.pkg.id.toCoordinates()}'."
Expand Down
34 changes: 0 additions & 34 deletions model/src/test/kotlin/IdentifierTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,40 +61,6 @@ class IdentifierTest : StringSpec() {
}
}

"Identifiers match correctly" {
val matching = mapOf(
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest:hamcrest-core:1.3",
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest:hamcrest-core:",
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest::1.3",
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest::"
)

val nonMatching = mapOf(
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest:hamcrest-core:1.2",
"maven:org.hamcrest:hamcrest-core:1.3"
to "maven:org.hamcrest:hamcrest-library:",
"maven:org.hamcrest:hamcrest-core:"
to "maven:org.hamcrest:hamcrest-library:",
"maven:org.hamcrest::"
to "maven:org.apache::",
"maven:org.hamcrest::"
to "gradle:org.hamcrest::"
)

matching.forEach { id1, id2 ->
Identifier(id1).matches(Identifier(id2)) shouldBe true
}

nonMatching.forEach { id1, id2 ->
Identifier(id1).matches(Identifier(id2)) shouldBe false
}
}

"Identifier is serialized to String" {
val id = Identifier("type", "namespace", "name", "version")

Expand Down

0 comments on commit cd8fc1b

Please sign in to comment.