Skip to content

Commit d017537

Browse files
committed
CLJ-2761 - add-libs, add-lib, sync-deps for the repl
1 parent d1ae278 commit d017537

File tree

4 files changed

+109
-4
lines changed

4 files changed

+109
-4
lines changed

build.xml

+5
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@
7777
<arg value="clojure.java.browse"/>
7878
<arg value="clojure.java.javadoc"/>
7979
<arg value="clojure.java.shell"/>
80+
<arg value="clojure.java.process"/>
8081
<arg value="clojure.java.browse-ui"/>
82+
<arg value="clojure.java.basis.impl"/>
83+
<arg value="clojure.java.basis"/>
8184
<arg value="clojure.string"/>
8285
<arg value="clojure.data"/>
8386
<arg value="clojure.reflect"/>
@@ -86,6 +89,8 @@
8689
<arg value="clojure.uuid"/>
8790
<arg value="clojure.core.reducers"/>
8891
<arg value="clojure.math"/>
92+
<arg value="clojure.tools.deps.interop"/>
93+
<arg value="clojure.repl.deps"/>
8994
</java>
9095
</target>
9196

src/clj/clojure/core.clj

+5
Original file line numberDiff line numberDiff line change
@@ -6340,6 +6340,11 @@ fails, attempts to require sym's namespace and retries."
63406340
:added "1.0"}
63416341
*e)
63426342

6343+
(def ^:dynamic
6344+
^{:doc "Bound to true in a repl thread"
6345+
:added "1.12"}
6346+
*repl*)
6347+
63436348
(defn trampoline
63446349
"trampoline can be used to convert algorithms requiring mutual
63456350
recursion without stack consumption. Calls f with supplied args, if

src/clj/clojure/main.clj

+5-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
" (" (.getFileName el) ":" (.getLineNumber el) ")")))
7474
;;;;;;;;;;;;;;;;;;; end of redundantly copied from clojure.repl to avoid dep ;;;;;;;;;;;;;;
7575

76-
7776
(defmacro with-bindings
7877
"Executes body in the context of thread-local bindings for several vars
7978
that often need to be set!: *ns* *warn-on-reflection* *math-context*
@@ -94,6 +93,7 @@
9493
*unchecked-math* *unchecked-math*
9594
*assert* *assert*
9695
clojure.spec.alpha/*explain-out* clojure.spec.alpha/*explain-out*
96+
*repl* true
9797
*1 nil
9898
*2 nil
9999
*3 nil
@@ -356,7 +356,8 @@
356356
by default when a new command-line REPL is started."} repl-requires
357357
'[[clojure.repl :refer (source apropos dir pst doc find-doc)]
358358
[clojure.java.javadoc :refer (javadoc)]
359-
[clojure.pprint :refer (pp pprint)]])
359+
[clojure.pprint :refer (pp pprint)]
360+
[clojure.repl.deps :refer (add-libs add-lib sync-deps)]])
360361

361362
(defmacro with-read-known
362363
"Evaluates body with *read-eval* set to a \"known\" value,
@@ -454,7 +455,7 @@ by default when a new command-line REPL is started."} repl-requires
454455
(prompt)
455456
(flush)
456457
(loop []
457-
(when-not
458+
(when-not
458459
(try (identical? (read-eval-print) request-exit)
459460
(catch Throwable e
460461
(caught e)
@@ -670,6 +671,6 @@ java -cp clojure.jar clojure.main -i init.clj script.clj args...")
670671
(catch Throwable t
671672
(report-error t :target "file")
672673
(System/exit 1))))
673-
(finally
674+
(finally
674675
(flush))))
675676

src/clj/clojure/repl/deps.clj

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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

Comments
 (0)