diff --git a/NOTICE b/NOTICE index afa62e9a3..2d660fdb8 100644 --- a/NOTICE +++ b/NOTICE @@ -6,3 +6,9 @@ The Apache Software Foundation (http://www.apache.org/). Apache Iceberg Copyright 2017-2022 The Apache Software Foundation + +--- + +Polaris distributions contain some or all of the following dependencies. + + diff --git a/aggregated-license-report/build.gradle.kts b/aggregated-license-report/build.gradle.kts new file mode 100644 index 000000000..923526659 --- /dev/null +++ b/aggregated-license-report/build.gradle.kts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import org.gradle.kotlin.dsl.support.unzipTo + +val licenseReports by configurations.creating { description = "Used to reference license reports" } + +dependencies { + licenseReports(project(":polaris-service", "licenseReports")) +} + +val collectLicenseReportJars by + tasks.registering(Sync::class) { + destinationDir = project.layout.buildDirectory.dir("tmp/license-report-jars").get().asFile + from(licenseReports) + } + +val aggregateLicenseReports by + tasks.registering { + group = "Build" + description = "Aggregates license reports" + val outputDir = project.layout.buildDirectory.dir("licenseReports") + outputs.dir(outputDir) + dependsOn(collectLicenseReportJars) + doLast { + delete(outputDir) + fileTree(collectLicenseReportJars.get().destinationDir).files.forEach { zip -> + val targetDirName = zip.name.replace("-license-report.zip", "") + unzipTo(outputDir.get().dir(targetDirName).asFile, zip) + } + } + } + +val aggregatedLicenseReportsZip by + tasks.registering(Zip::class) { + from(aggregateLicenseReports) + from(rootProject.layout.projectDirectory) { + include("NOTICE") + eachFile { path = "NOTICE.txt" } + } + archiveBaseName.set("polaris-aggregated-license-report-${project.version}") + destinationDirectory.set(layout.buildDirectory.dir("distributions")) + archiveExtension.set("zip") + } diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 546f691f6..684100e21 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -20,5 +20,6 @@ plugins { `kotlin-dsl` } dependencies { implementation(gradleKotlinDsl()) implementation(baselibs.idea.ext) + implementation(baselibs.license.report) implementation(baselibs.spotless) } diff --git a/build-logic/src/main/kotlin/NoticeReportValidation.kt b/build-logic/src/main/kotlin/NoticeReportValidation.kt new file mode 100644 index 000000000..dae3f9f8b --- /dev/null +++ b/build-logic/src/main/kotlin/NoticeReportValidation.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.github.jk1.license.LicenseReportExtension +import com.github.jk1.license.ProjectData +import com.github.jk1.license.filter.DependencyFilter +import java.io.File +import org.gradle.api.GradleException + +/** + * Validates that all dependencies with MIT/BSD/Go/UPL/ISC licenses, which do not have an Apache + * license, are mentioned in the `NOTICE` file. + */ +class NoticeReportValidation : DependencyFilter { + fun needsNoNotice(license: String?): Boolean = license != null && (license.contains("Apache")) + + fun needsNotice(license: String?): Boolean = + license != null && + (license.contains("MIT") || + license.contains("BSD") || + license.contains("Go") || + license.contains("ISC") || + license.contains("Universal Permissive")) + + override fun filter(data: ProjectData?): ProjectData { + data!! + + val rootNoticeFile = data.project.rootProject.file("NOTICE").readText() + + val licenseReport = data.project.extensions.getByType(LicenseReportExtension::class.java) + + val missing = mutableMapOf() + + data.allDependencies.forEach { mod -> + val licenses = + (mod.manifests.map { it.license } + + mod.licenseFiles.flatMap { it.fileDetails }.map { it.license } + + mod.poms.flatMap { it.licenses }.map { it.name }) + .distinct() + + if (!licenses.any { needsNoNotice(it) } && licenses.any { needsNotice(it) }) { + val groupModule = "${mod.group}:${mod.name}" + if (!rootNoticeFile.contains(groupModule)) { + missing.put( + "${mod.group}:${mod.name}", + """ + --- + ${mod.group}:${mod.name} + + ${mod.licenseFiles.flatMap { it.fileDetails }.filter { it.file != null }.map { it.file } + .map { File("${licenseReport.absoluteOutputDir}/$it").readText().trim() } + .distinct() + .map { "\n\n$it\n" } + .joinToString("\n") + } + """ + .trimIndent() + ) + } + } + } + + if (!missing.isEmpty()) { + throw GradleException( + "License information for the following artifacts is missing in the root NOTICE file: ${missing.map { it.value }.joinToString("\n")}" + ) + } + + return data + } +} diff --git a/build-logic/src/main/kotlin/polaris-license-report.gradle.kts b/build-logic/src/main/kotlin/polaris-license-report.gradle.kts new file mode 100644 index 000000000..66022142f --- /dev/null +++ b/build-logic/src/main/kotlin/polaris-license-report.gradle.kts @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Snowflake Computing Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.github.jk1.license.filter.LicenseBundleNormalizer +import com.github.jk1.license.render.InventoryHtmlReportRenderer +import com.github.jk1.license.render.JsonReportRenderer +import com.github.jk1.license.render.XmlReportRenderer +import java.util.* + +plugins { id("com.github.jk1.dependency-license-report") } + +afterEvaluate { + // Need to configure after evaluation, because the spark-extensions project use a custom + // `buildDir`. + licenseReport { + filters = + arrayOf( + LicenseBundleNormalizer( + "${rootProject.projectDir}/gradle/license/normalizer-bundle.json", + false + ), + NoticeReportValidation() + ) + allowedLicensesFile = rootProject.projectDir.resolve("gradle/license/allowed-licenses.json") + renderers = + arrayOf(InventoryHtmlReportRenderer("index.html"), JsonReportRenderer(), XmlReportRenderer()) + excludeBoms = true + excludes = + arrayOf( + "com.google.guava:guava-parent", + "io.opentelemetry:opentelemetry-bom-alpha", + "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha" + ) + outputDir = "${project.layout.buildDirectory.get()}/reports/dependency-license" + excludeGroups = arrayOf("org.projectnessie.nessie", "org.projectnessie.nessie-integrations") + } +} + +val generateLicenseReport = + tasks.named("generateLicenseReport") { + inputs + .files( + rootProject.projectDir.resolve("gradle/license/normalizer-bundle.json"), + rootProject.projectDir.resolve("gradle/license/allowed-licenses.json") + ) + .withPathSensitivity(PathSensitivity.RELATIVE) + inputs.property("renderersHash", Arrays.hashCode(licenseReport.renderers)) + inputs.property("filtersHash", Arrays.hashCode(licenseReport.filters)) + inputs.property("excludesHash", Arrays.hashCode(licenseReport.excludes)) + inputs.property("excludeGroupsHash", Arrays.hashCode(licenseReport.excludeGroups)) + } + +val licenseReportZip = + tasks.register("licenseReportZip") { + group = "documentation" + description = "License report as a ZIP" + dependsOn("checkLicense") + from(generateLicenseReport) + archiveClassifier.set("license-report") + archiveExtension.set("zip") + } + +val licenseReports by + configurations.creating { + isCanBeConsumed = true + isCanBeResolved = false + description = "License report files" + outgoing { artifact(licenseReportZip) } + } + +plugins.withType().configureEach { + configure { + publications { named("maven") { artifact(licenseReportZip) } } + } +} + +tasks.named("check") { dependsOn(generateLicenseReport) } diff --git a/gradle/baselibs.versions.toml b/gradle/baselibs.versions.toml index 76b505aa9..5f5196ac7 100644 --- a/gradle/baselibs.versions.toml +++ b/gradle/baselibs.versions.toml @@ -14,4 +14,5 @@ [libraries] idea-ext = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version = "1.1.8" } +license-report = { module = "com.github.jk1:gradle-license-report", version = "2.8" } spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "6.25.0" } diff --git a/gradle/license/allowed-licenses.json b/gradle/license/allowed-licenses.json new file mode 100644 index 000000000..3fc438751 --- /dev/null +++ b/gradle/license/allowed-licenses.json @@ -0,0 +1,96 @@ +{ + "allowedLicenses": [ + { + "moduleLicense": "Apache Software License, Version 2.0" + }, + { + "moduleLicense": "Apache Software License, version 2.0" + }, + { + "moduleLicense": "Apache License, Version 2.0" + }, + { + "moduleLicense": "Apache 2" + }, + { + "moduleLicense": "Apache 2.0" + }, + { + "moduleLicense": "Apache-2.0" + }, + { + "moduleLicense": "BSD-2.Clause" + }, + { + "moduleLicense": "Revised BSD" + }, + { + "moduleLicense": "BSD-3.Clause" + }, + { + "moduleLicense": "BSD New license" + }, + { + "moduleLicense": "Common Development and Distribution License (CDDL) v1.0" + }, + { + "moduleLicense": "Creative Commons 1.0 Universal" + }, + { + "moduleLicense": "Eclipse Distribution License, Version 1.0" + }, + { + "moduleLicense": "EDL 1.0" + }, + { + "moduleLicense": "Eclipse Public License, Version 1.0" + }, + { + "moduleLicense": "EPL 2.0" + }, + { + "moduleLicense": "Eclipse Public License, Version 2.0" + }, + { + "moduleLicense": "Eclipse Public License - v 2.0" + }, + { + "moduleLicense": "Eclipse Public License v. 2.0" + }, + { + "moduleLicense": "GNU General Public License, Version 2 with the GNU Classpath Exception" + }, + { + "moduleLicense": "GNU Lesser General Public License Version 2.1" + }, + { + "moduleLicense": "Go License" + }, + { + "moduleLicense": "MIT License" + }, + { + "moduleLicense": "MIT-0" + }, + { + "moduleLicense": "MIT" + }, + { + "moduleLicense": "Public Domain" + }, + { + "moduleLicense": "Public Domain, per Creative Commons CC0" + }, + { + "moduleLicense": "Universal Permissive License, Version 1.0" + }, + { + "moduleLicense": "Bouncy Castle Licence" + }, + { + "moduleLicense": null, + "moduleName": "javax.servlet.jsp:jsp-api", + "moduleVersion": "2.1" + } + ] +} diff --git a/gradle/license/normalizer-bundle.json b/gradle/license/normalizer-bundle.json new file mode 100644 index 000000000..c3836e0a7 --- /dev/null +++ b/gradle/license/normalizer-bundle.json @@ -0,0 +1,85 @@ +{ + "bundles" : [ + { "bundleName" : "apache2", "licenseName" : "Apache Software License, Version 2.0", "licenseUrl" : "http://www.apache.org/licenses/LICENSE-2.0" }, + { "bundleName" : "bsd2", "licenseName" : "BSD-2-Clause", "licenseUrl" : "http://opensource.org/licenses/BSD-2-Clause" }, + { "bundleName" : "bsd3", "licenseName" : "BSD-3-Clause", "licenseUrl" : "http://opensource.org/licenses/BSD-3-Clause" }, + { "bundleName" : "cc0", "licenseName" : "Creative Commons 1.0 Universal", "licenseUrl" : "http://creativecommons.org/publicdomain/zero/1.0/" }, + { "bundleName" : "cddl1", "licenseName" : "Common Development and Distribution License (CDDL) v1.0", "licenseUrl" : "http://opensource.org/licenses/CDDL-1.0" }, + { "bundleName" : "edl1", "licenseName" : "Eclipse Distribution License, Version 1.0", "licenseUrl": "http://www.eclipse.org/legal/edl-v10.html" }, + { "bundleName" : "epl1", "licenseName" : "Eclipse Public License, Version 1.0", "licenseUrl": "http://www.eclipse.org/legal/epl-v10.html" }, + { "bundleName" : "epl2", "licenseName" : "Eclipse Public License, Version 2.0", "licenseUrl": "http://www.eclipse.org/legal/epl-v20.html" }, + { "bundleName" : "go", "licenseName" : "Go License", "licenseUrl": "https://golang.org/LICENSE" }, + { "bundleName" : "gpl2", "licenseName" : "GNU General Public License, Version 2", "licenseUrl": "http://www.gnu.org/copyleft/gpl.html" }, + { "bundleName" : "gpl2ce", "licenseName" : "GNU General Public License, Version 2 with the GNU Classpath Exception", "licenseUrl": "https://www.gnu.org/software/classpath/license.html" }, + { "bundleName" : "lgpl21", "licenseName" : "GNU Lesser General Public License Version 2.1", "licenseUrl": "http://www.gnu.org/licenses/lgpl.html" }, + { "bundleName" : "lgpl3", "licenseName" : "GNU Lesser General Public License Version 3 or greater", "licenseUrl": "http://www.gnu.org/licenses/lgpl.html" }, + { "bundleName" : "mit", "licenseName" : "MIT License", "licenseUrl": "http://www.opensource.org/licenses/mit-license.php" }, + { "bundleName" : "mpl2", "licenseName" : "Mozilla Public License, Version 2.0", "licenseUrl": "https://www.mozilla.org/en-US/MPL/2.0/" }, + { "bundleName" : "upl", "licenseName" : "Universal Permissive License, Version 1.0", "licenseUrl": "http://opensource.org/licenses/UPL" } + ], + "transformationRules" : [ + { "bundleName" : "apache2", "licenseUrlPattern" : "https?://www.apache.org/licenses/LICENSE-2.0.*" }, + { "bundleName" : "apache2", "licenseUrlPattern" : "https?://opensource.org/licenses/Apache-2.0" }, + { "bundleName" : "apache2", "licenseUrlPattern" : "https?://repository.jboss.org/licenses/apache-2.0.*" }, + { "bundleName" : "apache2", "licenseUrlPattern" : "https?://aws.amazon.com/apache2.0" }, + + { "bundleName" : "bsd2", "licenseUrlPattern" : "https?://www.opensource.org/licenses/bsd-license.php" }, + { "bundleName" : "bsd2", "licenseUrlPattern" : "https?://opensource.org/licenses/BSD-2-Clause" }, + + { "bundleName" : "bsd3", "licenseNamePattern" : "BSD New license" }, + { "bundleName" : "bsd3", "licenseNamePattern" : "The BSD License" }, + + { "bundleName" : "cc0", "licenseUrlPattern" : "https?://creativecommons.org/publicdomain/zero/1.0/" }, + { "bundleName" : "cc0", "licenseNamePattern" : "CDDL License" }, + + { "bundleName" : "cddl1", "licenseUrlPattern" : "https?://repository.jboss.org/licenses/cc0-1.0.*" }, + { "bundleName" : "cddl1", "licenseNamePattern" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0" }, + { "bundleName" : "cddl1", "licenseNamePattern" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 (CDDL-1.0)" }, + + { "bundleName" : "edl1", "licenseUrlPattern" : "https?://www.eclipse.org/legal/edl-v10.*" }, + { "bundleName" : "edl1", "licenseUrlPattern" : "https?://www.eclipse.org/org/documents/edl-v10.*" }, + + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://www.eclipse.org/legal/epl-v10.*" }, + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://www.eclipse.org/legal/epl-1.0.*" }, + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://www.eclipse.org/org/documents/epl-1.0/EPL-1.0.*" }, + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://opensource.org/licenses/eclipse-1.0.php" }, + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://projects.eclipse.org/license/epl-1.0" }, + { "bundleName" : "epl2", "licenseNamePattern" : "Eclipse Public License - Version 1.0" }, + + { "bundleName" : "epl2", "licenseUrlPattern" : "https?://www.eclipse.org/legal/epl-v20.*" }, + { "bundleName" : "epl2", "licenseUrlPattern" : "https?://www.eclipse.org/legal/epl-2.0.*" }, + { "bundleName" : "epl2", "licenseUrlPattern" : "https?://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.*" }, + { "bundleName" : "epl1", "licenseUrlPattern" : "https?://opensource.org/licenses/eclipse-2.0.php" }, + { "bundleName" : "epl2", "licenseUrlPattern" : "https?://projects.eclipse.org/license/epl-2.0" }, + + { "bundleName" : "go", "licenseUrlPattern" : "https?://golang.org/LICENSE" }, + + { "bundleName" : "gpl2", "licenseUrlPattern" : "https?://www.gnu.org/licenses/gpl-2.0.html" }, + { "bundleName" : "gpl2", "licenseUrlPattern" : "https?://www.gnu.org/copyleft/gpl.html" }, + + { "bundleName" : "gpl2ce", "licenseUrlPattern" : "https?://www.gnu.org/software/classpath/license.*" }, + { "bundleName" : "gpl2ce", "licenseUrlPattern" : "https?://projects.eclipse.org/license/secondary-gpl-2.0-cp" }, + { "bundleName" : "gpl2ce", "licenseUrlPattern" : "http://glassfish.java.net/public/CDDL+GPL_1_1.html" }, + { "bundleName" : "gpl2ce", "licenseUrlPattern" : "https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html" }, + { "bundleName" : "gpl2ce", "licenseUrlPattern" : "https://github.com/javaee/javax.annotation/blob/master/LICENSE" }, + { "bundleName" : "gpl2ce", "licenseNamePattern" : "GPL2 w/ CPE" }, + + { "bundleName" : "lgpl21", "licenseNamePattern" : "GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1" }, + { "bundleName" : "lgpl21", "licenseNamePattern" : "LGPL, version 2.1" }, + { "bundleName" : "lgpl21", "licenseNamePattern" : "LGPL 2.1" }, + { "bundleName" : "lgpl21", "licenseNamePattern" : "LGPL-2.1" }, + { "bundleName" : "lgpl21", "licenseNamePattern" : "lgpl" }, + + { "bundleName" : "lgpl3", "licenseNamePattern" : "Lesser General Public License, version 3 or greater" }, + + { "bundleName" : "mit", "licenseUrlPattern" : "https?://spdx.org/licenses/MIT.txt" }, + { "bundleName" : "mit", "licenseUrlPattern" : "https?://spdx.org/licenses/MIT-0.*" }, + { "bundleName" : "mit", "licenseUrlPattern" : "https?://www.opensource.org/licenses/mit-license.php" }, + { "bundleName" : "mit", "licenseUrlPattern" : "https?://opensource.org/licenses/MIT" }, + { "bundleName" : "mit", "licenseNamePattern" : "The MIT License (MIT)" }, + + { "bundleName" : "mpl2", "licenseUrlPattern": "https?://www.mozilla.org/en-US/MPL/2.0/" }, + + { "bundleName" : "upl", "licenseUrlPattern": "https?://opensource.org/licenses/UPL" } + ] +} diff --git a/gradle/projects.main.properties b/gradle/projects.main.properties index d0ecccac3..d7229fc99 100644 --- a/gradle/projects.main.properties +++ b/gradle/projects.main.properties @@ -18,3 +18,4 @@ polaris-core=polaris-core polaris-service=polaris-service polaris-eclipselink=extension/persistence/eclipselink +aggregated-license-report=aggregated-license-report diff --git a/polaris-service/build.gradle.kts b/polaris-service/build.gradle.kts index b49e0b98b..fff6ec3d3 100644 --- a/polaris-service/build.gradle.kts +++ b/polaris-service/build.gradle.kts @@ -21,6 +21,7 @@ plugins { alias(libs.plugins.shadow) alias(libs.plugins.openapi.generator) id("polaris-server") + id("polaris-license-report") id("application") }