diff --git a/bin/build-mb/deps.edn b/bin/build-mb/deps.edn index 1ad799e2c16a29e7cbde503d9cc47e8e210193a9..a43859f42428e0696424701bb424ccd253c21b4b 100644 --- a/bin/build-mb/deps.edn +++ b/bin/build-mb/deps.edn @@ -1,10 +1,13 @@ {:paths ["src" "resources"] :deps - {common/common {:local/root "../common"} - build-drivers/build-drivers {:local/root "../build-drivers"} - i18n/i18n {:local/root "../i18n"} - org.flatland/ordered {:mvn/version "1.15.10"}} + {common/common {:local/root "../common"} + build-drivers/build-drivers {:local/root "../build-drivers"} + i18n/i18n {:local/root "../i18n"} + org.flatland/ordered {:mvn/version "1.15.10"} + io.github.clojure/tools.build {:git/tag "v0.7.5" :git/sha "2526f58"} + ;; value currently used in tools.build but top level since we directly depend on it + org.apache.maven/maven-model {:mvn/version "3.8.4"}} :aliases {:test {:extra-paths ["test"] diff --git a/bin/build-mb/src/build.clj b/bin/build-mb/src/build.clj index 9ffbfe5e1882a0196db0d68d265158bc4c3a70e6..45f979e4e38a69c97d3fab9f646d5b78d6f69642 100644 --- a/bin/build-mb/src/build.clj +++ b/bin/build-mb/src/build.clj @@ -5,6 +5,7 @@ [clojure.edn :as edn] [clojure.java.io :as io] [clojure.string :as str] + [clojure.tools.build.api :as b] [environ.core :as env] [flatland.ordered.map :as ordered-map] [i18n.create-artifacts :as i18n] @@ -48,15 +49,13 @@ [edition] {:pre [(#{:oss :ee} edition)]} (u/step "Generate backend license information from jar files" - (let [[classpath] (u/sh {:dir u/project-root-directory - :quiet? true} - "clojure" (str "-A" edition) "-Spath") + (let [basis (b/create-basis {:project (u/filename u/project-root-directory "deps.edn")}) output-filename (u/filename u/project-root-directory "resources" "license-backend-third-party.txt") - {:keys [without-license]} (license/generate {:classpath classpath + {:keys [without-license]} (license/generate {:basis basis :backfill (edn/read-string - (slurp (io/resource "overrides.edn"))) + (slurp (io/resource "overrides.edn"))) :output-filename output-filename :report? false})] (when (seq without-license) diff --git a/bin/build-mb/src/build/licenses.clj b/bin/build-mb/src/build/licenses.clj index 7acc5699a1e83595369b444ac8c95a76934cb9b3..4fe7f3f7c45a3b74951c2f736d42acc8b2b5e0f3 100644 --- a/bin/build-mb/src/build/licenses.clj +++ b/bin/build-mb/src/build/licenses.clj @@ -27,57 +27,24 @@ (:require [clojure.data.xml :as xml] [clojure.edn :as edn] [clojure.java.io :as io] - [clojure.string :as str]) - (:import (java.nio.file Files FileSystem FileSystems FileVisitOption LinkOption OpenOption Path Paths))) + [clojure.string :as str] + [clojure.tools.build.api :as b]) + (:import (java.nio.file Files FileSystem FileSystems FileVisitOption LinkOption OpenOption Path Paths) + (org.apache.maven.model License Model) + (org.apache.maven.model.io.xpp3 MavenXpp3Reader) + (java.io FileReader))) (set! *warn-on-reflection* true) -(def classpath-separator (System/getProperty "path.separator")) - -(defn jar-file? [filename] - (str/ends-with? filename "jar")) - -(def tag-name (comp keyword (fnil name "") :tag)) -(def ^:private tag-content (juxt tag-name (comp first :content))) - -(defn pom->coordinates [pom-xml] - (let [coords (->> pom-xml - :content - (filter #(#{:groupId :artifactId :version} (tag-name %))) - (map tag-content) - (into {})) - parent (->> pom-xml - :content - (filter #(#{:parent} (tag-name %))) - first - :content - (keep tag-content) - (into {}))] - {:group (or (:groupId coords) (:groupId parent)) - :artifact (:artifactId coords) - :version (or (:version coords) (:version parent))})) - -(defn pom->licenses [pom-xml] - (let [licenses (some->> pom-xml - :content - (filter #(#{:licenses} (tag-name %))) - first - :content - first - :content - (map tag-content) - (into {}))] - licenses)) - -(defn ->BiPredicate [f] +(defn- ->BiPredicate [f] (reify java.util.function.BiPredicate (test [_ x y] (f x y)))) -(def path-options (into-array String [])) -(def filevisit-options (into-array FileVisitOption [])) -(def link-options (into-array LinkOption [])) -(def open-options (into-array OpenOption [])) +(def ^:private path-options (into-array String [])) +(def ^:private filevisit-options (into-array FileVisitOption [])) +(def ^:private link-options (into-array LinkOption [])) +(def ^:private open-options (into-array OpenOption [])) (defn determine-pom "Produce a pom path from a jar. Looks first for a pom adjacent to the jar, and then finds all files that end with @@ -97,8 +64,7 @@ (str/ends-with? (str path) "pom.xml")))] (.. (Files/find jar-root Integer/MAX_VALUE pred filevisit-options) findFirst - (orElse nil))) - (throw (ex-info "Cannot locate pom" {:jar jar-filename})))) + (orElse nil))))) (defn do-with-path-is "Open an inputstream on `path` and call `f` with the inputstream as an argument. Function `f` should not be lazy." @@ -106,6 +72,27 @@ (with-open [is (Files/newInputStream path open-options)] (f is))) +(defn license-from-pom + "Read license information from a pom. This reads only the local pom and does not trace parent poms. Clojure.core.async includes a parent pom: + + <parent> + <groupId>org.clojure</groupId> + <artifactId>pom.contrib</artifactId> + <version>1.1.0</version> + </parent> + + which would specify a license. To trace this would require setting up way more machinery and so just let the + overrides catch this scenario." + [^Path pom-path] + (try + (let [reader (MavenXpp3Reader.) + model (.read reader (FileReader. (.toFile pom-path))) + license ^License (first (.getLicenses model))] + (when license + {:name (.getName license) + :url (.getUrl license)})) + (catch Exception _e nil))) + (def ^:private license-file-names ["LICENSE" "LICENSE.txt" "META-INF/LICENSE" "META-INF/LICENSE.txt" "license/LICENSE"]) @@ -120,55 +107,55 @@ (defn license-from-backfill "Look in the backfill information for license information." - [{:keys [group artifact]} backfill] - (if-let [license (some #(get-in backfill %) - [[group artifact] - [:override/group group]])] - (if (string? license) - license - (if-let [resource (io/resource (:resource license))] - (slurp resource) - (throw (ex-info (str "Missing license for " artifact) - {:group group - :artifact artifact - :backfill license})))))) + [lib-name backfill] + (let [[group artifact] ((juxt namespace name) lib-name)] + (when-let [license (some #(get-in backfill %) + [[group artifact] + [:override/group group]])] + (if (string? license) + license + (if-let [resource (io/resource (:resource license))] + (slurp resource) + (throw (ex-info (str "Missing license for " artifact) + {:group group + :artifact artifact + :backfill license}))))))) (defn discern-license-and-coords "Returns a tuple of [jar-filename {:coords :license [:error]}. Error is optional. License will be a string of license text, coords a map with group and artifact." - [^String jar-filename backfill] - (let [jar-path ^Path (Paths/get jar-filename path-options) - classloader ^ClassLoader (ClassLoader/getSystemClassLoader)] + [[lib-name info] backfill] + (let [jar-filename (-> info :paths first) + jar-path (Paths/get ^String jar-filename path-options) + classloader (ClassLoader/getSystemClassLoader)] (if-not (Files/exists jar-path link-options) - [jar-filename {:error "Jar does not exist"}] + [lib-name {:error "Jar does not exist"}] (try (with-open [jar-fs (FileSystems/newFileSystem jar-path classloader)] - (let [pom-path (determine-pom jar-filename jar-fs) - license-path (license-from-jar jar-fs) - [coords pom-license] (do-with-path-is pom-path - (comp (juxt pom->coordinates pom->licenses) - #(xml/parse % :skip-whitespace true))) - license (or (when license-path - (with-open [is (Files/newInputStream license-path open-options)] - (slurp is))) - (license-from-backfill coords backfill) - (let [{:keys [name url]} pom-license] - (when name (str name ": " url))))] - [jar-filename (cond-> {:coords coords :license license} - (not (and license coords)) - (assoc :error "Error determining license or coords"))])) + (let [license (or (when-let [license-path (license-from-jar jar-fs)] + (with-open [is (Files/newInputStream license-path open-options)] + (slurp is))) + (when-let [pom-path (determine-pom jar-filename jar-fs)] + (let [{:keys [name url]} (license-from-pom pom-path)] + (when name (str name ": " url)))) + (license-from-backfill lib-name backfill))] + [lib-name (cond-> {:coords {:group (namespace lib-name) + :artifact (name lib-name) + :version (:mvn/version info)} + :license license} + (not license) + (assoc :error "Error determining license"))])) (catch Exception e - [jar-filename {:error e}]))))) - -(defn write-license [success-os [_jar {:keys [coords license]}]] - (let [{:keys [group artifact]} coords] - (binding [*out* success-os] - (println "The following software may be included in this product: " - group ":" artifact - ". This software contains the following license and notice below:") - (println "\n") - (println license) - (println "\n\n----------\n")))) + [lib-name {:error e}]))))) + +(defn write-license [success-os [lib {:keys [coords license]}]] + (binding [*out* success-os] + (println "The following software may be included in this product:" + (str lib ": " (:version coords) ".") + "This software contains the following license and notice below:") + (println "\n") + (println license) + (println "\n\n----------\n"))) (defn report-missing [error-os [jar {:keys [coords]}]] (let [{:keys [group artifact]} coords @@ -178,24 +165,26 @@ (binding [*out* error-os] (println dep-name " : No license information found.")))) -(defn process* [{:keys [classpath-entries backfill]}] - (let [info (map #(discern-license-and-coords % backfill) classpath-entries) +(defn process* + "Returns a map of `:with-license` and `:without-license`." + [{:keys [libs backfill]}] + (let [info (map #(discern-license-and-coords % backfill) libs) categorized (group-by (comp (complement #(contains? % :error)) second) info)] {:with-license (categorized true) :without-license (categorized false)})) (defn jar-entries - "Returns a seq of jar entries on the classpath" - [classpath] - (->> (str/split classpath (re-pattern classpath-separator)) - (filter jar-file?))) + "Filters the `:libs` map of a basis to just `:mvn` based (jar based) libs. Keys are symbols of the lib and the value + is a map of `:mvn/version` and `:paths` amont others." + [basis] + (into {} (filter (comp #{:mvn} :deps/manifest val) (:libs basis)))) (defn generate - "Process a classpath, creating a file of all license information, writing to `:output-filename`. `classpath-entries` - should be a seq of classpath roots. Split a classpath on the classpath separator. Backfill is a clojure data - structure or a filename of an edn file of a clojure datastructure providing for backfilling license information if - it is not discernable from the jar. Should be of the form (note keys are strings not symbols) + "Process a basis from clojure.tools.build.api/create-basis, creating a file of all license information, writing to + `:output-filename`. Backfill is a clojure data structure or a filename of an edn file of a clojure datastructure + providing for backfilling license information if it is not discernable from the jar. Should be of the form (note + keys are strings not symbols) {\"group\" {\"artifact\" \"license text\"} \"group\" {\"artifact\" {:resource \"filename-of-license\"}} @@ -206,23 +195,24 @@ Algorithm is: - check jar for license file at a few different standard paths. If present keep this text. + - Look in pom file next to jar or in jar for license information. If found this information is used, + it is not expanded into a full license text. - look in provided backfill information for license text or a resource containing the license text - - Look in pom file next to jar or in jar for license information. If found this information is used, it is not - expanded into a full license text. - Reports if `:report?` is true (the default). Writes missing license information to *err* and summary of identified licenses to *out*. + Reports if `:report?` is true (the default). Writes missing license information to *err* and summary of identified + licenses to *out*. Returns a map - {:with-license [ [jar-filename {:coords {:group :artifact :version} :license <text>}] ...] - :without-license [ [jar-filename {:coords {:group :artifact :version} :error <text>}] ... ]}" - [{:keys [classpath backfill output-filename report?] :or {report? true}}] + {:with-license [ [lib-name {:coords {:group :artifact :version} :license <text>}] ...] + :without-license [ [lib-name {:coords {:group :artifact :version} :error <text>}] ... ]}" + [{:keys [basis backfill output-filename report?] :or {report? true}}] (let [backfill (if (string? backfill) (edn/read-string (slurp (io/resource backfill))) (or backfill {})) - entries (jar-entries classpath) + entries (jar-entries basis) {:keys [with-license without-license] :as license-info} - (process* {:classpath-entries entries - :backfill backfill})] + (process* {:libs entries + :backfill backfill})] (when (seq with-license) (with-open [os (io/writer output-filename)] (run! #(write-license os %) with-license))) @@ -236,3 +226,21 @@ ;; best defaults. Want to make sure we never kill our build script #_(System/exit (if (seq without-license) 1 0)))) license-info)) + +(comment + (def basis (b/create-basis {:project "path-to-metabase/deps.edn"})) + (def libs (process* {:libs (jar-entries basis) + :backfill (edn/read-string + (slurp (io/resource "overrides.edn")))})) + (process* {:libs (-> basis :libs (select-keys '[org.clojure/clojure + org.clojure/core.async]))}) + + (get-in basis [:libs 'org.clojure/clojure]) + + (->> libs + :without-license + (map first)) + + (def libs-no-overrides (process* {:libs (jar-entries basis)})) + (->> libs-no-overrides :without-license (map first)) + ) diff --git a/bin/build-mb/test/build/licenses_test.clj b/bin/build-mb/test/build/licenses_test.clj index a027918cc05659c9a5e16cd0cd63a8d3d58ef937..eb5f5ff5f5becd745eaa9e80278468076836edfc 100644 --- a/bin/build-mb/test/build/licenses_test.clj +++ b/bin/build-mb/test/build/licenses_test.clj @@ -5,24 +5,12 @@ [clojure.java.io :as io] [clojure.string :as str] [clojure.test :refer [deftest is run-tests testing]] + [clojure.tools.build.api :as b] [metabuild-common.core :as u]) (:import (java.io StringReader StringWriter) (java.nio.file Files FileSystems LinkOption Path Paths))) -(def classpath-urls (str/split (System/getProperty "java.class.path") - (re-pattern lic/classpath-separator))) - -(defn jar-filename-from-cp - [jar-filename classpath-urls] - (let [[x & rst :as found] (filter #(when (str/includes? % jar-filename) %) classpath-urls)] - (cond (seq rst) (throw (ex-info (str "Multiple jars found for " jar-filename) - {:filename jar-filename - :found found})) - (not x) (throw (ex-info (str "No results found for " jar-filename) - {:filename jar-filename})) - x x))) - -(defn parse [rdr] (xml/parse rdr :skip-whitespace true)) +(defn- parse [rdr] (xml/parse rdr :skip-whitespace true)) (def clojure-xml "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"> @@ -43,7 +31,8 @@ </project>" ) -(def clojure-jdbc-xml "<?xml version=\"1.0\" encoding=\"utf-8\"?> +(def ^:private clojure-jdbc-xml + "<?xml version=\"1.0\" encoding=\"utf-8\"?> <project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\"> <modelVersion>4.0.0</modelVersion> <artifactId>java.jdbc</artifactId> @@ -58,59 +47,40 @@ </project>" ) -(deftest pom->coordinates-test - (testing "Parses version information from pom" - (testing "When group information is top level" - (let [xml (parse (StringReader. clojure-xml))] - (is (= {:group "org.clojure" - :artifact "clojure" - :version "1.10.3"} - (lic/pom->coordinates xml))))) - (testing "When group informatoin is under the parent tag" - (let [xml (parse (StringReader. clojure-jdbc-xml))] - (is (= {:group "org.clojure" - :artifact "java.jdbc" - :version "0.7.11"} - (lic/pom->coordinates xml))))))) - -(defn jar->path [filename] +(defn jar->path + "Return a Path for a jar." + ^Path [filename] (let [path (Paths/get filename (into-array String []))] (if (Files/exists path (into-array LinkOption [])) path - (throw (ex-data (str "Jar " filename " not found") {:filename filename}))))) - -(defn jar [filename] - (jar->path (jar-filename-from-cp filename classpath-urls))) - -(deftest pom->license-test - (testing "Can find license information from the pom" - (testing "If present" - (let [xml (parse (StringReader. clojure-xml))] - (is (= {:name "Eclipse Public License 1.0" - :url "http://opensource.org/licenses/eclipse-1.0.php" - :distribution "repo"} - (lic/pom->licenses xml)))) - (let [jar-filename (jar-filename-from-cp "org/clojure/clojure" classpath-urls) - jar-path (Paths/get jar-filename (into-array String [])) - ^ClassLoader classloader nil] - (with-open [jar-fs (FileSystems/newFileSystem jar-path classloader)] - (let [pom-path (lic/determine-pom jar-filename jar-fs)] - (is (= {:name "Eclipse Public License 1.0" - :url "http://opensource.org/licenses/eclipse-1.0.php" - :distribution "repo"} - (lic/do-with-path-is pom-path (comp lic/pom->licenses parse)))))))) - (testing "Returning nil if not present" - (let [xml (parse (StringReader. clojure-jdbc-xml))] - (is (nil? (lic/pom->licenses xml))))))) + (throw (ex-info (str "Jar " filename " not found") {:filename filename}))))) + +(def ^:private basis + "A basis for this project suitable for testing." + (b/create-basis {:project (u/filename u/project-root-directory "deps.edn")})) + +(defn- jar ^Path [lib-name] + (jar->path (-> basis :libs (get lib-name) :paths first))) + +(deftest license-from-pom-test + (let [clojure-jar (-> basis :libs (get 'org.clojure/clojure) :paths first) + jar-path (Paths/get ^String clojure-jar (into-array String []))] + (with-open [jar-fs (FileSystems/newFileSystem jar-path + (ClassLoader/getSystemClassLoader))] + (let [clojure-pom (lic/determine-pom clojure-jar jar-fs)] + (is (some? clojure-pom) "Clojure pom not found") + (is (= {:name "Eclipse Public License 1.0" + :url "http://opensource.org/licenses/eclipse-1.0.php"} + (lic/license-from-pom clojure-pom)))))) + (let [libs (-> basis :libs (select-keys '[org.clojure/clojure]) first)] + (is (= "Eclipse Public License 1.0: http://opensource.org/licenses/eclipse-1.0.php" + (-> (lic/discern-license-and-coords libs {}) second :license))))) (deftest license-from-jar-test (letfn [(license-path [j f] - (let [cl ^ClassLoader (ClassLoader/getSystemClassLoader)] - ;; java 16 has a single arity that just takes a path, but older versions need a classloader. It can be - ;; nil but felt weird typehinting the nil in a binding - (with-open [jar-fs (FileSystems/newFileSystem ^Path (jar j) cl)] - (some-> (lic/license-from-jar jar-fs) - f)))) + (with-open [jar-fs (FileSystems/newFileSystem (jar j) (ClassLoader/getSystemClassLoader))] + (some-> (lic/license-from-jar jar-fs) + f))) (first-line [path] (lic/do-with-path-is path (fn [is] (->> (slurp is) @@ -120,11 +90,11 @@ str/trim))))] (testing "Can find license information bundled in the jar" (is (= "META-INF/LICENSE.txt" - (license-path "commons/commons-math3" str))) + (license-path 'org.apache.commons/commons-math3 str))) (is (= "Apache License" - (license-path "commons/commons-math3" first-line)))) + (license-path 'org.apache.commons/commons-math3 first-line)))) (testing "Nil if not present" - (is (nil? (license-path "cronparser/cron-parser-core" identity)))))) + (is (nil? (license-path 'net.redhogs.cronparser/cron-parser-core identity)))))) (deftest license-from-backfill-test (let [backfill {"a" {"a" "License Information"} @@ -133,20 +103,20 @@ {"group-y" "License Information" "group-z" {:resource "EPL.txt"}}}] (doseq [[coords expected-license] - [[{:group "a" :artifact "a"} "License Information"] - [{:group "b" :artifact "b"} "Permission is hereby granted"] - [{:group "group-y" :artifact "literally-any-package"} "License Information"] - [{:group "group-z" :artifact "literally-any-package"} "\nEclipse Public License"]]] + [['a/a "License Information"] + ['b/b "Permission is hereby granted"] + ['group-y/literally-anything "License Information"] + ['group-z/literally-anything "\nEclipse Public License"]]] (let [license (lic/license-from-backfill coords backfill)] (is (not (str/blank? license))) (is (str/starts-with? license expected-license)))))) (deftest process*-test (testing "categorizes jar entries" - (let [jars (map #(jar-filename-from-cp % classpath-urls) - ["org/clojure/clojure" - "commons/commons-math3" - "cronparser/cron-parser-core"]) + (let [jar-libs (select-keys (:libs basis) + '[org.clojure/clojure + org.apache.commons/commons-math3 + net.redhogs.cronparser/cron-parser-core]) normalize-entry (fn [[jar {:keys [coords license error]}]] [((juxt :group :artifact) coords) (cond-> {:license (not (str/blank? license))} @@ -162,9 +132,9 @@ ["org.apache.commons" "commons-math3"] {:license true}} :without-license {["net.redhogs.cronparser" "cron-parser-core"] - {:license false :error "Error determining license or coords"}}} - (normalize (lic/process* {:classpath-entries jars - :backfill {}}))))) + {:license false :error "Error determining license"}}} + (normalize (lic/process* {:libs jar-libs + :backfill {}}))))) (testing "with backfill by group and artifact" (is (= {:with-license {["org.clojure" "clojure"] {:license true} @@ -172,7 +142,7 @@ ["net.redhogs.cronparser" "cron-parser-core"] {:license true}} :without-license {}} - (normalize (lic/process* {:classpath-entries jars + (normalize (lic/process* {:libs jar-libs :backfill {"net.redhogs.cronparser" {"cron-parser-core" "license"}}}))))) @@ -183,24 +153,23 @@ ["net.redhogs.cronparser" "cron-parser-core"] {:license true}} :without-license {}} - (normalize (lic/process* {:classpath-entries jars + (normalize (lic/process* {:libs jar-libs :backfill {:override/group {"net.redhogs.cronparser" "license"}}})))))))) (deftest write-license-test - (is (= (str "The following software may be included in this product: a : a . " + (is (= (str "The following software may be included in this product: a/a: 1.0. " "This software contains the following license and notice below:\n\n\n" "license text\n\n\n----------\n\n") (let [os (StringWriter.)] - (lic/write-license os ["jar" {:coords {:group "a" :artifact "a"} - :license "license text"}]) + (lic/write-license os ['a/a {:coords {:group "a" :artifact "a" :version "1.0"} + :license "license text"}]) (str os))))) (defn- loop-until-success [f max step-name] (loop [n 0] - (let [success? (try - (do (f) true) + (let [success? (try (f) true (catch Exception _ false))] (cond success? ::done (and (not success?) (< n max)) (recur (inc n)) @@ -209,21 +178,19 @@ (deftest all-deps-have-licenses (testing "All deps on the classpath have licenses" (loop-until-success #(u/sh {:dir u/project-root-directory} "clojure" "-A:ee" "-P") 3 "download deps") - (let [classpath (u/sh {:dir u/project-root-directory - :quiet? true} - "clojure" - "-A:ee" - "-Spath") - classpath-entries (->> (str/split (last classpath) (re-pattern lic/classpath-separator)) - (filter lic/jar-file?))] - (let [results (lic/process* {:classpath-entries classpath-entries - :backfill (edn/read-string - (slurp (io/resource "overrides.edn")))})] - (is (nil? (:without-license results)) - (str "Deps without license information:\n" - (str/join "\n" (map first (:without-license results))))) - (is (= (set classpath-entries) - (into #{} (->> results :with-license (map first)))))) + (let [jar-libs (lic/jar-entries basis) + results (lic/process* {:libs jar-libs + :backfill (edn/read-string + (slurp (io/resource "overrides.edn")))})] + (is (nil? (:without-license results)) + (str "Deps without license information:\n" + (str/join "\n" (map first (:without-license results))))) + (is (= (set (keys jar-libs)) + (into #{} (->> results :with-license (map first))))) (is (some? (:without-license - (lic/process* {:classpath-entries classpath-entries - :backfill {}}))))))) + (lic/process* {:libs jar-libs + :backfill {}}))))))) + +(comment + (run-tests) + )