Suppress missing license information for sub-projects in a multi-project build.

This commit is contained in:
Robinson 2022-11-16 01:03:36 +01:00
parent b2bb05e6cc
commit 5d7e14d380
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
4 changed files with 73 additions and 35 deletions

View File

@ -15,9 +15,10 @@ object LicenseDependencyScanner {
// scans and loads license data into the extension // scans and loads license data into the extension
// - from jars on runtime/compile classpath // - from jars on runtime/compile classpath
data class ScanDep(val project: Project, val preloadedText: MutableList<String>, val embeddedText: MutableList<String>, val missingText: MutableList<String>)
// THIS MUST BE IN "afterEvaluate" or run from a specific task. // THIS MUST BE IN "afterEvaluate" or run from a specific task.
fun scanForLicenseData(project: Project, allProjects: Boolean, fun scanForLicenseData(project: Project, allProjects: Boolean, licenses: MutableList<LicenseData>): ScanDep {
licenses: MutableList<LicenseData>): Triple<MutableList<String>, MutableList<String>, MutableList<String>> {
val preloadedText = mutableListOf<String>(); val preloadedText = mutableListOf<String>();
val embeddedText = mutableListOf<String>(); val embeddedText = mutableListOf<String>();
@ -25,7 +26,7 @@ object LicenseDependencyScanner {
// NOTE: there will be some duplicates, so we want to remove them // NOTE: there will be some duplicates, so we want to remove them
val dependencies = mutableSetOf<Dependency>() val dependencies = mutableSetOf<ProjAndDependency>()
if (allProjects) { if (allProjects) {
project.allprojects.forEach { proj -> project.allprojects.forEach { proj ->
@ -39,36 +40,46 @@ object LicenseDependencyScanner {
dependencies.addAll(scan(project, "runtimeClasspath")) dependencies.addAll(scan(project, "runtimeClasspath"))
} }
// this will contain duplicates if sub-projects ALSO have the same deps
val projectDependencies = dependencies.toList() val projectDependencies = dependencies.toList()
val missingLicenseInfo = mutableSetOf<Dependency>() val missingLicenseInfo = mutableSetOf<ProjAndDependency>()
val actuallyMissingLicenseInfo = mutableSetOf<Dependency>() val actuallyMissingLicenseInfo = mutableSetOf<ProjAndDependency>()
val alreadyScanDeps = mutableSetOf<Dependency>()
if (licenses.isNotEmpty()) { if (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) // 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)
val primaryLicense = licenses.first() val primaryLicense = licenses.first()
// scan to see if we have in our predefined section // scan to see if we have in our predefined section
projectDependencies.forEach { info: Dependency -> projectDependencies.forEach { projAndDep: ProjAndDependency ->
val dep = projAndDep.dep
if (alreadyScanDeps.contains(dep)) {
return@forEach
}
val data: LicenseData? = try { val data: LicenseData? = try {
AppLicensing.getLicense(info.mavenId()) AppLicensing.getLicense(dep.mavenId())
} catch (e: Exception) { } catch (e: Exception) {
println("\tError getting license information for ${info.mavenId()}") println("\tError getting license information for ${dep.mavenId()}")
null null
} }
if (data == null) { if (data == null) {
missingLicenseInfo.add(info) missingLicenseInfo.add(projAndDep)
} else { } else {
if (!primaryLicense.extras.contains(data)) { if (!primaryLicense.extras.contains(data)) {
preloadedText.add("\t\t${info.mavenId()} [${data.license}]") alreadyScanDeps.add(dep)
preloadedText.add("\t\t${dep.mavenId()} [${data.license}]")
// NOTE: the END copyright for these are determined by the DATE of the files! // NOTE: the END copyright for these are determined by the DATE of the files!
// Some dates are WRONG (because the jar build is mucked with), so we manually fix it // Some dates are WRONG (because the jar build is mucked with), so we manually fix it
if (data.copyright == 0) { if (data.copyright == 0) {
// get the OLDEST date from the artifacts and use that as the copyright date // get the OLDEST date from the artifacts and use that as the copyright date
var oldestDate = 0L var oldestDate = 0L
info.artifacts.forEach { artifact -> dep.artifacts.forEach { artifact ->
// get the date of the manifest file (which is the first entry) // get the date of the manifest file (which is the first entry)
ZipInputStream(FileInputStream(artifact.file)).use { ZipInputStream(FileInputStream(artifact.file)).use {
oldestDate = oldestDate.coerceAtLeast(it.nextEntry.lastModifiedTime.toMillis()) oldestDate = oldestDate.coerceAtLeast(it.nextEntry.lastModifiedTime.toMillis())
@ -96,10 +107,15 @@ object LicenseDependencyScanner {
// now scan to see if the jar has a license blob in it // now scan to see if the jar has a license blob in it
if (missingLicenseInfo.isNotEmpty()) { if (missingLicenseInfo.isNotEmpty()) {
missingLicenseInfo.forEach { info -> missingLicenseInfo.forEach { projAndDep: ProjAndDependency ->
val dep = projAndDep.dep
if (alreadyScanDeps.contains(dep)) {
return@forEach
}
// see if we have it in the dependency jar // see if we have it in the dependency jar
var licenseData: License? = null var licenseData: License? = null
info.artifacts.forEach search@{ artifact -> dep.artifacts.forEach search@{ artifact ->
val file = artifact.file val file = artifact.file
try { try {
if (file.canRead()) { if (file.canRead()) {
@ -125,7 +141,7 @@ object LicenseDependencyScanner {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
println("\t$info [ERROR $file], ${e.message ?: e.javaClass}") println("\t$dep [ERROR $file], ${e.message ?: e.javaClass}")
} }
return@search return@search
@ -134,27 +150,48 @@ object LicenseDependencyScanner {
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
println("\t$info [ERROR $file], ${e.message ?: e.javaClass}") println("\t$dep [ERROR $file], ${e.message ?: e.javaClass}")
} }
} }
if (licenseData != null) { if (licenseData != null) {
embeddedText.add("\t\t$info [$licenseData]") alreadyScanDeps.add(dep)
embeddedText.add("\t\t$dep [$licenseData]")
} else { } else {
actuallyMissingLicenseInfo.add(info) actuallyMissingLicenseInfo.add(projAndDep)
} }
} }
} }
if (actuallyMissingLicenseInfo.isNotEmpty()) { if (actuallyMissingLicenseInfo.isNotEmpty()) {
actuallyMissingLicenseInfo.forEach { missingDep -> // we have to prune sub-project data first...
missingText.add("\t ${missingDep.mavenId()}") val projectMavenIds = mutableSetOf<String>()
project.allprojects.forEach {
projectMavenIds.add("${it.group}:${it.name}:${it.version}")
}
actuallyMissingLicenseInfo.forEach { missingDepAndProj ->
// we DO NOT want to show missing deps for project sub-projects.
val proj = missingDepAndProj.project
val dep = missingDepAndProj.dep
if (alreadyScanDeps.contains(dep)) {
return@forEach
}
alreadyScanDeps.add(dep)
val mavenId = dep.mavenId()
// if the missing dep IS ALSO the same as a subproject .... we don't want to report it
if (!projectMavenIds.contains(mavenId)) {
missingText.add("\t ${dep.mavenId()}")
}
} }
} }
} }
return Triple(preloadedText, embeddedText, missingText) return ScanDep(project, preloadedText, embeddedText, missingText)
} }
@ -167,9 +204,9 @@ object LicenseDependencyScanner {
* *
* This is an actual problem... * This is an actual problem...
*/ */
private fun scan(project: Project, configurationName: String): List<Dependency> { private fun scan(project: Project, configurationName: String): List<ProjAndDependency> {
val projectDependencies = mutableListOf<Dependency>() val projectDependencies = mutableListOf<ProjAndDependency>()
val config = project.configurations.getByName(configurationName) val config = project.configurations.getByName(configurationName)
if (!config.isCanBeResolved) { if (!config.isCanBeResolved) {
return projectDependencies return projectDependencies
@ -204,16 +241,19 @@ object LicenseDependencyScanner {
} catch (e: Exception) { } catch (e: Exception) {
listOf() listOf()
} }
projectDependencies.add(Dependency(group, name, version, artifacts)) projectDependencies.add(ProjAndDependency(project, Dependency(group, name, version, artifacts)))
} }
return projectDependencies return projectDependencies
} }
internal data class Dependency(val group: String, internal data class ProjAndDependency(val project: Project, val dep: Dependency)
val name: String,
val version: String, internal data class Dependency(
val artifacts: List<Artifact>) { val group: String,
val name: String,
val version: String,
val artifacts: List<Artifact>) {
fun mavenId(): String { fun mavenId(): String {
return "$group:$name:$version" return "$group:$name:$version"

View File

@ -36,7 +36,7 @@ internal open class LicenseInjector @Inject constructor(@Internal val extension:
// This MUST be first, since it loads license data that is used elsewhere // This MUST be first, since it loads license data that is used elsewhere
// show scanning or missing, but not both // show scanning or missing, but not both
// NOTE: we scan the dependencies in ALL subprojects as well. // NOTE: we scan the dependencies in ALL subprojects as well.
val (preloadedText, embeddedText, missingText) = extension.scanDependencies(project, true) val (proj, preloadedText, embeddedText, missingText) = extension.scanDependencies(project, true)
// validate the license text configuration section in the gradle file ONLY WHEN PUSHING A JAR // validate the license text configuration section in the gradle file ONLY WHEN PUSHING A JAR
val licensing = extension.licenses val licensing = extension.licenses

View File

@ -53,7 +53,3 @@ class LicensePlugin : Plugin<Project> {
} }
} }
} }

View File

@ -181,14 +181,16 @@ open class Licensing(private val project: Project) {
} }
// scan as part of the plugin // scan as part of the plugin
fun scanDependencies(project: Project, allProjects: Boolean): Triple<MutableList<String>, MutableList<String>, MutableList<String>> { fun scanDependencies(project: Project, allProjects: Boolean): LicenseDependencyScanner.ScanDep {
// now we want to add license information that we know about from our dependencies to our list // 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+ // 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. // we associate the artifact group + id + (start) version as a license.
// if a license for a dependency is UNKNOWN, then we emit a warning to the user to add it as a pull request // if a license for a dependency is UNKNOWN, then we emit a warning to the user to add it as a pull request
// if a license version is not specified, then we use the default // if a license version is not specified, then we use the default
val textOutput = LicenseDependencyScanner.scanForLicenseData(project, allProjects, this.licenses) val depInfo = LicenseDependencyScanner.scanForLicenseData(project, allProjects, this.licenses)
// we only should include the kotlin license information IF we actually use kotlin. // we only should include the kotlin license information IF we actually use kotlin.
@ -211,7 +213,7 @@ open class Licensing(private val project: Project) {
} }
} }
return textOutput return depInfo
} }
/** /**