forked from hivemq/hivemq-edge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e1e6799
commit 68dd16d
Showing
12 changed files
with
467 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
edge-plugins/src/main/kotlin/com/hivemq/licensethirdparty/DependencyReport.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.hivemq.licensethirdparty | ||
|
||
import com.fasterxml.jackson.core.type.TypeReference | ||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper | ||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty | ||
|
||
class DependencyReport { | ||
|
||
class Root : TypeReference<List<Dependency>>() | ||
|
||
class Dependency( | ||
|
||
@param:JacksonXmlProperty(localName = "name", isAttribute = true) | ||
val name: String, | ||
|
||
@param:JacksonXmlProperty(localName = "file") | ||
val file: String, | ||
|
||
@param:JacksonXmlElementWrapper(useWrapping = false) | ||
@param:JacksonXmlProperty(localName = "license") | ||
val licenses: List<License>, | ||
) | ||
|
||
class License( | ||
|
||
@param:JacksonXmlProperty(localName = "name", isAttribute = true) | ||
val name: String, | ||
|
||
@param:JacksonXmlProperty(localName = "url", isAttribute = true) | ||
val url: String?, | ||
) | ||
} |
39 changes: 39 additions & 0 deletions
39
edge-plugins/src/main/kotlin/com/hivemq/licensethirdparty/License.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.hivemq.licensethirdparty | ||
|
||
interface License { | ||
val fullName: String | ||
val url: String? | ||
} | ||
|
||
enum class KnownLicense(val id: String, override val fullName: String, override val url: String) : License { | ||
APACHE_2_0("Apache-2.0", "Apache License 2.0", "https://spdx.org/licenses/Apache-2.0.html"), | ||
BLUE_OAK_1_0_0("BlueOak-1.0.0", "Blue Oak Model License 1.0.0", "https://spdx.org/licenses/BlueOak-1.0.0.html"), | ||
BOUNCY_CASTLE("MIT", "Bouncy Castle Licence", "https://www.bouncycastle.org/licence.html"), | ||
BSD_2_CLAUSE("BSD-2-Clause", "BSD 2-Clause \"Simplified\" License", "https://spdx.org/licenses/BSD-2-Clause.html"), | ||
BSD_3_CLAUSE("BSD-3-Clause", "BSD 3-Clause \"New\" or \"Revised\" License", "https://spdx.org/licenses/BSD-3-Clause.html"), | ||
CC_BY_4_0("CC-BY-4.0", "Creative Commons Attribution 4.0 International", "https://spdx.org/licenses/CC-BY-4.0.html"), | ||
CC0_1_0("CC0-1.0", "Creative Commons Zero v1.0 Universal", "https://spdx.org/licenses/CC0-1.0.html"), | ||
CDDL_1_0("CDDL-1.0", "Common Development and Distribution License 1.0", "https://spdx.org/licenses/CDDL-1.0.html"), | ||
CDDL_1_1("CDDL-1.1", "Common Development and Distribution License 1.1", "https://spdx.org/licenses/CDDL-1.1.html"), | ||
// EDL has BSD-3-Clause as SPDX id, documented in the following links: | ||
// https://spdx.org/licenses/BSD-3-Clause.html | ||
// https://www.eclipse.org/org/documents/edl-v10.php | ||
// https://lists.spdx.org/g/Spdx-legal/topic/request_for_adding_eclipse/67981884 | ||
EDL_1_0("BSD-3-Clause", "Eclipse Distribution License - v 1.0", "https://www.eclipse.org/org/documents/edl-v10.php"), | ||
EPL_1_0("EPL-1.0", "Eclipse Public License 1.0", "https://spdx.org/licenses/EPL-1.0.html"), | ||
EPL_2_0("EPL-2.0", "Eclipse Public License 2.0", "https://spdx.org/licenses/EPL-2.0.html"), | ||
GO("BSD-3-Clause", "Go License", "https://golang.org/LICENSE"), | ||
ISC("ISC", "ISC License", "https://spdx.org/licenses/ISC.html"), | ||
LGPL_2_1_OR_LATER("LGPL-2.1-or-later", "GNU Lesser General Public License v2.1 or later", "https://spdx.org/licenses/LGPL-2.1-or-later.html"), | ||
MIT("MIT", "MIT License", "https://spdx.org/licenses/MIT.html"), | ||
MIT_0("MIT-0", "MIT No Attribution", "https://spdx.org/licenses/MIT-0.html"), | ||
OFL_1_1("OFL-1.1", "SIL Open Font License 1.1", "https://spdx.org/licenses/OFL-1.1.html"), | ||
PUBLIC_DOMAIN("Public Domain", "Public Domain", ""), | ||
UNICODE_DFS_2016("Unicode-DFS-2016", "Unicode License Agreement - Data Files and Software (2016)", "https://spdx.org/licenses/Unicode-DFS-2016.html"), | ||
UNLICENSE("Unlicense", "Unlicense Yourself: Set Your Code Free", "https://unlicense.org/"), | ||
W3C_19980720("W3C-19980720", "W3C Software Notice and License (1998-07-20)", "https://spdx.org/licenses/W3C-19980720.html"), | ||
ZERO_BSD("0BSD", "BSD Zero Clause License", "https://spdx.org/licenses/0BSD.html"), | ||
} | ||
|
||
|
||
data class UnknownLicense(override val fullName: String, override val url: String?) : License |
12 changes: 12 additions & 0 deletions
12
...-plugins/src/main/kotlin/com/hivemq/licensethirdparty/ThirdPartyLicenseGeneratorPlugin.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.hivemq.licensethirdparty | ||
|
||
import org.gradle.api.Plugin | ||
import org.gradle.api.Project | ||
import org.gradle.kotlin.dsl.register | ||
|
||
class ThirdPartyLicenseGeneratorPlugin : Plugin<Project> { | ||
|
||
override fun apply(project: Project) { | ||
project.tasks.register<UpdateThirdPartyLicensesTask>("updateThirdPartyLicenses") | ||
} | ||
} |
268 changes: 268 additions & 0 deletions
268
edge-plugins/src/main/kotlin/com/hivemq/licensethirdparty/UpdateThirdPartyLicensesTask.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
package com.hivemq.licensethirdparty | ||
|
||
import com.fasterxml.jackson.databind.DeserializationFeature | ||
import com.fasterxml.jackson.dataformat.xml.XmlMapper | ||
import org.gradle.api.DefaultTask | ||
import org.gradle.api.file.DirectoryProperty | ||
import org.gradle.api.file.RegularFileProperty | ||
import org.gradle.api.tasks.Input | ||
import org.gradle.api.tasks.InputFile | ||
import org.gradle.api.tasks.OutputDirectory | ||
import org.gradle.api.tasks.TaskAction | ||
import org.gradle.kotlin.dsl.property | ||
import java.util.* | ||
|
||
/** | ||
* Reads the `dependency-license.xml` file created by the `downloadLicenses` task and creates `licenses` and | ||
* `licenses.html` files in the configured [outputDirectory]. | ||
*/ | ||
abstract class UpdateThirdPartyLicensesTask : DefaultTask() { | ||
|
||
companion object { | ||
|
||
// defines the artifacts that should be ignored in the third-party license report | ||
private fun shouldIgnore(coordinates: Coordinates) = | ||
coordinates.group.startsWith("com.hivemq") && (coordinates.name != "hivemq-mqtt-client") | ||
|
||
// defines the license to choose, if multiple licenses are available for an artifact | ||
private val LICENSE_ORDER = listOf( | ||
KnownLicense.APACHE_2_0, | ||
KnownLicense.MIT, | ||
KnownLicense.MIT_0, | ||
KnownLicense.ZERO_BSD, | ||
KnownLicense.UNLICENSE, | ||
KnownLicense.BOUNCY_CASTLE, | ||
KnownLicense.BLUE_OAK_1_0_0, | ||
KnownLicense.ISC, | ||
KnownLicense.BSD_3_CLAUSE, | ||
KnownLicense.BSD_2_CLAUSE, | ||
KnownLicense.GO, | ||
KnownLicense.CC0_1_0, | ||
KnownLicense.CC_BY_4_0, | ||
KnownLicense.OFL_1_1, | ||
KnownLicense.PUBLIC_DOMAIN, | ||
KnownLicense.W3C_19980720, | ||
KnownLicense.EDL_1_0, | ||
KnownLicense.EPL_2_0, | ||
KnownLicense.EPL_1_0, | ||
KnownLicense.CDDL_1_1, | ||
KnownLicense.CDDL_1_0, | ||
KnownLicense.UNICODE_DFS_2016, | ||
KnownLicense.LGPL_2_1_OR_LATER | ||
) | ||
} | ||
|
||
@get:Input | ||
val projectName = project.objects.property<String>() | ||
|
||
@get:InputFile | ||
val dependencyLicense: RegularFileProperty = project.objects.fileProperty() | ||
|
||
|
||
@get:OutputDirectory | ||
val outputDirectory: DirectoryProperty = project.objects.directoryProperty() | ||
|
||
@TaskAction | ||
protected fun run() { | ||
val productName = projectName.get() | ||
val dependencyLicenseFile = dependencyLicense.get().asFile.absoluteFile | ||
val resultPlaintextFile = outputDirectory.get().asFile.resolve(productName) | ||
val resultHtmlFile = outputDirectory.get().asFile.resolve("$productName.html") | ||
|
||
check(productName.isNotBlank()) { "Project name is blank" } | ||
if (resultPlaintextFile.exists()) { | ||
check(resultPlaintextFile.delete()) { "Could not delete file '$resultPlaintextFile'" } | ||
} | ||
if (resultHtmlFile.exists()) { | ||
check(resultHtmlFile.delete()) { "Could not delete file '$resultHtmlFile'" } | ||
} | ||
|
||
val xmlMapper = XmlMapper() | ||
xmlMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) | ||
val dependencies = xmlMapper.readValue(dependencyLicenseFile, DependencyReport.Root()) | ||
val entries = TreeMap<String, Pair<Coordinates, KnownLicense>>() | ||
for (dependency in dependencies) { | ||
if (dependency.name.endsWith(".jar")) { | ||
System.err.println("Skipping jar dependency: " + dependency.name) | ||
continue | ||
} | ||
|
||
val nameParts = dependency.name.split(":") | ||
check(nameParts.size == 3) { "Invalid dependency '${dependency.name}'" } | ||
val coordinates = Coordinates(nameParts[0], nameParts[1], nameParts[2]) | ||
if (shouldIgnore(coordinates)) continue | ||
val licenses = dependency.licenses.map { convertLicense(it, coordinates) } | ||
|
||
val chosenLicense = | ||
checkNotNull(chooseLicense(licenses)) { "[Edge Plugin] License can not be determined for '$coordinates'" } | ||
entries[coordinates.moduleId] = Pair(coordinates, chosenLicense) | ||
} | ||
|
||
val licensePlaintext = StringBuilder() | ||
val licenseHtml = StringBuilder() | ||
licensePlaintext.addHeaderPlaintext(productName) | ||
licenseHtml.addHeaderHtml(productName) | ||
for ((coordinates, chosenLicense) in entries.values) { | ||
licensePlaintext.addLinePlaintext(coordinates, chosenLicense) | ||
licenseHtml.addLineHtml(coordinates, chosenLicense) | ||
} | ||
licensePlaintext.addFooterPlaintext() | ||
licenseHtml.addFooterHtml() | ||
|
||
resultPlaintextFile.writeText(licensePlaintext.toString()) | ||
resultHtmlFile.writeText(licenseHtml.toString()) | ||
} | ||
|
||
private fun convertLicense(license: DependencyReport.License, coordinates: Coordinates): License { | ||
val name = license.name | ||
val url = license.url | ||
return when { | ||
name.matches(".*(Apache|APACHE).*[\\s\\-v](2\\.0.*|2(\\s.*|$))".toRegex()) -> KnownLicense.APACHE_2_0 | ||
name == "Bouncy Castle Licence" -> KnownLicense.BOUNCY_CASTLE | ||
name == "Bouncy Castle License" -> KnownLicense.BOUNCY_CASTLE | ||
name.matches("(.*BSD.*2.*[Cc]lause.*)|(.*2.*[Cc]lause.*BSD.*)".toRegex()) -> KnownLicense.BSD_2_CLAUSE | ||
name.matches("(.*BSD.*3.*[Cc]lause.*)|(.*3.*[Cc]lause.*BSD.*)|(.*[Nn]ew.*BSD.*)|(.*BSD.*[Nn]ew.*)".toRegex()) || (url == "https://opensource.org/licenses/BSD-3-Clause") -> KnownLicense.BSD_3_CLAUSE | ||
name == "CC0" -> KnownLicense.CC0_1_0 | ||
url == "https://glassfish.dev.java.net/public/CDDLv1.0.html" -> KnownLicense.CDDL_1_0 | ||
(url == "https://oss.oracle.com/licenses/CDDL+GPL-1.1") || (url == "https://github.com/javaee/javax.annotation/blob/master/LICENSE") || (url == "https://glassfish.java.net/public/CDDL+GPL_1_1.html") -> KnownLicense.CDDL_1_1 | ||
name.matches(".*(EDL|Eclipse.*Distribution.*License).*1\\.0.*".toRegex()) -> KnownLicense.EDL_1_0 | ||
name.matches(".*(EPL|Eclipse.*Public.*License).*1\\.0.*".toRegex()) -> KnownLicense.EPL_1_0 | ||
name.matches(".*(EPL|Eclipse.*Public.*License).*2\\.0.*".toRegex()) -> KnownLicense.EPL_2_0 | ||
name == "Go License" -> KnownLicense.GO | ||
name.matches(".*MIT(\\s.*|$)".toRegex()) -> KnownLicense.MIT | ||
name.matches(".*MIT-0.*".toRegex()) -> KnownLicense.MIT_0 | ||
name == "Public Domain" -> KnownLicense.PUBLIC_DOMAIN | ||
url == "http://www.w3.org/Consortium/Legal/copyright-software-19980720" -> KnownLicense.W3C_19980720 | ||
// from here license name and url are not enough to determine the exact license, so we checked the specific dependency manually | ||
(name == "BSD") && (coordinates.group == "dk.brics") && (coordinates.name == "automaton") -> KnownLicense.BSD_3_CLAUSE | ||
(name == "BSD") && (coordinates.group == "org.picocontainer") && (coordinates.name == "picocontainer") -> KnownLicense.BSD_3_CLAUSE | ||
(name == "BSD") && (coordinates.group == "org.ow2.asm") && (coordinates.name == "asm") -> KnownLicense.BSD_3_CLAUSE | ||
(name == "BSD licence") && (coordinates.group == "org.antlr") && (coordinates.name == "antlr-runtime") -> KnownLicense.BSD_3_CLAUSE | ||
(name == "The BSD License") && (coordinates.group == "org.antlr") && (coordinates.name == "ST4") -> KnownLicense.BSD_3_CLAUSE | ||
(name == "The BSD License") && (coordinates.group == "org.codehaus.woodstox") && (coordinates.name == "stax2-api") -> KnownLicense.BSD_2_CLAUSE | ||
(name == "Unicode/ICU License") && (coordinates.group == "com.ibm.icu") && (coordinates.name == "icu4j") && (coordinates.version == "72.1") -> KnownLicense.UNICODE_DFS_2016 | ||
(name == "LGPL-2.1") && (coordinates.group == "org.mariadb.jdbc") && (coordinates.name == "mariadb-java-client") -> KnownLicense.LGPL_2_1_OR_LATER | ||
name == "CC-BY-4.0" -> KnownLicense.CC_BY_4_0 | ||
name == "BlueOak-1.0.0" -> KnownLicense.BLUE_OAK_1_0_0 | ||
name == "0BSD" -> KnownLicense.ZERO_BSD | ||
name == "OFL-1.1" -> KnownLicense.OFL_1_1 | ||
name == "ISC" -> KnownLicense.ISC | ||
name == "Unlicense" -> KnownLicense.UNLICENSE | ||
else -> { | ||
UnknownLicense(name, url) | ||
} | ||
} | ||
} | ||
|
||
private fun chooseLicense(licenses: List<License>): KnownLicense? { | ||
var chosenLicense: KnownLicense? = null | ||
var indexOfChosenLicense = Int.MAX_VALUE | ||
for (license in licenses) { | ||
if (license is KnownLicense) { | ||
val indexOfLicense = LICENSE_ORDER.indexOf(license) | ||
if ((indexOfLicense != -1) && (indexOfLicense < indexOfChosenLicense)) { | ||
chosenLicense = license | ||
indexOfChosenLicense = indexOfLicense | ||
} | ||
} | ||
} | ||
return chosenLicense | ||
} | ||
|
||
private fun StringBuilder.addHeaderPlaintext(productName: String) = append( | ||
""" | ||
Third Party Licenses | ||
============================== | ||
$productName uses the following third party libraries: | ||
Module | Version | License ID | License URL | ||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ||
""".trimIndent() | ||
) | ||
|
||
private fun StringBuilder.addHeaderHtml(productName: String) = append( | ||
""" | ||
<head> | ||
<title>Third Party Licences</title> | ||
<style> | ||
table, th, td { | ||
border: 1px solid black; | ||
border-collapse: collapse; | ||
border-spacing: 0; | ||
} | ||
th, td { | ||
padding: 5px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h2>Third Party Licenses</h2> | ||
<p>$productName uses the following third party libraries</p> | ||
<table> | ||
<tbody> | ||
<tr> | ||
<th>Module</th> | ||
<th>Version</th> | ||
<th>License ID</th> | ||
<th>License URL</th> | ||
</tr> | ||
""".trimIndent() | ||
) | ||
|
||
private fun StringBuilder.addLinePlaintext(coordinates: Coordinates, license: KnownLicense) = | ||
append( | ||
" ${"%-74s".format(coordinates.moduleId)} | ${"%-41s".format(coordinates.version)} | ${ | ||
"%-13s".format( | ||
license.id | ||
) | ||
} | ${license.url}\n" | ||
) | ||
|
||
private fun StringBuilder.addLineHtml(coordinates: Coordinates, license: KnownLicense) = append( | ||
""" | ||
| <tr> | ||
| <td>${coordinates.moduleId}</td> | ||
| <td>${coordinates.version}</td> | ||
| <td>${license.id}</td> | ||
| <td> | ||
| <a href="${license.url}">${license.url}</a> | ||
| </td> | ||
| <td></td> | ||
| </tr> | ||
| | ||
""".trimMargin() | ||
) | ||
|
||
private fun StringBuilder.addFooterPlaintext() = append( | ||
""" | ||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ||
The open source code of the libraries can be obtained by sending an email to [email protected]. | ||
""".trimIndent() | ||
) | ||
|
||
private fun StringBuilder.addFooterHtml() = append( | ||
""" | ||
</tbody> | ||
</table> | ||
<p>The open source code of the libraries can be obtained by sending an email to <a href="mailto:[email protected]">[email protected]</a>. | ||
</p> | ||
</body> | ||
""".trimIndent() | ||
) | ||
} | ||
|
||
data class Coordinates(val group: String, val name: String, val version: String) { | ||
val moduleId get() = "$group:$name" | ||
|
||
override fun toString() = "$group:$name:$version" | ||
} |
Oops, something went wrong.