From 641906f7e6bfa720929b945f2a2e212c4ee0a309 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Fri, 30 Sep 2022 11:24:06 +0200 Subject: [PATCH] Add support for CLJ_JVM_OPTS and JAVA_OPTS (#60) Co-authored-by: ikappaki --- README.md | 4 +- bb.edn | 16 +++++++ src/borkdude/deps.clj | 41 +++++++++++------- test/borkdude/deps_test.clj | 85 ++++++++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 19 deletions(-) create mode 100644 bb.edn diff --git a/README.md b/README.md index 2ebb1eb..af66aae 100644 --- a/README.md +++ b/README.md @@ -296,13 +296,13 @@ $ lein run -m borkdude.deps -Spath To run jvm tests: ``` -$ script/jvm_test +$ bb test ``` To run with babashka after making changes to `src/borkdude/deps.clj`, you should run: ``` -$ script/gen_script.clj +$ bb gen-script ``` and then: diff --git a/bb.edn b/bb.edn new file mode 100644 index 0000000..41a1459 --- /dev/null +++ b/bb.edn @@ -0,0 +1,16 @@ +{:paths ["resources"] + + :tasks + {:requires [[babashka.deps :as deps] + [babashka.process :as p]] + + gen-script {:doc "Regen `./deps[.clj|.bat]` from `src/borkdude/deps.clj`." + :task (load-file "script/gen_script.clj")} + + test {:doc "Run all tests." + :task + (doseq [args '[[-M:test] [-M -m borkdude.deps -M:test]]] + (println :testing... 'clojure args) + (-> (deps/clojure args) + p/check) + (println))}}} diff --git a/src/borkdude/deps.clj b/src/borkdude/deps.clj index 541c7a3..99d1dee 100755 --- a/src/borkdude/deps.clj +++ b/src/borkdude/deps.clj @@ -193,6 +193,11 @@ For more info, see: (print "\n ") (describe-line line)) (println "}"))) +(defn ^:private ^:dynamic *getenv-fn* + "Get ENV'ironment variable." + ^String [env] + (java.lang.System/getenv env)) + (defn cksum [^String s] (let [hashed (.digest (java.security.MessageDigest/getInstance "MD5") @@ -204,7 +209,7 @@ For more info, see: (str sw))) (defn which [executable] - (when-let [path (System/getenv "PATH")] + (when-let [path (*getenv-fn* "PATH")] (let [paths (.split path path-separator)] (loop [paths paths] (when-first [p paths] @@ -217,7 +222,7 @@ For more info, see: (defn home-dir [] (if windows? ;; workaround for https://github.com/oracle/graal/issues/1630 - (System/getenv "userprofile") + (*getenv-fn* "userprofile") (System/getProperty "user.home"))) (defn download [source dest] @@ -289,10 +294,10 @@ For more info, see: (defn jvm-proxy-settings [] - (let [http-proxy (parse-proxy-info (or (System/getenv "http_proxy") - (System/getenv "HTTP_PROXY"))) - https-proxy (parse-proxy-info (or (System/getenv "https_proxy") - (System/getenv "HTTPS_PROXY")))] + (let [http-proxy (parse-proxy-info (or (*getenv-fn* "http_proxy") + (*getenv-fn* "HTTP_PROXY"))) + https-proxy (parse-proxy-info (or (*getenv-fn* "https_proxy") + (*getenv-fn* "HTTPS_PROXY")))] (when http-proxy (System/setProperty "http.proxyHost" (:host http-proxy)) (System/setProperty "http.proxyPort" (:port http-proxy))) @@ -428,10 +433,10 @@ For more info, see: (defn -main [& command-line-args] (let [opts (parse-args command-line-args) java-cmd - (or (System/getenv "JAVA_CMD") + (or (*getenv-fn* "JAVA_CMD") (let [java-cmd (which java-exe)] (if (str/blank? java-cmd) - (let [java-home (System/getenv "JAVA_HOME")] + (let [java-home (*getenv-fn* "JAVA_HOME")] (if-not (str/blank? java-home) (let [f (io/file java-home "bin" java-exe)] (if (and (.exists f) @@ -442,8 +447,8 @@ For more info, see: java-cmd))) env-tools-dir (or ;; legacy name - (System/getenv "CLOJURE_TOOLS_DIR") - (System/getenv "DEPS_CLJ_TOOLS_DIR")) + (*getenv-fn* "CLOJURE_TOOLS_DIR") + (*getenv-fn* "DEPS_CLJ_TOOLS_DIR")) tools-dir (or env-tools-dir (.getPath (io/file (home-dir) ".deps.clj" @@ -475,15 +480,18 @@ For more info, see: deps-edn (or (:deps-file opts) (.getPath (io/file *dir* "deps.edn"))) + clj-jvm-opts (some-> (*getenv-fn* "CLJ_JVM_OPTS") (str/split #" ")) clj-main-cmd (vec (concat [java-cmd] + clj-jvm-opts proxy-settings ["-Xms256m" "-classpath" tools-cp "clojure.main"])) config-dir - (or (System/getenv "CLJ_CONFIG") - (when-let [xdg-config-home (System/getenv "XDG_CONFIG_HOME")] + (or (*getenv-fn* "CLJ_CONFIG") + (when-let [xdg-config-home (*getenv-fn* "XDG_CONFIG_HOME")] (.getPath (io/file xdg-config-home "clojure"))) - (.getPath (io/file (home-dir) ".clojure")))] + (.getPath (io/file (home-dir) ".clojure"))) + java-opts (some-> (*getenv-fn* "JAVA_OPTS") (str/split #" "))] ;; If user config directory does not exist, create it (let [config-dir (io/file config-dir)] (when-not (.exists config-dir) @@ -503,8 +511,8 @@ For more info, see: (io/copy install-tools-edn config-tools-edn))) ;; Determine user cache directory (let [user-cache-dir - (or (System/getenv "CLJ_CACHE") - (when-let [xdg-config-home (System/getenv "XDG_CACHE_HOME")] + (or (*getenv-fn* "CLJ_CACHE") + (when-let [xdg-config-home (*getenv-fn* "XDG_CACHE_HOME")] (.getPath (io/file xdg-config-home "clojure"))) (.getPath (io/file config-dir ".cpcache"))) ;; Chain deps.edn in config paths. repro=skip config dir @@ -637,7 +645,7 @@ For more info, see: {:to-string? tree?})] (when tree? (print res) (flush)))) - (let [cp (cond (or classpath-not-needed? + (let [cp (cond (or classpath-not-needed? (:prep opts)) nil (not (str/blank? (:force-cp opts))) (:force-cp opts) :else (slurp (io/file cp-file)))] @@ -691,6 +699,7 @@ For more info, see: (str cp path-separator exec-cp) cp) main-args (concat [java-cmd] + java-opts proxy-settings jvm-cache-opts (:jvm-opts opts) diff --git a/test/borkdude/deps_test.clj b/test/borkdude/deps_test.clj index 2ea83ee..4235f02 100644 --- a/test/borkdude/deps_test.clj +++ b/test/borkdude/deps_test.clj @@ -9,11 +9,16 @@ [clojure.test :as t :refer [deftest is testing]])) (def invoke-deps-cmd + "Returns the command to invoke `borkdude.deps` on the current system, + based on the value of env variable `DEPS_CLJ_TEST_ENV`." (case (System/getenv "DEPS_CLJ_TEST_ENV") "babashka" (let [classpath (str/join deps/path-separator ["src" "test" "resources"])] (str "bb -cp " classpath " -m borkdude.deps ")) "native" "./deps " - "clojure -M -m borkdude.deps ")) + (cond->> + "clojure -M -m borkdude.deps " + deps/windows? + (str "powershell -NoProfile -Command ")))) (deftest parse-args-test (is (= {:mode :repl, :jvm-opts ["-Dfoo=bar" "-Dbaz=quuz"]} @@ -132,3 +137,81 @@ (deftest tools-test (deps/-main "-Ttools" "list")) + +(defmacro get-shell-command-args + "Executes BODY with the given ENV'ironment variables added to the + `babashka.deps` scope, presumbably to indirectly invoke + `babashka.deps/shell-command` whose invocation ARGS captures and + returns with this call. + + It overrides `baabashka.deps/*exit-fn*` so as to never exit the + program, but throws an exception in case of error while is still in + the `babashka.deps` scope." + [env-vars & body] + (let [body-str (pr-str body)] + `(let [shell-command# deps/shell-command + ret*# (promise) + sh-mock# (fn mock# + ([args#] + (mock# args# nil)) + ([args# opts#] + (let [ret# (shell-command# args# opts#)] + (deliver ret*# args#) + ret#)))] + ;; need to override both *process-fn* and deps/shell-command. + (binding [deps/*process-fn* sh-mock# + deps/*exit-fn* (fn + ([exit-code#] (when-not (= exit-code# 0) + (throw (ex-info "mock-shell-failed" {:exit-code exit-code#})))) + ([exit-code# msg#] (throw (ex-info "mock-shell-failed" + {:exit-code exit-code# :msg msg#})))) + deps/*getenv-fn* #(or (get ~env-vars %) + (System/getenv %))] + (with-redefs [deps/shell-command sh-mock#] + ~@body + (or (deref ret*# 500 false) (ex-info "No shell-command invoked in body." {:body ~body-str}))))))) + +(deftest clj-jvm-opts+java-opts + ;; The `CLJ_JVM_OPTS` env var should only apply to -P and -Spom. + ;; The `JAVA_OPTS` env varshould only apply to everything else. + ;; + ;; Some harmless cli flags are used below to demonstrate the + ;; succesful passing of cli arguments to the java executable. + + (let [xx-pclf "-XX:+PrintCommandLineFlags" + xx-gc-threads "-XX:ConcGCThreads=1"] + + (testing "CLJ-JVM-OPTS with prepare deps" + (let [sh-args (get-shell-command-args + {"CLJ_JVM_OPTS" (str/join " " [xx-pclf xx-gc-threads])} + (deps/-main "-P"))] + (is (some #{xx-pclf} sh-args)) + ;; second and third args + (is (= [xx-pclf xx-gc-threads] (->> (rest sh-args) (take 2)))))) + + (testing "CLJ-JVM-OPTS with pom" + (let [sh-args (get-shell-command-args + {"CLJ_JVM_OPTS" (str/join " " [xx-pclf xx-gc-threads])} + (deps/-main "-Spom"))] + (is (some #{xx-pclf} sh-args)) + (is (= [xx-pclf xx-gc-threads] (->> (rest sh-args) (take 2)))))) + + (testing "CLJ-JVM-OPTS outside of prepare deps" + (let [sh-args (get-shell-command-args + {"CLJ_JVM_OPTS" xx-pclf} + (deps/-main "-e" "123"))] + ;; shouldn't find the flag + (is (not (some #{xx-pclf} sh-args))))) + + (testing "JAVA-OPTS outside of prepare deps" + (let [sh-args (get-shell-command-args + {"JAVA_OPTS" (str/join " " [xx-pclf xx-gc-threads])} + (deps/-main "-e" "123"))] + (is (some #{xx-pclf} sh-args)) + (is (= [xx-pclf xx-gc-threads] (->> (rest sh-args) (take 2)))))) + + (testing "JAVA-OPTS with prepare deps" + (let [sh-args (get-shell-command-args + {"JAVA_OPTS" xx-pclf} + (deps/-main "-P"))] + (is (not (some #{xx-pclf} sh-args)))))))