/
classpath.clj
109 lines (97 loc) · 4.91 KB
/
classpath.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
(ns puppetlabs.kitchensink.classpath
(:import (java.net URLClassLoader URL))
(:require [clojure.java.io :refer [file Coercions]]
[dynapath.util :as dp])
(:refer-clojure :exclude (add-classpath)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Here, we have copied `add-classpath` out of pomegranate
;;;; (https://github.com/cemerick/pomegranate).
;;;;
;;;; We did this because we needed a corollary to the (deprecated)
;;;; `add-classpath` function in clojure.core,
;;;; but we did not want to pull pomegranate's rather large dependency tree
;;;; (we tried excluding as many of its dependencies as possible, but only
;;;; a small part of its dependency tree was exclude-able).
;;;;
;;;; There are no tests for these functions because pomegranate does not
;;;; contain any tests for them.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- classloader-hierarchy
"Returns a seq of classloaders, with the tip of the hierarchy first.
Uses the current thread context ClassLoader as the tip ClassLoader
if one is not provided."
([] (classloader-hierarchy (. (Thread/currentThread) getContextClassLoader)))
([tip]
(->> tip
(iterate #(.getParent ^ClassLoader %))
(take-while boolean))))
(defn- modifiable-classloader?
"Returns true iff the given ClassLoader is of a type that satisfies
the dynapath.dynamic-classpath/DynamicClasspath protocol, and it can
be modified."
[cl]
(dp/addable-classpath? cl))
(defn- ensure-modifiable-classloader
"Check if there is a modifiable classloader in the current hierarchy, and add
one if not."
[]
(let [classloader (.. Thread currentThread getContextClassLoader)]
(when (not-any? modifiable-classloader? (classloader-hierarchy classloader))
(let [new-cl (clojure.lang.DynamicClassLoader. classloader)]
(.. Thread currentThread (setContextClassLoader new-cl))))))
(defn add-classpath
"A corollary to the (deprecated) `add-classpath` in clojure.core. This implementation
requires a java.io.File or String path to a jar file or directory, and will attempt
to add that path to the right classloader (with the search rooted at the current
thread's context classloader).
Because this function is a replacement for `add-classpath` in clojure.core,
if you simply `:require` this namespace and then `:refer` to this function, you
will get the following warning:
WARNING: add-classpath already refers to: #'clojure.core/add-classpath in
namespace: [...], being replaced by:
#'puppetlabs.kitchensink.classpath/add-classpath
You can avoid this by referencing this function through its namespace.
This function is copied out of the 'pomegranate' library
(https://github.com/cemerick/pomegranate)."
([jar-or-dir classloader]
(when-not (dp/add-classpath-url classloader (.toURL (file jar-or-dir)))
(throw (IllegalStateException. (str classloader " is not a modifiable classloader")))))
([jar-or-dir]
(ensure-modifiable-classloader)
(let [classloaders (classloader-hierarchy)]
(if-let [cl (last (filter modifiable-classloader? classloaders))]
(add-classpath jar-or-dir cl)
(throw (IllegalStateException. (str "Could not find a suitable classloader to modify from "
classloaders)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; end of functions copied from pomegranate (see note above)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn jar-or-dir-to-url
"Given the path to a jar file or a directory, return a `java.net.URL`
object suitable for using with a `URLClassLoader`"
[jar-or-dir]
{:pre [(satisfies? Coercions jar-or-dir)]
:post [(instance? URL %)]}
;; explicitly calling `getAbsoluteFile` causes relative file paths to
;; be evaluated relative to the system property `user.dir` (which is
;; usually set to the current working directory). This is useful for
;; tests and other code that wants to emulated changing the working
;; directory.
(.. (file jar-or-dir) getAbsoluteFile toURL))
(defmacro with-additional-classpath-entries
"This macro takes a list of paths as an argument. It then temporarily
overrides the classpath to include the specified paths; the original
classpath is restored prior to returning."
[jars-and-dirs & body]
`{:pre [(coll? ~jars-and-dirs)
(every? (partial satisfies? Coercions) ~jars-and-dirs)]}
`(let [orig-loader# (.. Thread currentThread getContextClassLoader)
temp-loader# (URLClassLoader.
(into-array
URL
(map jar-or-dir-to-url ~jars-and-dirs))
orig-loader#)]
(try
(.. Thread currentThread (setContextClassLoader temp-loader#))
~@body
(finally (.. Thread currentThread (setContextClassLoader orig-loader#))))))