Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save iamutkarshtiwari/8a99b51cedf695367d2fc9411a76cebe to your computer and use it in GitHub Desktop.
Save iamutkarshtiwari/8a99b51cedf695367d2fc9411a76cebe to your computer and use it in GitHub Desktop.
package com.cookpad.android.licensetools
import groovy.json.JsonBuilder
import groovy.util.slurpersupport.GPathResult
import groovyjarjarantlr.StringUtils
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ResolvedArtifact
import org.xml.sax.helpers.DefaultHandler
import org.yaml.snakeyaml.Yaml
class LicenseToolsPlugin implements Plugin<Project> {
final yaml = new Yaml()
final DependencySet librariesYaml = new DependencySet() // based on libraries.yml
final DependencySet dependencyLicenses = new DependencySet() // based on license plugin's dependency-license.xml
@Override
void apply(Project project) {
project.extensions.add(LicenseToolsExtension.NAME, LicenseToolsExtension)
def checkLicenses = project.task('checkLicenses').doLast {
initialize(project)
def notInDependencies = librariesYaml.notListedIn(dependencyLicenses)
addExistingLicences(project.file(ext.licensesYaml))
def notDocumented = dependencyLicenses.notListedIn(librariesYaml)
def licensesNotMatched = dependencyLicenses.licensesNotMatched(librariesYaml)
if (notDocumented.empty && notInDependencies.empty && licensesNotMatched.empty) {
project.logger.info("checkLicenses: ok")
return
}
LicenseToolsExtension ext = project.extensions.findByType(LicenseToolsExtension)
if (notDocumented.size() > 0) {
project.logger.warn("# Libraries not listed in ${ext.licensesYaml}: are being added...")
def outFile = ext.licensesYaml
def writer = outFile.newWriter()
// Add the licences not listed yet
notDocumented.each { libraryInfo ->
// def message = new StringBuffer()
writer.append("- artifact: ${libraryInfo.artifactId.withWildcardVersion()}\n")
writer.append(" name: ${libraryInfo.escapedName ?: "#NAME#"}\n")
if (libraryInfo.getCopyrightHolder().length() == 0) {
writer.append(" copyrightHolder: ${libraryInfo.copyrightHolder ?: "#COPYRIGHT_HOLDER#"}\n")
} else {
writer.append(" copyrightHolder: ${libraryInfo.getCopyrightHolder()}\n")
}
if (libraryInfo.getLicense().length() == 0) {
writer.append(" license: ${libraryInfo.license ?: "#LICENSE"}\n")
} else {
writer.append(" license: ${libraryInfo.getLicense()}\n")
}
if (libraryInfo.getLicenseUrl().length() == 0) {
writer.append(" licenseUrl: ${libraryInfo.licenseUrl ?: "#LICENSEURL#"}\n")
} else {
writer.append(" licenseUrl: ${libraryInfo.getLicenseUrl().length()}\n")
}
if (libraryInfo.getUrl().length() == 0) {
writer.append(" url: ${libraryInfo.url ?: "#URL#"}\n")
} else {
writer.append(" url: ${libraryInfo.getUrl()}\n")
}
if (libraryInfo.getSkip()) {
writer.append(" skip: ${libraryInfo.getSkip()}\n")
}
}
writer.close()
}
if (notInDependencies.size() > 0) {
project.logger.warn("# Libraries listed in ${ext.licensesYaml} but not in dependencies:")
notInDependencies.each { libraryInfo ->
project.logger.warn("- artifact: ${libraryInfo.artifactId}\n")
}
}
if (licensesNotMatched.size() > 0) {
project.logger.warn("# Licenses not matched with pom.xml in dependencies:")
licensesNotMatched.each { libraryInfo ->
project.logger.warn("- artifact: ${libraryInfo.artifactId}\n license: ${libraryInfo.license}")
}
}
// throw new GradleException("checkLicenses: missing libraries in ${ext.licensesYaml}")
}
checkLicenses.configure {
group = "Verification"
description = 'Check whether dependency licenses are listed in licenses.yml'
}
def generateLicensePage = project.task('generateLicensePage').doLast {
initialize(project)
generateLicensePage(project)
}
generateLicensePage.dependsOn('checkLicenses')
def generateLicenseJson = project.task('generateLicenseJson').doLast {
initialize(project)
generateLicenseJson(project)
}
generateLicenseJson.dependsOn('checkLicenses')
project.tasks.findByName("check").dependsOn('checkLicenses')
}
void addExistingLicences(File yamlFile) {
def outFile = yamlFile
def writer = outFile.newWriter()
// Rewrite all the existing libraries
if (librariesYaml.size() > 0) {
librariesYaml.each { libraryInfo ->
if (!notInDependencies.contains(libraryInfo.getArtifactId())) {
writer.append("- artifact: ${libraryInfo.artifactId.withWildcardVersion()}\n")
writer.append(" name: ${libraryInfo.getEscapedName()}\n")
writer.append(" copyrightHolder: ${libraryInfo.getCopyrightHolder()}\n")
writer.append(" license: ${libraryInfo.getLicense()}\n")
writer.append(" licenseUrl: ${libraryInfo.getLicenseUrl()}\n")
writer.append(" url: ${libraryInfo.getUrl()}\n")
if (libraryInfo.getSkip()) {
writer.append(" skip: ${libraryInfo.getSkip()}\n")
}
}
}
}
writer.close()
}
void initialize(Project project) {
LicenseToolsExtension ext = project.extensions.findByType(LicenseToolsExtension)
loadLibrariesYaml(project.file(ext.licensesYaml))
loadDependencyLicenses(project, ext.ignoredGroups, ext.ignoredProjects)
cleanYaml(project.file(ext.licensesYaml))
}
void cleanYaml(File yamlFile) {
def outFile = yamlFile
def writer = outFile.newWriter()
writer << ""
writer.close()
}
void loadLibrariesYaml(File licensesYaml) {
if (!licensesYaml.exists()) {
return
}
def libraries = loadYaml(licensesYaml)
for (lib in libraries) {
def libraryInfo = LibraryInfo.fromYaml(lib)
librariesYaml.add(libraryInfo)
}
}
void loadDependencyLicenses(Project project, Set<String> ignoredGroups, Set<String> ignoredProjects) {
resolveProjectDependencies(project, ignoredProjects).each { d ->
if (d.moduleVersion.id.version == "unspecified") {
return
}
if (ignoredGroups.contains(d.moduleVersion.id.group)) {
return
}
def dependencyDesc = "$d.moduleVersion.id.group:$d.moduleVersion.id.name:$d.moduleVersion.id.version"
def libraryInfo = new LibraryInfo()
libraryInfo.artifactId = ArtifactId.parse(dependencyDesc)
libraryInfo.filename = d.file
dependencyLicenses.add(libraryInfo)
Dependency pomDependency = project.dependencies.create("$dependencyDesc@pom")
Configuration pomConfiguration = project.configurations.detachedConfiguration(pomDependency)
pomConfiguration.resolve().each {
project.logger.info("POM: ${it}")
}
File pStream
try {
pStream = pomConfiguration.resolve().asList().first()
} catch (Exception e) {
project.logger.warn("Unable to retrieve license for $dependencyDesc")
return
}
XmlSlurper slurper = new XmlSlurper(true, false)
slurper.setErrorHandler(new DefaultHandler())
GPathResult xml = slurper.parse(pStream)
libraryInfo.libraryName = xml.name.text()
libraryInfo.url = xml.url.text()
xml.licenses.license.each {
if (!libraryInfo.license) {
// takes the first license
libraryInfo.license = it.name.text().trim()
libraryInfo.licenseUrl = it.url.text().trim()
}
}
}
}
Map<String, ?> loadYaml(File yamlFile) {
return yaml.load(yamlFile.text) as Map<String, ?> ?: [:]
}
void generateLicensePage(Project project) {
def ext = project.extensions.getByType(LicenseToolsExtension)
def noLicenseLibraries = new ArrayList<LibraryInfo>()
def content = new StringBuilder()
librariesYaml.each { libraryInfo ->
if (libraryInfo.skip) {
project.logger.info("generateLicensePage: skip ${libraryInfo.name}")
return
}
// merge dependencyLicenses's libraryInfo into librariesYaml's
def o = dependencyLicenses.find(libraryInfo.artifactId)
if (o) {
libraryInfo.license = libraryInfo.license ?: o.license
libraryInfo.filename = o.filename
libraryInfo.artifactId = o.artifactId
libraryInfo.url = libraryInfo.url ?: o.url
}
try {
content.append(Templates.buildLicenseHtml(libraryInfo));
} catch (NotEnoughInformationException e) {
noLicenseLibraries.add(e.libraryInfo)
}
}
assertEmptyLibraries(noLicenseLibraries)
def assetsDir = project.file("src/main/assets")
if (!assetsDir.exists()) {
assetsDir.mkdirs()
}
project.logger.info("render ${assetsDir}/${ext.outputHtml}")
project.file("${assetsDir}/${ext.outputHtml}").write(Templates.wrapWithLayout(content))
}
void generateLicenseJson(Project project) {
def ext = project.extensions.getByType(LicenseToolsExtension)
def noLicenseLibraries = new ArrayList<LibraryInfo>()
def json = new JsonBuilder()
def librariesArray = []
librariesYaml.each { libraryInfo ->
if (libraryInfo.skip) {
project.logger.info("generateLicensePage: skip ${libraryInfo.name}")
return
}
// merge dependencyLicenses's libraryInfo into librariesYaml's
def o = dependencyLicenses.find(libraryInfo.artifactId)
if (o) {
libraryInfo.license = libraryInfo.license ?: o.license
// libraryInfo.filename = o.filename
libraryInfo.artifactId = o.artifactId
libraryInfo.url = libraryInfo.url ?: o.url
}
try {
Templates.assertLicenseAndStatement(libraryInfo)
librariesArray << libraryInfo
} catch (NotEnoughInformationException e) {
noLicenseLibraries.add(e.libraryInfo)
}
}
assertEmptyLibraries(noLicenseLibraries)
def assetsDir = project.file("src/main/assets")
if (!assetsDir.exists()) {
assetsDir.mkdirs()
}
json {
libraries librariesArray.collect {
l ->
return [
notice: l.notice,
copyrightHolder: l.copyrightHolder,
copyrightStatement: l.copyrightStatement,
license: l.license,
licenseUrl: l.licenseUrl,
normalizedLicense: l.normalizedLicense,
year: l.year,
url: l.url,
libraryName: l.libraryName,
// I don't why artifactId won't serialize, and this is the only way
// I've found -- vishna
artifactId: [
name: l.artifactId.name,
group: l.artifactId.group,
version: l.artifactId.version,
]
]
}
}
project.logger.info("render ${assetsDir}/${ext.outputJson}")
project.file("${assetsDir}/${ext.outputJson}").write(json.toString())
}
static void assertEmptyLibraries(ArrayList<LibraryInfo> noLicenseLibraries) {
if (noLicenseLibraries.empty) return;
StringBuilder message = new StringBuilder();
message.append("Not enough information for:\n")
message.append("---\n")
noLicenseLibraries.each { libraryInfo ->
message.append("- artifact: ${libraryInfo.artifactId}\n")
message.append(" name: ${libraryInfo.name}\n")
if (!libraryInfo.license) {
message.append(" license: #LICENSE#\n")
}
if (!libraryInfo.copyrightStatement) {
message.append(" copyrightHolder: #AUTHOR# (or authors: [...])\n")
message.append(" year: #YEAR# (optional)\n")
}
}
throw new RuntimeException(message.toString())
}
// originated from https://github.com/hierynomus/license-gradle-plugin DependencyResolver.groovy
Set<ResolvedArtifact> resolveProjectDependencies(Project project, Set<String> ignoredProjects) {
def subprojects = project.rootProject.subprojects.findAll { Project p -> !ignoredProjects.contains(p.name) }
.groupBy { Project p -> "$p.group:$p.name:$p.version" }
List<ResolvedArtifact> runtimeDependencies = []
project.rootProject.subprojects.findAll { Project p -> !ignoredProjects.contains(p.name) }.each { Project subproject ->
runtimeDependencies << subproject.configurations.all.findAll { Configuration c ->
// compile|implementation|api, release(Compile|Implementation|Api), releaseProduction(Compile|Implementation|Api), and so on.
c.name.matches(/^(?:release\w*)?([cC]ompile|[cC]ompileOnly|[iI]mplementation|[aA]pi)$/)
}.collect {
Configuration copyConfiguration = it.copyRecursive()
if (copyConfiguration.metaClass.respondsTo(copyConfiguration, "setCanBeResolved", Boolean)) {
copyConfiguration.setCanBeResolved(true)
}
copyConfiguration.resolvedConfiguration.resolvedArtifacts
}.flatten() as List<ResolvedArtifact>
}
runtimeDependencies = runtimeDependencies.flatten()
runtimeDependencies.removeAll([null])
def seen = new HashSet<String>()
def dependenciesToHandle = new HashSet<ResolvedArtifact>()
runtimeDependencies.each { ResolvedArtifact d ->
String dependencyDesc = "$d.moduleVersion.id.group:$d.moduleVersion.id.name:$d.moduleVersion.id.version"
if (!seen.contains(dependencyDesc)) {
dependenciesToHandle.add(d)
Project subproject = subprojects[dependencyDesc]?.first()
if (subproject) {
dependenciesToHandle.addAll(resolveProjectDependencies(subproject))
}
}
}
return dependenciesToHandle
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment