From 437dbf6c33cb0319853c79704c163b317cf57a35 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sat, 8 Jan 2022 13:15:41 +0300 Subject: [PATCH 1/4] license-gather: add license compatibility validation task --- README.md | 5 +- .../vlsi/gradle/license/GatherLicenseTask.kt | 5 + .../license/LicenseCompatibilityConfig.kt | 26 +++ .../LicenseCompatibilityInterpreter.kt | 127 ++++++++++ .../vlsi/gradle/license/MetadataStore.kt | 3 +- .../license/VerifyLicenseCompatibilityTask.kt | 208 +++++++++++++++++ .../github/vlsi/gradle/license/api/License.kt | 10 +- .../gradle/license/api/LicenseExpression.kt | 29 ++- .../api/LicenseExpressionExtensions.kt | 14 ++ .../license/api/LicenseExpressionSet.kt | 23 ++ .../api/LicenseExpressionSetOperation.kt | 22 ++ .../vlsi/gradle/release/AsfLicenseCategory.kt | 41 ++-- .../LicenseCompatibilityInterpreterTest.kt | 147 ++++++++++++ .../VerifyLicenseCompatibilityTaskTest.kt | 219 ++++++++++++++++++ 14 files changed, 855 insertions(+), 24 deletions(-) create mode 100644 plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityConfig.kt create mode 100644 plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt create mode 100644 plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTask.kt create mode 100644 plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSet.kt create mode 100644 plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSetOperation.kt rename plugins/{stage-vote-release-plugin => license-gather-plugin}/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt (86%) create mode 100644 plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt create mode 100644 plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt diff --git a/README.md b/README.md index b6351d4..0dc58cf 100644 --- a/README.md +++ b/README.md @@ -191,8 +191,9 @@ Change log ---------- v1.78 * chore: bump Gradle 6.7 -> 6.9.1 -* license-gather: ignore xml namespaces when parsing POM files (see #43) -* license-gather: fix license inference from Bundle-License manifest attribute (see #48) +* license-gather: ignore xml namespaces when parsing POM files (see [issue #43](https://github.com/vlsi/vlsi-release-plugins/issues/43)) +* license-gather: fix license inference from Bundle-License manifest attribute (see [issue #48](https://github.com/vlsi/vlsi-release-plugins/issues/48)) +* license-gather: implement VerifyLicenseCompatibilityTask for verifying license compatibility (see [pr #49](https://github.com/vlsi/vlsi-release-plugins/pull/49)) Thanks to [Florian Dreier](https://github.com/DreierF) for identifying bugs and suggesting fixes. diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt index 42a8435..a077851 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt @@ -96,6 +96,11 @@ open class GatherLicenseTask @Inject constructor( objectFactory: ObjectFactory, private val workerExecutor: WorkerExecutor ) : DefaultTask() { + init { + // TODO: capture [licenseOverrides] as input + outputs.upToDateWhen { false } + } + @InputFiles val configurations = objectFactory.setProperty() diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityConfig.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityConfig.kt new file mode 100644 index 0000000..12c29b2 --- /dev/null +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityConfig.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license + +interface LicenseCompatibilityConfig { + /** + * Clarifies the reason for [LicenseCompatibility] so the output of + * [VerifyLicenseCompatibilityTask] is easier to understand. + */ + fun because(reason: String) +} diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt new file mode 100644 index 0000000..c1c8086 --- /dev/null +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license + +import com.github.vlsi.gradle.license.CompatibilityResult.ALLOW +import com.github.vlsi.gradle.license.CompatibilityResult.REJECT +import com.github.vlsi.gradle.license.CompatibilityResult.UNKNOWN +import com.github.vlsi.gradle.license.api.ConjunctionLicenseExpression +import com.github.vlsi.gradle.license.api.DisjunctionLicenseExpression +import com.github.vlsi.gradle.license.api.LicenseEquivalence +import com.github.vlsi.gradle.license.api.LicenseExpression +import com.github.vlsi.gradle.license.api.disjunctions + +enum class CompatibilityResult { + ALLOW, UNKNOWN, REJECT; +} + +data class LicenseCompatibility( + val type: CompatibilityResult, val reason: String +) : java.io.Serializable + +internal data class ResolvedLicenseCompatibility( + val type: CompatibilityResult, val reasons: List +) : Comparable { + constructor(type: CompatibilityResult, vararg reasons: String) : this(type, reasons.toList()) + + override fun compareTo(other: ResolvedLicenseCompatibility) = + compareValuesBy(this, other, { it.type }, { it.reasons.toString() }) +} + +internal fun LicenseCompatibility.asResolved(license: LicenseExpression) = + ResolvedLicenseCompatibility( + type, + if (reason.isEmpty()) "$license: $type" else "$license: $reason" + ) + +internal class LicenseCompatibilityInterpreter( + private val licenseEquivalence: LicenseEquivalence, + private val resolvedCases: Map +) { + val resolvedParts = resolvedCases.asSequence().flatMap { (license, _) -> + licenseEquivalence.expand(license).disjunctions().asSequence().map { it to license } + }.groupingBy { it.first }.aggregate { key, acc: LicenseExpression?, element, first -> + if (first) { + element.second + } else { + throw IllegalArgumentException( + "License $key participates in multiple resolved cases: $acc and ${element.second}. " + "Please make sure resolvedCases do not intersect" + ) + } + } + + override fun toString() = "LicenseCompatibilityInterpreter(resolved=$resolvedCases)" + + fun eval(licenseExpression: LicenseExpression?): ResolvedLicenseCompatibility { + if (licenseExpression == null) { + return ResolvedLicenseCompatibility(REJECT, listOf("License is null")) + } + // If the case is already resolved, just return the resolution + resolvedCases[licenseExpression]?.let { return it.asResolved(licenseExpression) } + + // Expand the license (e.g. expand OR_LATER into OR ... OR) + val e = licenseEquivalence.expand(licenseExpression) + + return when (e) { + is DisjunctionLicenseExpression -> + // A or X => A + e.unordered.takeIf { it.isNotEmpty() }?.map { eval(it) }?.reduce { a, b -> + when { + a.type == b.type -> ResolvedLicenseCompatibility( + a.type, + a.reasons + b.reasons + ) + // allow OR (unknown | reject) -> allow + a.type == ALLOW -> a + b.type == ALLOW -> b + // reject OR unknown -> unknown + else -> ResolvedLicenseCompatibility( + UNKNOWN, + a.reasons.map { "${a.type}: $it" } + b.reasons.map { "${b.type}: $it" } + ) + } + } + is ConjunctionLicenseExpression -> e.unordered.takeIf { it.isNotEmpty() } + ?.map { eval(it) }?.reduce { a, b -> + when { + a.type == b.type -> ResolvedLicenseCompatibility( + a.type, + a.reasons + b.reasons + ) + // allow OR next=(unknown | reject) -> next + a.type == ALLOW -> b + b.type == ALLOW -> a + // reject OR unknown -> reject + else -> ResolvedLicenseCompatibility( + REJECT, + a.reasons.map { "${a.type}: $it" } + b.reasons.map { "${b.type}: $it" } + ) + } + } + else -> resolvedParts[e]?.let { resolved -> + resolvedCases.getValue(resolved).let { + if (e == resolved) { + it.asResolved(resolved) + } else { + it.asResolved(e) + } + } + } + } ?: ResolvedLicenseCompatibility(UNKNOWN, listOf("No rules found for $licenseExpression")) + } +} diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt index 68304fc..8ab6d52 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt @@ -26,6 +26,7 @@ import com.github.vlsi.gradle.license.api.LicenseException import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.LicenseExpressionParser import com.github.vlsi.gradle.license.api.LicenseExpressionSet +import com.github.vlsi.gradle.license.api.LicenseExpressionSetExpression import com.github.vlsi.gradle.license.api.OrLaterLicense import com.github.vlsi.gradle.license.api.SimpleLicense import com.github.vlsi.gradle.license.api.SimpleLicenseExpression @@ -170,7 +171,7 @@ object MetadataStore { when (this) { is SimpleLicenseExpression -> license.providerId() is WithException -> license.providerId() - is LicenseExpressionSet -> + is LicenseExpressionSetExpression-> unordered.map { it.providerId() }.distinct().let { if (it.size == 1) it.first() else null } diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTask.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTask.kt new file mode 100644 index 0000000..dcfd14f --- /dev/null +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTask.kt @@ -0,0 +1,208 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license + +import com.github.vlsi.gradle.license.api.LicenseEquivalence +import com.github.vlsi.gradle.license.api.LicenseExpression +import com.github.vlsi.gradle.license.api.LicenseExpressionSet +import org.gradle.api.Action +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.artifacts.component.ModuleComponentIdentifier +import org.gradle.api.file.ProjectLayout +import org.gradle.api.logging.LogLevel +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.Console +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.mapProperty +import org.gradle.kotlin.dsl.property +import java.lang.Math.max +import java.util.* +import javax.inject.Inject + +open class VerifyLicenseCompatibilityTask @Inject constructor( + objectFactory: ObjectFactory, + layout: ProjectLayout +) : DefaultTask() { + @InputFiles + val metadata = objectFactory.fileCollection() + + @Input + val failOnIncompatibleLicense = objectFactory.property().convention(true) + + @Input + val resolvedCases = objectFactory.mapProperty() + + @Input + val licenseSimilarityNormalizationThreshold = + objectFactory.property().convention(42) + + @Option(option = "print", description = "prints the verification results to console") + @Console + val printResults = objectFactory.property().convention(false) + + /** + * Outputs the license verification results (incompatible and unknown licenses are listed first). + */ + @OutputFile + val outputFile = objectFactory.fileProperty() + .convention(layout.buildDirectory.file("verifyLicense/$name/verification_result.txt")) + + fun registerResolution( + licenseSet: LicenseExpressionSet, + type: CompatibilityResult, + action: Action? = null + ) { + val reason = action?.let { + object : LicenseCompatibilityConfig { + var reason = "" + override fun because(reason: String) { + this.reason = reason + } + }.let { + action.invoke(it) + it.reason + } + } ?: "" + val licenseCompatibility = LicenseCompatibility(type, reason) + for (licenseExpression in licenseSet.disjunctions) { + resolvedCases.put(licenseExpression, licenseCompatibility) + } + } + + @JvmOverloads + fun allow( + licenseSet: LicenseExpressionSet, + action: Action? = null + ) { + registerResolution(licenseSet, CompatibilityResult.ALLOW, action) + } + + @JvmOverloads + fun reject( + licenseSet: LicenseExpressionSet, + action: Action? = null + ) { + registerResolution(licenseSet, CompatibilityResult.REJECT, action) + } + + @JvmOverloads + fun unknown( + licenseSet: LicenseExpressionSet, + action: Action? = null + ) { + registerResolution(licenseSet, CompatibilityResult.UNKNOWN, action) + } + + @TaskAction + fun run() { + val dependencies = MetadataStore.load(metadata).dependencies + + val licenseNormalizer = GuessBasedNormalizer( + logger, licenseSimilarityNormalizationThreshold.get().toDouble() + ) + val licenseCompatibilityInterpreter = LicenseCompatibilityInterpreter( + // TODO: make it configurable + LicenseEquivalence(), + resolvedCases.get().mapKeys { + licenseNormalizer.normalize(it.key) + } + ) + + val ok = StringBuilder() + val ko = StringBuilder() + + dependencies + .asSequence() + .map { (component, licenseInfo) -> component to licenseInfo.license } + .groupByTo(TreeMap()) { (component, license) -> + licenseCompatibilityInterpreter.eval(license).also { + logger.log( + when (it.type) { + CompatibilityResult.ALLOW -> LogLevel.DEBUG + CompatibilityResult.UNKNOWN -> LogLevel.LIFECYCLE + CompatibilityResult.REJECT -> LogLevel.LIFECYCLE + }, + "License compatibility for {}: {} -> {}", component, license, it + ) + } + } + .forEach { (licenseCompatibility, components) -> + val header = + "${licenseCompatibility.type}\n${licenseCompatibility.reasons.joinToString("\n ", " ")}" + val sb = if (licenseCompatibility.type == CompatibilityResult.ALLOW) ok else ko + if (sb.isNotEmpty()) { + sb.append('\n') + } + sb.append(header).append('\n') + val headerWidth = + licenseCompatibility.reasons.fold(licenseCompatibility.type.toString().length) { a, b -> + a.coerceAtLeast(b.length + 2) + } + sb.append("=".repeat(headerWidth)).append('\n') + sb.appendComponents(components) + } + + val errorMessage = ko.toString() + val result = ko.apply { + if (isNotEmpty() && ok.isNotEmpty()) { + append('\n') + } + append(ok) + while (endsWith('\n')) { + setLength(length - 1) + } + }.toString() + + if (printResults.get()) { + println(result) + } + + outputFile.get().asFile.apply { + parentFile.mkdirs() + writeText(result) + } + + if (errorMessage.isNotEmpty()) { + if (failOnIncompatibleLicense.get()) { + throw GradleException(errorMessage) + } else { + logger.warn(errorMessage) + } + } + } + + private fun Appendable.appendComponents( + components: List> + ) = + components + .groupByTo(TreeMap(nullsFirst(LicenseExpression.NATURAL_ORDER)), + { it.second }, { it.first }) + .forEach { (license, components) -> + append('\n') + append(license?.toString() ?: "Unknown license").append('\n') + components.forEach { + append("* ${it.displayName}\n") + } + } +} diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt index af0432f..2f7d5e3 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt @@ -19,12 +19,18 @@ package com.github.vlsi.gradle.license.api import java.net.URI -interface License { +interface License : LicenseExpressionSet, java.io.Serializable { val title: String val uri: List + + override val disjunctions: Set + get() = setOf(asExpression()) + + override val conjunctions: Set + get() = disjunctions } -interface LicenseException { +interface LicenseException : java.io.Serializable { val title: String val uri: List } diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt index 5d72787..4d6ca10 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt @@ -19,7 +19,7 @@ package com.github.vlsi.gradle.license.api import java.net.URI -sealed class LicenseExpression { +sealed class LicenseExpression : LicenseExpressionSet, java.io.Serializable { companion object { val NATURAL_ORDER = compareBy( { it.weight() }, @@ -27,6 +27,12 @@ sealed class LicenseExpression { ) } + override val conjunctions: Set + get() = setOf(this) + + override val disjunctions: Set + get() = setOf(this) + object NONE : LicenseExpression() object NOASSERTION : LicenseExpression() } @@ -65,8 +71,15 @@ data class WithException( } } -abstract class LicenseExpressionSet : LicenseExpression() { +abstract class LicenseExpressionSetExpression : LicenseExpression() { abstract val unordered: Set + abstract val operation: LicenseExpressionSetOperation + + override val disjunctions: Set + get() = if (operation == LicenseExpressionSetOperation.OR) unordered else setOf(this) + + override val conjunctions: Set + get() = if (operation == LicenseExpressionSetOperation.AND) unordered else setOf(this) val ordered: List get() = unordered.sortedWith( @@ -85,10 +98,13 @@ abstract class LicenseExpressionSet : LicenseExpression() { data class ConjunctionLicenseExpression( val licenses: Set -) : LicenseExpressionSet() { +) : LicenseExpressionSetExpression() { override val unordered: Set get() = licenses + override val operation: LicenseExpressionSetOperation + get() = LicenseExpressionSetOperation.AND + override fun toString() = ordered.joinToString(" AND ") { when (it) { @@ -100,10 +116,13 @@ data class ConjunctionLicenseExpression( data class DisjunctionLicenseExpression( val licenses: Set -) : LicenseExpressionSet() { +) : LicenseExpressionSetExpression() { override val unordered: Set get() = licenses + override val operation: LicenseExpressionSetOperation + get() = LicenseExpressionSetOperation.OR + override fun toString() = ordered.joinToString(" OR ") } @@ -117,4 +136,4 @@ fun LicenseExpression.weight(): Int = is ConjunctionLicenseExpression -> licenses.map { it.weight() + 1 }.max() ?: 1 is DisjunctionLicenseExpression -> licenses.map { it.weight() + 1 }.max() ?: 1 else -> TODO("Unexpected expression: ${this::class.simpleName}: $this") - } \ No newline at end of file + } diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt index 1056d63..90387fb 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt @@ -15,6 +15,8 @@ * */ +@file:JvmName("LicenseExpressionExtensions") + package com.github.vlsi.gradle.license.api fun License.asExpression(): JustLicense = JustLicense(this) @@ -35,12 +37,24 @@ infix fun LicenseExpression.or(other: License): LicenseExpression = this or othe infix fun License.or(other: License): LicenseExpression = asExpression() or other infix fun License.or(other: LicenseExpression): LicenseExpression = other or this +@Deprecated( + "Use member function LicenseExpression.disjunctions()", + replaceWith = ReplaceWith("disjunctions"), + level = DeprecationLevel.WARNING +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") fun LicenseExpression.disjunctions() = when (this) { is DisjunctionLicenseExpression -> unordered else -> setOf(this) } +@Deprecated( + "Use member function LicenseExpression.conjunctions()", + replaceWith = ReplaceWith("conjunctions"), + level = DeprecationLevel.WARNING +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") fun LicenseExpression.conjunctions() = when (this) { is ConjunctionLicenseExpression -> unordered diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSet.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSet.kt new file mode 100644 index 0000000..dfd725d --- /dev/null +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSet.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license.api + +interface LicenseExpressionSet { + val disjunctions: Set + val conjunctions: Set +} diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSetOperation.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSetOperation.kt new file mode 100644 index 0000000..89e128f --- /dev/null +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionSetOperation.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license.api + +enum class LicenseExpressionSetOperation { + AND, OR; +} diff --git a/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt similarity index 86% rename from plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt rename to plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt index 228e1ad..057c5c6 100644 --- a/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt @@ -14,12 +14,15 @@ * limitations under the License. * */ + package com.github.vlsi.gradle.release -import com.github.vlsi.gradle.license.api.JustLicense +import com.github.vlsi.gradle.license.api.DisjunctionLicenseExpression import com.github.vlsi.gradle.license.api.License import com.github.vlsi.gradle.license.api.LicenseEquivalence import com.github.vlsi.gradle.license.api.LicenseExpression +import com.github.vlsi.gradle.license.api.LicenseExpressionSet +import com.github.vlsi.gradle.license.api.LicenseExpressionSetOperation import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException import com.github.vlsi.gradle.license.api.asExpression @@ -30,27 +33,37 @@ import com.github.vlsi.gradle.license.api.with /** * See https://apache.org/legal/resolved.html */ -enum class AsfLicenseCategory { +enum class AsfLicenseCategory : LicenseExpressionSet { A, B, X, UNKNOWN; + override val disjunctions: Set + get() = when (this) { + A -> aLicenses + B -> bLicenses + X -> xLicenses + UNKNOWN -> setOf() + } + + override val conjunctions: Set + get() = setOf(DisjunctionLicenseExpression(disjunctions)) + companion object { - private fun of(license: License?) = - when (license) { - in bLicenses -> B - else -> null - } + @JvmStatic + fun of(license: License) = of(license.asExpression()) @JvmStatic fun of(expr: LicenseExpression) = when (expr) { in aLicenses -> A + in bLicenses -> B in xLicenses -> X - is JustLicense -> of(expr.license) else -> null } + private val equivalence = LicenseEquivalence() + // For the purposes of being included in an Apache Software Foundation product, the following licenses are considered to be similar in terms to the Apache License 2.0 - private val aLicenses = setOf( + private val aLicenses : Set = listOf( SpdxLicense.Apache_2_0, SpdxLicense.Apache_1_0, SpdxLicense.Apache_1_1, @@ -104,7 +117,7 @@ enum class AsfLicenseCategory { ).asSequence() .map { it.asExpression() } .plus( - LicenseEquivalence().expand( + equivalence.expand( SpdxLicense.GPL_1_0_or_later with SpdxLicenseException.Classpath_exception_2_0 ).disjunctions() ) @@ -112,7 +125,7 @@ enum class AsfLicenseCategory { // Software under the following licenses may be included in binary form within an Apache product if the inclusion is appropriately labeled (see above) // By including only the object/binary form, there is less exposed surface area of the third-party work from which a work might be derived; this addresses the second guiding principle of this policy - private val bLicenses = setOf( + private val bLicenses: Set = listOf( SpdxLicense.CDDL_1_0, SpdxLicense.CDDL_1_1, SpdxLicense.CPL_1_0, @@ -139,10 +152,10 @@ enum class AsfLicenseCategory { SpdxLicense.IPA, SpdxLicense.Ruby, SpdxLicense.EPL_2_0 - ) + ).mapTo(mutableSetOf()) { it.asExpression() } // The following licenses may NOT be included within Apache products - private val xLicenses = setOf( + private val xLicenses : Set = listOf( // Binary Code License (BCL) // Intel Simplified Software License // JSR-275 License @@ -178,7 +191,7 @@ enum class AsfLicenseCategory { SpdxLicense.AGPL_3_0_only, SpdxLicense.LGPL_2_0_only ) - .flatMap { LicenseEquivalence().expand(it.orLater()).disjunctions() }) + .flatMap { equivalence.expand(it.orLater()).disjunctions() }) .toSet() } } diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt new file mode 100644 index 0000000..6f55f93 --- /dev/null +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license + +import com.github.vlsi.gradle.license.CompatibilityResult.ALLOW +import com.github.vlsi.gradle.license.CompatibilityResult.REJECT +import com.github.vlsi.gradle.license.CompatibilityResult.UNKNOWN +import com.github.vlsi.gradle.license.api.LicenseEquivalence +import com.github.vlsi.gradle.license.api.LicenseExpression +import com.github.vlsi.gradle.license.api.SpdxLicense +import com.github.vlsi.gradle.license.api.SpdxLicenseException +import com.github.vlsi.gradle.license.api.and +import com.github.vlsi.gradle.license.api.asExpression +import com.github.vlsi.gradle.license.api.or +import com.github.vlsi.gradle.license.api.orLater +import com.github.vlsi.gradle.license.api.with +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments.arguments +import org.junit.jupiter.params.provider.MethodSource + +class LicenseCompatibilityInterpreterTest { + companion object { + private val interpreter = LicenseCompatibilityInterpreter( + LicenseEquivalence(), + mapOf( + SpdxLicense.CC0_1_0.asExpression() to LicenseCompatibility( + ALLOW, + "Public domain is OK" + ), + SpdxLicense.MIT.asExpression() to LicenseCompatibility(ALLOW, ""), + SpdxLicense.Apache_2_0.asExpression() to LicenseCompatibility( + ALLOW, + "ISSUE-2: Apache Category A licenses are ok" + ), + (SpdxLicense.Apache_1_0.orLater() and SpdxLicense.GPL_1_0_or_later) to + LicenseCompatibility( + REJECT, + "If both Apache1+ and GPL1+, then we are fine" + ), + SpdxLicense.LGPL_3_0_or_later.asExpression() to + LicenseCompatibility( + UNKNOWN, + "See ISSUE-23, the use of LGPL 3.0+ needs to be decided" + ), + SpdxLicense.LGPL_2_0_only or SpdxLicense.LGPL_2_1_only to + LicenseCompatibility( + REJECT, + "See ISSUE-21, LGPL less than 3.0 can't be used for sure" + ) + ) + ) + + @JvmStatic + fun data() = listOf( + arguments( + SpdxLicense.MIT.asExpression(), + ResolvedLicenseCompatibility(ALLOW, "MIT: ALLOW") + ), + arguments( + SpdxLicense.MIT or SpdxLicense.CC0_1_0, + ResolvedLicenseCompatibility( + ALLOW, + "MIT: ALLOW", + "CC0-1.0: Public domain is OK" + ) + ), + arguments( + SpdxLicense.GFDL_1_1_only or SpdxLicense.MIT, + ResolvedLicenseCompatibility( + ALLOW, + "MIT: ALLOW" + ) + ), + arguments( + SpdxLicense.GFDL_1_1_only and SpdxLicense.MIT, + ResolvedLicenseCompatibility(UNKNOWN, "No rules found for GFDL-1.1-only") + ), + arguments( + SpdxLicense.Apache_1_0.asExpression(), + ResolvedLicenseCompatibility(UNKNOWN, "No rules found for Apache-1.0") + ), + arguments( + SpdxLicense.Apache_1_0.orLater(), + ResolvedLicenseCompatibility( + ALLOW, + "Apache-2.0: ISSUE-2: Apache Category A licenses are ok" + ) + ), + arguments( + SpdxLicense.Apache_2_0 with SpdxLicenseException.Classpath_exception_2_0, + ResolvedLicenseCompatibility( + UNKNOWN, + "No rules found for Apache-2.0 WITH Classpath-exception-2.0" + ) + ), + arguments( + (SpdxLicense.Apache_2_0 with SpdxLicenseException.Classpath_exception_2_0) or + (SpdxLicense.MIT with SpdxLicenseException.LLVM_exception), + ResolvedLicenseCompatibility( + UNKNOWN, + "No rules found for Apache-2.0 WITH Classpath-exception-2.0", + "No rules found for MIT WITH LLVM-exception" + ) + ), + arguments( + SpdxLicense.LGPL_3_0_only or SpdxLicense.LGPL_2_0_only, + ResolvedLicenseCompatibility( + UNKNOWN, + "UNKNOWN: LGPL-3.0-only: See ISSUE-23, the use of LGPL 3.0+ needs to be decided", + "REJECT: LGPL-2.0-only: See ISSUE-21, LGPL less than 3.0 can't be used for sure" + ) + ), + arguments( + SpdxLicense.LGPL_3_0_only and SpdxLicense.LGPL_2_0_only, + ResolvedLicenseCompatibility( + REJECT, + "UNKNOWN: LGPL-3.0-only: See ISSUE-23, the use of LGPL 3.0+ needs to be decided", + "REJECT: LGPL-2.0-only: See ISSUE-21, LGPL less than 3.0 can't be used for sure" + ) + ) + ) + } + + @ParameterizedTest + @MethodSource("data") + internal fun test(input: LicenseExpression?, expected: ResolvedLicenseCompatibility) { + assertEquals(expected, interpreter.eval(input)) { + "input: $input evaluated with $interpreter" + } + } +} diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt new file mode 100644 index 0000000..1565256 --- /dev/null +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt @@ -0,0 +1,219 @@ +/* + * Copyright 2019 Vladimir Sitnikov + * + * 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 + * + * http://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. + * + */ + +package com.github.vlsi.gradle.license + +import org.gradle.api.JavaVersion +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.nio.file.Path + +class VerifyLicenseCompatibilityTaskTest { + companion object { + @JvmStatic + private fun gradleVersionAndSettings(): Iterable { + return mutableListOf().apply { + if (JavaVersion.current() <= JavaVersion.VERSION_14) { + add(Arguments.of("6.0", "// no extra settings")) + add(Arguments.of("6.1.1", "// no extra settings")) + } + add(Arguments.of("7.3", "// no extra settings")) + } + } + } + + private val gradleRunner = GradleRunner.create().withPluginClasspath() + + @TempDir + lateinit var projectDir: Path + + fun Path.write(text: String) = this.toFile().writeText(text) + + @ParameterizedTest + @MethodSource("gradleVersionAndSettings") + fun `licenseGathering works`(gradleVersion: String, extraSettings: String) { + projectDir.resolve("settings.gradle").write( + """ + rootProject.name = 'sample' + $extraSettings + """ + ) + projectDir.resolve("build.gradle").write( + /* language=groovy */ """ + import com.github.vlsi.gradle.license.GatherLicenseTask + import com.github.vlsi.gradle.license.VerifyLicenseCompatibilityTask + import com.github.vlsi.gradle.release.AsfLicenseCategory + import com.github.vlsi.gradle.license.api.LicenseExpressionExtensions + import com.github.vlsi.gradle.license.api.SpdxLicense + import com.github.vlsi.gradle.license.api.SimpleLicense + + plugins { + id('java') + id('com.github.vlsi.license-gather') + } + group = 'org.example' + version = '0.0.1' + + repositories { + mavenCentral() + jcenter() + } + + dependencies { + runtimeOnly("javax.inject:javax.inject:1") + runtimeOnly("org.slf4j:slf4j-api:1.7.25") + runtimeOnly("org.junit.jupiter:junit-jupiter:5.4.2") + runtimeOnly("org.jodd:jodd-core:5.0.6") + runtimeOnly("org.jetbrains.lets-plot:lets-plot-batik:2.1.0") + runtimeOnly("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2") + } + + def generateLicense = tasks.register("generateLicense", GatherLicenseTask.class) { + configurations.add(project.configurations.runtimeClasspath) + ignoreMissingLicenseFor(SpdxLicense.BSD_2_Clause) + ignoreMissingLicenseFor(SpdxLicense.MIT) + } + + tasks.register("verifyLicenses", VerifyLicenseCompatibilityTask.class) { + metadata.from(generateLicense) + allow(SpdxLicense.EPL_2_0) { + because("JUnit is OK") + } + allow(new SimpleLicense("The W3C License", uri("http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/java-binding.zip"))) { + because("ISSUE-42: John Smith decided the license is OK") + } + // No reason, for test purposes + allow(LicenseExpressionExtensions.orLater(SpdxLicense.SAX_PD)) + allow(AsfLicenseCategory.A) { + because("The ASF category A is allowed") + } + reject(AsfLicenseCategory.X) { + because("The ASF category X is forbidden") + } + } + """ + ) + + val result = + runGradleBuild(gradleVersion, "verifyLicenses", "--print", "--quiet", "--stacktrace") + Assertions.assertEquals( + """ + ALLOW + Apache-2.0: The ASF category A is allowed + SAX-PD: ALLOW + The W3C License (http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/java-binding.zip): ISSUE-42: John Smith decided the license is OK + ============================================================================================================================================ + + Apache-2.0 AND SAX-PD AND The W3C License (http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/java-binding.zip) + * xml-apis:xml-apis:1.4.01 + + ALLOW + Apache-2.0: The ASF category A is allowed + =========================================== + + Apache-2.0 + * commons-io:commons-io:1.3.1 + * commons-logging:commons-logging:1.0.4 + * io.github.microutils:kotlin-logging-jvm:2.0.5 + * javax.inject:javax.inject:1 + * org.apache.xmlgraphics:batik-anim:1.14 + * org.apache.xmlgraphics:batik-awt-util:1.14 + * org.apache.xmlgraphics:batik-bridge:1.14 + * org.apache.xmlgraphics:batik-codec:1.14 + * org.apache.xmlgraphics:batik-constants:1.14 + * org.apache.xmlgraphics:batik-css:1.14 + * org.apache.xmlgraphics:batik-dom:1.14 + * org.apache.xmlgraphics:batik-ext:1.14 + * org.apache.xmlgraphics:batik-gvt:1.14 + * org.apache.xmlgraphics:batik-i18n:1.14 + * org.apache.xmlgraphics:batik-parser:1.14 + * org.apache.xmlgraphics:batik-script:1.14 + * org.apache.xmlgraphics:batik-shared-resources:1.14 + * org.apache.xmlgraphics:batik-svg-dom:1.14 + * org.apache.xmlgraphics:batik-svggen:1.14 + * org.apache.xmlgraphics:batik-transcoder:1.14 + * org.apache.xmlgraphics:batik-util:1.14 + * org.apache.xmlgraphics:batik-xml:1.14 + * org.apache.xmlgraphics:xmlgraphics-commons:2.6 + * org.apiguardian:apiguardian-api:1.0.0 + * org.jetbrains.kotlin:kotlin-reflect:1.5.21 + * org.jetbrains.kotlin:kotlin-stdlib-common:1.5.21 + * org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.21 + * org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21 + * org.jetbrains.kotlin:kotlin-stdlib:1.5.21 + * org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3 + * org.jetbrains:annotations:13.0 + * org.opentest4j:opentest4j:1.1.1 + * xalan:serializer:2.7.2 + * xalan:xalan:2.7.2 + * xml-apis:xml-apis-ext:1.3.04 + + ALLOW + BSD-2-Clause: The ASF category A is allowed + ============================================= + + BSD-2-Clause + * org.jodd:jodd-core:5.0.6 + + ALLOW + EPL-2.0: JUnit is OK + ====================== + + EPL-2.0 + * org.junit.jupiter:junit-jupiter-api:5.4.2 + * org.junit.jupiter:junit-jupiter-engine:5.4.2 + * org.junit.jupiter:junit-jupiter-params:5.4.2 + * org.junit.jupiter:junit-jupiter:5.4.2 + * org.junit.platform:junit-platform-commons:1.4.2 + * org.junit.platform:junit-platform-engine:1.4.2 + + ALLOW + MIT: The ASF category A is allowed + ==================================== + + MIT + * org.jetbrains.lets-plot:base-portable-jvm:2.1.0 + * org.jetbrains.lets-plot:lets-plot-batik:2.1.0 + * org.jetbrains.lets-plot:lets-plot-common:2.1.0 + * org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2 + * org.jetbrains.lets-plot:plot-base-portable-jvm:2.1.0 + * org.jetbrains.lets-plot:plot-builder-portable-jvm:2.1.0 + * org.jetbrains.lets-plot:plot-common-portable-jvm:2.1.0 + * org.jetbrains.lets-plot:plot-config-portable-jvm:2.1.0 + * org.jetbrains.lets-plot:vis-svg-portable-jvm:2.1.0 + * org.slf4j:slf4j-api:1.7.29 + """.trimIndent().normalizeEol() + "\n", + result.output.normalizeEol().replace("texts\\", "texts/") + ) + } + + private fun String.normalizeEol() = replace(Regex("\r\n?"), "\n") + + private fun runGradleBuild(gradleVersion: String, vararg arguments: String): BuildResult { + return gradleRunner + .withGradleVersion(gradleVersion) + .withProjectDir(projectDir.toFile()) + .withArguments(*arguments) + .forwardOutput() + .build() + } +} From 95940322d2a1a12d02142fb4fa2661e905be986a Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sat, 12 Feb 2022 21:25:58 +0300 Subject: [PATCH 2/4] Move LicenseExpressionExtensions to instance functions for better API from Groovy and Java --- .../vlsi/gradle/license/GatherLicenseTask.kt | 17 +- .../gradle/license/GuessBasedNormalizer.kt | 7 +- .../LicenseCompatibilityInterpreter.kt | 3 +- .../vlsi/gradle/license/MetadataStore.kt | 5 +- .../vlsi/gradle/license/PomLicenseLoader.kt | 5 +- .../github/vlsi/gradle/license/api/License.kt | 17 +- .../gradle/license/api/LicenseExpression.kt | 27 ++++ .../api/LicenseExpressionExtensions.kt | 151 ++++++++++++------ .../api/LicenseExpressionNormalizer.kt | 4 +- .../license/api/LicenseExpressionParser.kt | 6 +- .../license/api/SpdxLicenseEquivalence.kt | 10 +- .../vlsi/gradle/release/AsfLicenseCategory.kt | 14 +- .../vlsi/gradle/license/EquivalenceTest.kt | 24 ++- .../license/GuessBasedNormalizerTest.kt | 3 +- .../LicenseCompatibilityInterpreterTest.kt | 21 +-- .../vlsi/gradle/license/MetadataStoreTest.kt | 8 +- .../license/OsgiBundleLicenseParserTest.kt | 5 +- .../VerifyLicenseCompatibilityTaskTest.kt | 7 +- .../vlsi/gradle/release/AsfLicenseGroup.kt | 5 +- .../release/Apache2LicenseInterpreterTest.kt | 3 +- 20 files changed, 204 insertions(+), 138 deletions(-) diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt index a077851..0c2baed 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt @@ -24,7 +24,6 @@ import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.LicenseExpressionParser import com.github.vlsi.gradle.license.api.OsgiBundleLicenseParser import com.github.vlsi.gradle.license.api.SpdxLicense -import com.github.vlsi.gradle.license.api.asExpression import com.github.vlsi.gradle.license.api.text import groovy.util.slurpersupport.GPathResult import org.gradle.api.Action @@ -137,8 +136,8 @@ open class GatherLicenseTask @Inject constructor( objectFactory.setProperty() .convention( listOf( - SpdxLicense.Apache_2_0.asExpression(), - SpdxLicense.MPL_2_0.asExpression() + SpdxLicense.Apache_2_0.expression, + SpdxLicense.MPL_2_0.expression ) ) @@ -152,7 +151,7 @@ open class GatherLicenseTask @Inject constructor( objectFactory.setProperty() fun ignoreMissingLicenseFor(license: License) { - ignoreMissingLicenseFor(license.asExpression()) + ignoreMissingLicenseFor(license.expression) } fun ignoreMissingLicenseFor(license: LicenseExpression) { @@ -186,7 +185,7 @@ open class GatherLicenseTask @Inject constructor( private val licenseExpressionParser = LicenseExpressionParser() fun addDependency(module: String, license: License) { - addDependency(module, license.asExpression()) + addDependency(module, license.expression) } fun addDependency(module: String, licenseExpression: LicenseExpression) { @@ -202,7 +201,7 @@ open class GatherLicenseTask @Inject constructor( } fun expectLicense(module: String, license: License) { - expectLicense(module, license.asExpression()) + expectLicense(module, license.expression) } fun expectLicense(module: String, licenseExpression: LicenseExpression) { @@ -212,7 +211,7 @@ open class GatherLicenseTask @Inject constructor( } fun overrideLicense(module: String, license: License) { - overrideLicense(module, license.asExpression()) + overrideLicense(module, license.expression) } fun overrideLicense(module: String, licenseExpression: LicenseExpression) { @@ -224,7 +223,7 @@ open class GatherLicenseTask @Inject constructor( private fun Any.toLicenseExpression() = when (this) { is String -> licenseExpressionParser.parse(this) - is License -> this.asExpression() + is License -> this.expression is LicenseExpression -> this else -> throw GradleException("Illegal value $this for LicenseExpression. Expecting String, License, or LicenseExpression") } @@ -489,7 +488,7 @@ open class GatherLicenseTask @Inject constructor( licenseExpressionParser: LicenseExpressionParser ) { val bundleLicenseParser = OsgiBundleLicenseParser(licenseExpressionParser) { - SpdxLicense.fromUriOrNull(it)?.asExpression() + SpdxLicense.fromUriOrNull(it)?.expression } for (e in detectedLicenses) { if (e.value.license != null) { diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizer.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizer.kt index c7ed5f5..17c1027 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizer.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizer.kt @@ -23,7 +23,6 @@ import com.github.vlsi.gradle.license.api.SimpleLicense import com.github.vlsi.gradle.license.api.SimpleLicenseExpression import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.WithException -import com.github.vlsi.gradle.license.api.asExpression import org.slf4j.Logger import java.net.URI @@ -33,7 +32,7 @@ class GuessBasedNormalizer( ) : LicenseExpressionNormalizer() { private val nameGuesser = TfIdfBuilder().apply { - SpdxLicense.values().forEach { addDocument(it.asExpression(), it.title) } + SpdxLicense.values().forEach { addDocument(it.expression, it.title) } }.build() private fun String.trimTextExtensions() = removeSuffix(".txt").removeSuffix(".md") @@ -52,9 +51,9 @@ class GuessBasedNormalizer( override fun normalize(license: SimpleLicense): LicenseExpression? { if (license.title.equals("PUBLIC DOMAIN", ignoreCase = true)) { - return SpdxLicense.CC0_1_0.asExpression() + return SpdxLicense.CC0_1_0.expression } - SpdxLicense.fromIdOrNull(license.title)?.let { return it.asExpression() } + SpdxLicense.fromIdOrNull(license.title)?.let { return it.expression } val guessList = nameGuesser.predict(license.title) .entries diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt index c1c8086..df36852 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreter.kt @@ -24,7 +24,6 @@ import com.github.vlsi.gradle.license.api.ConjunctionLicenseExpression import com.github.vlsi.gradle.license.api.DisjunctionLicenseExpression import com.github.vlsi.gradle.license.api.LicenseEquivalence import com.github.vlsi.gradle.license.api.LicenseExpression -import com.github.vlsi.gradle.license.api.disjunctions enum class CompatibilityResult { ALLOW, UNKNOWN, REJECT; @@ -54,7 +53,7 @@ internal class LicenseCompatibilityInterpreter( private val resolvedCases: Map ) { val resolvedParts = resolvedCases.asSequence().flatMap { (license, _) -> - licenseEquivalence.expand(license).disjunctions().asSequence().map { it to license } + licenseEquivalence.expand(license).disjunctions.asSequence().map { it to license } }.groupingBy { it.first }.aggregate { key, acc: LicenseExpression?, element, first -> if (first) { element.second diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt index 8ab6d52..41b32f7 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/MetadataStore.kt @@ -34,7 +34,6 @@ import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.StandardLicense import com.github.vlsi.gradle.license.api.StandardLicenseException import com.github.vlsi.gradle.license.api.WithException -import com.github.vlsi.gradle.license.api.asExpression import com.github.vlsi.gradle.license.api.orLater import groovy.util.XmlSlurper import groovy.util.slurpersupport.GPathResult @@ -86,8 +85,8 @@ object MetadataStore { fun GPathResult.readLicenseExpression(): LicenseExpression = when (name()) { - "license" -> toLicense().asExpression() - "or-later" -> toLicense().orLater() + "license" -> toLicense().expression + "or-later" -> toLicense().orLater "expression" -> expressionParser.parse(text()) "and" -> ConjunctionLicenseExpression( getList("*") diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/PomLicenseLoader.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/PomLicenseLoader.kt index 800c9cd..bd73fff 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/PomLicenseLoader.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/PomLicenseLoader.kt @@ -22,7 +22,6 @@ import com.github.vlsi.gradle.license.api.License import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.LicenseExpressionNormalizer import com.github.vlsi.gradle.license.api.SimpleLicense -import com.github.vlsi.gradle.license.api.asExpression import groovy.util.XmlSlurper import groovy.util.slurpersupport.GPathResult import kotlinx.coroutines.runBlocking @@ -76,12 +75,12 @@ class LicenseDetector( } else { val parsedRawLicense = if (licenses.size == 1) { - licenses.first().asExpression() + licenses.first().expression } else { // When more than one license is present, assume AND was intended // It allows less freedom, however it seems to be a safe choice. ConjunctionLicenseExpression( - licenses.mapTo(mutableSetOf()) { it.asExpression() } + licenses.mapTo(mutableSetOf()) { it.expression } ) } normalizer.normalize(parsedRawLicense) diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt index 2f7d5e3..4b9057d 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/License.kt @@ -24,10 +24,25 @@ interface License : LicenseExpressionSet, java.io.Serializable { val uri: List override val disjunctions: Set - get() = setOf(asExpression()) + get() = setOf(expression) override val conjunctions: Set get() = disjunctions + + val expression: JustLicense + get() = JustLicense(this) + + val orLater: OrLaterLicense + get() = OrLaterLicense(this) + + infix fun with(exception: LicenseException): LicenseExpression = + expression with exception + + infix fun and(other: License): LicenseExpression = expression and other + infix fun and(other: LicenseExpression): LicenseExpression = expression and other + + infix fun or(other: License): LicenseExpression = expression or other + infix fun or(other: LicenseExpression): LicenseExpression = expression or other } interface LicenseException : java.io.Serializable { diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt index 4d6ca10..c4a455b 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpression.kt @@ -33,6 +33,27 @@ sealed class LicenseExpression : LicenseExpressionSet, java.io.Serializable { override val disjunctions: Set get() = setOf(this) + infix fun and(other: License): LicenseExpression = this and other.expression + infix fun or(other: License): LicenseExpression = this or other.expression + + infix fun and(other: LicenseExpression): LicenseExpression { + val ops = conjunctions + other.conjunctions + return when (ops.size) { + 0 -> throw IllegalArgumentException("Empty argument to ConjunctionLicenseExpression") + 1 -> ops.first() + else -> ConjunctionLicenseExpression(ops) + } + } + + infix fun or(other: LicenseExpression): LicenseExpression { + val ops = disjunctions + other.disjunctions + return when (ops.size) { + 0 -> throw IllegalArgumentException("Empty argument to DisjunctionLicenseExpression") + 1 -> ops.first() + else -> DisjunctionLicenseExpression(ops) + } + } + object NONE : LicenseExpression() object NOASSERTION : LicenseExpression() } @@ -50,10 +71,16 @@ abstract class SimpleLicenseExpression(open val license: License) : LicenseExpre else -> it.title + it.uri.asString() } } + + infix fun with(exception: LicenseException): WithException = + WithException(this, exception) } data class JustLicense(override val license: License) : SimpleLicenseExpression(license) { override fun toString() = super.toString() + + val orLater: OrLaterLicense + get() = OrLaterLicense(license) } data class OrLaterLicense(override val license: License) : SimpleLicenseExpression(license) { diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt index 90387fb..2336561 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionExtensions.kt @@ -19,23 +19,92 @@ package com.github.vlsi.gradle.license.api -fun License.asExpression(): JustLicense = JustLicense(this) -fun License.orLater(): SimpleLicenseExpression = OrLaterLicense(this) -fun JustLicense.orLater(): LicenseExpression = OrLaterLicense(license) +@Deprecated( + "Use .expression", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("expression") +) +fun License.asExpression(): JustLicense = expression + +@Deprecated( + "Use .orLater", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("orLater") +) +fun License.orLater(): SimpleLicenseExpression = orLater +@Deprecated( + "Use .orLater", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("orLater") +) +fun JustLicense.orLater(): LicenseExpression = orLater + +@Deprecated( + "Use .with(LicenseException)", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this with exception") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") infix fun License.with(exception: LicenseException): LicenseExpression = - asExpression() with exception + expression with exception +@Deprecated( + "Use .with(LicenseException)", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this with exception") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") infix fun SimpleLicenseExpression.with(exception: LicenseException): LicenseExpression = - WithException(this, exception) + this with exception + +@Deprecated( + "Use .and", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this and other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun LicenseExpression.and(other: License): LicenseExpression = this and other + +@Deprecated( + "Use .and", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this and other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun License.and(other: License): LicenseExpression = this and other -infix fun LicenseExpression.and(other: License): LicenseExpression = this and other.asExpression() -infix fun License.and(other: License): LicenseExpression = asExpression() and other -infix fun License.and(other: LicenseExpression): LicenseExpression = other and this +@Deprecated( + "Use .and", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this and other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun License.and(other: LicenseExpression): LicenseExpression = this and other + +@Deprecated( + "Use .or", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this or other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun LicenseExpression.or(other: License): LicenseExpression = this or other + +@Deprecated( + "Use .or", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this or other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun License.or(other: License): LicenseExpression = this or other -infix fun LicenseExpression.or(other: License): LicenseExpression = this or other.asExpression() -infix fun License.or(other: License): LicenseExpression = asExpression() or other -infix fun License.or(other: LicenseExpression): LicenseExpression = other or this +@Deprecated( + "Use .or", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this or other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun License.or(other: LicenseExpression): LicenseExpression = this or other @Deprecated( "Use member function LicenseExpression.disjunctions()", @@ -43,11 +112,7 @@ infix fun License.or(other: LicenseExpression): LicenseExpression = other or thi level = DeprecationLevel.WARNING ) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") -fun LicenseExpression.disjunctions() = - when (this) { - is DisjunctionLicenseExpression -> unordered - else -> setOf(this) - } +fun LicenseExpression.disjunctions() = disjunctions @Deprecated( "Use member function LicenseExpression.conjunctions()", @@ -55,38 +120,22 @@ fun LicenseExpression.disjunctions() = level = DeprecationLevel.WARNING ) @Suppress("EXTENSION_SHADOWED_BY_MEMBER") -fun LicenseExpression.conjunctions() = - when (this) { - is ConjunctionLicenseExpression -> unordered - else -> setOf(this) - } - -infix fun LicenseExpression.and(other: LicenseExpression): LicenseExpression { - fun LicenseExpression.ops() = - when (this) { - is ConjunctionLicenseExpression -> licenses - else -> setOf(this) - } - - val ops = ops() + other.ops() - return when (ops.size) { - 0 -> throw IllegalArgumentException("Empty argument to ConjunctionLicenseExpression") - 1 -> ops.first() - else -> ConjunctionLicenseExpression(ops) - } -} - -infix fun LicenseExpression.or(other: LicenseExpression): LicenseExpression { - fun LicenseExpression.ops() = - when (this) { - is DisjunctionLicenseExpression -> licenses - else -> setOf(this) - } - - val ops = ops() + other.ops() - return when (ops.size) { - 0 -> throw IllegalArgumentException("Empty argument to DisjunctionLicenseExpression") - 1 -> ops.first() - else -> DisjunctionLicenseExpression(ops) - } -} +fun LicenseExpression.conjunctions() = conjunctions + +@Deprecated( + "Use .and", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this and other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun LicenseExpression.and(other: LicenseExpression): LicenseExpression = + this and other + +@Deprecated( + "Use .or", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this or other") +) +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +infix fun LicenseExpression.or(other: LicenseExpression): LicenseExpression = + this or other diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionNormalizer.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionNormalizer.kt index 06e626e..0be3b3e 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionNormalizer.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionNormalizer.kt @@ -51,8 +51,8 @@ abstract class LicenseExpressionNormalizer { fun normalize(expression: LicenseExpression): LicenseExpression = when (expression) { - is JustLicense -> normalizeLicense(expression, expression.license) { asExpression() } - is OrLaterLicense -> normalizeLicense(expression, expression.license) { orLater() } + is JustLicense -> normalizeLicense(expression, expression.license) { this.expression } + is OrLaterLicense -> normalizeLicense(expression, expression.license) { orLater } is WithException -> { val license = normalize(expression.license) as SimpleLicenseExpression val exception = normalize(expression.exception) diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionParser.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionParser.kt index 3e43c6a..62e1368 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionParser.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/LicenseExpressionParser.kt @@ -111,8 +111,8 @@ class LicenseExpressionParser(private val titleParser: LicenseParser = DefaultLi t.value == "NOASSERION" -> result.push(LicenseExpression.NOASSERTION) rpn.peekFirst()?.type != TokenType.WITH -> result.push( titleParser.parseLicense( - t.value - ).asExpression() + t.value + ).expression ) else -> { val withToken = rpn.pop() @@ -165,7 +165,7 @@ class LicenseExpressionParser(private val titleParser: LicenseParser = DefaultLi value ) } - result.push(license.orLater()) + result.push(license.orLater) } TokenType.LBRACE -> throw ParseException("Unclosed open brace", t.position, value) TokenType.RBRACE -> throw ParseException("Extra closing brace", t.position, value) diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/SpdxLicenseEquivalence.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/SpdxLicenseEquivalence.kt index 7cdc6b5..d288d98 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/SpdxLicenseEquivalence.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/api/SpdxLicenseEquivalence.kt @@ -23,7 +23,7 @@ object SpdxLicenseEquivalence { val map: Map> = licenseVersions(AFL_1_1, AFL_1_2, AFL_2_0, AFL_2_1, AFL_3_0) .plus(SpdxLicense.values() - .map { it.orLater() to setOf(it.asExpression()) }) + .map { it.orLater to setOf(it.expression) }) .plus(licenseVersions(AFL_1_1, AFL_1_2, AFL_2_0, AFL_2_1, AFL_3_0)) .plusOrLater(AGPL_1_0_or_later) .plusOrLater(AGPL_3_0_or_later) @@ -153,18 +153,18 @@ private fun Sequence>>.plusLicens license: License, equivalence: LicenseExpression ) = - plusElement(license.asExpression() to setOf(equivalence)) + plusElement(license.expression to setOf(equivalence)) private fun Sequence>>.plusOrLater(license: SpdxLicense) = - plusElement(license.asExpression() to setOf(valueOf(license.name.removeSuffix("_or_later") + "_only").orLater())) + plusElement(license.expression to setOf(valueOf(license.name.removeSuffix("_or_later") + "_only").orLater)) fun licenseVersions(vararg licenses: License) = licenses .asSequence() .windowed(size = 2, partialWindows = true) { w -> if (w.size == 2) { - w[0].orLater() to setOf(w[0].asExpression(), w[1].orLater()) + w[0].orLater to setOf(w[0].expression, w[1].orLater) } else { // The latest version - w[0].orLater() to setOf(w[0].asExpression()) + w[0].orLater to setOf(w[0].expression) } } diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt index 057c5c6..3ef18f7 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseCategory.kt @@ -25,8 +25,6 @@ import com.github.vlsi.gradle.license.api.LicenseExpressionSet import com.github.vlsi.gradle.license.api.LicenseExpressionSetOperation import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException -import com.github.vlsi.gradle.license.api.asExpression -import com.github.vlsi.gradle.license.api.disjunctions import com.github.vlsi.gradle.license.api.orLater import com.github.vlsi.gradle.license.api.with @@ -49,7 +47,7 @@ enum class AsfLicenseCategory : LicenseExpressionSet { companion object { @JvmStatic - fun of(license: License) = of(license.asExpression()) + fun of(license: License) = of(license.expression) @JvmStatic fun of(expr: LicenseExpression) = @@ -115,11 +113,11 @@ enum class AsfLicenseCategory : LicenseExpressionSet { // Open Grid Forum License // IP Rights Grant ).asSequence() - .map { it.asExpression() } + .map { it.expression } .plus( equivalence.expand( SpdxLicense.GPL_1_0_or_later with SpdxLicenseException.Classpath_exception_2_0 - ).disjunctions() + ).disjunctions ) .toSet() @@ -152,7 +150,7 @@ enum class AsfLicenseCategory : LicenseExpressionSet { SpdxLicense.IPA, SpdxLicense.Ruby, SpdxLicense.EPL_2_0 - ).mapTo(mutableSetOf()) { it.asExpression() } + ).mapTo(mutableSetOf()) { it.expression } // The following licenses may NOT be included within Apache products private val xLicenses : Set = listOf( @@ -180,7 +178,7 @@ enum class AsfLicenseCategory : LicenseExpressionSet { // The "Don't Be A Dick" Public License SpdxLicense.JSON ).asSequence() - .map { it.asExpression() } + .map { it.expression } .plus(listOf( // Creative Commons Non-Commercial variants SpdxLicense.CC_BY_NC_1_0, @@ -191,7 +189,7 @@ enum class AsfLicenseCategory : LicenseExpressionSet { SpdxLicense.AGPL_3_0_only, SpdxLicense.LGPL_2_0_only ) - .flatMap { equivalence.expand(it.orLater()).disjunctions() }) + .flatMap { equivalence.expand(it.orLater).disjunctions }) .toSet() } } diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/EquivalenceTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/EquivalenceTest.kt index e4fe041..70b8683 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/EquivalenceTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/EquivalenceTest.kt @@ -21,10 +21,6 @@ import com.github.vlsi.gradle.license.api.LicenseEquivalence import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException -import com.github.vlsi.gradle.license.api.asExpression -import com.github.vlsi.gradle.license.api.or -import com.github.vlsi.gradle.license.api.orLater -import com.github.vlsi.gradle.license.api.with import org.junit.jupiter.api.Assertions import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments @@ -37,33 +33,33 @@ class EquivalenceTest { private fun expressions(): Iterable { return listOf( Arguments.of( - SpdxLicense.GPL_1_0_or_later.asExpression(), + SpdxLicense.GPL_1_0_or_later.expression, SpdxLicense.GPL_1_0_only or SpdxLicense.GPL_2_0_only or SpdxLicense.GPL_3_0_only ), Arguments.of( - SpdxLicense.GPL_1_0_or_later.orLater(), + SpdxLicense.GPL_1_0_or_later.orLater, SpdxLicense.GPL_1_0_only or SpdxLicense.GPL_2_0_only or SpdxLicense.GPL_3_0_only ), Arguments.of( - SpdxLicense.GPL_2_0_only.orLater(), + SpdxLicense.GPL_2_0_only.orLater, SpdxLicense.GPL_2_0_only or SpdxLicense.GPL_3_0_only ), Arguments.of( - SpdxLicense.GPL_2_0_only.asExpression(), - SpdxLicense.GPL_2_0_only.asExpression() + SpdxLicense.GPL_2_0_only.expression, + SpdxLicense.GPL_2_0_only.expression ), Arguments.of( - SpdxLicense.GPL_2_0_or_later.orLater() with SpdxLicenseException.Classpath_exception_2_0, + SpdxLicense.GPL_2_0_or_later.orLater with SpdxLicenseException.Classpath_exception_2_0, (SpdxLicense.GPL_2_0_only with SpdxLicenseException.Classpath_exception_2_0) or (SpdxLicense.GPL_3_0_only with SpdxLicenseException.Classpath_exception_2_0) ), Arguments.of( - SpdxLicense.MIT.asExpression(), - SpdxLicense.MIT.asExpression() + SpdxLicense.MIT.expression, + SpdxLicense.MIT.expression ), Arguments.of( - SpdxLicense.MIT.orLater(), - SpdxLicense.MIT.asExpression() + SpdxLicense.MIT.orLater, + SpdxLicense.MIT.expression ) ) } diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizerTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizerTest.kt index cb797e0..f67a0ea 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizerTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/GuessBasedNormalizerTest.kt @@ -20,7 +20,6 @@ package com.github.vlsi.gradle.license import com.github.vlsi.gradle.license.api.SimpleLicense import com.github.vlsi.gradle.license.api.SpdxLicense -import com.github.vlsi.gradle.license.api.asExpression import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory @@ -34,7 +33,7 @@ class GuessBasedNormalizerTest { val n = GuessBasedNormalizer(logger) for (v in SpdxLicense.values()) { val normalized = n.normalize(SimpleLicense(v.title)) - Assertions.assertEquals(v.asExpression(), normalized) { "normalize(${v.title})" } + Assertions.assertEquals(v.expression, normalized) { "normalize(${v.title})" } } } diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt index 6f55f93..b4243d1 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/LicenseCompatibilityInterpreterTest.kt @@ -24,11 +24,6 @@ import com.github.vlsi.gradle.license.api.LicenseEquivalence import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException -import com.github.vlsi.gradle.license.api.and -import com.github.vlsi.gradle.license.api.asExpression -import com.github.vlsi.gradle.license.api.or -import com.github.vlsi.gradle.license.api.orLater -import com.github.vlsi.gradle.license.api.with import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments.arguments @@ -39,21 +34,21 @@ class LicenseCompatibilityInterpreterTest { private val interpreter = LicenseCompatibilityInterpreter( LicenseEquivalence(), mapOf( - SpdxLicense.CC0_1_0.asExpression() to LicenseCompatibility( + SpdxLicense.CC0_1_0.expression to LicenseCompatibility( ALLOW, "Public domain is OK" ), - SpdxLicense.MIT.asExpression() to LicenseCompatibility(ALLOW, ""), - SpdxLicense.Apache_2_0.asExpression() to LicenseCompatibility( + SpdxLicense.MIT.expression to LicenseCompatibility(ALLOW, ""), + SpdxLicense.Apache_2_0.expression to LicenseCompatibility( ALLOW, "ISSUE-2: Apache Category A licenses are ok" ), - (SpdxLicense.Apache_1_0.orLater() and SpdxLicense.GPL_1_0_or_later) to + (SpdxLicense.Apache_1_0.orLater and SpdxLicense.GPL_1_0_or_later) to LicenseCompatibility( REJECT, "If both Apache1+ and GPL1+, then we are fine" ), - SpdxLicense.LGPL_3_0_or_later.asExpression() to + SpdxLicense.LGPL_3_0_or_later.expression to LicenseCompatibility( UNKNOWN, "See ISSUE-23, the use of LGPL 3.0+ needs to be decided" @@ -69,7 +64,7 @@ class LicenseCompatibilityInterpreterTest { @JvmStatic fun data() = listOf( arguments( - SpdxLicense.MIT.asExpression(), + SpdxLicense.MIT.expression, ResolvedLicenseCompatibility(ALLOW, "MIT: ALLOW") ), arguments( @@ -92,11 +87,11 @@ class LicenseCompatibilityInterpreterTest { ResolvedLicenseCompatibility(UNKNOWN, "No rules found for GFDL-1.1-only") ), arguments( - SpdxLicense.Apache_1_0.asExpression(), + SpdxLicense.Apache_1_0.expression, ResolvedLicenseCompatibility(UNKNOWN, "No rules found for Apache-1.0") ), arguments( - SpdxLicense.Apache_1_0.orLater(), + SpdxLicense.Apache_1_0.orLater, ResolvedLicenseCompatibility( ALLOW, "Apache-2.0: ISSUE-2: Apache Category A licenses are ok" diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/MetadataStoreTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/MetadataStoreTest.kt index a9fd216..1af70b6 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/MetadataStoreTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/MetadataStoreTest.kt @@ -22,10 +22,6 @@ import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.SimpleLicense import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException -import com.github.vlsi.gradle.license.api.and -import com.github.vlsi.gradle.license.api.or -import com.github.vlsi.gradle.license.api.orLater -import com.github.vlsi.gradle.license.api.with import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import java.io.File @@ -36,7 +32,7 @@ class MetadataStoreTest { fun spdxExpression() { saveMetadata( (SpdxLicense.Apache_2_0 or SpdxLicense.MIT) and - (SpdxLicense.GPL_2_0_only.orLater() with SpdxLicenseException.Classpath_exception_2_0), + (SpdxLicense.GPL_2_0_only.orLater with SpdxLicenseException.Classpath_exception_2_0), """ @@ -55,7 +51,7 @@ class MetadataStoreTest { fun nonStandardLicense() { saveMetadata( (SpdxLicense.Apache_2_0 or SimpleLicense("WTFYWPL")) and - (SpdxLicense.GPL_2_0_only.orLater() with SpdxLicenseException.Classpath_exception_2_0), + (SpdxLicense.GPL_2_0_only.orLater with SpdxLicenseException.Classpath_exception_2_0), """ diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/OsgiBundleLicenseParserTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/OsgiBundleLicenseParserTest.kt index 861e0f1..4aacd2d 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/OsgiBundleLicenseParserTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/OsgiBundleLicenseParserTest.kt @@ -20,7 +20,6 @@ package com.github.vlsi.gradle.license import com.github.vlsi.gradle.license.api.LicenseExpressionParser import com.github.vlsi.gradle.license.api.OsgiBundleLicenseParser import com.github.vlsi.gradle.license.api.SpdxLicense -import com.github.vlsi.gradle.license.api.asExpression import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.params.ParameterizedTest @@ -38,7 +37,7 @@ class OsgiBundleLicenseParserTest { ) fun success(comment: String, expected: String, input: String) { val parser = OsgiBundleLicenseParser(LicenseExpressionParser()) { - SpdxLicense.fromUriOrNull(it)?.asExpression() + SpdxLicense.fromUriOrNull(it)?.expression } assertEquals(expected, parser.parseOrNull(input, "test input").toString()) { "$comment, input: $input" @@ -55,7 +54,7 @@ class OsgiBundleLicenseParserTest { ) fun fail(comment: String, input: String) { val parser = OsgiBundleLicenseParser(LicenseExpressionParser()) { - SpdxLicense.fromUriOrNull(it)?.asExpression() + SpdxLicense.fromUriOrNull(it)?.expression } assertNull(parser.parseOrNull(input, "test input")) { "$comment should cause OsgiBundleLicenseParser.parse failure, input: $input" diff --git a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt index 1565256..999f23f 100644 --- a/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt +++ b/plugins/license-gather-plugin/src/test/kotlin/com/github/vlsi/gradle/license/VerifyLicenseCompatibilityTaskTest.kt @@ -62,7 +62,6 @@ class VerifyLicenseCompatibilityTaskTest { import com.github.vlsi.gradle.license.GatherLicenseTask import com.github.vlsi.gradle.license.VerifyLicenseCompatibilityTask import com.github.vlsi.gradle.release.AsfLicenseCategory - import com.github.vlsi.gradle.license.api.LicenseExpressionExtensions import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SimpleLicense @@ -87,14 +86,14 @@ class VerifyLicenseCompatibilityTaskTest { runtimeOnly("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:3.0.2") } - def generateLicense = tasks.register("generateLicense", GatherLicenseTask.class) { + def gatherLicense = tasks.register("gatherLicense", GatherLicenseTask.class) { configurations.add(project.configurations.runtimeClasspath) ignoreMissingLicenseFor(SpdxLicense.BSD_2_Clause) ignoreMissingLicenseFor(SpdxLicense.MIT) } tasks.register("verifyLicenses", VerifyLicenseCompatibilityTask.class) { - metadata.from(generateLicense) + metadata.from(gatherLicense) allow(SpdxLicense.EPL_2_0) { because("JUnit is OK") } @@ -102,7 +101,7 @@ class VerifyLicenseCompatibilityTaskTest { because("ISSUE-42: John Smith decided the license is OK") } // No reason, for test purposes - allow(LicenseExpressionExtensions.orLater(SpdxLicense.SAX_PD)) + allow(SpdxLicense.SAX_PD.orLater) allow(AsfLicenseCategory.A) { because("The ASF category A is allowed") } diff --git a/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseGroup.kt b/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseGroup.kt index 1389135..fffd2b1 100644 --- a/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseGroup.kt +++ b/plugins/stage-vote-release-plugin/src/main/kotlin/com/github/vlsi/gradle/release/AsfLicenseGroup.kt @@ -18,7 +18,6 @@ package com.github.vlsi.gradle.release import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.SpdxLicense -import com.github.vlsi.gradle.license.api.asExpression import org.gradle.api.artifacts.component.ModuleComponentIdentifier internal enum class LicenseGroup { @@ -58,9 +57,9 @@ internal fun licenseGroupOf( license == null -> LicenseGroup.UNCLEAR id.looksLikeApache() -> when (license) { - SpdxLicense.Apache_2_0.asExpression() -> LicenseGroup.ASF_AL2 + SpdxLicense.Apache_2_0.expression -> LicenseGroup.ASF_AL2 else -> LicenseGroup.ASF_OTHER } - license == SpdxLicense.Apache_2_0.asExpression() -> LicenseGroup.AL2 + license == SpdxLicense.Apache_2_0.expression -> LicenseGroup.AL2 else -> LicenseGroup.OTHER } diff --git a/plugins/stage-vote-release-plugin/src/test/kotlin/com/github/vlsi/gradle/release/Apache2LicenseInterpreterTest.kt b/plugins/stage-vote-release-plugin/src/test/kotlin/com/github/vlsi/gradle/release/Apache2LicenseInterpreterTest.kt index 9412179..a6227ef 100644 --- a/plugins/stage-vote-release-plugin/src/test/kotlin/com/github/vlsi/gradle/release/Apache2LicenseInterpreterTest.kt +++ b/plugins/stage-vote-release-plugin/src/test/kotlin/com/github/vlsi/gradle/release/Apache2LicenseInterpreterTest.kt @@ -20,7 +20,6 @@ import com.github.vlsi.gradle.license.api.LicenseExpression import com.github.vlsi.gradle.license.api.SpdxLicense import com.github.vlsi.gradle.license.api.SpdxLicenseException import com.github.vlsi.gradle.license.api.and -import com.github.vlsi.gradle.license.api.asExpression import com.github.vlsi.gradle.license.api.or import com.github.vlsi.gradle.license.api.with import org.junit.jupiter.api.Assertions @@ -36,7 +35,7 @@ class Apache2LicenseInterpreterTest { return listOf( Arguments.of( AsfLicenseCategory.A, - SpdxLicense.MIT.asExpression() + SpdxLicense.MIT.expression ), Arguments.of( AsfLicenseCategory.A, From be02a4e0b5e89eb1ecace69541d5fdf7bceeeaf9 Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sun, 13 Feb 2022 11:48:09 +0300 Subject: [PATCH 3/4] Fix incremental execution of GatherLicenseTask: the task now is non-incremental in case overides are present Ideally the task should be incremental in all the cases, however, override tracking is not implemented yet. --- .../kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt | 2 +- .../kotlin/com/github/vlsi/gradle/license/LicenseOverrides.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt index 0c2baed..abded60 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/GatherLicenseTask.kt @@ -97,7 +97,7 @@ open class GatherLicenseTask @Inject constructor( ) : DefaultTask() { init { // TODO: capture [licenseOverrides] as input - outputs.upToDateWhen { false } + outputs.upToDateWhen { licenseOverrides.isEmpty() } } @InputFiles diff --git a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseOverrides.kt b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseOverrides.kt index 41b9fb8..f9f4cf8 100644 --- a/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseOverrides.kt +++ b/plugins/license-gather-plugin/src/main/kotlin/com/github/vlsi/gradle/license/LicenseOverrides.kt @@ -24,6 +24,8 @@ class LicenseOverrides { private val map = mutableMapOf() private val usedOverrides = mutableSetOf() + fun isEmpty(): Boolean = map.isEmpty() + val unusedOverrides: Set get() = map.keys.minus(usedOverrides) fun configurationComplete() { From a2de23e1897f4c512f05196844c8266e075985cf Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Sun, 13 Feb 2022 11:48:32 +0300 Subject: [PATCH 4/4] Update documentation: move license-gather and crlf doc to their own README files --- README.md | 108 +------------- plugins/checksum-dependency-plugin/README.md | 2 +- plugins/crlf-plugin/README.md | 47 ++++++ plugins/gradle-extensions-plugin/README.md | 2 +- plugins/jandex-plugin/README.md | 2 +- plugins/license-gather-plugin/README.md | 143 +++++++++++++++++++ plugins/stage-vote-release-plugin/README.md | 2 +- 7 files changed, 197 insertions(+), 109 deletions(-) create mode 100644 plugins/crlf-plugin/README.md create mode 100644 plugins/license-gather-plugin/README.md diff --git a/README.md b/README.md index 0dc58cf..30bf2ea 100644 --- a/README.md +++ b/README.md @@ -54,45 +54,8 @@ CRLF Plugin Adds Kotlin DSL to specify CRLF/LF filtering for `CopySpec`. Enables to use `.gitignore` and `.gitattributes` for building `CopySpec`. -Usage ------ +See [crlf-plugin description](plugins/crlf-plugin/README.md) for configuration options. -Kotlin DSL: - -```kotlin -// Loads .gitattributes and .gitignore from rootDir (and subdirs) -val gitProps by tasks.registering(FindGitAttributes::class) { - // Scanning for .gitignore and .gitattributes files in a task avoids doing that - // when distribution build is not required (e.g. code is just compiled) - root.set(rootDir) -} - -fun CrLfSpec.sourceLayout() = copySpec { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - gitattributes(gitProps) - into(baseFolder) { - // Note: license content is taken from "/build/..", so gitignore should not be used - // Note: this is a "license + third-party licenses", not just Apache-2.0 - dependencyLicenses(sourceLicense) - // Include all the source files - from(rootDir) { - gitignore(gitProps) - } - } -} - -for (archive in listOf(Zip::class, Tar::class)) { - tasks.register("dist${archive.simpleName}", archive) { - val eol = if (archive == Tar::class) LineEndings.LF else LineEndings.CRLF - if (this is Tar) { - compression = Compression.GZIP - } - CrLfSpec(eol).run { - with(sourceLayout()) - } - } -} -``` IDE Plugin ========== @@ -104,74 +67,9 @@ IDE Plugin License Gather Plugin ===================== -The purpose of the plugin is to analyze and infer license names for the dependencies. -The plugin checks for 3 places: MANIFEST.MF (`Bundle-License` attribute), -`pom.xml` (`licenses/license` tags), and finally `LICENSE`-like files. -Note: for now only fuzzy-match is implemented, and by default a similarity threshold of 42% is used. - -License Gather Plugin uses https://github.com/spdx/license-list-data for the list of licenses. - -Prior art ---------- - -https://github.com/jk1/Gradle-License-Report - -Gradle-License-Report is nice (it is), however there are certain pecularities (as of 2019-06-04) - -* It can't generate multiple lists within a single project (e.g. license for source / binary artifacts) -* The model for imported/discovered licenses is differnet -* There's no way to override license detection - -https://github.com/eskatos/honker-gradle - -* There's no way to override license files -* [SPDX](https://spdx.org/licenses/) is not used - -Features --------- +The purpose of the plugin is to analyze and infer license names for the dependencies, and verify license compatibility. -* LICENSE file generation -* License whitelisting -* The detected licenses can be overridden -* Support for incremental-builds (discovery does not run in case dependencies do not change) -* Type-safe license enumeration (based on SPDX): - - com.github.vlsi.gradle.license.api.SpdxLicense#Apache_2_0 - -Usage ------ - -Gradle (Groovy DSL): -```groovy -plugins { - id('com.github.vlsi.license-gather') version '1.0.0' -} - -tasks.register('generateLicense', GatherLicenseTask.class) { - configurations.add(project.configurations.runtimeClasspath) - outputFile.set(file("$buildDir/result.txt")) - - doLast { - println(outputFile.get().asFile.text) - } -} -``` - -Gradle (Kotlin DSL): -```groovy -plugins { - id("com.github.vlsi.license-gather") version "1.0.0" -} - -tasks.register("generateLicense", GatherLicenseTask::class) { - configurations.add(project.configurations.runtimeClasspath) - outputFile.set(file("$buildDir/result.txt")) - - doLast { - println(outputFile.get().asFile.readText()) - } -} -``` +See [license-gather-plugin description](plugins/license-gather-plugin/README.md) for configuration options. Gettext Plugin ============== diff --git a/plugins/checksum-dependency-plugin/README.md b/plugins/checksum-dependency-plugin/README.md index e5d4d5d..39880ae 100644 --- a/plugins/checksum-dependency-plugin/README.md +++ b/plugins/checksum-dependency-plugin/README.md @@ -1,4 +1,4 @@ -[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/github/vlsi/gradle/checksum-dependency-plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.github.vlsi.checksum-dependency) +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/gradle/checksum-dependency-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.checksum-dependency) Checksum Dependency Plugin ========================== diff --git a/plugins/crlf-plugin/README.md b/plugins/crlf-plugin/README.md new file mode 100644 index 0000000..08adff1 --- /dev/null +++ b/plugins/crlf-plugin/README.md @@ -0,0 +1,47 @@ +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/gradle/crlf-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.crlf) + +CRLF Plugin +=========== + +Adds Kotlin DSL to specify CRLF/LF filtering for `CopySpec`. +Enables to use `.gitignore` and `.gitattributes` for building `CopySpec`. + +Usage +----- + +Kotlin DSL: + +```kotlin +// Loads .gitattributes and .gitignore from rootDir (and subdirs) +val gitProps by tasks.registering(FindGitAttributes::class) { + // Scanning for .gitignore and .gitattributes files in a task avoids doing that + // when distribution build is not required (e.g. code is just compiled) + root.set(rootDir) +} + +fun CrLfSpec.sourceLayout() = copySpec { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + gitattributes(gitProps) + into(baseFolder) { + // Note: license content is taken from "/build/..", so gitignore should not be used + // Note: this is a "license + third-party licenses", not just Apache-2.0 + dependencyLicenses(sourceLicense) + // Include all the source files + from(rootDir) { + gitignore(gitProps) + } + } +} + +for (archive in listOf(Zip::class, Tar::class)) { + tasks.register("dist${archive.simpleName}", archive) { + val eol = if (archive == Tar::class) LineEndings.LF else LineEndings.CRLF + if (this is Tar) { + compression = Compression.GZIP + } + CrLfSpec(eol).run { + with(sourceLayout()) + } + } +} +``` diff --git a/plugins/gradle-extensions-plugin/README.md b/plugins/gradle-extensions-plugin/README.md index 3bc82f0..56a9ac6 100644 --- a/plugins/gradle-extensions-plugin/README.md +++ b/plugins/gradle-extensions-plugin/README.md @@ -1,4 +1,4 @@ -[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/github/vlsi/gradle/gradle-extensions-plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.github.vlsi.gradle-extensions) +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/gradle/gradle-extensions-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.gradle-extensions) Gradle Extensions Plugin ========================= diff --git a/plugins/jandex-plugin/README.md b/plugins/jandex-plugin/README.md index 1769eab..726cc10 100644 --- a/plugins/jandex-plugin/README.md +++ b/plugins/jandex-plugin/README.md @@ -1,4 +1,4 @@ -[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/github/vlsi/jandex/jandex-plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.github.vlsi.jandex) +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/jandex/jandex-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.jandex) Jandex Gradle Plugin ==================== diff --git a/plugins/license-gather-plugin/README.md b/plugins/license-gather-plugin/README.md new file mode 100644 index 0000000..a9dcb03 --- /dev/null +++ b/plugins/license-gather-plugin/README.md @@ -0,0 +1,143 @@ +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/gradle/license-gather-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.license-gather) + +License Gather Plugin +===================== + +The purpose of the plugin is to analyze and infer license names for the dependencies, and verify license compatibility. +The plugin checks for 3 places: MANIFEST.MF (`Bundle-License` attribute), +`pom.xml` (`licenses/license` tags), and finally `LICENSE`-like files. +Note: for now only fuzzy-match is implemented, and by default a similarity threshold of 42% is used. + +License Gather Plugin uses https://github.com/spdx/license-list-data for the list of licenses. + +Prior art +--------- + +https://github.com/jk1/Gradle-License-Report + +Gradle-License-Report is nice (it is), however there are certain pecularities (as of 2019-06-04) + +* It can't generate multiple lists within a single project (e.g. license for source / binary artifacts) +* The model for imported/discovered licenses is differnet +* There's no way to override license detection + +https://github.com/eskatos/honker-gradle + +* There's no way to override license files +* [SPDX](https://spdx.org/licenses/) is not used + +License Gather Plugin Features +------------------------------ + +* LICENSE file generation +* License whitelisting +* The detected licenses can be overridden +* Verify license compatibility +* Type-safe license enumeration (based on SPDX): + + com.github.vlsi.gradle.license.api.SpdxLicense.Apache_2_0 + +Usage +----- + +Gradle (Groovy DSL): +```groovy +import com.github.vlsi.gradle.license.GatherLicenseTask +import com.github.vlsi.gradle.license.VerifyLicenseCompatibilityTask +import com.github.vlsi.gradle.release.AsfLicenseCategory +import com.github.vlsi.gradle.license.api.SpdxLicense +import com.github.vlsi.gradle.license.api.SimpleLicense + +plugins { + id('com.github.vlsi.license-gather') version '1.78' +} + +// Gathers license information and license files from the runtime dependencies +def gatherLicense = tasks.register('gatherLicense', GatherLicenseTask.class) { + configurations.add(project.configurations.runtimeClasspath) +} + +tasks.register("verifyLicenses", VerifyLicenseCompatibilityTask.class) { + metadata.from(gatherLicense) + allow(SpdxLicense.EPL_2_0) { + // The message would be displayed, so the verification results are easier to understand + because("ISSUE-23: EPL-2.0 is fine in our projects") + } + allow(new SimpleLicense("The W3C License", uri("http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/java-binding.zip"))) { + because("ISSUE-42: John Smith decided the license is OK") + } + // License category + // See https://www.apache.org/legal/resolved.html + allow(AsfLicenseCategory.A) { + because("The ASF category A is allowed") + } + reject(AsfLicenseCategory.X) { + because("The ASF category X is forbidden") + } +} +``` + +Gradle (Kotlin DSL): +```kotlin +import com.github.vlsi.gradle.license.GatherLicenseTask +import com.github.vlsi.gradle.license.VerifyLicenseCompatibilityTask +import com.github.vlsi.gradle.release.AsfLicenseCategory +import com.github.vlsi.gradle.license.api.SpdxLicense +import com.github.vlsi.gradle.license.api.SimpleLicense + +plugins { + id("com.github.vlsi.license-gather") version "1.78" +} + +// Gathers license information and license files from the runtime dependencies +val generateLicense by tasks.registering(GatherLicenseTask::class) { + configurations.add(project.configurations.runtimeClasspath) + // In the ideal case, each dependency should ship a copy of the full license text + // just in case it has been modified. + // For instance, "MIT licence" and "BSD-* license" are almost always modified, + // and they have custom "copyright" section. + // However, certain licenses like Apache-2.0, MPL-2.0, have fixed texts, so + // we can ignore the failure if project is MPL-2.0 licensed, and it omits the license file + ignoreMissingLicenseFor.add(SpdxLicense.Apache_2_0.asExpression()) + + defaultTextFor.add(SpdxLicense.MPL_2_0.asExpression()) + + // Artifact version in override is optional + overrideLicense("com.thoughtworks.xstream:xstream:1.4.11") { + // This version reads "BSD style" in pom.xml, however, their license + // is the same as BSD-3-Clause, so we override it + // expectedLicense helps to protect from accidental overrides if the project changes version + expectedLicense = SimpleLicense("BSD style", uri("http://x-stream.github.io/license.html")) + // https://github.com/x-stream/xstream/issues/151 + // https://github.com/x-stream/xstream/issues/153 + effectiveLicense = SpdxLicense.BSD_3_Clause + } + + overrideLicense("com.formdev:svgSalamander") { + // See https://github.com/blackears/svgSalamander/blob/d6b6fe9a8ece7d0e0e7aeb3de82f027a38a6fe25/www/license/license-bsd.txt + effectiveLicense = SpdxLicense.BSD_3_Clause + } +} + +val verifyLicenses by tasks.registering(VerifyLicenseCompatibilityTask::class) { + metadata.from(gatherLicense) + // License with SPDX ID (see https://spdx.org/licenses/) + allow(SpdxLicense.EPL_2_0) { + // The message would be displayed, so the verification results are easier to understand + because("ISSUE-23: EPL-2.0 is fine in our projects") + } + // A custom license that is not present in SPDX + allow(SimpleLicense("The W3C License", uri("http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/java-binding.zip"))) { + because("ISSUE-42: John Smith decided the license is OK") + } + // License category + // See https://www.apache.org/legal/resolved.html + allow(AsfLicenseCategory.A) { + // The reason will be displayed + because("The ASF category A is allowed") + } + reject(AsfLicenseCategory.X) { + because("The ASF category X is forbidden") + } +} +``` diff --git a/plugins/stage-vote-release-plugin/README.md b/plugins/stage-vote-release-plugin/README.md index e458bb8..d942551 100644 --- a/plugins/stage-vote-release-plugin/README.md +++ b/plugins/stage-vote-release-plugin/README.md @@ -1,4 +1,4 @@ -[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/plugins.gradle.org/m2/com/github/vlsi/gradle/stage-vote-release-plugin/maven-metadata.xml.svg?colorB=007ec6&label=gradle)](https://plugins.gradle.org/plugin/com.github.vlsi.stage-vote-release) +[![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v/https/repo1.maven.org/maven2/com/github/vlsi/gradle/stage-vote-release-plugin/maven-metadata.xml.svg?colorB=007ec6&label=latest%20version)](https://plugins.gradle.org/plugin/com.github.vlsi.stage-vote-release) Stage Vote Release Plugin =========================