Fix unknown licenses (#31223)
The goal of this commit is to address unknown licenses when producing the dependencies info report. We have two different checks that we run on licenses. The first check is whether or not we have stashed a copy of the license text for a dependency in the repository. The second is to map every dependency to a license type (e.g., BSD 3-clause). The problem here is that the way we were handling licenses in the second check differs from how we handle licenses in the first check. The first check works by finding a license file with the name of the artifact followed by the text -LICENSE.txt. Yet in some cases we allow mapping an artifact name to another name used to check for the license (e.g., we map lucene-.* to lucene, and opensaml-.* to shibboleth. The second check understood the first way of looking for a license file but not the second way. So in this commit we teach the second check about the mappings from artifact names to license names. We do this by copying the configuration from the dependencyLicenses task to the dependenciesInfo task and then reusing the code from the first check in the second check. There were some other challenges here though. For example, dependenciesInfo was checking too many dependencies. For now, we should only be checking direct dependencies and leaving transitive dependencies from another org.elasticsearch artifact to that artifact (we want to do this differently in a follow-up). We also want to disable dependenciesInfo for projects that we do not publish, users only care about licenses they might be exposed to if they use our assembled products. With all of the changes in this commit we have eliminated all unknown licenses. A follow-up will enforce that when we add a new dependency it does not get mapped to unknown, these will be forbidden in the future. Therefore, with this change and earlier changes are left having no unknown licenses and two custom licenses; custom here means it does not map to an SPDX license type. Those two licenses are xz and ldapsdk. A future change will not allow additional custom licenses unless they are explicitly whitelisted. This ensures that if a new dependency is added it is mapped to an SPDX license or mapped to custom because it does not have an SPDX license.
This commit is contained in:
parent
3db1fe7afe
commit
65c107b47d
|
@ -543,7 +543,7 @@ subprojects { project ->
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove assemble on all qa projects because we don't need to publish
|
/* Remove assemble/dependenciesInfo on all qa projects because we don't need to publish
|
||||||
* artifacts for them. */
|
* artifacts for them. */
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
subprojects {
|
subprojects {
|
||||||
|
@ -553,6 +553,11 @@ gradle.projectsEvaluated {
|
||||||
project.tasks.remove(assemble)
|
project.tasks.remove(assemble)
|
||||||
project.build.dependsOn.remove('assemble')
|
project.build.dependsOn.remove('assemble')
|
||||||
}
|
}
|
||||||
|
Task dependenciesInfo = project.tasks.findByName('dependenciesInfo')
|
||||||
|
if (dependenciesInfo) {
|
||||||
|
project.tasks.remove(dependenciesInfo)
|
||||||
|
project.precommit.dependsOn.remove('dependenciesInfo')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -762,6 +762,10 @@ class BuildPlugin implements Plugin<Project> {
|
||||||
|
|
||||||
private static configureDependenciesInfo(Project project) {
|
private static configureDependenciesInfo(Project project) {
|
||||||
Task deps = project.tasks.create("dependenciesInfo", DependenciesInfoTask.class)
|
Task deps = project.tasks.create("dependenciesInfo", DependenciesInfoTask.class)
|
||||||
deps.dependencies = project.configurations.compile.allDependencies
|
deps.runtimeConfiguration = project.configurations.runtime
|
||||||
|
deps.compileOnlyConfiguration = project.configurations.compileOnly
|
||||||
|
project.afterEvaluate {
|
||||||
|
deps.mappings = project.dependencyLicenses.mappings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,19 @@
|
||||||
|
|
||||||
package org.elasticsearch.gradle
|
package org.elasticsearch.gradle
|
||||||
|
|
||||||
|
import org.elasticsearch.gradle.precommit.DependencyLicensesTask
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.artifacts.Configuration
|
||||||
import org.gradle.api.artifacts.Dependency
|
import org.gradle.api.artifacts.Dependency
|
||||||
|
import org.gradle.api.artifacts.DependencyResolutionListener
|
||||||
import org.gradle.api.artifacts.DependencySet
|
import org.gradle.api.artifacts.DependencySet
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
import org.gradle.api.tasks.InputDirectory
|
import org.gradle.api.tasks.InputDirectory
|
||||||
import org.gradle.api.tasks.OutputFile
|
import org.gradle.api.tasks.OutputFile
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task to gather information about the dependencies and export them into a csv file.
|
* A task to gather information about the dependencies and export them into a csv file.
|
||||||
|
@ -44,7 +49,14 @@ public class DependenciesInfoTask extends DefaultTask {
|
||||||
|
|
||||||
/** Dependencies to gather information from. */
|
/** Dependencies to gather information from. */
|
||||||
@Input
|
@Input
|
||||||
public DependencySet dependencies
|
public Configuration runtimeConfiguration
|
||||||
|
|
||||||
|
/** We subtract compile-only dependencies. */
|
||||||
|
@Input
|
||||||
|
public Configuration compileOnlyConfiguration
|
||||||
|
|
||||||
|
@Input
|
||||||
|
public LinkedHashMap<String, String> mappings
|
||||||
|
|
||||||
/** Directory to read license files */
|
/** Directory to read license files */
|
||||||
@InputDirectory
|
@InputDirectory
|
||||||
|
@ -59,15 +71,34 @@ public class DependenciesInfoTask extends DefaultTask {
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
public void generateDependenciesInfo() {
|
public void generateDependenciesInfo() {
|
||||||
|
|
||||||
|
final DependencySet runtimeDependencies = runtimeConfiguration.getAllDependencies()
|
||||||
|
// we have to resolve the transitive dependencies and create a group:artifactId:version map
|
||||||
|
final Set<String> compileOnlyArtifacts =
|
||||||
|
compileOnlyConfiguration
|
||||||
|
.getResolvedConfiguration()
|
||||||
|
.resolvedArtifacts
|
||||||
|
.collect { it -> "${it.moduleVersion.id.group}:${it.moduleVersion.id.name}:${it.moduleVersion.id.version}" }
|
||||||
|
|
||||||
final StringBuilder output = new StringBuilder()
|
final StringBuilder output = new StringBuilder()
|
||||||
|
|
||||||
for (Dependency dependency : dependencies) {
|
for (final Dependency dependency : runtimeDependencies) {
|
||||||
// Only external dependencies are checked
|
// we do not need compile-only dependencies here
|
||||||
if (dependency.group != null && dependency.group.contains("elasticsearch") == false) {
|
if (compileOnlyArtifacts.contains("${dependency.group}:${dependency.name}:${dependency.version}")) {
|
||||||
final String url = createURL(dependency.group, dependency.name, dependency.version)
|
continue
|
||||||
final String licenseType = getLicenseType(dependency.group, dependency.name)
|
|
||||||
output.append("${dependency.group}:${dependency.name},${dependency.version},${url},${licenseType}\n")
|
|
||||||
}
|
}
|
||||||
|
// only external dependencies are checked
|
||||||
|
if (dependency.group != null && dependency.group.contains("org.elasticsearch")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
final String url = createURL(dependency.group, dependency.name, dependency.version)
|
||||||
|
final String dependencyName = DependencyLicensesTask.getDependencyName(mappings, dependency.name)
|
||||||
|
logger.info("mapped dependency ${dependency.group}:${dependency.name} to ${dependencyName} for license info")
|
||||||
|
|
||||||
|
final String licenseType = getLicenseType(dependency.group, dependencyName)
|
||||||
|
output.append("${dependency.group}:${dependency.name},${dependency.version},${url},${licenseType}\n")
|
||||||
|
|
||||||
}
|
}
|
||||||
outputFile.setText(output.toString(), 'UTF-8')
|
outputFile.setText(output.toString(), 'UTF-8')
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,10 @@ public class DependencyLicensesTask extends DefaultTask {
|
||||||
mappings.put(from, to)
|
mappings.put(from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LinkedHashMap<String, String> getMappings() {
|
||||||
|
return new LinkedHashMap<>(mappings)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a rule which will skip SHA checking for the given dependency name. This should be used for
|
* Add a rule which will skip SHA checking for the given dependency name. This should be used for
|
||||||
* locally build dependencies, which cause the sha to change constantly.
|
* locally build dependencies, which cause the sha to change constantly.
|
||||||
|
@ -129,10 +133,6 @@ public class DependencyLicensesTask extends DefaultTask {
|
||||||
throw new GradleException("Licences dir ${licensesDir} does not exist, but there are dependencies")
|
throw new GradleException("Licences dir ${licensesDir} does not exist, but there are dependencies")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// order is the same for keys and values iteration since we use a linked hashmap
|
|
||||||
List<String> mapped = new ArrayList<>(mappings.values())
|
|
||||||
Pattern mappingsPattern = Pattern.compile('(' + mappings.keySet().join(')|(') + ')')
|
|
||||||
Map<String, Integer> licenses = new HashMap<>()
|
Map<String, Integer> licenses = new HashMap<>()
|
||||||
Map<String, Integer> notices = new HashMap<>()
|
Map<String, Integer> notices = new HashMap<>()
|
||||||
Set<File> shaFiles = new HashSet<File>()
|
Set<File> shaFiles = new HashSet<File>()
|
||||||
|
@ -162,16 +162,10 @@ public class DependencyLicensesTask extends DefaultTask {
|
||||||
checkSha(dependency, jarName, shaFiles)
|
checkSha(dependency, jarName, shaFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Checking license/notice for " + depName)
|
final String dependencyName = getDependencyName(mappings, depName)
|
||||||
Matcher match = mappingsPattern.matcher(depName)
|
logger.info("mapped dependency name ${depName} to ${dependencyName} for license/notice check")
|
||||||
if (match.matches()) {
|
checkFile(dependencyName, jarName, licenses, 'LICENSE')
|
||||||
int i = 0
|
checkFile(dependencyName, jarName, notices, 'NOTICE')
|
||||||
while (i < match.groupCount() && match.group(i + 1) == null) ++i;
|
|
||||||
logger.info("Mapped dependency name ${depName} to ${mapped.get(i)} for license check")
|
|
||||||
depName = mapped.get(i)
|
|
||||||
}
|
|
||||||
checkFile(depName, jarName, licenses, 'LICENSE')
|
|
||||||
checkFile(depName, jarName, notices, 'NOTICE')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
licenses.each { license, count ->
|
licenses.each { license, count ->
|
||||||
|
@ -189,6 +183,19 @@ public class DependencyLicensesTask extends DefaultTask {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDependencyName(final LinkedHashMap<String, String> mappings, final String dependencyName) {
|
||||||
|
// order is the same for keys and values iteration since we use a linked hashmap
|
||||||
|
List<String> mapped = new ArrayList<>(mappings.values())
|
||||||
|
Pattern mappingsPattern = Pattern.compile('(' + mappings.keySet().join(')|(') + ')')
|
||||||
|
Matcher match = mappingsPattern.matcher(dependencyName)
|
||||||
|
if (match.matches()) {
|
||||||
|
int i = 0
|
||||||
|
while (i < match.groupCount() && match.group(i + 1) == null) ++i;
|
||||||
|
return mapped.get(i)
|
||||||
|
}
|
||||||
|
return dependencyName
|
||||||
|
}
|
||||||
|
|
||||||
private File getShaFile(String jarName) {
|
private File getShaFile(String jarName) {
|
||||||
return new File(licensesDir, jarName + SHA_EXTENSION)
|
return new File(licensesDir, jarName + SHA_EXTENSION)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ esplugin {
|
||||||
tasks.remove(assemble)
|
tasks.remove(assemble)
|
||||||
build.dependsOn.remove('assemble')
|
build.dependsOn.remove('assemble')
|
||||||
|
|
||||||
|
dependencyLicenses.enabled = false
|
||||||
|
dependenciesInfo.enabled = false
|
||||||
|
|
||||||
compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked"
|
compileJava.options.compilerArgs << "-Xlint:-cast,-deprecation,-rawtypes,-try,-unchecked"
|
||||||
|
|
||||||
// no unit tests
|
// no unit tests
|
||||||
|
|
|
@ -32,6 +32,11 @@ Collection distributions = project('archives').subprojects + project('packages')
|
||||||
|
|
||||||
// Concatenates the dependencies CSV files into a single file
|
// Concatenates the dependencies CSV files into a single file
|
||||||
task generateDependenciesReport(type: ConcatFilesTask) {
|
task generateDependenciesReport(type: ConcatFilesTask) {
|
||||||
|
project.rootProject.allprojects {
|
||||||
|
afterEvaluate {
|
||||||
|
if (it.tasks.findByName("dependenciesInfo")) dependsOn it.tasks.dependenciesInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
files = fileTree(dir: project.rootDir, include: '**/dependencies.csv' )
|
files = fileTree(dir: project.rootDir, include: '**/dependencies.csv' )
|
||||||
headerLine = "name,version,url,license"
|
headerLine = "name,version,url,license"
|
||||||
target = new File(System.getProperty('csv')?: "${project.buildDir}/dependencies/es-dependencies.csv")
|
target = new File(System.getProperty('csv')?: "${project.buildDir}/dependencies/es-dependencies.csv")
|
||||||
|
|
|
@ -22,3 +22,5 @@ test.enabled = false
|
||||||
// Not published so no need to assemble
|
// Not published so no need to assemble
|
||||||
tasks.remove(assemble)
|
tasks.remove(assemble)
|
||||||
build.dependsOn.remove('assemble')
|
build.dependsOn.remove('assemble')
|
||||||
|
|
||||||
|
dependenciesInfo.enabled = false
|
||||||
|
|
|
@ -28,5 +28,9 @@ gradle.projectsEvaluated {
|
||||||
project.tasks.remove(assemble)
|
project.tasks.remove(assemble)
|
||||||
project.build.dependsOn.remove('assemble')
|
project.build.dependsOn.remove('assemble')
|
||||||
}
|
}
|
||||||
|
Task dependenciesInfo = project.tasks.findByName('dependenciesInfo')
|
||||||
|
if (dependenciesInfo) {
|
||||||
|
project.precommit.dependsOn.remove('dependenciesInfo')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ dependencies {
|
||||||
test.enabled = false
|
test.enabled = false
|
||||||
|
|
||||||
dependencyLicenses.enabled = false
|
dependencyLicenses.enabled = false
|
||||||
|
dependenciesInfo.enabled = false
|
||||||
|
|
||||||
// the main files are actually test files, so use the appropriate forbidden api sigs
|
// the main files are actually test files, so use the appropriate forbidden api sigs
|
||||||
forbiddenApisMain {
|
forbiddenApisMain {
|
||||||
|
|
|
@ -10,6 +10,7 @@ dependencies {
|
||||||
forbiddenApisMain.enabled = true
|
forbiddenApisMain.enabled = true
|
||||||
|
|
||||||
dependencyLicenses.enabled = false
|
dependencyLicenses.enabled = false
|
||||||
|
dependenciesInfo.enabled = false
|
||||||
|
|
||||||
jarHell.enabled = false
|
jarHell.enabled = false
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue