You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2019/12/12 18:26:00 UTC

[lucene-solr] 03/03: Add support for validating the presence of licenses and notices.

This is an automated email from the ASF dual-hosted git repository.

dweiss pushed a commit to branch gradle-master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit a392a83558dc49e2c429dda64eb9e3e1cb9f4033
Author: Dawid Weiss <dw...@apache.org>
AuthorDate: Thu Dec 12 19:25:46 2019 +0100

    Add support for validating the presence of licenses and notices.
---
 gradle/validation/jar-checks.gradle | 112 +++++++++++++++++++++++++++++++++---
 1 file changed, 103 insertions(+), 9 deletions(-)

diff --git a/gradle/validation/jar-checks.gradle b/gradle/validation/jar-checks.gradle
index e11497e..de29170 100644
--- a/gradle/validation/jar-checks.gradle
+++ b/gradle/validation/jar-checks.gradle
@@ -27,6 +27,23 @@ configure(project(":solr")) {
   ext.licensesDir = file("licenses")
 }
 
+// All known license types. If 'noticeOptional' is true then
+// the notice file must accompany the license.
+def licenseTypes = [
+    "ASL": [name: "Apache Software License 2.0"],
+    "BSD": [name: "Berkeley Software Distribution"],
+    //BSD like just means someone has taken the BSD license and put in their name, copyright, or it's a very similar license.
+    "BSD_LIKE": [name: "BSD like license"],
+    "CDDL": [name: "Common Development and Distribution License", noticeOptional: true],
+    "CPL": [name: "Common Public License"],
+    "EPL": [name: "Eclipse Public License Version 1.0", noticeOptional: true],
+    "MIT": [name: "Massachusetts Institute of Tech. License", noticeOptional: true],
+    "MPL": [name: "Mozilla Public License", noticeOptional: true /* NOT SURE on the required notice */],
+    "PD": [name: "Public Domain", noticeOptional: true],
+    "SUN": [name: "Sun Open Source License", noticeOptional: true],
+    "COMPOUND": [name: "Compound license (details in NOTICE file)."],
+]
+
 subprojects {
   // Configure jarValidation configuration for all projects. Any dependency
   // declared on this configuration (or any configuration it extends from) will
@@ -45,6 +62,10 @@ subprojects {
     }
   }
 
+  // Collects dependency JAR information for a project and saves it in
+  // project.ext.jarInfos. Each dependency has a map of attributes
+  // which make it easier to process it later on (name, hash, origin module,
+  // see the code below for details).
   task collectJarInfos() {
     dependsOn configurations.jarValidation
 
@@ -70,16 +91,14 @@ subprojects {
     }
   }
 
+  def failOnError = false
+
+  // Verifies that each JAR has a corresponding checksum and that it matches actual JAR available for this dependency.
   task validateJarChecksums() {
     group = 'Dependency validation'
-    description = "Validate project dependency checksums"
-
-    dependsOn configurations.jarValidation
+    description = "Validate checksums of dependencies"
     dependsOn collectJarInfos
 
-    // TODO: validation should fail the build but we're out of sync with master.
-    def fail = false
-
     doLast {
       def errors = []
       jarInfos.each { dep ->
@@ -91,13 +110,80 @@ subprojects {
           def actual = dep.checksum.trim()
           if (expected.compareToIgnoreCase(actual) != 0) {
             errors << "Dependency checksum mismatch ('${dep.module}'), expected it to be: ${expected}, but was: ${actual}"
+          } else {
+            logger.log(LogLevel.INFO, "Dependency checksum OK ('${dep.module}')")
           }
         }
       }
 
       if (errors) {
         def msg = "Dependency checksum validation failed:\n  - " + errors.join("\n  - ")
-        if (fail) {
+        if (failOnError) {
+          throw new GradleException(msg)
+        } else {
+          logger.log(LogLevel.WARN, "WARNING: ${msg}")
+        }
+      }
+    }
+  }
+
+  // Locate the set of license file candidates for this dependency. We
+  // search for [jar-or-prefix]-LICENSE-[type].txt
+  // where 'jar-or-prefix' can be any '-'-delimited prefix of the dependency JAR's name.
+  // So for 'commons-io' it can be 'commons-io-LICENSE-foo.txt' or
+  // 'commons-LICENSE.txt'
+  task validateJarLicenses() {
+    group = 'Dependency validation'
+    description = "Validate license and notice files of dependencies"
+    dependsOn collectJarInfos
+
+    doLast {
+      def errors = []
+      jarInfos.each { dep ->
+        def baseName = dep.name
+        def found = []
+        def candidates = []
+        while (true) {
+          candidates += file("${licensesDir}/${baseName}-LICENSE-[type].txt")
+          found += fileTree(dir: licensesDir, include: "${baseName}-LICENSE-*.txt").files
+          def prefix = baseName.replaceAll(/[\-][^-]+$/, "")
+          if (found || prefix == baseName) {
+            break
+          }
+          baseName = prefix
+        }
+
+        if (found.size() == 0) {
+          errors << "License file missing ('${dep.module}'), expected it at: ${candidates.join(" or ")}," +
+              " where [type] can be any of ${licenseTypes.keySet()}."
+        } else if (found.size() > 1) {
+          errors << "Multiple license files matching for ('${dep.module}'): ${found.join(", ")}"
+        } else {
+          def licenseFile = found.get(0)
+          def m = (licenseFile.name =~ /LICENSE-(.+)\.txt$/)
+          if (!m) throw new GradleException("License file name doesn't contain license type?: ${licenseFile.name}")
+
+          def licenseName = m[0][1]
+          def licenseType = licenseTypes[licenseName]
+          if (!licenseType) {
+            errors << "Unknown license type suffix for ('${dep.module}'): ${licenseFile} (must be one of ${licenseTypes.keySet()})"
+          } else {
+            logger.log(LogLevel.INFO, "Dependency license file OK ('${dep.module}'): " + licenseName)
+
+            // Look for sibling NOTICE file.
+            def noticeFile = file(licenseFile.path.replaceAll(/\-LICENSE-.+/, "-NOTICE.txt"))
+            if (noticeFile.exists()) {
+              logger.log(LogLevel.INFO, "Dependency notice file OK ('${dep.module}'): " + noticeFile)
+            } else if (!licenseType.noticeOptional) {
+              errors << "Notice file missing for ('${dep.module}'), expected it at: ${noticeFile}"
+            }
+          }
+        }
+      }
+
+      if (errors) {
+        def msg = "Certain license/ notice files are missing:\n  - " + errors.join("\n  - ")
+        if (failOnError) {
           throw new GradleException(msg)
         } else {
           logger.log(LogLevel.WARN, "WARNING: ${msg}")
@@ -105,9 +191,17 @@ subprojects {
       }
     }
   }
+
+  task validateJars() {
+    dependsOn validateJarChecksums, validateJarLicenses
+  }
+
+  check.dependsOn(validateJars)
 }
 
-// Disable validation for these projects.
+// Disable validation for these projects (should it be disabled?)
 configure(project(":solr:solr-ref-guide")) {
-  validateJarChecksums.enabled = false
+  [validateJarLicenses, validateJarChecksums].each { task ->
+    task.enabled = false
+  }
 }
\ No newline at end of file