diff --git a/LICENSE b/LICENSE index b3ecd61..482f9d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,25 @@ - Licensing - License definitions and legal management plugin for the Gradle build system + [The Apache Software License, Version 2.0] https://git.dorkbox.com/dorkbox/Licensing - Copyright 2020 - The Apache Software License, Version 2.0 + Copyright 2020 Dorkbox LLC + + Extra license information + - Kotlin - + [The Apache Software License, Version 2.0] + https://github.com/JetBrains/kotlin + Copyright 2020 + JetBrains s.r.o. and Kotlin Programming Language contributors + Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply + See: https://github.com/JetBrains/kotlin/blob/master/license/README.md + + - Version - Java Semantic Versioning with exceptions. Minor/Patch number optional and build-after-final-dot (minor/patch) permitted. + [MIT License] + https://git.dorkbox.com/dorkbox/Version + Copyright 2020 + Dorkbox LLC + G. Richard Bellamy + Kenduck + Larry Bordowitz + Martin Rüegg + Zafar Khaja diff --git a/LICENSE.MIT b/LICENSE.MIT new file mode 100644 index 0000000..2e9f5aa --- /dev/null +++ b/LICENSE.MIT @@ -0,0 +1,21 @@ + MIT License + + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 77d8bfb..e6f0c3d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,13 +16,18 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.time.Instant +gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace! +gradle.startParameter.warningMode = WarningMode.All + + plugins { java `java-gradle-plugin` id("com.gradle.plugin-publish") version "0.12.0" - id("com.dorkbox.VersionUpdate") version "1.7" + id("com.dorkbox.Licensing") version "2.1" + id("com.dorkbox.VersionUpdate") version "1.8" id("com.dorkbox.GradleUtils") version "1.8" kotlin("jvm") version "1.3.72" @@ -33,7 +38,7 @@ object Extras { // set for the project const val description = "License definitions and legal management plugin for the Gradle build system" const val group = "com.dorkbox" - const val version = "2.0" + const val version = "2.1" // set as project.ext const val name = "Gradle Licensing Plugin" @@ -53,6 +58,15 @@ object Extras { GradleUtils.load("$projectDir/../../gradle.properties", Extras) GradleUtils.fixIntellijPaths() + +licensing { + license(License.APACHE_2) { + description(Extras.description) + author(Extras.vendor) + url(Extras.url) + } +} + sourceSets { main { java { @@ -83,7 +97,7 @@ dependencies { // the kotlin version is taken from the plugin, so it is not necessary to set it here implementation("org.jetbrains.kotlin:kotlin-gradle-plugin") - implementation("com.dorkbox:Version:1.2") + implementation("com.dorkbox:Version:2.4") } java { diff --git a/src/dorkbox/license/AppLicensing.kt b/src/dorkbox/license/AppLicensing.kt index eb5832a..c289169 100644 --- a/src/dorkbox/license/AppLicensing.kt +++ b/src/dorkbox/license/AppLicensing.kt @@ -43,7 +43,6 @@ object AppLicensing { private val map = listOf( LicenseChain("org.jetbrains.kotlin", LicenseData("Kotlin", License.APACHE_2).apply { - copyright(2000) author("JetBrains s.r.o. and Kotlin Programming Language contributors") url("https://github.com/JetBrains/kotlin") note("Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply") @@ -54,7 +53,6 @@ object AppLicensing { LicenseData("Java Annotations", License.APACHE_2).apply { description("Annotations for JVM-based languages") url("https://github.com/JetBrains/java-annotations") - copyright(2000) author("JetBrains s.r.o.") } ), @@ -62,7 +60,6 @@ object AppLicensing { LicenseData("kotlinx.coroutines", License.APACHE_2).apply { description("Library support for Kotlin coroutines with multiplatform support") url("https://github.com/Kotlin/kotlinx.coroutines") - copyright(2000) author("JetBrains s.r.o.") } ), @@ -70,7 +67,6 @@ object AppLicensing { LicenseData("kotlin-logging", License.APACHE_2).apply { description("Lightweight logging framework for Kotlin") url("https://github.com/MicroUtils/kotlin-logging") - copyright(2016) author("Ohad Shai") } ), @@ -78,7 +74,6 @@ object AppLicensing { LicenseData("SLF4J", License.MIT).apply { description("Simple facade or abstraction for various logging frameworks") url("http://www.slf4j.org") - copyright(2004) author("QOS.ch") } ), @@ -86,7 +81,6 @@ object AppLicensing { LicenseData("JNA", License.LGPLv2_1).apply { description("Simplified native library access for Java.") url("https://github.com/twall/jna") - copyright(2004) author("Timothy Wall") } ), @@ -94,7 +88,6 @@ object AppLicensing { LicenseData("JNA", License.APACHE_2).apply { description("Simplified native library access for Java.") url("https://github.com/twall/jna") - copyright(2013) author("Timothy Wall") } ), @@ -103,7 +96,6 @@ object AppLicensing { LicenseData("JNA-Platform", License.LGPLv2_1).apply { description("Mappings for a number of commonly used platform functions") url("https://github.com/twall/jna") - copyright(2013) author("Timothy Wall") } ), @@ -111,7 +103,6 @@ object AppLicensing { LicenseData("JNA-Platform", License.APACHE_2).apply { description("Mappings for a number of commonly used platform functions") url("https://github.com/twall/jna") - copyright(2013) author("Timothy Wall") } ), @@ -120,29 +111,24 @@ object AppLicensing { LicenseData("SSHJ", License.APACHE_2).apply { description("SSHv2 library for Java") url("https://github.com/hierynomus/sshj") - copyright(2009) author("Jeroen van Erp") author("SSHJ Contributors") extra("Apache MINA", License.APACHE_2) { it.url("https://mina.apache.org/sshd-project/") - it.copyright(2003).to(2017) it.author("The Apache Software Foundation") } extra("Apache Commons-Net", License.APACHE_2) { it.url("https://commons.apache.org/proper/commons-net/") - it.copyright(2001).to(2017) it.author("The Apache Software Foundation") } extra("JZlib", License.APACHE_2) { it.url("http://www.jcraft.com/jzlib") - it.copyright(2002).to(2008) it.author("Atsuhiko Yamanaka") it.author("JCraft, Inc.") } extra("Bouncy Castle Crypto", License.APACHE_2) { it.url("http://www.bouncycastle.org") - it.copyright(2000).to(2006) it.author("The Legion of the Bouncy Castle Inc") } extra("ed25519-java", License.CC0) { @@ -155,7 +141,6 @@ object AppLicensing { LicenseChain("org.bouncycastle", LicenseData("Bouncy Castle Crypto", License.APACHE_2).apply { description("Lightweight cryptography API and JCE Extension") - copyright(2000) author("The Legion of the Bouncy Castle Inc") url("http://www.bouncycastle.org") } @@ -164,7 +149,6 @@ object AppLicensing { LicenseChain("com.fasterxml.uuid:java-uuid-generator", LicenseData("Java Uuid Generator", License.APACHE_2).apply { description("A set of Java classes for working with UUIDs") - copyright(2002) author("Tatu Saloranta (tatu.saloranta@iki.fi)") author("Contributors. See source release-notes/CREDITS") url("https://github.com/cowtowncoder/java-uuid-generator") @@ -181,7 +165,6 @@ object AppLicensing { LicenseChain("io.netty", LicenseData("Netty", License.APACHE_2).apply { description("An event-driven asynchronous network application framework") - copyright(2014) author("The Netty Project") author("Contributors. See source NOTICE") url("https://netty.io") @@ -190,7 +173,6 @@ object AppLicensing { LicenseChain("org.lwjgl:lwjgl-xxhash", LicenseData("Lightweight Java Game Library", License.BSD_3).apply { description("Java library that enables cross-platform access to popular native APIs") - copyright(2012) author("Lightweight Java Game Library") url("https://github.com/LWJGL/lwjgl3") } @@ -199,7 +181,6 @@ object AppLicensing { LicenseChain("com.github.ben-manes:gradle-versions-plugin", LicenseData("Gradle Versions Plugin", License.APACHE_2).apply { description("This plugin provides a task to determine which dependencies have updates") - copyright(2012) author("Ben Manes") url("https://github.com/ben-manes/gradle-versions-plugin") } @@ -208,7 +189,6 @@ object AppLicensing { LicenseChain("org.json:json", LicenseData("JSON in Java", License.JSON).apply { description("A light-weight language independent data interchange format.") - copyright(2002) author("JSON.org") url("https://github.com/stleary/JSON-java") url("https://www.json.org/json-en.html") @@ -218,7 +198,6 @@ object AppLicensing { LicenseChain("net.jodah:typetools", LicenseData("TypeTools", License.APACHE_2).apply { description("A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.") - copyright(2010) author("Jonathan Halterman and friends") url("https://github.com/jhalterman/typetools") } diff --git a/src/dorkbox/license/DependencyScanner.kt b/src/dorkbox/license/DependencyScanner.kt index 34239d4..e9ea33a 100644 --- a/src/dorkbox/license/DependencyScanner.kt +++ b/src/dorkbox/license/DependencyScanner.kt @@ -40,17 +40,16 @@ class DependencyScanner(private val project: Project, private val extension: Lic } - val missingLicenseInfo = mutableListOf() - val actuallyMissingLicenseInfo = mutableListOf() + val missingLicenseInfo = mutableSetOf() + val actuallyMissingLicenseInfo = mutableSetOf() if (extension.licenses.isNotEmpty()) { // when we scan, we ONLY want to scan a SINGLE LAYER (if we have license info for module ID, then we don't need license info for it's children) - println("\tLicense Detection") - print("\t\tScanning for preloaded license data...") + println("\t\tScanning for preloaded license data...") val primaryLicense = extension.licenses.first() - var found = false + // scan to see if we have in our predefined section projectDependencies.forEach { info: Dependency -> val license: LicenseData? = try { AppLicensing.getLicense(info.mavenId()) @@ -64,6 +63,8 @@ class DependencyScanner(private val project: Project, private val extension: Lic missingLicenseInfo.add(info) } else { if (!primaryLicense.extras.contains(license)) { + println("\t\t\t${info.mavenId()}") + // get the OLDEST date from the artifacts and use that as the copyright date var oldestDate = 0L info.artifacts.forEach { artifact -> @@ -77,35 +78,22 @@ class DependencyScanner(private val project: Project, private val extension: Lic oldestDate = Instant.now().toEpochMilli() } + // as per US Code Title 17, Chapter 4; for "visually perceptive copies" (which includes software). + // http://www.copyright.gov/title17/92chap4.html + // it is ONLY... © year name val year = Date(oldestDate).toInstant().atZone(ZoneId.systemDefault()).toLocalDate().year - if (license.copyrights.size == 1) { - if (year > license.copyrights.first()) { - // only do this if we have copyright greater than our specified (it can be the SAME, so don't want to do this!) - try { - CopyrightRange(license, license.copyrights.first(), license.copyrights).to(year) - } catch (e: GradleException) { - throw GradleException("Error assigning internal copyright for ${info.mavenId()}", e) - } - } - } else { - license.copyright(year) - } + license.copyright = year primaryLicense.extras.add(license) } } } - if (found) { - found = false - println(" found") - } else { - println() - } - print("\t\tScanning for embedded license data...") + println("\t\tScanning for embedded license data...") + // now scan to see if the jar has a license blob in it if (missingLicenseInfo.isNotEmpty()) { missingLicenseInfo.forEach { info -> // see if we have it in the dependency jar @@ -114,12 +102,12 @@ class DependencyScanner(private val project: Project, private val extension: Lic info.artifacts.forEach search@{ artifact -> ZipFile(artifact.file).use { try { + // read the license blob information val ze = it.getEntry(LicenseInjector.LICENSE_BLOB) if (ze != null) { it.getInputStream(ze).use { licenseStream -> licenseStream.copyTo(output) missingFound = true - found = true return@search } } @@ -132,7 +120,7 @@ class DependencyScanner(private val project: Project, private val extension: Lic if (!missingFound) { actuallyMissingLicenseInfo.add(info) } else { -// println("Found license info in: $info") + println("\t\t\t$info") try { ObjectInputStream(ByteArrayInputStream(output.toByteArray())).use { ois -> @@ -141,7 +129,11 @@ class DependencyScanner(private val project: Project, private val extension: Lic val license = LicenseData("", License.CUSTOM) license.readObject(ois) - // println("Adding license: $license") + // as per US Code Title 17, Chapter 4; for "visually perceptive copies" (which includes software). + // http://www.copyright.gov/title17/92chap4.html + // it is ONLY... © year name + // + // this is correctly saved in the license blob if (!primaryLicense.extras.contains(license)) { primaryLicense.extras.add(license) } @@ -154,23 +146,23 @@ class DependencyScanner(private val project: Project, private val extension: Lic } } - - if (found) { - found = false - println(" found") - } else { - println() - } - if (actuallyMissingLicenseInfo.isNotEmpty()) { - println("\t\tLicense information is missing for the following. Please submit an issue with this information to include it in future license scans\n") + println("\t\tLicense information is missing for the following. Please submit an issue with this information to include it in future license scans") actuallyMissingLicenseInfo.forEach { missingDep -> val flatDependencies = mutableSetOf() missingDep.children.forEach { flattenDep(it, flatDependencies) } - println("\t\t ${missingDep.mavenId()} ${flatDependencies.map { it.mavenId() }}") + + val flat = flatDependencies.map { it.mavenId() } + val extras = if (flat.isEmpty()) { + "" + } else { + flat.toString() + } + + println("\t\t ${missingDep.mavenId()} $extras") } } } diff --git a/src/dorkbox/license/LicenseData.kt b/src/dorkbox/license/LicenseData.kt index eb33b8f..7491f5c 100644 --- a/src/dorkbox/license/LicenseData.kt +++ b/src/dorkbox/license/LicenseData.kt @@ -38,17 +38,33 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable } /** - * Copyright + * The Berne Convention leaves "implementation details" up to individual countries. + * + * In the US (which is the relevant country for FSF documents), copyright notices are defined by Title 17, Chapter 4; + * for "visually perceptive copies" (which includes software). http://www.copyright.gov/title17/92chap4.html + * + * These take the form : + * + * © year name + * + * where + * + * `©` is specifically that symbol (not "(c)", which has no legal value), and can be replaced by "Copyright" or "Copr." (§ 401(b)(1)) + * + * `year` is the year of first publication of the work, or the year of first publication of the compilation or derivative work if relevant + * + * `name` is the name of the owner of the copyright. + * + * What counts is years of publication; for software this is generally considered years in which the software is released. + * + * So if you release a piece of software in 2014, and release it again in 2016 without making changes in 2015, the years of publication + * would be 2014 and 2016, and the copyright notices would be "© 2014" in the first release and "© 2016" in the second release. + * + * + * + * When scanning for license copyright dates, we use the date of publication (the date of the manifest file, within the published jar) */ - val copyrights = mutableListOf() - - /** - * If not specified, will use the current year - */ - fun copyright(copyright: Int = LocalDate.now().year): CopyrightRange { - copyrights.add(copyright) - return CopyrightRange(this, copyright, copyrights) - } + var copyright = LocalDate.now().year /** * URL @@ -129,7 +145,7 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable if (name != other.name) return false if (license != other.license) return false if (description != other.description) return false - if (copyrights != other.copyrights) return false + if (copyright != other.copyright) return false if (urls != other.urls) return false if (notes != other.notes) return false if (authors != other.authors) return false @@ -142,7 +158,7 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable var result = name.hashCode() result = 31 * result + license.hashCode() result = 31 * result + description.hashCode() - result = 31 * result + copyrights.hashCode() + result = 31 * result + copyright result = 31 * result + urls.hashCode() result = 31 * result + notes.hashCode() result = 31 * result + authors.hashCode() @@ -156,10 +172,7 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable s.writeUTF(license.name) s.writeUTF(description) - s.writeInt(copyrights.size) - copyrights.forEach { - s.writeInt(it) - } + s.writeInt(copyright) s.writeInt(urls.size) urls.forEach { @@ -184,18 +197,14 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable } } - // Gradle only needs to serialize objects, so this isn't strictly needed + // this is used to load license data from supported dependencies @Throws(IOException::class) fun readObject(s: ObjectInputStream) { name = s.readUTF() license = License.valueOfLicenseName(s.readUTF()) description = s.readUTF() - - val copyrightsSize = s.readInt() - for (i in 1..copyrightsSize) { - copyrights.add(s.readInt()) - } + copyright = s.readInt() val urlsSize = s.readInt() for (i in 1..urlsSize) { @@ -225,10 +234,9 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable // NOTE: we ALWAYS use unix line endings! private const val NL = "\n" - private const val HEADER = " - " - private const val HEADR4 = " ---- " + private const val HEADER1 = " - " + private const val HEADER4 = " ---- " private const val SPACER3 = " " - private const val SPACER4 = " " private fun prefix(prefix: Int, builder: StringBuilder): StringBuilder { if (prefix == 0) { @@ -279,43 +287,25 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable // NOTE: we ALWAYS use unix line endings! private fun buildLicenseString(b: StringBuilder, license: LicenseData, prefixOffset: Int) { - line(prefixOffset, b, HEADER, license.name, " - ", license.description) + line(prefixOffset, b, HEADER1, license.name, " - ", license.description) + + line(prefixOffset, b, SPACER3, "[", license.license.preferedName, "]") license.urls.forEach { line(prefixOffset, b, SPACER3, it) } - prefix(prefixOffset, b).append(SPACER3).append("Copyright") - if (license.copyrights.isEmpty()) { - // append the current year - b.append(" ").append(LocalDate.now().year) - } - else if (license.copyrights.size == 1) { - // this is 2001 - b.append(" ").append(license.copyrights.first()) - } else { - // is this 2001,2002,2004,2014 2001-2014 - val sumA = license.copyrights.sum() - val sumB = license.copyrights.first().rangeTo(license.copyrights.last()).sum() - if (sumA == sumB) { - // this is 2001-2004 - b.append(" ").append(license.copyrights.first()).append("-").append(license.copyrights.last()) - } else { - // this is 2001,2002,2004 - license.copyrights.forEach { - b.append(" ").append(it).append(",") - } - b.deleteCharAt(b.length-1) - } - } - b.append(HEADER).append(license.license.preferedName).append(NL) - + // as per US Code Title 17, Chapter 4; for "visually perceptive copies" (which includes software). + // http://www.copyright.gov/title17/92chap4.html + // it is ONLY... © year name (or Copyright year name) + // authors go on a separate line + line(prefixOffset, b, SPACER3, "Copyright ", license.copyright) license.authors.forEach { - line(prefixOffset, b, SPACER4, it) + line(prefixOffset, b, SPACER3, " ", it) } if (license.license === License.CUSTOM) { - line(prefixOffset, b, HEADR4) + line(prefixOffset, b, HEADER4) } license.notes.forEach { @@ -371,26 +361,3 @@ class LicenseData(var name: String, var license: License) : java.io.Serializable } } } - -class CopyrightRange internal constructor(private val license: LicenseData, - private val start: Int, - private val copyrights: MutableList) { - fun to(copyRight: Int) { - if (start >= copyRight) { - throw GradleException("Cannot have a start copyright date that is equal or greater than the `to` copyright date for ${license.name}") - } - - val newStart = start+1 - if (newStart < copyRight) { - // increment by 1, since the first part of the range is already added - copyrights.addAll((newStart).rangeTo(copyRight)) - } - } - - fun toNow() { - val nowYear = LocalDate.now().year - if (start < nowYear) { - to(nowYear) - } - } -} diff --git a/src/dorkbox/license/LicenseInjector.kt b/src/dorkbox/license/LicenseInjector.kt index 5ca2785..5a50a3d 100644 --- a/src/dorkbox/license/LicenseInjector.kt +++ b/src/dorkbox/license/LicenseInjector.kt @@ -2,40 +2,49 @@ package dorkbox.license import org.gradle.api.DefaultTask import org.gradle.api.GradleException +import org.gradle.api.Project import org.gradle.api.tasks.* import java.io.File import java.io.FileOutputStream import java.io.ObjectOutputStream +import java.util.* import javax.inject.Inject -internal open class LicenseInjector @Inject constructor(@Internal val extension: Licensing) : DefaultTask() { - // only want to build these files once - private var alreadyBuilt = false - +internal open class LicenseInjector @Inject constructor(project: Project, @Internal val extension: Licensing) : DefaultTask() { companion object { const val LICENSE_FILE = "LICENSE" const val LICENSE_BLOB = "LICENSE.blob" } - @Input lateinit var licenses: MutableList - @OutputDirectory lateinit var outputDir: File - @InputDirectory lateinit var rootDir: File + @Input val licenses = extension.licenses + + @Internal private val outputBuildDir = File(project.buildDir, "licensing") + @OutputFiles val outputFiles = mutableListOf() init { + /// outputBuildDir + outputFiles.add(File(outputBuildDir, LICENSE_FILE)) + outputFiles.add(File(outputBuildDir, LICENSE_BLOB)) + licenses.forEach { + outputFiles.add(File(outputBuildDir, it.license.licenseFile)) + } + + /// root dir + outputFiles.add(File(project.rootDir, LICENSE_FILE)) + outputFiles.add(File(project.rootDir, LICENSE_BLOB)) + licenses.forEach { + outputFiles.add(File(project.rootDir, it.license.licenseFile)) + } + outputs.upToDateWhen { - alreadyBuilt || !(checkLicenseFiles(outputDir, licenses) && checkLicenseFiles(rootDir, licenses)) + !(checkLicenseFiles(outputBuildDir, licenses) && checkLicenseFiles(project.rootDir, licenses)) } } @TaskAction fun doTask() { - if (alreadyBuilt) { - return - } - alreadyBuilt = true - // now we want to add license information that we know about from our dependencies to our list // just to make it clear, license information CAN CHANGE BETWEEN VERSIONS! For example, JNA changed from GPL to Apache in version 4+ // we associate the artifact group + id + (start) version as a license. @@ -44,14 +53,13 @@ internal open class LicenseInjector @Inject constructor(@Internal val extension: DependencyScanner(project, extension).scanForLicenseData() // true if there was any work done - didWork = buildLicenseFiles(outputDir, licenses) && buildLicenseFiles(rootDir, licenses) + didWork = buildLicenseFiles(outputBuildDir, licenses, true) && buildLicenseFiles(project.rootDir, licenses, false) } /** * @return true when there is work that needs to be done */ private fun checkLicenseFiles(outputDir: File, licenses: MutableList): Boolean { - var needsToDoWork = false if (!outputDir.exists()) outputDir.mkdirs() val licenseText = LicenseData.buildString(licenses) @@ -59,29 +67,26 @@ internal open class LicenseInjector @Inject constructor(@Internal val extension: if (fileIsNotSame(licenseFile, licenseText)) { // write out the LICENSE and various license files - needsToDoWork = true + return true } - if (!needsToDoWork) { - licenses.forEach { - val license = it.license - val file = File(outputDir, license.licenseFile) - val sourceText = license.licenseText + licenses.forEach { + val license = it.license + val file = File(outputDir, license.licenseFile) + val sourceText = license.licenseText - if (fileIsNotSame(file, sourceText)) { - needsToDoWork = true - } + if (fileIsNotSame(file, sourceText)) { + return true } } - - return needsToDoWork + return false } /** * @return true when there is work that has be done */ - private fun buildLicenseFiles(outputDir: File, licenses: MutableList): Boolean { + private fun buildLicenseFiles(outputDir: File, licenses: MutableList, buildLicenseBlob: Boolean): Boolean { var hasDoneWork = false if (!outputDir.exists()) outputDir.mkdirs() @@ -97,20 +102,36 @@ internal open class LicenseInjector @Inject constructor(@Internal val extension: // write out the LICENSE files licenseFile.writeText(licenseText) - // save off the blob, so we can check when reading dependencies if we can - // import this license info as extra license info for the project - ObjectOutputStream(FileOutputStream(licenseBlob)).use { oos -> - oos.writeInt(licenses.size) + if (buildLicenseBlob) { + // save off the blob, so we can check when reading dependencies if we can + // import this license info as extra license info for the project + ObjectOutputStream(FileOutputStream(licenseBlob)).use { oos -> + oos.writeInt(licenses.size) - licenses.forEach { - it.writeObject(oos) + licenses.forEach { + it.writeObject(oos) + } } } hasDoneWork = true } - licenses.forEach { + // for the license files, we have to FLATTEN the list of licenses! + val flattenedLicenses = mutableSetOf() + val scanningLicenses: LinkedList = LinkedList() + scanningLicenses.addAll(licenses) + + while(scanningLicenses.isNotEmpty()) { + val license = scanningLicenses.remove() + val wasAdded = flattenedLicenses.add(license) + if (wasAdded) { + // should always be added, but MAYBE there is a loop somewhere. this hopefully prevents that + scanningLicenses.addAll(license.extras) + } + } + + flattenedLicenses.forEach { val license = it.license val file = File(outputDir, license.licenseFile) val sourceText = license.licenseText diff --git a/src/dorkbox/license/LicensePlugin.kt b/src/dorkbox/license/LicensePlugin.kt index d561fb7..82fecc5 100644 --- a/src/dorkbox/license/LicensePlugin.kt +++ b/src/dorkbox/license/LicensePlugin.kt @@ -19,6 +19,7 @@ import License import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.plugins.JavaPlugin @@ -41,21 +42,14 @@ import java.util.zip.ZipInputStream class LicensePlugin : Plugin { override fun apply(project: Project) { - val outputDir = File(project.buildDir, "licensing") - // Create the Plugin extension object (for users to configure our execution). - val extension: Licensing = project.extensions.create(Licensing.NAME, Licensing::class.java, project, outputDir) + val extension: Licensing = project.extensions.create(Licensing.NAME, Licensing::class.java, project) - val licenseInjector = project.tasks.create("generateLicenseFiles", LicenseInjector::class.java, extension).apply { + val licenseInjector = project.tasks.create("generateLicenseFiles", LicenseInjector::class.java, project, extension).apply { group = "other" } - licenseInjector.outputDir = outputDir - licenseInjector.rootDir = project.rootDir - licenseInjector.licenses = extension.licenses - project.afterEvaluate { prj -> - // the task will only build files that it needs to (and will only run once) prj.tasks.forEach { when (it) { @@ -64,6 +58,16 @@ class LicensePlugin : Plugin { } } + // Make sure to cleanup the generated license files on clean + project.tasks.getByName("clean").apply { + // delete the license info + extension.output().forEach { + if (it.exists()) { + it.delete() + } + } + } + prj.configurations.asIterable().forEach { extension.projectDependencies.addAll(it.dependencies) } val licensing = extension.licenses @@ -82,7 +86,7 @@ class LicensePlugin : Plugin { if (MavenPublication::class.java.isAssignableFrom(it.javaClass)) { it as MavenPublication - // add the license information. ONLY THE FIRST ONE! + // get the license information. ONLY FROM THE FIRST ONE! (which is the license for our project) val licenseData = extension.licenses.first() val license = licenseData.license it.pom.licenses { licSpec -> diff --git a/src/dorkbox/license/Licensing.kt b/src/dorkbox/license/Licensing.kt index 1b9140a..48be841 100644 --- a/src/dorkbox/license/Licensing.kt +++ b/src/dorkbox/license/Licensing.kt @@ -21,7 +21,7 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Dependency import java.io.File -open class Licensing(project: Project, private val outputDir: File) { +open class Licensing(private val project: Project) { private val projectName = project.name val projectDependencies = mutableListOf() @@ -32,13 +32,15 @@ open class Licensing(project: Project, private val outputDir: File) { * Gets a list of files, representing the on-disk location of each generated license file */ fun output() : List { + val outputBuilDir = File(project.buildDir, "licensing") + val files = mutableSetOf() - files.add(File(outputDir, LicenseInjector.LICENSE_FILE)) - files.add(File(outputDir, LicenseInjector.LICENSE_BLOB)) + files.add(File(outputBuilDir, LicenseInjector.LICENSE_FILE)) + files.add(File(outputBuilDir, LicenseInjector.LICENSE_BLOB)) licenses.forEach { - files.add(File(outputDir, it.license.licenseFile)) + files.add(File(outputBuilDir, it.license.licenseFile)) } return files.toList()