Created
February 28, 2018 09:42
-
-
Save iamutkarshtiwari/8a99b51cedf695367d2fc9411a76cebe to your computer and use it in GitHub Desktop.
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
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