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
- all-loaded-artifacts
- clean
- current-version
- load
- load-artifact
- loaded-artifact?
- resolve
- resolve-classloader
- resolve-jar-entry
- unload
- unload-artifact
- version-map
all-loaded ^
[coord] [coord loader]
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?
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]}]
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]
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]
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"]]
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]
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}]
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]
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}}]
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"])
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]
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"]