Skip to content

Commit 0d98ee6

Browse files
committed
More bug fixes after #70 and #73
- When determining whether two classloaders are compatible?, also check for java class names, not just clj namespaces. This identifies JarHell as a major source of compile errors and non-determinism - gen_build: Make clojure_library targets depend on clojure.core.specs.alpha rather than clojure.core. This fixes a bug where clojure.core.specs.alpha gets compiled into user jars, causing false positives in compatibility checks. - minor comments and code golfing
1 parent c805dac commit 0d98ee6

File tree

9 files changed

+174
-126
lines changed

9 files changed

+174
-126
lines changed

src/rules_clojure/compile.clj

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
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)
@@ -15,9 +14,6 @@
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]
@@ -30,23 +26,25 @@
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)]}
@@ -62,6 +60,13 @@
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
@@ -84,21 +89,24 @@
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]

src/rules_clojure/gen_build.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@
258258
class-file (str (.substring ^String root-resource 1) "__init.class")]
259259
(some #(= class-file %) (classpath-files path))))
260260

261-
(def special-namespaces '#{clojure.core.specs.alpha})
261+
(def special-namespaces '#{})
262262

263263
(defn should-compile-namespace? [deps-bazel path ns]
264264
(and (not (contains? special-namespaces ns))

src/rules_clojure/jar.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
(try
6868
(util/shim-invoke cl "rules-clojure.compile" "compile!" classes-dir aot-arr *out*)
6969
(catch Throwable t
70-
(throw (ex-info "while compiling" {:args args} t))))))
70+
(throw (ex-info "while compiling" {:args args
71+
:classloader cl} t))))))
7172

7273
(defn create-jar [{:keys [src-dir classes-dir output-jar resources aot-nses] :as args}]
7374
(s/assert ::compile args)

src/rules_clojure/namespace/find.clj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,7 @@
9999
both defined in clojure.tools.namespace.find."
100100
{:added "0.3.0"}
101101
[^JarFile jar-file platform]
102-
{:pre [(do (when-not (jarfile? jar-file)
103-
(println "filenames-in-jar:" jar-file (class jar-file))) true)
104-
(jarfile? jar-file) platform]}
102+
{:pre [(jarfile? jar-file) platform]}
105103
(let [{:keys [extensions]} platform]
106104
(->> jar-file
107105
(.entries)

src/rules_clojure/persistentClassLoader.clj

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
(set! *warn-on-reflection* true)
5858

5959
(defn clojure-find-class [this name]
60-
{:post [(do (util/print-err "found class in-mem" %) true)]}
6160
(when-let [rt-class ^Class (.parentFindClass this "clojure.lang.RT")]
6261
(let [baseloader-method (.getDeclaredMethod rt-class "baseLoader" (into-array Class []))
6362
loader (.invoke baseloader-method rt-class (into-array Object []))]
@@ -68,5 +67,5 @@
6867
[this name]
6968
(locking this
7069
(or
71-
(.parentFindClass this name)
72-
(clojure-find-class this name))))
70+
(.parentFindClass this name)
71+
(clojure-find-class this name))))

0 commit comments

Comments
 (0)