jvm.deps

JVM artifact resolution and classpath dependency inspection

Connect artifact coordinates to classpath locations and inspect which dependency versions are available to a loader.

1    Overview

jvm.deps resolves classes and namespaces to classpath locations, maps dependency coordinates to local archive paths, and reports artifacts visible through a classloader.

2    Walkthrough

2.1    Resolve code locations

(require '[jvm.deps :as deps])

(deps/resolve-classloader String)
(deps/resolve-classloader 'clojure.core)
(deps/resolve 'clojure.core
              '[org.clojure/clojure "1.11.1"])

2.2    Inspect dependencies

(deps/all-loaded-artifacts)
(deps/version-map)
(deps/current-version 'org.clojure/clojure)
(deps/loaded-artifact?
 '[org.clojure/clojure "1.11.1"])

2.3    Classpath operations

Collection operations are available for adding known local coordinates to a mutable loader and removing them again. The single-coordinate functions are useful when callers need precise control over reporting and error handling.

3    API



all-loaded ^

[coord] [coord loader]
Added 3.0

returns all the loaded artifacts of the same group and name

v 3.0
(defn all-loaded
  ([coord]
   (all-loaded coord loader/+base+))
  ([coord loader]
   (let [{:keys [group artifact version]} (artifact/artifact coord)]
     (->> (all-loaded-artifacts loader :default)
          (filter #(and (= artifact (:artifact %))
                        (= group (:group %))))))))
link
(all-loaded 'org.clojure/clojure) ;;=> ('org.clojure:clojure:jar:) => sequential?

all-loaded-artifacts ^

[] [type] [loader type]
Added 3.0

returns all loaded artifacts

v 3.0
(defn all-loaded-artifacts
  ([]
   (all-loaded-artifacts :default))
  ([type]
   (all-loaded-artifacts loader/+base+ type))
  ([loader type]
   (->> (try (loader/all-urls loader)
             (catch Throwable t))
        (map #(.getFile ^java.net.URL %))
        (filter #(.endsWith ^String % ".jar"))
        (map (partial artifact/artifact type)))))
link
(all-loaded-artifacts) => sequential?

clean ^

[artifact {:keys [full simulate]}]
Added 3.0

cleans the maven entries for the artifact, `:full` deletes all the versions

v 3.0
(defn clean
  ([artifact {:keys [full simulate]}]
   (let [path (fs/path (artifact/artifact-path artifact))
         del-path (nth (iterate fs/parent path)
                       (if full 2 1))]
     (fs/delete del-path {:simulate simulate}))))
link
(clean '[org.clojure/clojure "2.4.8"] {:full true :simulate true}) => set?

current-version ^

[artifact]
Added 3.0

finds the current artifact version for a given classloader

v 3.0
(defn current-version
  ([artifact]
   (let [{:keys [group artifact] :as m} (artifact/artifact-default artifact)
         sym (symbol group artifact)
         version (or ((version-map) sym)
                     (if (= sym 'org.clojure/clojure)
                       (clojure-version))
                     (throw (ex-info "Cannot find the version of artifact."
                                     {:artifact artifact
                                      :available (keys (version-map))})))]
     version)))
link
(current-version 'org.clojure/clojure) => string?

load ^

[coords] [coords loader]
Added 3.0

loads all artifacts in list, unloading previous versions of the same artifact

v 3.0
(defn load
  ([coords]
   (load coords loader/+base+))
  ([coords loader]
   (let [output (->> (keep #(load-artifact % loader) coords)
                     (sort-by (juxt :group :artifact :version))
                     (vec))]
     (when-not (empty? output)
       (print/print-title (format "LOADED ARTIFACTS (%d)" (count output)))
       (doseq [x output]
         (print/println (ansi/bold (str "  " x)) "<=" (artifact/artifact-path x))))
     output)))
link
(let [coord '[org.clojure/clojure "1.12.0"] cl (loader/url-classloader [])] (load [coord] cl)) => ['[org.clojure/clojure "1.12.0"]]

load-artifact ^

[coord] [coord loader]
Added 3.0

loads an artifact into the system

v 3.0
(defn load-artifact
  ([coord]
   (load-artifact coord loader/+base+))
  ([coord loader]
   (if-not (loaded-artifact? coord loader)
     (let [path (artifact/artifact-path coord)]
       (if (fs/exists? path)
         (do (loader/add-url loader path)
             coord)
         (throw (ex-info "Jar file does not exist:" {:path path
                                                     :coord coord})))))))
link
(let [coord '[org.clojure/clojure "1.12.0"] cl (loader/url-classloader [])] [(load-artifact coord cl) (loaded-artifact? coord cl)]) => ['[org.clojure/clojure "1.12.0"] true]

loaded-artifact? ^

[coord] [coord loader]
Added 3.0

checks if artifact has been loaded

v 3.0
(defn loaded-artifact?
  ([coord]
   (loaded-artifact? coord loader/+base+))
  ([coord loader]
   (loader/has-url? loader (artifact/artifact-path coord))))
link
(let [coord '[org.clojure/clojure "1.12.0"] cl (loader/url-classloader [])] [(loaded-artifact? coord cl) (do (loader/add-url cl (artifact/artifact-path coord)) (loaded-artifact? coord cl))]) => [false true]

resolve ^

[x] [x context] [x context {:keys [tag], :or {tag :path}, :as opts}]
Added 3.0

resolves a class or namespace within a context

v 3.0
(defn resolve
  ([x]
   (resolve x nil))
  ([x context]
   (resolve x context {}))
  ([x context {:keys [tag] :or {tag :path} :as opts}]
   (cond (nil? context)
         (resolve-classloader x)

         (and (vector? context)
              (not (artifact/coord? context)))
         (first (keep #(resolve x % opts)
                      context))

         :else
         (resolve-jar-entry x (artifact/artifact :path context) opts))))
link
(resolve 'clojure.core ['org.clojure/clojure (current-version 'org.clojure/clojure)]) => (contains [string? "clojure/core.clj"])

resolve-classloader ^

[x] [x loader]
Added 3.0

resolves a class or namespace to a physical location

v 3.0
(defn resolve-classloader
  ([x] (resolve-classloader x loader/+base+))
  ([x loader]
   (let [resource (-> (base/resource-entry x)
                      (io/resource loader))]
     (if-let [path (if resource (.getPath resource))]
       (cond (.startsWith path "file:")
             (-> (subs path (count "file:"))
                 (clojure.string/split #"!/"))

             (.startsWith path "/")
             [nil path])))))
link
(resolve-classloader String) => (contains [anything #"java/lang/String.class"]) (resolve-classloader 'code.test) => (contains [nil (str (fs/path "src/code/test.clj"))])

resolve-jar-entry ^

[x artifact] [x artifact {:keys [tag], :or {tag :path}}]
Added 3.0

resolves a class or namespace within a jar

v 3.0
(defn resolve-jar-entry
  ([x artifact]
   (resolve-jar-entry x artifact {}))
  ([x artifact {:keys [tag] :or {tag :path}}]
   (let [path  (artifact/artifact :path artifact)
         entry (base/resource-entry x)]
     (if (archive/has? path entry)
       [(if (= :path tag)
          path
          (artifact/artifact tag path))
        entry]))))
link
(resolve-jar-entry 'clojure.core ['org.clojure/clojure (current-version 'org.clojure/clojure)]) => (contains-in [string? "clojure/core.clj"])

unload ^

[coords] [coords loader version]
Added 3.0

unloads all artifacts in list

v 3.0
(defn unload
  ([coords]
   (unload coords loader/+base+ :any))
  ([coords loader version]
   (let [all (all-loaded-artifacts loader :default)
         coords (map artifact/artifact coords)
         match-fn (fn [a0 a1] (and (= (:artifact a0) (:artifact a1))
                                   (= (:group a0) (:group a1))
                                   (case version
                                     :same (= (:version a0) (:version a1))
                                     :different (not= (:version a0) (:version a1))
                                     true)))
         artifacts (keep (fn [obj]
                           (if (some #(match-fn obj %) coords)
                             obj))
                         all)
         output (->> (keep #(unload-artifact % loader) artifacts)
                     (sort-by (juxt :group :artifact :version))
                     (vec))]
     (when-not (empty? output)
       (print/print-title (format "UNLOADED ARTIFACTS (%d)" (count output)))
       (doseq [x output]
         (println (ansi/bold (str "  " x)) "<=" (artifact/artifact-path x))))
     output)))
link
(let [coord '[org.clojure/clojure "1.12.0"] cl (loader/url-classloader [])] (with-redefs [all-loaded-artifacts (fn [_ _] [(artifact/artifact coord)]) unload-artifact (fn [x _] x)] (mapv artifact/rep? (unload [coord] cl :same)))) => [true]

unload-artifact ^

[coord] [coord loader]
Added 3.0

unloads an artifact from the system

v 3.0
(defn unload-artifact
  ([coord]
   (unload-artifact coord loader/+base+))
  ([coord loader]
   (if (loaded-artifact? coord loader)
     (try
       (do (loader/remove-url loader (artifact/artifact-path coord))
           coord)
       (catch Throwable t)))))
link
(let [coord '[org.clojure/clojure "1.12.0"] cl (loader/url-classloader [])] (with-redefs [loaded-artifact? (fn [_ _] true) loader/remove-url (fn [& _] nil)] (unload-artifact coord cl))) => '[org.clojure/clojure "1.12.0"]

version-map ^

[]
Added 3.0

returns all the loaded artifacts and their versions

v 3.0
(defn version-map
  ([]
   (->> (all-loaded-artifacts)
        (map (juxt :group :artifact :version))
        (map (fn [[group artifact version]]
               [(symbol group artifact) version]))
        (into {}))))
link
(version-map) => map?