Skip to content

Commit e037150

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 e037150

File tree

9 files changed

+161
-130
lines changed

9 files changed

+161
-130
lines changed

src/rules_clojure/compile.clj

Lines changed: 45 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)]}
@@ -84,21 +82,24 @@
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" (src-resource ns) " 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]

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)