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 )]}
6260
6361 (contains? (loaded-libs ) ns ))
6462
63+ (defn loaded?-str [ns ]
64+ {:pre [(string? ns )]
65+ :post [(boolean? %)]}
66+ ; ; called from the worker's class loader, so we can't pass a
67+ ; ; `symbol` through
68+ (loaded? (symbol ns )))
69+
6570; ; we can be asked to AOT a namespace after it is already loaded. If
6671; ; the namespace contains protocols AND it's already loaded, that
6772; ; would break all downstream users of the protocol. Also, we want to
8489; ; the class files from temp directory. We fingerprint namespaces
8590; ; using the SHA of the file contents
8691
87-
88-
89- (defn ns->resource-name
90- " given a namespace symbol, return the name of the resource where it can
91- be found"
92+ (defn ns->class-resource-name
93+ " given a namespace symbol, return the name of classfile that will load it"
9294 [ns ]
9395 (-> ns
9496 (munge )
9597 (str/replace #"\. " " /" )
9698 (str " __init.class" )))
9799
98100(defn compiled?
99- " truthy if the namespace has AOT .class files on the classpath"
100101 [ns ]
101- (io/resource (ns->resource-name ns )))
102+ ; ; We could use Class/forName, but that would attempt to load the
103+ ; ; class. Use resource instead to avoid the side effect
104+ (resource (ns->class-resource-name ns )))
105+
106+ (defn add-classpath! [dir]
107+ (let [dir-f (fs/path->file dir)]
108+ (assert (.exists dir-f) (print-str dir-f " not found" ))
109+ (.addURL (.getClassLoader (class add-classpath!)) (.toURL dir-f))))
102110
103111; ; root directory for all compiles. Each compile will be a subdir of
104112; ; this
@@ -111,10 +119,10 @@ be found"
111119 " return the hash of the ns file contents"
112120 [ns ]
113121 {:pre [(symbol? ns )]}
114- (assert (src-resource ns ) (print-str " couldn't find src resource for" ns ))
122+ (let [cl (.getContextClassLoader (Thread/currentThread ))]
123+ (assert (src-resource ns ) (print-str " couldn't find src resource" (src-resource ns ) " for" ns " with context classloader" cl (cp/classpath cl))))
115124 (-> ns
116125 (src-resource )
117- second
118126 (io/input-stream )
119127 (.readAllBytes )
120128 (shasum )))
@@ -193,14 +201,12 @@ be found"
193201 java.io.PushbackReader.))
194202
195203(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 )))
204+ (some-> ns
205+ src-resource
206+ reader
207+ parse/read-ns-decl
208+ parse/deps-from-ns-decl
209+ (disj ns )))
204210
205211(def ns-deps (memoize ns-deps-))
206212
@@ -217,6 +223,7 @@ be found"
217223 :bound-require? (bound? #'clojure.core/require)
218224 :sha sha))
219225 (assert (not (loaded? ns )) (print-str ns :compiled? (compiled? ns ) :loaded? (loaded? ns ) :sha sha))
226+ (add-classpath! classes-dir)
220227 (binding [*compile-path* (str classes-dir)]
221228 (compile ns )
222229 (assert (seq (fs/ls-r classes-dir)) (print-str " compile-: no .class files found in" classes-dir)))))))
@@ -251,17 +258,17 @@ be found"
251258(defn context-classloader-conveyor-fn [f]
252259 ; ; context classloaders are not conveyed by default in futures, but we set it in rules-clojure.jar/compile!
253260 (let [cl (.getContextClassLoader (Thread/currentThread ))]
254- (fn
255- ([]
256- (.setContextClassLoader (Thread/currentThread ) cl)
257- (f )))))
261+ (fn []
262+ (.setContextClassLoader (Thread/currentThread ) cl)
263+ (f ))))
258264
259265(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
266+ ; ; don't use clojure.core/binding-conveyor-fn, because that uses
267+ ; ; clone/reset ThreadBindings; It allows the conveyed thread to read
268+ ; ; bindings, but not set! them (because it clones TBoxes and the
269+ ; ; cloned tbox stores the thread.id), which breaks
270+ ; ; clojure.lang.Compiler. Instead, use push/pop ThreadBindings
271+ ; ; which creates new tboxes and allows set! to work.
265272 (let [bindings (clojure.lang.Var/getThreadBindings )]
266273 (fn []
267274 (try
@@ -292,7 +299,6 @@ be found"
292299 [ns f]
293300 {:pre [(symbol? ns )]
294301 :post [(future? %)]}
295- (debug " ns-send-sync" ns )
296302 (-> ns-futures
297303 (swap! update ns (fn [**f]
298304 (or **f
@@ -412,7 +418,7 @@ be found"
412418 (debug " WARNING no ns found for" p)) true )]}
413419 (->> [" .clj" " .cljc" ]
414420 (keep (fn [ext]
415- (io/ resource (load-path (str p ext)))))
421+ (resource (load-path (str p ext)))))
416422 (keep (fn [r]
417423 (with-open [rdr (java.io.PushbackReader. (io/reader r))]
418424 (let [ns (parse/name-from-ns-decl (parse/read-ns-decl rdr))]
@@ -421,13 +427,11 @@ be found"
421427
422428(defn spy-load [& paths]
423429 (let [ns-sym (symbol (str *ns*))]
424- (debug " spy-load" paths)
425430 (->> paths
426431 (mapv (fn [p]
427432 (if-let [dep-ns (load->ns p)]
428433 @(pcompile ns-sym dep-ns)
429- (real-load p)))))
430- (debug " spy-load" ns-sym paths " done" )))
434+ (real-load p)))))))
431435
432436(defn spy-require [& args]
433437 ; ; the ns block will add `ns` to clojure.core/*loaded-libs*, so it won't be eval'd twice.
@@ -441,7 +445,6 @@ be found"
441445
442446(defn spy-load-one
443447 [lib need-ns require ]
444- (debug " spy-load-one" lib)
445448 (spy-load (root-resource lib))
446449 (throw-if (and need-ns (not (find-ns lib)))
447450 " namespace '%s' not found after loading '%s'"
@@ -453,7 +456,6 @@ be found"
453456; ; we need this because dtype-next calls load-lib directly rather than `load` or `require` ಠ_ಠ
454457(defn spy-load-lib
455458 [prefix lib & options]
456- (debug " spy-load-lib" lib)
457459 (throw-if (and prefix (pos? (.indexOf (name lib) (int \.))))
458460 " Found lib name '%s' containing period with prefix '%s'. lib names inside prefix lists must not contain periods"
459461 (name lib) prefix)
@@ -474,10 +476,8 @@ be found"
474476 (binding [clojure.core/*loading-verbosely* (or @#'clojure.core/*loading-verbosely* verbose)]
475477 (if load
476478 (try
477- (debug " spy-load-lib actually loading" load lib)
478479 (load lib need-ns require )
479480 (catch Exception e
480- (debug " spy-load-lib while loading" lib e)
481481 (when undefined-on-entry
482482 (remove-ns lib))
483483 (throw e)))
@@ -528,9 +528,9 @@ be found"
528528(defn compile! [classes-dir aot-nses out]
529529 {:pre [(string? classes-dir)
530530 (every? string? aot-nses)]}
531+ ; ; make sure this is loaded before any compile so it doesn't end up in user jars
532+ @(pcompile nil 'clojure.core.specs.alpha)
531533 (binding [*out* out]
532- (when (seq aot-nses)
533- (debug " compile!" (seq aot-nses)))
534534 (with-spy
535535 (let [aot-nses (map symbol aot-nses)]
536536 (doseq [n aot-nses]
0 commit comments