Skip to content

Commit 5451de0

Browse files
authored
[ISSUE #4830] Generate LICENSE and NOTICE with Gradle tasks (#4831)
* Update com.github.jk1.dependency-license-report to cyclonedx-gradle-plugin * Remove redundant mavenLocal() * Store license files by license name * Sort by version additionally * Stick to bundled dependencies * Auto generate NOTICE file * Add comments * Display "/" for 'Unicode/ICU License' * Rename 'third-party-licenses' to 'dist-license' to suit LICENSE file's meaning * Doing some paperwork * failed to apply plugins block, minor adjust * Remove logback completely * download license if only url is given * delete mysql's license * exempt licenses * minor optimize * Add checkDeniedLicense task * Output success
1 parent 1d0bfba commit 5451de0

File tree

334 files changed

+6902
-56534
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

334 files changed

+6902
-56534
lines changed

Diff for: .licenserc.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@ header:
4444
- 'gradlew'
4545
- 'gradlew.bat'
4646
- '**/*.txt'
47+
- 'tools/dist-license/licenses/**'
4748

4849
comment: on-failure

Diff for: build.gradle

+226-14
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,26 @@
1515
* limitations under the License.
1616
*/
1717

18+
import groovy.json.JsonSlurper
19+
import org.apache.commons.io.IOUtils
20+
import org.apache.http.client.config.RequestConfig
21+
import org.apache.http.client.methods.CloseableHttpResponse
22+
import org.apache.http.client.methods.HttpGet
23+
import org.apache.http.impl.client.CloseableHttpClient
24+
import org.apache.http.impl.client.HttpClients
25+
import org.apache.http.util.EntityUtils
26+
27+
import java.nio.charset.StandardCharsets
28+
import java.nio.file.Files
29+
import java.nio.file.Paths
1830
import java.util.concurrent.TimeUnit
1931

20-
21-
2232
buildscript {
2333
repositories {
24-
mavenLocal()
2534
mavenCentral()
2635
maven {
2736
url "https://maven.aliyun.com/repository/public"
2837
}
29-
3038
maven {
3139
url "https://plugins.gradle.org/m2/"
3240
}
@@ -35,11 +43,18 @@ buildscript {
3543
dependencies {
3644
classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14"
3745
classpath "io.spring.gradle:dependency-management-plugin:1.0.11.RELEASE"
38-
classpath "com.github.jk1:gradle-license-report:1.17"
3946
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"
47+
48+
classpath "org.apache.httpcomponents:httpclient:4.5.13"
49+
classpath "commons-io:commons-io:2.11.0"
4050
}
4151
}
4252

53+
plugins {
54+
id 'org.cyclonedx.bom' version '1.8.2'
55+
id 'com.github.jk1.dependency-license-report' version '2.6'
56+
}
57+
4358
// Remove doclint warnings that pollute javadoc logs when building
4459
if (JavaVersion.current().isJava8()) {
4560
allprojects {
@@ -53,16 +68,15 @@ allprojects {
5368
apply plugin: 'java'
5469
apply plugin: "eclipse"
5570
apply plugin: "idea"
56-
apply plugin: "project-reports"
5771
apply plugin: "maven-publish"
58-
apply plugin: "com.github.spotbugs"
5972
apply plugin: "project-reports"
6073
apply plugin: "jacoco"
6174
apply plugin: "pmd"
6275
apply plugin: "java-library"
6376
apply plugin: 'signing'
6477
apply plugin: 'checkstyle'
6578
apply plugin: 'com.diffplug.spotless'
79+
apply plugin: "com.github.spotbugs"
6680

6781
[compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'
6882

@@ -96,7 +110,6 @@ allprojects {
96110

97111
dependencies {
98112
repositories {
99-
mavenLocal()
100113
mavenCentral()
101114
maven {
102115
url "https://maven.aliyun.com/repository/public"
@@ -124,7 +137,6 @@ allprojects {
124137
removeUnusedImports()
125138
}
126139
}
127-
128140
afterEvaluate {
129141
tasks.forEach {task ->
130142
if (task.name.contains("spotlessJava")) {
@@ -143,6 +155,7 @@ tasks.register('dist') {
143155
subprojects.forEach { subProject ->
144156
dependsOn("${subProject.path}:jar")
145157
}
158+
dependsOn('generateDistLicense', 'generateDistNotice')
146159
def includedProjects =
147160
["eventmesh-common",
148161
"eventmesh-meta:eventmesh-meta-api",
@@ -184,7 +197,7 @@ tasks.register('dist') {
184197
}
185198
}
186199
copy {
187-
from 'tools/third-party-licenses'
200+
from 'tools/dist-license'
188201
into rootProject.file('dist')
189202
}
190203
}
@@ -252,6 +265,206 @@ tasks.register('printProjects') {
252265
})
253266
}
254267

268+
cyclonedxBom {
269+
includeConfigs = ["runtimeClasspath"]
270+
}
271+
272+
tasks.register('generateDistLicense') {
273+
dependsOn('cyclonedxBom') // Task from 'org.cyclonedx.bom' plugin
274+
doLast {
275+
// Inputs
276+
def bomFile = file("$buildDir/reports/bom.json")
277+
def bom = new JsonSlurper().parseText(bomFile.text)
278+
def projectLicenseText = file('LICENSE').text
279+
280+
// Outputs
281+
def distLicenseFile = file('tools/dist-license/LICENSE')
282+
def licensesDir = file('tools/dist-license/licenses/java/')
283+
if (licensesDir.exists()) {
284+
licensesDir.eachFile { it.delete() }
285+
} else {
286+
licensesDir.mkdirs()
287+
}
288+
289+
List<Map<String, String>> thirdPartyArtifacts = new ArrayList<Map<String, String>>()
290+
// Parse BOM
291+
bom.components.each { component ->
292+
// Exclude project modules
293+
if (!component.group.startsWith('org.apache.eventmesh')) {
294+
component.licenses.each { artifactLicense ->
295+
if (artifactLicense.license != null) {
296+
Map<String, String> artifact = new HashMap<String, String>()
297+
artifact.put("name", component.name)
298+
artifact.put("version", component.version)
299+
if (artifactLicense.license.id != null) {
300+
artifact.put("license", artifactLicense.license.id)
301+
if (artifactLicense.license.text != null) {
302+
artifact.put("licenseContent", new String(artifactLicense.license.text.content.decodeBase64()))
303+
}
304+
} else {
305+
artifact.put("license", artifactLicense.license.name)
306+
artifact.put("licenseContent", artifactLicense.license.url)
307+
}
308+
thirdPartyArtifacts.add(artifact)
309+
}
310+
}
311+
}
312+
}
313+
thirdPartyArtifacts.sort { a, b ->
314+
def nameComparison = a.name <=> b.name
315+
if (nameComparison == 0) {
316+
return a.version <=> b.version
317+
} else {
318+
return nameComparison
319+
}
320+
}
321+
322+
def distLicenseText = projectLicenseText + "\n=======================================================================\n" +
323+
"This distribution contains the following third-party artifacts:\n\n"
324+
thirdPartyArtifacts.each { artifact ->
325+
// Write licenses
326+
def artifactLicenseFilename = artifact.license.replaceAll("/", "-") + ".txt"
327+
def artifactLicenseFile = new File(licensesDir, artifactLicenseFilename)
328+
if (artifact.licenseContent != null) {
329+
artifactLicenseFile.text = artifact.licenseContent
330+
if (isURL(artifact.licenseContent)) {
331+
def licenseUrlFilename = artifact.licenseContent.substring(artifact.licenseContent.lastIndexOf("/") + 1)
332+
def downloadedLicenseFilename = artifact.license.replaceAll("/", "-") + "-downloaded-" + licenseUrlFilename
333+
def downloadedLicenseFile = new File(licensesDir, downloadedLicenseFilename)
334+
downloadFileFromURL(artifact.licenseContent, downloadedLicenseFile.path)
335+
}
336+
} else {
337+
artifactLicenseFile.text = "No license content provided by the artifact."
338+
logger.warn("No '${artifact.license}' license content provided by ${artifact.name} ${artifact.version}. Please add manually.")
339+
}
340+
341+
// Assemble LICENSE
342+
distLicenseText += "${artifact.name} ${artifact.version} licensed under '${artifact.license}'. " +
343+
"For details see: licenses/${artifactLicenseFilename}\n"
344+
}
345+
distLicenseFile.text = distLicenseText
346+
}
347+
}
348+
349+
static boolean isURL(String urlString) {
350+
if (!urlString.startsWith("http")) {
351+
return false
352+
}
353+
try {
354+
new URL(urlString)
355+
return true
356+
} catch (MalformedURLException e) {
357+
return false
358+
}
359+
}
360+
361+
void downloadFileFromURL(String urlString, String destinationPath) throws Exception {
362+
int timeout = 5 * 1000
363+
RequestConfig config = RequestConfig.custom()
364+
.setConnectTimeout(timeout)
365+
.setConnectionRequestTimeout(timeout)
366+
.setSocketTimeout(timeout)
367+
.build()
368+
369+
CloseableHttpClient httpClient = HttpClients.custom()
370+
.setDefaultRequestConfig(config)
371+
.build()
372+
373+
HttpGet httpGet = new HttpGet(urlString)
374+
CloseableHttpResponse response
375+
try {
376+
response = httpClient.execute(httpGet)
377+
} catch (Exception e) {
378+
logger.error("Failed to download " + urlString + " : " + e.getMessage())
379+
return
380+
}
381+
382+
if (response.getStatusLine().getStatusCode() == 200) {
383+
try (InputStream is = response.getEntity().getContent()) {
384+
String respContent = IOUtils.toString(is, StandardCharsets.UTF_8)
385+
if (respContent.startsWith("../")) {
386+
// Follow GitHub symlink
387+
URL baseUrl = new URL(urlString);
388+
URL absoluteUrl = new URL(baseUrl, respContent);
389+
downloadFileFromURL(absoluteUrl.toString(), destinationPath);
390+
} else {
391+
Files.write(Paths.get(destinationPath), respContent.getBytes(StandardCharsets.UTF_8))
392+
}
393+
}
394+
} else {
395+
logger.error("Failed to download " + urlString + " : " + response.getStatusLine())
396+
}
397+
398+
EntityUtils.consume(response.getEntity())
399+
response.close()
400+
}
401+
402+
tasks.register('checkDeniedLicense') {
403+
dependsOn('generateDistLicense')
404+
doLast {
405+
def deniedLicenses = [
406+
"MS-LPL", "BUSL-1.1",
407+
"CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5", "CC-BY-NC-3.0", "CC-BY-NC-4.0",
408+
"GPL-1.0", "GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.0", "LGPL-2.1", "LGPL-3.0",
409+
"GPL-1.0-only", "GPL-2.0-only", "GPL-3.0-only", "AGPL-3.0-only", "LGPL-2.0-only", "LGPL-2.1-only", "LGPL-3.0-only",
410+
"QPL-1.0", "Sleepycat", "SSPL-1.0", "CPOL-1.02",
411+
"BSD-4-Clause", "BSD-4-Clause-UC", "NPL-1.0", "NPL-1.1", "JSON"
412+
]
413+
// Update exemptions according to https://github.com/apache/eventmesh/issues/4842
414+
def allowedArtifacts = ["amqp-client", "stax-api", "javassist", "hibernate-core", "hibernate-commons-annotations", "ST4", "xsdlib"]
415+
416+
def licenseFile = file('tools/dist-license/LICENSE')
417+
def lines = licenseFile.readLines()
418+
def hasFailed = false
419+
420+
lines.each { line ->
421+
deniedLicenses.each { deniedLicense ->
422+
if (line.contains("'${deniedLicense}'")) {
423+
def isAllowed = allowedArtifacts.any { allowedArtifact ->
424+
line.contains(allowedArtifact)
425+
}
426+
if (!isAllowed) {
427+
logger.warn("Incompatible license '${deniedLicense}' found in line: ${line}")
428+
hasFailed = true
429+
}
430+
}
431+
}
432+
}
433+
434+
if (hasFailed) {
435+
throw new GradleException("Check failed due to incompatible licenses found. Please remove these dependencies or add exemptions.")
436+
} else {
437+
logger.lifecycle("Check passed, no incompatible licenses found.")
438+
}
439+
}
440+
}
441+
442+
tasks.register('generateDistNotice') {
443+
dependsOn('generateLicenseReport') // Task from 'com.github.jk1.dependency-license-report' plugin
444+
doLast {
445+
// Inputs
446+
def reportsDir = file("$buildDir/reports/dependency-license/")
447+
def projectNoticeText = file('NOTICE').text
448+
449+
// Outputs
450+
def distNoticeFile = file('tools/dist-license/NOTICE')
451+
452+
def distNoticeText = projectNoticeText
453+
reportsDir.eachDir { dir ->
454+
dir.eachFileRecurse (groovy.io.FileType.FILES) { file ->
455+
// Find NOTICE files
456+
if (file.name.length() >= 6 && file.name.substring(0, 6).equalsIgnoreCase("NOTICE")) {
457+
def artifactName = dir.name.replace(".jar", "")
458+
distNoticeText += "\n=======================================================================\n\n" +
459+
"${artifactName} NOTICE\n" + "\n=======================================================================\n\n"
460+
distNoticeText += file.text
461+
}
462+
}
463+
}
464+
distNoticeFile.text = distNoticeText
465+
}
466+
}
467+
255468
subprojects {
256469

257470
apply plugin: "io.spring.dependency-management"
@@ -260,7 +473,6 @@ subprojects {
260473
main {
261474
java.srcDirs = ['src/main/java']
262475
}
263-
264476
test {
265477
java.srcDirs = ['src/test/java']
266478
}
@@ -271,6 +483,9 @@ subprojects {
271483
delete 'dist'
272484
}
273485

486+
// Print all dependencies trees, useful for finding artifacts
487+
tasks.register('printAllDependencyTrees', DependencyReportTask) {}
488+
274489
jacoco {
275490
toolVersion = "0.8.6"
276491
}
@@ -297,7 +512,6 @@ subprojects {
297512
}
298513

299514
spotbugsMain {
300-
301515
reports {
302516
xml.required = false
303517
html {
@@ -374,7 +588,6 @@ subprojects {
374588
}
375589

376590
repositories {
377-
mavenLocal()
378591
mavenCentral()
379592
maven { url "https://maven.aliyun.com/repository/public" }
380593
}
@@ -539,7 +752,6 @@ subprojects {
539752

540753
dependency "software.amazon.awssdk:s3:2.20.29"
541754
dependency "com.github.rholder:guava-retrying:2.0.0"
542-
543755
}
544756
}
545757
}

Diff for: eventmesh-examples/build.gradle

+2-3
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ dependencies {
2222
implementation project(":eventmesh-common")
2323
implementation project(":eventmesh-storage-plugin:eventmesh-storage-api")
2424
implementation project(":eventmesh-connectors:eventmesh-connector-spring")
25-
implementation('org.springframework.boot:spring-boot-starter') {
26-
exclude module: 'spring-boot-starter-logging'
25+
implementation('org.springframework.boot:spring-boot-starter-web') {
26+
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
2727
}
28-
implementation 'org.springframework.boot:spring-boot-starter-web'
2928
implementation 'io.netty:netty-all'
3029
implementation "io.cloudevents:cloudevents-core"
3130
implementation "io.cloudevents:cloudevents-json-jackson"

0 commit comments

Comments
 (0)