From 76e779d0d3fc12d33477736241211af38aedd3d0 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Wed, 1 Feb 2023 21:53:52 +0100 Subject: [PATCH] Compatibility with `clojure.tools.namespace.repl/refresh` and `clojure.java.classpath` (#1479) --- CHANGELOG.md | 1 + deps.edn | 4 +- {reify => impl-java}/.dir-locals.el | 0 {reify => impl-java}/bb.edn | 0 {reify => impl-java}/build.clj | 10 ++- {reify => impl-java}/build/reify2.clj | 0 {reify => impl-java}/deps.edn | 0 .../babashka/impl/URLClassLoader.java | 16 ++++ .../src/babashka/impl/reify2.clj | 0 .../src/babashka/impl/reify2/interfaces.clj | 0 project.clj | 4 +- resources/META-INF/babashka/deps.edn | 4 +- src/babashka/impl/classes.clj | 25 +++++- src/babashka/impl/classpath.clj | 76 ++++++++++--------- src/babashka/main.clj | 19 ++--- test/babashka/classpath_test.clj | 7 ++ test/babashka/test_utils.clj | 2 +- 17 files changed, 109 insertions(+), 59 deletions(-) rename {reify => impl-java}/.dir-locals.el (100%) rename {reify => impl-java}/bb.edn (100%) rename {reify => impl-java}/build.clj (84%) rename {reify => impl-java}/build/reify2.clj (100%) rename {reify => impl-java}/deps.edn (100%) create mode 100644 impl-java/src-java/babashka/impl/URLClassLoader.java rename {reify => impl-java}/src/babashka/impl/reify2.clj (100%) rename {reify => impl-java}/src/babashka/impl/reify2/interfaces.clj (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab9128dc..bbd97a9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ A preview of the next release can be installed from ## Unreleased - [#1473](https://github.com/babashka/babashka/issues/1473): make relative paths in bb.edn resolve relative to it ([@lispyclouds](https://github.com/lispyclouds)) +- Compatibility with `clojure.tools.namespace.repl/refresh` and `clojure.java.classpath` ## 1.1.172 (2023-01-23) diff --git a/deps.edn b/deps.edn index a64072fd..fa24a181 100644 --- a/deps.edn +++ b/deps.edn @@ -17,10 +17,10 @@ "depstar/src" "process/src" "deps.clj/src" "deps.clj/resources" "resources" "sci/resources" - "reify/src"], + "impl-java/src"], :deps {org.clojure/clojure {:mvn/version "1.11.1"}, org.babashka/sci {:local/root "sci"} - org.babashka/babashka.impl.reify {:mvn/version "0.1.5"} + org.babashka/babashka.impl.java {:mvn/version "0.1.6"} org.babashka/sci.impl.types {:mvn/version "0.0.2"} babashka/babashka.curl {:local/root "babashka.curl"} babashka/fs {:local/root "fs"} diff --git a/reify/.dir-locals.el b/impl-java/.dir-locals.el similarity index 100% rename from reify/.dir-locals.el rename to impl-java/.dir-locals.el diff --git a/reify/bb.edn b/impl-java/bb.edn similarity index 100% rename from reify/bb.edn rename to impl-java/bb.edn diff --git a/reify/build.clj b/impl-java/build.clj similarity index 84% rename from reify/build.clj rename to impl-java/build.clj index 0b951173..3d256312 100644 --- a/reify/build.clj +++ b/impl-java/build.clj @@ -2,8 +2,8 @@ (:require [build.reify2 :as reify2] [clojure.tools.build.api :as b])) -(def lib 'org.babashka/babashka.impl.reify) -(def version "0.1.5") +(def lib 'org.babashka/babashka.impl.java) +(def version "0.1.6") (def class-dir "target/classes") (def basis (b/create-basis {:project "deps.edn"})) (def jar-file (format "target/%s-%s.jar" (name lib) version)) @@ -14,7 +14,13 @@ (defn gen-classes [_] (reify2/gen-classes nil)) +(defn compile-java [_] + (b/javac {:src-dirs ["src-java"] + :class-dir class-dir + :basis basis})) + (defn jar [_] + (compile-java nil) (gen-classes nil) (b/write-pom {:class-dir class-dir :lib lib diff --git a/reify/build/reify2.clj b/impl-java/build/reify2.clj similarity index 100% rename from reify/build/reify2.clj rename to impl-java/build/reify2.clj diff --git a/reify/deps.edn b/impl-java/deps.edn similarity index 100% rename from reify/deps.edn rename to impl-java/deps.edn diff --git a/impl-java/src-java/babashka/impl/URLClassLoader.java b/impl-java/src-java/babashka/impl/URLClassLoader.java new file mode 100644 index 00000000..d2cf476e --- /dev/null +++ b/impl-java/src-java/babashka/impl/URLClassLoader.java @@ -0,0 +1,16 @@ +package babashka.impl; + +public class URLClassLoader extends java.net.URLClassLoader { + + public URLClassLoader(java.net.URL[] urls) { + super(urls); + } + + public URLClassLoader(java.net.URL[] urls, java.net.URLClassLoader parent) { + super(urls, parent); + } + + public void _addURL(java.net.URL url) { + super.addURL(url); + } +} diff --git a/reify/src/babashka/impl/reify2.clj b/impl-java/src/babashka/impl/reify2.clj similarity index 100% rename from reify/src/babashka/impl/reify2.clj rename to impl-java/src/babashka/impl/reify2.clj diff --git a/reify/src/babashka/impl/reify2/interfaces.clj b/impl-java/src/babashka/impl/reify2/interfaces.clj similarity index 100% rename from reify/src/babashka/impl/reify2/interfaces.clj rename to impl-java/src/babashka/impl/reify2/interfaces.clj diff --git a/project.clj b/project.clj index 8124d914..6e99e405 100644 --- a/project.clj +++ b/project.clj @@ -11,7 +11,7 @@ "babashka.core/src" "babashka.nrepl/src" "depstar/src" "process/src" "deps.clj/src" "deps.clj/resources" - "reify/src"] + "impl-java/src"] ;; for debugging Reflector.java code: ;; :java-source-paths ["sci/reflector/src-java"] :java-source-paths ["src-java"] @@ -26,7 +26,7 @@ [nrepl/bencode "1.1.0"] [borkdude/sci.impl.reflector "0.0.1"] [org.babashka/sci.impl.types "0.0.2"] - [org.babashka/babashka.impl.reify "0.1.5"] + [org.babashka/babashka.impl.java "0.1.6"] [org.clojure/core.async "1.6.673"] [org.clojure/test.check "1.1.1"] [com.github.clj-easy/graal-build-time "0.1.0"] diff --git a/resources/META-INF/babashka/deps.edn b/resources/META-INF/babashka/deps.edn index a64072fd..fa24a181 100644 --- a/resources/META-INF/babashka/deps.edn +++ b/resources/META-INF/babashka/deps.edn @@ -17,10 +17,10 @@ "depstar/src" "process/src" "deps.clj/src" "deps.clj/resources" "resources" "sci/resources" - "reify/src"], + "impl-java/src"], :deps {org.clojure/clojure {:mvn/version "1.11.1"}, org.babashka/sci {:local/root "sci"} - org.babashka/babashka.impl.reify {:mvn/version "0.1.5"} + org.babashka/babashka.impl.java {:mvn/version "0.1.6"} org.babashka/sci.impl.types {:mvn/version "0.0.2"} babashka/babashka.curl {:local/root "babashka.curl"} babashka/fs {:local/root "fs"} diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 89ba5660..351963a1 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -111,7 +111,9 @@ clojure.lang.RT {:methods [{:name "aget"} {:name "aset"} - {:name "aclone"}]} + {:name "aclone"} + ;; we expose this via the Compiler/LOADER dynamic var + {:name "baseLoader"}]} clojure.lang.Compiler {:fields [{:name "specials"} {:name "CHAR_MAP"}]} @@ -140,7 +142,18 @@ {:methods [{:name "hasNext"} {:name "next"}]} java.util.TimeZone - {:methods [{:name "getTimeZone"}]}}) + {:methods [{:name "getTimeZone"}]} + java.net.URLClassLoader + {:methods [{:name "close"} + {:name "findResource"} + {:name "findResources"} + {:name "getResourceAsStream"} + {:name "getURLs"}]} + java.lang.ClassLoader + {:methods [{:name "getResource"} + {:name "getResources"} + {:name "getResourceAsStream"} + {:name "getParent"}]}}) (def custom-map (cond-> @@ -561,7 +574,6 @@ java.lang.LinkageError java.lang.ThreadDeath java.lang.VirtualMachineError - java.net.URLClassLoader java.sql.Timestamp java.util.concurrent.TimeoutException java.util.Collection @@ -630,6 +642,10 @@ (instance? sci.impl.types.IReified v) (first (t/getInterfaces v)) ;; fix for #1061 + (instance? java.net.URLClassLoader v) + java.net.URLClassLoader + (instance? java.lang.ClassLoader v) + java.lang.ClassLoader (instance? java.io.Closeable v) java.io.Closeable (instance? java.nio.file.attribute.BasicFileAttributes v) @@ -765,8 +781,9 @@ (sort-by :name) (vec))) -(defn all-classes [] +(defn all-classes "Returns every java.lang.Class instance Babashka supports." + [] (->> (reflection-file-entries) (map :name) (map #(Class/forName %)))) diff --git a/src/babashka/impl/classpath.clj b/src/babashka/impl/classpath.clj index 7e1c9ac9..76c76dfb 100644 --- a/src/babashka/impl/classpath.clj +++ b/src/babashka/impl/classpath.clj @@ -10,30 +10,46 @@ (set! *warn-on-reflection* true) -(defprotocol IResourceResolver - (getResource [this paths opts])) - -(deftype Loader [class-loader] - IResourceResolver - (getResource [_ resource-paths url?] - (some (fn [resource] - (when-let [^java.net.URL res (.findResource ^java.net.URLClassLoader class-loader resource)] - (if url? - res - {:file (if (= "jar" (.getProtocol res)) - resource - (.getFile res)) - :source (slurp res)}))) - resource-paths))) +(defn getResource [^babashka.impl.URLClassLoader class-loader resource-paths url?] + (some (fn [resource] + (when-let [^java.net.URL res (.findResource class-loader resource)] + (if url? + res + {:file (if (= "jar" (.getProtocol res)) + resource + (.getFile res)) + :source (slurp res)}))) + resource-paths)) (def path-sep (System/getProperty "path.separator")) -(defn ->url [^String s] +(defn ->url ^java.net.URL [^String s] (.toURL (java.io.File. s))) -(defn loader [^String classpath] - (let [parts (.split classpath path-sep)] - (Loader. (java.net.URLClassLoader/newInstance (into-array java.net.URL (map ->url parts)))))) +(defn new-loader ^babashka.impl.URLClassLoader + ([paths] + (babashka.impl.URLClassLoader. (into-array java.net.URL (map ->url paths))))) + +(def ^babashka.impl.URLClassLoader the-url-loader (delay (new-loader []))) + +(defn add-classpath + "Adds extra-classpath, a string as for example returned by clojure + -Spath, to the current classpath." + [^String extra-classpath] + (let [paths (.split extra-classpath path-sep) + paths (map ->url paths) + loader @the-url-loader] + (run! (fn [path] + (._addURL ^babashka.impl.URLClassLoader loader path) + loader) + paths) + ;; (run! prn (.getURLs the-url-loader)) + (System/setProperty "java.class.path" + (let [system-cp (System/getProperty "java.class.path")] + (-> (cond-> system-cp + (not (str/blank? system-cp)) (str path-sep)) + (str extra-classpath))))) + nil) (defn resource-paths [namespace] (let [ns-str (name namespace) @@ -55,21 +71,6 @@ (.getValue "Main-Class") (demunge)))) -(def cp-state (atom nil)) - -(defn add-classpath - "Adds extra-classpath, a string as for example returned by clojure - -Spath, to the current classpath." - [extra-classpath] - (swap! cp-state - (fn [{:keys [:cp]}] - (let [new-cp - (if-not cp extra-classpath - (str cp path-sep extra-classpath))] - {:loader (loader new-cp) - :cp new-cp}))) - nil) - (defn split-classpath "Returns the classpath as a seq of strings, split by the platform specific path separator." @@ -78,10 +79,12 @@ (defn get-classpath "Returns the current classpath as set by --classpath, BABASHKA_CLASSPATH and add-classpath." [] - (:cp @cp-state)) + (let [cp (System/getProperty "java.class.path")] + (when-not (str/blank? cp) + cp))) (defn resource - (^URL [path] (when-let [st @cp-state] (resource (:loader st) path))) + (^URL [path] (resource @the-url-loader path)) (^URL [loader path] (if (str/starts-with? path "/") nil ;; non-relative paths always return nil (getResource loader [path] true)))) @@ -101,4 +104,3 @@ (def l (loader cp)) (source-for-namespace l 'babashka.impl.cheshire nil) (time (:file (source-for-namespace l 'cheshire.core nil)))) ;; 20ms -> 2.25ms - diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 298eda52..1c8cf536 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -781,7 +781,8 @@ Use bb run --help to show this help output. env-os-arch-present? (not= env-os-arch sys-os-arch)))) (defn exec [cli-opts] - (binding [*unrestricted* true] + (with-bindings {#'*unrestricted* true + clojure.lang.Compiler/LOADER @cp/the-url-loader} (sci/binding [core/warn-on-reflection @core/warn-on-reflection core/unchecked-math @core/unchecked-math core/data-readers @core/data-readers @@ -837,8 +838,7 @@ Use bb run --help to show this help output. _ (when jar (cp/add-classpath jar)) load-fn (fn [{:keys [:namespace :reload]}] - (let [{:keys [loader]} - @cp/cp-state] + (let [loader @cp/the-url-loader] (or (when ;; ignore built-in namespaces when uberscripting, unless with :reload (and uberscript @@ -884,7 +884,7 @@ Use bb run --help to show this help output. nil)))) main (if (and jar (not main)) (when-let [res (cp/getResource - (cp/loader jar) + (cp/new-loader [jar]) ["META-INF/MANIFEST.MF"] {:url? true})] (cp/main-ns res)) main) @@ -934,7 +934,7 @@ Use bb run --help to show this help output. :debug debug :preloads preloads :init init - :loader (:loader @cp/cp-state)})))) + :loader @cp/the-url-loader})))) expression (str/join " " expressions) ;; this might mess with the locations... exit-code ;; handle preloads @@ -948,7 +948,7 @@ Use bb run --help to show this help output. :debug debug :preloads preloads :init init - :loader (:loader @cp/cp-state)}))))) + :loader @cp/the-url-loader}))))) nil)) exit-code ;; handle --init @@ -961,7 +961,7 @@ Use bb run --help to show this help output. :debug debug :preloads preloads :init init - :loader (:loader @cp/cp-state)})))) + :loader @cp/the-url-loader})))) nil)) ;; socket REPL is start asynchronously. when no other args are ;; provided, a normal REPL will be started as well, which causes the @@ -1020,7 +1020,7 @@ Use bb run --help to show this help output. (error-handler e {:expression expression :debug debug :preloads preloads - :loader (:loader @cp/cp-state)})))) + :loader @cp/the-url-loader})))) clojure [nil (if-let [proc (bdeps/clojure command-line-args)] (-> @proc :exit) 0)] @@ -1080,7 +1080,7 @@ Use bb run --help to show this help output. abs-path #(-> % io/file .getAbsolutePath) bb-edn-file (cond config (when (fs/exists? config) (abs-path config)) - jar (some-> jar cp/loader (cp/resource "META-INF/bb.edn") .toString) + jar (some-> [jar] cp/new-loader (cp/resource "META-INF/bb.edn") .toString) :else (when (fs/exists? "bb.edn") (abs-path "bb.edn"))) bb-edn (when (or bb-edn-file merge-deps) (when bb-edn-file (System/setProperty "babashka.config" bb-edn-file)) @@ -1099,6 +1099,7 @@ Use bb run --help to show this help output. (vreset! common/bb-edn edn))) ;; _ (.println System/err (str bb-edn)) min-bb-version (:min-bb-version bb-edn)] + (System/setProperty "java.class.path" "") (when min-bb-version (when-not (satisfies-min-version? min-bb-version) (binding [*out* *err*] diff --git a/test/babashka/classpath_test.clj b/test/babashka/classpath_test.clj index 61808c77..3b4beeff 100644 --- a/test/babashka/classpath_test.clj +++ b/test/babashka/classpath_test.clj @@ -70,3 +70,10 @@ (str/trim (tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test/foo.jar" "(pos? (count (slurp (io/resource \"foo.clj\")))) ")))))) + +(deftest classloader-test + (let [url + (tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test/foo.jar" + "(first (map str (.getURLs (clojure.lang.RT/baseLoader))))")] + (is (str/includes? url "file:")) + (is (str/includes? url "foo.jar")))) diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index ce2ac58f..b2b440eb 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -44,7 +44,7 @@ (System/exit 1))))) (defn bb-jvm [input-or-opts & args] - (reset! cp/cp-state nil) + (alter-var-root #'cp/the-url-loader (constantly (delay (cp/new-loader [])))) (reset! main/env {}) (vreset! common/bb-edn nil) (System/clearProperty "babashka.config")