11(ns rules-clojure.compile
2- (:refer-clojure :exclude [agent send await])
32 (:require [clojure.java.io :as io]
43 [clojure.string :as str]
54 [rules-clojure.java.classpath :as cp]
65 [rules-clojure.namespace.parse :as parse]
76 [rules-clojure.fs :as fs]
8- [rules-clojure.util :refer [shasum]])
7+ [rules-clojure.util :refer [shasum debug ]])
98 (:import [java.util.concurrent CompletableFuture]))
109
1110(set! *warn-on-reflection* true )
1514; ; dependencies. If we require a new clojure version, that requires
1615; ; all users to upgrade so try to do that sparingly as well.
1716
18- (defn debug [& args]
19- #_ (println (locking true (apply print-str args))))
20-
2117(defn deref!
2218 " throw `ex` if *f does not complete within timeout"
2319 [*f timeout ex]
3026(defn src-resource-name [ns ]
3127 (.substring ^String (#'clojure.core/root-resource ns ) 1 ))
3228
29+ (defn resource
30+ [path]
31+ (assert (instance? ClassLoader (.getContextClassLoader (Thread/currentThread ))))
32+ (assert (= (.getContextClassLoader (Thread/currentThread ))
33+ (.getClassLoader (class resource)))
34+ (print-str " context classloader:" (.getContextClassLoader (Thread/currentThread ))
35+ " #'resource loader:" (.getClassLoader (class resource))))
36+ (io/resource path))
37+
3338(defn src-resource
3439 " given a namespace symbol, return a tuple of [filename URL] where the
3540 backing .clj is located, or nil if it couldn't be found"
3641 [ns ]
3742 {:pre [(symbol? ns )]}
3843 (->> [" .clj" " .cljc" ]
39- (map (fn [ext]
44+ (some (fn [ext]
4045 (let [src-path (str (src-resource-name ns ) ext)
41- src-resource (io/resource src-path)]
42- (when src-resource
43- [src-path src-resource]))))
44- (filter identity)
45- ; ; ((fn [srcs]
46- ; ; (when (> (count srcs) 1)
47- ; ; (println "WARNING multiple copies of" ns "found:" srcs))
48- ; ; srcs))
49- (first )))
46+ src-resource (resource src-path)]
47+ src-resource)))))
5048
5149(defn loaded? [ns ]
5250 {:pre [(symbol? ns )]}
8482; ; the class files from temp directory. We fingerprint namespaces
8583; ; using the SHA of the file contents
8684
87-
88-
89- (defn ns->resource-name
90- " given a namespace symbol, return the name of the resource where it can
91- be found"
85+ (defn ns->class-resource-name
86+ " given a namespace symbol, return the name of classfile that will load it"
9287 [ns ]
9388 (-> ns
9489 (munge )
9590 (str/replace #"\. " " /" )
9691 (str " __init.class" )))
9792
9893(defn compiled?
99- " truthy if the namespace has AOT .class files on the classpath"
10094 [ns ]
101- (io/resource (ns->resource-name ns )))
95+ ; ; We could use Class/forName, but that would attempt to load the
96+ ; ; class. Use resource instead to avoid the side effect
97+ (resource (ns->class-resource-name ns )))
98+
99+ (defn add-classpath! [dir]
100+ (let [dir-f (fs/path->file dir)]
101+ (assert (.exists dir-f) (print-str dir-f " not found" ))
102+ (.addURL (.getClassLoader (class add-classpath!)) (.toURL dir-f))))
102103
103104; ; root directory for all compiles. Each compile will be a subdir of
104105; ; this
@@ -111,10 +112,10 @@ be found"
111112 " return the hash of the ns file contents"
112113 [ns ]
113114 {:pre [(symbol? ns )]}
114- (assert (src-resource ns ) (print-str " couldn't find src resource for" ns ))
115+ (let [cl (.getContextClassLoader (Thread/currentThread ))]
116+ (assert (src-resource ns ) (print-str " couldn't find src resource for" ns " with context classloader" cl (cp/classpath cl))))
115117 (-> ns
116118 (src-resource )
117- second
118119 (io/input-stream )
119120 (.readAllBytes )
120121 (shasum )))
@@ -193,14 +194,12 @@ be found"
193194 java.io.PushbackReader.))
194195
195196(defn ns-deps- [ns ]
196- (assert (src-resource ns ) (print-str " couldn't find resource for" ns ))
197- (-> ns
198- src-resource
199- second
200- reader
201- parse/read-ns-decl
202- parse/deps-from-ns-decl
203- (disj ns )))
197+ (some-> ns
198+ src-resource
199+ reader
200+ parse/read-ns-decl
201+ parse/deps-from-ns-decl
202+ (disj ns )))
204203
205204(def ns-deps (memoize ns-deps-))
206205
@@ -217,6 +216,7 @@ be found"
217216 :bound-require? (bound? #'clojure.core/require)
218217 :sha sha))
219218 (assert (not (loaded? ns )) (print-str ns :compiled? (compiled? ns ) :loaded? (loaded? ns ) :sha sha))
219+ (add-classpath! classes-dir)
220220 (binding [*compile-path* (str classes-dir)]
221221 (compile ns )
222222 (assert (seq (fs/ls-r classes-dir)) (print-str " compile-: no .class files found in" classes-dir)))))))
@@ -251,17 +251,17 @@ be found"
251251(defn context-classloader-conveyor-fn [f]
252252 ; ; context classloaders are not conveyed by default in futures, but we set it in rules-clojure.jar/compile!
253253 (let [cl (.getContextClassLoader (Thread/currentThread ))]
254- (fn
255- ([]
256- (.setContextClassLoader (Thread/currentThread ) cl)
257- (f )))))
254+ (fn []
255+ (.setContextClassLoader (Thread/currentThread ) cl)
256+ (f ))))
258257
259258(defn binding-conveyor-fn [f]
260- ; ; don't use clojure.core/binding-conveyor-fn, because that usees
261- ; ; clone/reset ThreadBindings, rather than push/pop. We use push and
262- ; ; pop because clone shares TBoxes, which store the threadId they
263- ; ; came from, which breaks clojure.lang.Compiler when an in-progress
264- ; ; compile attempts to Var/set! from a new thread
259+ ; ; don't use clojure.core/binding-conveyor-fn, because that uses
260+ ; ; clone/reset ThreadBindings; It allows the conveyed thread to read
261+ ; ; bindings, but not set! them (because it clones TBoxes and the
262+ ; ; cloned tbox stores the thread.id), which breaks
263+ ; ; clojure.lang.Compiler. Instead, use push/pop ThreadBindings
264+ ; ; which creates new tboxes and allows set! to work.
265265 (let [bindings (clojure.lang.Var/getThreadBindings )]
266266 (fn []
267267 (try
@@ -292,7 +292,6 @@ be found"
292292 [ns f]
293293 {:pre [(symbol? ns )]
294294 :post [(future? %)]}
295- (debug " ns-send-sync" ns )
296295 (-> ns-futures
297296 (swap! update ns (fn [**f]
298297 (or **f
@@ -412,7 +411,7 @@ be found"
412411 (debug " WARNING no ns found for" p)) true )]}
413412 (->> [" .clj" " .cljc" ]
414413 (keep (fn [ext]
415- (io/ resource (load-path (str p ext)))))
414+ (resource (load-path (str p ext)))))
416415 (keep (fn [r]
417416 (with-open [rdr (java.io.PushbackReader. (io/reader r))]
418417 (let [ns (parse/name-from-ns-decl (parse/read-ns-decl rdr))]
@@ -421,13 +420,11 @@ be found"
421420
422421(defn spy-load [& paths]
423422 (let [ns-sym (symbol (str *ns*))]
424- (debug " spy-load" paths)
425423 (->> paths
426424 (mapv (fn [p]
427425 (if-let [dep-ns (load->ns p)]
428426 @(pcompile ns-sym dep-ns)
429- (real-load p)))))
430- (debug " spy-load" ns-sym paths " done" )))
427+ (real-load p)))))))
431428
432429(defn spy-require [& args]
433430 ; ; the ns block will add `ns` to clojure.core/*loaded-libs*, so it won't be eval'd twice.
@@ -441,7 +438,6 @@ be found"
441438
442439(defn spy-load-one
443440 [lib need-ns require ]
444- (debug " spy-load-one" lib)
445441 (spy-load (root-resource lib))
446442 (throw-if (and need-ns (not (find-ns lib)))
447443 " namespace '%s' not found after loading '%s'"
@@ -453,7 +449,6 @@ be found"
453449; ; we need this because dtype-next calls load-lib directly rather than `load` or `require` ಠ_ಠ
454450(defn spy-load-lib
455451 [prefix lib & options]
456- (debug " spy-load-lib" lib)
457452 (throw-if (and prefix (pos? (.indexOf (name lib) (int \.))))
458453 " Found lib name '%s' containing period with prefix '%s'. lib names inside prefix lists must not contain periods"
459454 (name lib) prefix)
@@ -474,10 +469,8 @@ be found"
474469 (binding [clojure.core/*loading-verbosely* (or @#'clojure.core/*loading-verbosely* verbose)]
475470 (if load
476471 (try
477- (debug " spy-load-lib actually loading" load lib)
478472 (load lib need-ns require )
479473 (catch Exception e
480- (debug " spy-load-lib while loading" lib e)
481474 (when undefined-on-entry
482475 (remove-ns lib))
483476 (throw e)))
@@ -528,9 +521,9 @@ be found"
528521(defn compile! [classes-dir aot-nses out]
529522 {:pre [(string? classes-dir)
530523 (every? string? aot-nses)]}
524+ ; ; make sure this is loaded before any compile so it doesn't end up in user jars
525+ @(pcompile nil 'clojure.core.specs.alpha)
531526 (binding [*out* out]
532- (when (seq aot-nses)
533- (debug " compile!" (seq aot-nses)))
534527 (with-spy
535528 (let [aot-nses (map symbol aot-nses)]
536529 (doseq [n aot-nses]
0 commit comments