Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify SPDX compound expressions #9360

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ packages:
filesAnalyzed: false
homepage: "NONE"
licenseConcluded: "NOASSERTION"
licenseDeclared: "Apache-2.0 AND Apache-2.0 AND Apache-2.0 AND MIT AND MIT AND MIT"
licenseDeclared: "Apache-2.0 AND MIT"
name: "gopkg.in/yaml.v3"
summary: "NONE"
versionInfo: "3.0.1"
Expand All @@ -288,7 +288,7 @@ packages:
filesAnalyzed: false
homepage: "NONE"
licenseConcluded: "NOASSERTION"
licenseDeclared: "Apache-2.0 AND Apache-2.0 AND Apache-2.0 AND MIT AND MIT AND MIT"
licenseDeclared: "Apache-2.0 AND MIT"
name: "gopkg.in/yaml.v3"
summary: "NONE"
versionInfo: "3.0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"filesAnalyzed" : false,
"homepage" : "first package's homepage URL",
"licenseConcluded" : "BSD-2-Clause AND BSD-3-Clause AND MIT",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND MIT",
"name" : "first-package",
"summary" : "A package with all supported attributes set, with a VCS URL containing a user name, and with two scan results for the VCS containing copyright findings matched to a license finding.",
"versionInfo" : "0.0.1"
Expand All @@ -63,7 +63,7 @@
"hasFiles" : [ "SPDXRef-File-1" ],
"homepage" : "first package's homepage URL",
"licenseConcluded" : "NOASSERTION",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND MIT",
"licenseInfoFromFiles" : [ "Apache-2.0", "BSD-2-Clause" ],
"name" : "first-package",
"packageVerificationCode" : {
Expand All @@ -87,7 +87,7 @@
"filesAnalyzed" : false,
"homepage" : "first package's homepage URL",
"licenseConcluded" : "NOASSERTION",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT",
"licenseDeclared" : "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND MIT",
"name" : "first-package",
"summary" : "A package with all supported attributes set, with a VCS URL containing a user name, and with two scan results for the VCS containing copyright findings matched to a license finding.",
"versionInfo" : "0.0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ packages:
filesAnalyzed: false
homepage: "first package's homepage URL"
licenseConcluded: "BSD-2-Clause AND BSD-3-Clause AND MIT"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause\
\ AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause\
\ AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only\
\ OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only\
\ OR MIT) AND MIT"
name: "first-package"
summary: "A package with all supported attributes set, with a VCS URL containing\
\ a user name, and with two scan results for the VCS containing copyright findings\
Expand All @@ -78,10 +76,8 @@ packages:
- "SPDXRef-File-1"
homepage: "first package's homepage URL"
licenseConcluded: "NOASSERTION"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause\
\ AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause\
\ AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only\
\ OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only\
\ OR MIT) AND MIT"
licenseInfoFromFiles:
- "Apache-2.0"
- "BSD-2-Clause"
Expand All @@ -107,10 +103,8 @@ packages:
filesAnalyzed: false
homepage: "first package's homepage URL"
licenseConcluded: "NOASSERTION"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-2-Clause AND BSD-2-Clause\
\ AND BSD-2-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause AND BSD-3-Clause\
\ AND BSD-3-Clause AND BSD-3-Clause AND (GPL-2.0-only OR MIT) AND (GPL-2.0-only\
\ OR MIT) AND (GPL-2.0-only OR MIT) AND MIT AND MIT AND MIT"
licenseDeclared: "Apache-2.0 AND BSD-2-Clause AND BSD-3-Clause AND (GPL-2.0-only\
\ OR MIT) AND MIT"
name: "first-package"
summary: "A package with all supported attributes set, with a VCS URL containing\
\ a user name, and with two scan results for the VCS containing copyright findings\
Expand Down
1 change: 1 addition & 0 deletions plugins/reporters/spdx/src/main/kotlin/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ internal fun Package.toSpdxPackage(
SpdxConstants.NONE
} else {
packageLicenseExpressions.reduce(SpdxExpression::and)
.simplify()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will now throw an exception if the input contains only two identical licenses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, see my comment above.

.sorted()
.nullOrBlankToSpdxNoassertionOrNone()
},
Expand Down
26 changes: 24 additions & 2 deletions utils/spdx/src/main/kotlin/SpdxExpression.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ sealed class SpdxExpression {
*/
abstract fun normalize(mapDeprecated: Boolean = true): SpdxExpression

/**
* Return a simplified expression that has e.g. redundancies removed.
*/
open fun simplify(): SpdxExpression = this

/**
* Return this expression sorted lexicographically.
*/
Expand Down Expand Up @@ -200,12 +205,14 @@ sealed class SpdxExpression {
/**
* Concatenate [this][SpdxExpression] and [other] using [SpdxOperator.AND].
*/
infix fun and(other: SpdxExpression) = SpdxCompoundExpression(SpdxOperator.AND, listOf(this, other))
infix fun and(other: SpdxExpression) =
takeIf { this == other } ?: SpdxCompoundExpression(SpdxOperator.AND, listOf(this, other))

/**
* Concatenate [this][SpdxExpression] and [other] using [SpdxOperator.OR].
*/
infix fun or(other: SpdxExpression) = SpdxCompoundExpression(SpdxOperator.OR, listOf(this, other))
infix fun or(other: SpdxExpression) =
takeIf { this == other } ?: SpdxCompoundExpression(SpdxOperator.OR, listOf(this, other))
}

/**
Expand Down Expand Up @@ -242,6 +249,21 @@ class SpdxCompoundExpression(
override fun normalize(mapDeprecated: Boolean) =
SpdxCompoundExpression(operator, children.map { it.normalize(mapDeprecated) })

override fun simplify(): SpdxExpression {
val flattenedChildren = children.flatMapTo(mutableSetOf()) { child ->
val simplifiedChild = child.simplify()

if (simplifiedChild is SpdxCompoundExpression && simplifiedChild.operator == operator) {
// Inline nested children of the same operator.
simplifiedChild.children.map { it.simplify() }
} else {
setOf(simplifiedChild)
}
}

return flattenedChildren.singleOrNull() ?: SpdxCompoundExpression(operator, flattenedChildren)
}

override fun sorted(): SpdxExpression {
/**
* Get all transitive children of this expression that are concatenated with the same operator as this compound
Expand Down
83 changes: 83 additions & 0 deletions utils/spdx/src/test/kotlin/SpdxCompoundExpressionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2017 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/

package org.ossreviewtoolkit.utils.spdx

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe

class SpdxCompoundExpressionTest : WordSpec({

Check warning on line 26 in utils/spdx/src/test/kotlin/SpdxCompoundExpressionTest.kt

View workflow job for this annotation

GitHub Actions / qodana-scan

Unused symbol

Class "SpdxCompoundExpressionTest" is never used
Dismissed Show dismissed Hide dismissed
"Creating a compound expression" should {
"fail if the expression has less than two children" {
shouldThrow<IllegalArgumentException> {
SpdxCompoundExpression(SpdxOperator.AND, emptyList())
}

shouldThrow<IllegalArgumentException> {
SpdxCompoundExpression(SpdxOperator.AND, listOf(SpdxLicenseIdExpression("license")))
}
}
}

"Simplifying a compound expression" should {
"inline nested children of the same operator" {
val expression = SpdxCompoundExpression(
SpdxOperator.AND,
listOf(
SpdxCompoundExpression(
SpdxOperator.AND,
listOf(
SpdxLicenseIdExpression("MIT"),
SpdxCompoundExpression(
SpdxOperator.AND,
listOf(
SpdxLicenseIdExpression("MIT"),
SpdxLicenseIdExpression("Apache-2.0")
)
)
)
),
SpdxLicenseIdExpression("Apache-2.0")
)
)

// Compare string representations to not rely on semantic equality.
expression.simplify().toString() shouldBe SpdxCompoundExpression(
SpdxOperator.AND,
listOf(
SpdxLicenseIdExpression("MIT"),
SpdxLicenseIdExpression("Apache-2.0")
)
).toString()
}

"create a single expression for equal operands" {
val expression = SpdxCompoundExpression(
SpdxOperator.AND,
listOf(
SpdxLicenseIdExpression("MIT"),
SpdxLicenseIdExpression("MIT")
)
)

expression.simplify() shouldBe SpdxLicenseIdExpression("MIT")
}
}
})
Loading
Loading