|
| 1 | +; Copyright (c) Rich Hickey. All rights reserved. |
| 2 | +; The use and distribution terms for this software are covered by the |
| 3 | +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) |
| 4 | +; which can be found in the file epl-v10.html at the root of this distribution. |
| 5 | +; By using this software in any fashion, you are agreeing to be bound by |
| 6 | +; the terms of this license. |
| 7 | +; You must not remove this notice, or any other, from this software. |
| 8 | +(ns clojure.repl.deps |
| 9 | + "clojure.repl.deps provides facilities for dynamically modifying the available |
| 10 | + libraries in the runtime when running at the REPL, without restarting" |
| 11 | + (:require |
| 12 | + [clojure.java.io :as jio] |
| 13 | + [clojure.java.basis :as basis] |
| 14 | + [clojure.java.basis.impl :as basis-impl] |
| 15 | + [clojure.tools.deps.interop :as tool]) |
| 16 | + (:import |
| 17 | + [clojure.lang DynamicClassLoader] |
| 18 | + [java.io File])) |
| 19 | + |
| 20 | +(set! *warn-on-reflection* true) |
| 21 | + |
| 22 | +(defn- add-loader-url |
| 23 | + "Add url string or URL to the highest level DynamicClassLoader url set." |
| 24 | + [url] |
| 25 | + (let [u (if (string? url) (java.net.URL. url) url) |
| 26 | + loader (loop [loader (.getContextClassLoader (Thread/currentThread))] |
| 27 | + (let [parent (.getParent loader)] |
| 28 | + (if (instance? DynamicClassLoader parent) |
| 29 | + (recur parent) |
| 30 | + loader)))] |
| 31 | + (if (instance? DynamicClassLoader loader) |
| 32 | + (.addURL ^DynamicClassLoader loader u) |
| 33 | + (throw (IllegalAccessError. "Context classloader is not a DynamicClassLoader"))))) |
| 34 | + |
| 35 | +(defn add-libs |
| 36 | + "Given lib-coords, a map of lib to coord, will resolve all transitive deps for the libs |
| 37 | + together and add them to the repl classpath, unlike separate calls to add-lib." |
| 38 | + {:added "1.12"} |
| 39 | + [lib-coords] |
| 40 | + (when-not *repl* (throw (RuntimeException. "add-libs is only available at the REPL"))) |
| 41 | + (let [{:keys [libs] :as basis} (basis/current-basis) |
| 42 | + current-libs (set (keys libs)) |
| 43 | + lib-coords (reduce-kv #(if (contains? current-libs %2) %1 (assoc %1 %2 %3)) |
| 44 | + {} lib-coords)] |
| 45 | + (when-not (empty? lib-coords) |
| 46 | + (let [procurer (dissoc basis [:basis-config :paths :deps :aliases :argmap :classpath :classpath-roots]) |
| 47 | + tool-args {:existing libs, :add lib-coords, :procurer procurer} |
| 48 | + {:keys [added] :as _res} (tool/invoke-tool {:tool-alias :deps, :fn 'clojure.tools.deps/resolve-added-libs, :args tool-args}) |
| 49 | + ;_ (clojure.pprint/pprint _res) |
| 50 | + paths (mapcat :paths (vals added)) |
| 51 | + urls (->> paths (map jio/file) (map #(.toURL ^File %)))] |
| 52 | + (run! add-loader-url urls) |
| 53 | + (basis-impl/update-basis! update :libs merge added) |
| 54 | + (let [ret (-> added keys sort vec)] |
| 55 | + (when (seq ret) ret)))))) |
| 56 | + |
| 57 | +(defn add-lib |
| 58 | + "Given a lib that is not yet on the repl classpath, make it available by |
| 59 | + downloading the library if necessary and adding it to the classloader. |
| 60 | + Libs already on the classpath are not updated. Requires a valid parent |
| 61 | + DynamicClassLoader. |
| 62 | +
|
| 63 | + lib - symbol identifying a library, for Maven: groupId/artifactId |
| 64 | + coord - optional map of location information specific to the procurer, |
| 65 | + or latest if not supplied |
| 66 | +
|
| 67 | + Returns coll of libs loaded, including transitive (or nil if none). |
| 68 | +
|
| 69 | + For info on libs, coords, and versions, see: |
| 70 | + https://clojure.org/reference/deps_and_cli" |
| 71 | + {:added "1.12"} |
| 72 | + ([lib coord] |
| 73 | + (add-libs {lib coord})) |
| 74 | + ([lib] |
| 75 | + (let [procurer (select-keys (basis/current-basis) [:mvn/repos :mvn/local-repo]) |
| 76 | + coord (tool/invoke-tool {:tool-alias :deps |
| 77 | + :fn 'clojure.tools.deps/find-latest-version |
| 78 | + :args {:lib lib, :procurer procurer}})] |
| 79 | + (if coord |
| 80 | + (add-libs {lib coord}) |
| 81 | + (throw (ex-info (str "No version found for lib " lib) {})))))) |
| 82 | + |
| 83 | +(defn sync-deps |
| 84 | + "Calls add-libs with any libs present in deps.edn but not yet present on the classpath. |
| 85 | +
|
| 86 | + :aliases - coll of alias keywords to use during the sync" |
| 87 | + {:added "1.12"} |
| 88 | + [& {:as opts}] |
| 89 | + (let [{:keys [aliases]} opts |
| 90 | + basis-config (:basis-config (basis/current-basis)) |
| 91 | + new-basis-config (update basis-config :aliases (fnil into []) aliases) |
| 92 | + new-basis (tool/invoke-tool {:tool-alias :deps, :fn 'clojure.tools.deps/create-basis, :args new-basis-config}) |
| 93 | + new-libs (:libs new-basis)] |
| 94 | + (add-libs new-libs))) |
0 commit comments