diff --git a/API.md b/API.md new file mode 100644 index 0000000..5c66ae8 --- /dev/null +++ b/API.md @@ -0,0 +1,283 @@ +# Table of contents +- [`borkdude.deps`](#borkdude.deps) - Port of https://github.com/clojure/brew-install/blob/1.11.1/src/main/resources/clojure/install/clojure in Clojure. + - [`*aux-process-fn*`](#borkdude.deps/*aux-process-fn*) - Invokes java with arguments to calculate classpath, etc. + - [`*clojure-process-fn*`](#borkdude.deps/*clojure-process-fn*) - Invokes java with arguments to clojure.main to start Clojure. + - [`*clojure-tools-download-fn*`](#borkdude.deps/*clojure-tools-download-fn*) - Can be dynamically rebound to customise the download of the Clojure tools. + - [`*dir*`](#borkdude.deps/*dir*) - Directory in which deps.clj should be executed. + - [`*exit-fn*`](#borkdude.deps/*exit-fn*) - Function that is called on exit with :exit code and :message, an exceptional message when exit is non-zero. + - [`-main`](#borkdude.deps/-main) - See help-text. + - [`clojure-tools-download!`](#borkdude.deps/clojure-tools-download!) - Downloads clojure tools archive in :out-dir, if not already there, and extracts in-place the clojure tools jar file and other important files. + - [`clojure-tools-download-direct!`](#borkdude.deps/clojure-tools-download-direct!) - Downloads from :url to :dest file returning true on success. + - [`clojure-tools-download-java!`](#borkdude.deps/clojure-tools-download-java!) - Downloads :url zip file to :dest by invoking java with :proxy options on a .java program file, and returns true on success. + - [`deps-clj-version`](#borkdude.deps/deps-clj-version) - The current version of deps.clj. + - [`get-cache-dir`](#borkdude.deps/get-cache-dir) - Returns cache dir (.cpcache) from either local dir, if deps-edn exists, or the user cache dir. + - [`get-config-dir`](#borkdude.deps/get-config-dir) - Retrieves configuration directory. + - [`get-config-paths`](#borkdude.deps/get-config-paths) - Returns vec of configuration paths, i.e. + - [`get-help`](#borkdude.deps/get-help) - Returns help text as string. + - [`get-install-dir`](#borkdude.deps/get-install-dir) - Retrieves the install directory where tools jar is located (after download). + - [`get-local-deps-edn`](#borkdude.deps/get-local-deps-edn) - Returns the path of the deps.edn file (as string) in the current directory or as set by -Sdeps-file. + - [`get-proxy-info`](#borkdude.deps/get-proxy-info) - Returns a map with proxy information parsed from env vars. + - [`parse-cli-opts`](#borkdude.deps/parse-cli-opts) - Parses the command line options. + - [`print-help`](#borkdude.deps/print-help) - Print help text. + - [`proxy-jvm-opts`](#borkdude.deps/proxy-jvm-opts) - Returns a vector containing the JVM system property arguments to be passed to a new process to set its proxy system properties. + - [`set-proxy-system-props!`](#borkdude.deps/set-proxy-system-props!) - Sets the proxy system properties in the current JVM. + +----- +# borkdude.deps + + +Port of https://github.com/clojure/brew-install/blob/1.11.1/src/main/resources/clojure/install/clojure in Clojure + + + + +## `*aux-process-fn*` +``` clojure + +(*aux-process-fn* {:keys [cmd out]}) +``` + +Invokes `java` with arguments to calculate classpath, etc. May be + replacement by rebinding this dynamic var. + + Called with a map of: + + - `:cmd`: a vector of strings + - `:out`: if set to `:string`, `:out` key in result must contains stdout + + Returns a map of: + + - `:exit`, the exit code of the process + - `:out`, the string of stdout, if the input `:out` was set to `:string` +

Source

+ +## `*clojure-process-fn*` +``` clojure + +(*clojure-process-fn* {:keys [cmd]}) +``` + +Invokes `java` with arguments to `clojure.main` to start Clojure. May + be replacement by rebinding this dynamic var. + + Called with a map of: + + - `:cmd`: a vector of strings + + Must return a map of `:exit`, the exit code of te process. +

Source

+ +## `*clojure-tools-download-fn*` + + + + +Can be dynamically rebound to customise the download of the Clojure tools. + Should be bound to a function accepting a map with: + - `:url`: The URL to download, as a string + - `:dest`: The path to the file to download it to, as a string + - `:proxy-opts`: a map as returned by [`get-proxy-info`](#borkdude.deps/get-proxy-info) + - `:clj-jvm-opts`: a vector of JVM opts (as passed on the command line). + Should return true if the `download` was successful, or false if not. +

Source

+ +## `*dir*` + + + + +Directory in which deps.clj should be executed. +

Source

+ +## `*exit-fn*` +``` clojure + +(*exit-fn* {:keys [exit message]}) +``` + +Function that is called on exit with `:exit` code and `:message`, an exceptional message when exit is non-zero +

Source

+ +## `-main` +``` clojure + +(-main & command-line-args) +``` + +See [`help-text`](#borkdude.deps/help-text). + + In addition + + - the values of the `CLJ_JVM_OPTS` and `JAVA_OPTIONS` environment + variables are passed to the java subprocess as command line options + when downloading dependencies and running any other commands + respectively. + + - if the clojure tools jar cannot be located and the clojure tools + archive is not found, an attempt is made to download the archive + from the official site and extract its contents locally. The archive + is downloaded from this process directly, unless the `CLJ_JVM_OPTS` + env variable is set and a succesful attempt is made to download the + archive by invoking a java subprocess passing the env variable value + as command line options. +

Source

+ +## `clojure-tools-download!` +``` clojure + +(clojure-tools-download! {:keys [out-dir debug proxy-opts clj-jvm-opts]}) +``` + +Downloads clojure tools archive in `:out-dir`, if not already there, + and extracts in-place the clojure tools jar file and other important + files. + + The download is attempted directly from this process, unless + `:jvm-opts` is set, in which case a java subprocess + is created to download the archive passing in its value as command + line options. + + It calls [`*exit-fn*`](#borkdude.deps/*exit-fn*) if it cannot download the archive, with + instructions how to manually download it. +

Source

+ +## `clojure-tools-download-direct!` +``` clojure + +(clojure-tools-download-direct! {:keys [url dest]}) +``` + +Downloads from `:url` to `:dest` file returning true on success. +

Source

+ +## `clojure-tools-download-java!` +``` clojure + +(clojure-tools-download-java! {:keys [url dest proxy-opts clj-jvm-opts]}) +``` + +Downloads `:url` zip file to `:dest` by invoking `java` with + `:proxy` options on a `.java` program file, and returns true on + success. Requires Java 11+ (JEP 330). +

Source

+ +## `deps-clj-version` + + + + +The current version of deps.clj +

Source

+ +## `get-cache-dir` +``` clojure + +(get-cache-dir {:keys [deps-edn config-dir]}) +``` + +Returns cache dir (`.cpcache`) from either local dir, if `deps-edn` + exists, or the user cache dir. +

Source

+ +## `get-config-dir` +``` clojure + +(get-config-dir) +``` + +Retrieves configuration directory. + First tries `CLJ_CONFIG` env var, then `$XDG_CONFIG_HOME/clojure`, then ~/.clojure. +

Source

+ +## `get-config-paths` +``` clojure + +(get-config-paths {:keys [cli-opts deps-edn config-dir install-dir]}) +``` + +Returns vec of configuration paths, i.e. deps.edn from: + - `:install-dir` as obtained thrhough [`get-install-dir`](#borkdude.deps/get-install-dir) + - `:config-dir` as obtained through [`get-config-dir`](#borkdude.deps/get-config-dir) + - `:deps-edn` as obtained through [`get-local-deps-edn`](#borkdude.deps/get-local-deps-edn) +

Source

+ +## `get-help` +``` clojure + +(get-help) +``` + +Returns help text as string. +

Source

+ +## `get-install-dir` +``` clojure + +(get-install-dir) +``` + +Retrieves the install directory where tools jar is located (after download). + Defaults to ~/.deps.clj//ClojureTools. +

Source

+ +## `get-local-deps-edn` +``` clojure + +(get-local-deps-edn {:keys [cli-opts]}) +``` + +Returns the path of the `deps.edn` file (as string) in the current directory or as set by `-Sdeps-file`. + Required options: + * `:cli-opts`: command line options as parsed by `parse-opts` +

Source

+ +## `get-proxy-info` +``` clojure + +(get-proxy-info) +``` + +Returns a map with proxy information parsed from env vars. The map + will contain :http-proxy and :https-proxy entries if the relevant + env vars are set and parsed correctly. The value for each is a map + with :host and :port entries. +

Source

+ +## `parse-cli-opts` +``` clojure + +(parse-cli-opts args) +``` + +Parses the command line options. +

Source

+ +## `print-help` +``` clojure + +(print-help) +``` + +Print help text +

Source

+ +## `proxy-jvm-opts` +``` clojure + +(proxy-jvm-opts {:keys [http-proxy https-proxy]}) +``` + +Returns a vector containing the JVM system property arguments to be passed to a new process + to set its proxy system properties. + proxy-info parameter is as returned from env-proxy-info. +

Source

+ +## `set-proxy-system-props!` +``` clojure + +(set-proxy-system-props! {:keys [http-proxy https-proxy]}) +``` + +Sets the proxy system properties in the current JVM. + proxy-info parameter is as returned from env-proxy-info. +

Source

diff --git a/bb.edn b/bb.edn index 5e627e0..7f564dd 100644 --- a/bb.edn +++ b/bb.edn @@ -39,4 +39,11 @@ (run task) (println))} - upload-artifact {:task (exec 'artifact/upload)}}} + upload-artifact {:task (exec 'artifact/upload)} + + quickdoc {:doc "Invoke quickdoc" + :extra-deps {io.github.borkdude/quickdoc {:git/sha "32e726cd6d785d00e49d4e614a05f7436d3831c0"}} + :task (exec 'quickdoc.api/quickdoc) + :exec-args {:git/branch "master" + :github/repo "https://github.com/borkdude/deps.clj" + :source-paths ["src"]}}}} diff --git a/src/borkdude/deps.clj b/src/borkdude/deps.clj index 9cdbb95..5c49338 100755 --- a/src/borkdude/deps.clj +++ b/src/borkdude/deps.clj @@ -11,21 +11,22 @@ (:gen-class)) (set! *warn-on-reflection* true) -(def path-separator (System/getProperty "path.separator")) +(def ^:private path-separator (System/getProperty "path.separator")) ;; see https://github.com/clojure/brew-install/blob/1.11.1/CHANGELOG.md -(def version +(def ^:private version (delay (or (System/getenv "DEPS_CLJ_TOOLS_VERSION") "1.11.1.1273"))) -(def cache-version "4") +(def ^:private cache-version "4") (def deps-clj-version + "The current version of deps.clj" (-> (io/resource "DEPS_CLJ_VERSION") (slurp) (str/trim))) -(defn warn [& strs] +(defn- warn [& strs] (binding [*out* *err*] (apply println strs))) @@ -35,22 +36,19 @@ (with-out-str (apply println strs)))) -(def ^:private ^:dynamic *exit-fn* - (fn - ([exit-code] (System/exit exit-code)) - ([exit-code msg] - (warn msg) - (System/exit exit-code)))) +(defn ^:dynamic *exit-fn* + "Function that is called on exit with `:exit` code and `:message`, an exceptional message when exit is non-zero" + [{:keys [exit message]}] + (when message (warn message)) + (System/exit exit)) -(def windows? +(def ^:private windows? (-> (System/getProperty "os.name") (str/lower-case) (str/includes? "windows"))) -(def ^:private ^:dynamic *dir* nil) - -(def ^:private ^:dynamic *env* nil) -(def ^:private ^:dynamic *extra-env* nil) +(def ^:dynamic *dir* "Directory in which deps.clj should be executed." + nil) (defn- as-string-map "Helper to coerce a Clojure map with keyword keys into something coerceable to Map @@ -77,16 +75,17 @@ (.putAll (as-string-map env))) pb) -(defn shell-command +(defn- internal-shell-command "Executes shell command. Accepts the following options: `:to-string?`: instead of writing to stdoud, write to a string and return it." - ([args] (shell-command args nil)) - ([args {:keys [:to-string?]}] - (let [args (mapv str args) + ([args] (internal-shell-command args nil)) + ([args {:keys [out env extra-env]}] + (let [to-string? (= :string out) + args (mapv str args) args (if (and windows? (not (System/getenv "DEPS_CLJ_NO_WINDOWS_FIXES"))) (mapv #(str/replace % "\"" "\\\"") args) args) @@ -96,9 +95,9 @@ true (.redirectInput ProcessBuilder$Redirect/INHERIT)) _ (when-let [dir *dir*] (.directory pb (io/file dir))) - _ (when-let [env *env*] + _ (when-let [env env] (set-env pb env)) - _ (when-let [extra-env *extra-env*] + _ (when-let [extra-env extra-env] (add-env pb extra-env)) proc (.start pb) string-out @@ -109,12 +108,39 @@ (str sw))) exit-code (.waitFor proc)] (when (not (zero? exit-code)) - (*exit-fn* exit-code)) - string-out))) + (*exit-fn* {:exit exit-code})) + {:out string-out + :exit exit-code}))) + +(defn ^:dynamic *aux-process-fn* + "Invokes `java` with arguments to calculate classpath, etc. May be + replacement by rebinding this dynamic var. + + Called with a map of: + + - `:cmd`: a vector of strings + - `:out`: if set to `:string`, `:out` key in result must contains stdout + + Returns a map of: -(def ^:private ^:dynamic *process-fn* shell-command) + - `:exit`, the exit code of the process + - `:out`, the string of stdout, if the input `:out` was set to `:string`" + [{:keys [cmd out]}] + (internal-shell-command cmd {:out out})) -(def help-text (delay (str "Version: " @version " +(defn ^:dynamic *clojure-process-fn* + "Invokes `java` with arguments to `clojure.main` to start Clojure. May + be replacement by rebinding this dynamic var. + + Called with a map of: + + - `:cmd`: a vector of strings + + Must return a map of `:exit`, the exit code of te process." + [{:keys [cmd]}] + (internal-shell-command cmd)) + +(def ^:private help-text (delay (str "Version: " @version " You use the Clojure tools ('clj' or 'clojure') to run Clojure programs on the JVM, e.g. to start a REPL or invoke a specific function with data. @@ -196,10 +222,10 @@ For more info, see: https://clojure.org/guides/deps_and_cli https://clojure.org/reference/repl_and_main"))) -(defn describe-line [[kw val]] +(defn- describe-line [[kw val]] (pr kw val)) -(defn describe [lines] +(defn- describe [lines] (let [[first-line & lines] lines] (print "{") (describe-line first-line) (doseq [line lines @@ -207,12 +233,12 @@ For more info, see: (print "\n ") (describe-line line)) (println "}"))) -(defn ^:private ^:dynamic *getenv-fn* - "Get ENV'ironment variable." +(defn- ^:dynamic *getenv-fn* + "Get ENV'ironment variable. Only used for testing, not part of the public API (yet)." ^String [env] (java.lang.System/getenv env)) -(defn cksum +(defn- cksum [^String s] (let [hashed (.digest (java.security.MessageDigest/getInstance "MD5") (.getBytes s)) @@ -222,7 +248,7 @@ For more info, see: (print (format "%02X" byte)))) (str sw))) -(defn which [executable] +(defn- which [executable] (when-let [path (*getenv-fn* "PATH")] (let [paths (.split path path-separator)] (loop [paths paths] @@ -233,13 +259,13 @@ For more info, see: (.getCanonicalPath f) (recur (rest paths))))))))) -(defn home-dir [] +(defn- home-dir [] (if windows? ;; workaround for https://github.com/oracle/graal/issues/1630 (*getenv-fn* "userprofile") (System/getProperty "user.home"))) -(def java-exe (if windows? "java.exe" "java")) +(def ^:private java-exe (if windows? "java.exe" "java")) (defn- get-java-cmd "Returns path to java executable to invoke sub commands with." @@ -260,11 +286,11 @@ For more info, see: (def ^:private authenticated-proxy-re #".+:.+@(.+):(\d+).*") (def ^:private unauthenticated-proxy-re #"(.+):(\d+).*") -(defn proxy-info [m] +(defn- proxy-info [m] {:host (nth m 1) :port (nth m 2)}) -(defn parse-proxy-info +(defn- parse-proxy-info [s] (when s (let [p (cond @@ -285,14 +311,14 @@ For more info, see: (do (warn "WARNING: Can't parse proxy info - found:" s "- proceeding without using proxy!") nil))))) -(defn env-proxy-info +(defn get-proxy-info "Returns a map with proxy information parsed from env vars. The map will contain :http-proxy and :https-proxy entries if the relevant env vars are set and parsed correctly. The value for each is a map with :host and :port entries." [] - (let [http-proxy (parse-proxy-info (or (*getenv-fn* "http_proxy") - (*getenv-fn* "HTTP_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")))] (cond-> {} @@ -302,22 +328,20 @@ For more info, see: (defn set-proxy-system-props! "Sets the proxy system properties in the current JVM. proxy-info parameter is as returned from env-proxy-info." - [proxy-info] - (let [{:keys [http-proxy https-proxy]} proxy-info] - (when http-proxy - (System/setProperty "http.proxyHost" (:host http-proxy)) - (System/setProperty "http.proxyPort" (:port http-proxy))) - (when https-proxy - (System/setProperty "https.proxyHost" (:host https-proxy)) - (System/setProperty "https.proxyPort" (:port https-proxy))))) - - -(defn clojure-tools-download-direct - "Downloads from SOURCE url to DEST file returning true on success." - [source dest] + [{:keys [http-proxy https-proxy]}] + (when http-proxy + (System/setProperty "http.proxyHost" (:host http-proxy)) + (System/setProperty "http.proxyPort" (:port http-proxy))) + (when https-proxy + (System/setProperty "https.proxyHost" (:host https-proxy)) + (System/setProperty "https.proxyPort" (:port https-proxy)))) + +(defn clojure-tools-download-direct! + "Downloads from `:url` to `:dest` file returning true on success." + [{:keys [url dest]}] (try - (set-proxy-system-props! (env-proxy-info)) - (let [source (URL. source) + (set-proxy-system-props! (get-proxy-info)) + (let [source (URL. url) dest (io/file dest) conn ^URLConnection (.openConnection ^URL source)] (when (instance? HttpURLConnection conn) @@ -349,14 +373,14 @@ For more info, see: :ct-zip-name The file name to store the archive as." (delay (let [version @version] - {:ct-base-dir "ClojureTools" + {:ct-base-dir "ClojureTools" :ct-error-exit-code 99 :ct-aux-files-names ["exec.jar" "example-deps.edn" "tools.edn"] :ct-jar-name (format "clojure-tools-%s.jar" version) :ct-url-str (format "https://download.clojure.org/install/clojure-tools-%s.zip" version) :ct-zip-name "tools.zip"}))) -(defn unzip +(defn- unzip [zip-file destination-dir] (let [{:keys [ct-aux-files-names ct-jar-name]} @clojure-tools-info* zip-file (io/file zip-file) @@ -366,8 +390,8 @@ For more info, see: zip-file (.toPath zip-file) files (into #{ct-jar-name} ct-aux-files-names)] (with-open - [fis (Files/newInputStream zip-file (into-array java.nio.file.OpenOption [])) - zis (ZipInputStream. fis)] + [fis (Files/newInputStream zip-file (into-array java.nio.file.OpenOption [])) + zis (ZipInputStream. fis)] (loop [] (when-let [entry (.getNextEntry zis)] (let [entry-name (.getName entry) @@ -419,93 +443,92 @@ public class ClojureToolsDownloader { System.exit(1); }}}")) dest-file)) -(defn- clojure-tools-download-java - "Downloads URL file to DEST-ZIP-FILE by invoking `java` with JVM-OPTS - on a `.java` program file, and returns true on success. Requires - Java 11+ (JEP 330)." - [url dest-zip-file jvm-opts] - (let [dest-dir (.getCanonicalPath (io/file dest-zip-file "..")) +(defn proxy-jvm-opts + "Returns a vector containing the JVM system property arguments to be passed to a new process + to set its proxy system properties. + proxy-info parameter is as returned from env-proxy-info." + [{:keys [http-proxy https-proxy]}] + (cond-> [] + http-proxy (concat [(str "-Dhttp.proxyHost=" (:host http-proxy)) + (str "-Dhttp.proxyPort=" (:port http-proxy))]) + https-proxy (concat [(str "-Dhttps.proxyHost=" (:host https-proxy)) + (str "-Dhttps.proxyPort=" (:port https-proxy))]))) + +(defn clojure-tools-download-java! + "Downloads `:url` zip file to `:dest` by invoking `java` with + `:proxy` options on a `.java` program file, and returns true on + success. Requires Java 11+ (JEP 330)." + [{:keys [url dest proxy-opts clj-jvm-opts]}] + (let [dest-dir (.getCanonicalPath (io/file dest "..")) dlr-path (clojure-tools-java-downloader-spit dest-dir) java-cmd [(get-java-cmd) "-XX:-OmitStackTraceInFastThrow"] success?* (atom true)] - (binding [*exit-fn* (fn - ([exit-code] (when-not (= exit-code 0) (reset! success?* false))) - ([exit-code msg] (when-not (= exit-code 0) - (warn msg) - (reset! success?* false))))] - (shell-command (vec (concat java-cmd - jvm-opts - [dlr-path url (str dest-zip-file)]))) + (binding [*exit-fn* (fn [{:keys [exit message]}] + (when-not (= exit 0) + (warn message) + (reset! success?* false)))] + (*aux-process-fn* {:cmd (vec (concat java-cmd + clj-jvm-opts + (proxy-jvm-opts proxy-opts) + [dlr-path url (str dest)]))}) (io/delete-file dlr-path true) @success?*))) -(def ^:private ^:dynamic *custom-clojure-tool-downloader* +(def ^:dynamic *clojure-tools-download-fn* "Can be dynamically rebound to customise the download of the Clojure tools. - Should be bound to a function accepting three parameters: - - The URL to download, as a string - - The path to the file to download it to, as a string - - a proxy info structure, as returned by env-proxy-info - Should return true if the download was successful, or false if not." + Should be bound to a function accepting a map with: + - `:url`: The URL to download, as a string + - `:dest`: The path to the file to download it to, as a string + - `:proxy-opts`: a map as returned by `get-proxy-info` + - `:clj-jvm-opts`: a vector of JVM opts (as passed on the command line). + Should return true if the `download` was successful, or false if not." nil) - -(defn clojure-tools-jar-download - "Downloads clojure tools archive in OUT-DIR, if not already there, +(defn clojure-tools-download! + "Downloads clojure tools archive in `:out-dir`, if not already there, and extracts in-place the clojure tools jar file and other important files. The download is attempted directly from this process, unless - JAVA-ARGS-WITH-CLJ-JVM-OPTS is set, in which case a java subprocess + `:jvm-opts` is set, in which case a java subprocess is created to download the archive passing in its value as command line options. It calls `*exit-fn*` if it cannot download the archive, with instructions how to manually download it." - [out-dir java-args-with-clj-jvm-opts {:keys [debug] :as _opts}] + [{:keys [out-dir debug proxy-opts clj-jvm-opts]}] (let [{:keys [ct-error-exit-code ct-url-str ct-zip-name]} @clojure-tools-info* dir (io/file out-dir) zip-file (io/file out-dir ct-zip-name)] (when-not (.exists zip-file) (warn "Downloading" ct-url-str "to" (str zip-file)) (.mkdirs dir) - (or (when *custom-clojure-tool-downloader* + (or (when *clojure-tools-download-fn* (when debug (warn "Attempting download using custom download function...")) - (*custom-clojure-tool-downloader* ct-url-str (str zip-file) (env-proxy-info))) - (when java-args-with-clj-jvm-opts + (*clojure-tools-download-fn* {:url ct-url-str :dest (str zip-file) :proxy-opts proxy-opts :clj-jvm-opts clj-jvm-opts})) + (when (seq clj-jvm-opts) (when debug (warn "Attempting download using java subprocess... (requires Java11+)")) - (clojure-tools-download-java ct-url-str (str zip-file) java-args-with-clj-jvm-opts)) + (clojure-tools-download-java! {:url ct-url-str :dest (str zip-file) :proxy-opts proxy-opts :clj-jvm-opts clj-jvm-opts})) (do (when debug (warn "Attempting direct download...")) - (clojure-tools-download-direct ct-url-str zip-file)) - (*exit-fn* ct-error-exit-code (str "Error: Cannot download Clojure tools." - " Please download manually from " ct-url-str - " to " (str (io/file dir ct-zip-name)))) - {:url ct-url-str :dest-dir (str dir)})) + (clojure-tools-download-direct! {:url ct-url-str :dest zip-file})) + (*exit-fn* {:exit ct-error-exit-code + :message (str "Error: Cannot download Clojure tools." + " Please download manually from " ct-url-str + " to " (str (io/file dir ct-zip-name)))}) + {:url ct-url-str :out-dir (str dir)})) (warn "Unzipping" (str zip-file) "...") (unzip zip-file (.getPath dir)) (.delete zip-file)) (warn "Successfully installed clojure tools!")) -(defn jvm-proxy-settings - "Returns a vector containing the JVM args to be passed to a new process - to set its proxy system properties. - proxy-info parameter is as returned from env-proxy-info." - [proxy-info] - (let [{:keys [http-proxy https-proxy]} proxy-info] - (cond-> [] - http-proxy (concat [(str "-Dhttp.proxyHost=" (:host http-proxy)) - (str "-Dhttp.proxyPort=" (:port http-proxy))]) - https-proxy (concat [(str "-Dhttps.proxyHost=" (:host https-proxy)) - (str "-Dhttps.proxyPort=" (:port https-proxy))])))) - -(def parse-opts->keyword +(def ^:private parse-opts->keyword {"-J" :jvm-opts "-R" :resolve-aliases "-C" :classpath-aliases - "-A" :repl-aliases - }) + "-A" :repl-aliases}) -(def bool-opts->keyword +(def ^:private bool-opts->keyword {"-Spath" :print-classpath "-Sverbose" :verbose "-Strace" :trace @@ -516,20 +539,22 @@ public class ClojureToolsDownloader { "-Spom" :pom "-P" :prep}) -(def string-opts->keyword +(def ^:private string-opts->keyword {"-Sdeps" :deps-data "-Scp" :force-cp "-Sdeps-file" :deps-file "-Scommand" :command "-Sthreads" :threads}) -(defn non-blank [s] +(defn ^:private non-blank [s] (when-not (str/blank? s) s)) -(def vconj (fnil conj [])) +(def ^:private vconj (fnil conj [])) -(defn parse-args [args] +(defn parse-cli-opts + "Parses the command line options." + [args] (loop [args (seq args) acc {:mode :repl}] (if args @@ -569,17 +594,17 @@ public class ClojureToolsDownloader { ;; deprecations (some #(str/starts-with? arg %) ["-R" "-C"]) (do (warn arg "-R is no longer supported, use -A with repl, -M for main, -X for exec, -T for tool") - (*exit-fn* 1)) + (*exit-fn* {:exit 1})) (some #(str/starts-with? arg %) ["-O"]) (let [msg (str arg " is no longer supported, use -A with repl, -M for main, -X for exec, -T for tool")] - (*exit-fn* 1 msg)) + (*exit-fn* {:exit 1 :message msg})) (= "-Sresolve-tags" arg) (let [msg "Option changed, use: clj -X:deps git-resolve-tags"] - (*exit-fn* 1 msg)) + (*exit-fn* {:exit 1 :message msg})) ;; end deprecations (= "-A" arg) (let [msg "-A requires an alias"] - (*exit-fn* 1 msg)) + (*exit-fn* {:exit 1 :message msg})) (some #(str/starts-with? arg %) ["-J" "-C" "-O" "-A"]) (recur (next args) (update acc (get parse-opts->keyword (subs arg 0 2)) @@ -592,7 +617,7 @@ public class ClojureToolsDownloader { (assoc acc string-opt-keyword (second args))) (str/starts-with? arg "-S") (let [msg (str "Invalid option: " arg)] - (*exit-fn* 1 msg)) + (*exit-fn* {:exit 1 :message msg})) (and (not (some acc [:main-aliases :all-aliases])) (or (= "-h" arg) @@ -606,48 +631,63 @@ public class ClojureToolsDownloader { (if (instance? Path path) path (.toPath (io/file path)))) -(defn unixify +(defn- unixify ^Path [f] (as-path (if windows? (-> f as-path .toUri .getPath) (str f)))) (defn- relativize - "Returns relative path by comparing this with other. Returns absolute path unchanged." + "Returns relative path as string by comparing this with other. Returns + absolute path unchanged." ^Path [f] - (if (.isAbsolute (as-path f)) - f - (if-let [dir *dir*] - (str (.relativize (unixify (.toAbsolutePath (as-path dir))) - (unixify (.toAbsolutePath (as-path f))))) - f))) - -(defn calculate-env-tools-dir [] + (str (if (.isAbsolute (as-path f)) + f + (if-let [dir *dir*] + (.relativize (unixify (.toAbsolutePath (as-path dir))) + (unixify (.toAbsolutePath (as-path f)))) + f)))) + +(defn- get-env-tools-dir + "Retrieves the tools-directory from environment variable `DEPS_CLJ_TOOLS_DIR`" + [] (or ;; legacy name - (*getenv-fn* "CLOJURE_TOOLS_DIR") - (*getenv-fn* "DEPS_CLJ_TOOLS_DIR"))) + (*getenv-fn* "CLOJURE_TOOLS_DIR") + (*getenv-fn* "DEPS_CLJ_TOOLS_DIR"))) -(defn calculate-tools-dir [] +(defn get-install-dir + "Retrieves the install directory where tools jar is located (after download). + Defaults to ~/.deps.clj//ClojureTools." + [] (let [{:keys [ct-base-dir]} @clojure-tools-info*] - (or (calculate-env-tools-dir) + (or (get-env-tools-dir) (.getPath (io/file (home-dir) ".deps.clj" @version ct-base-dir))))) -(defn calculate-config-dir [] +(defn get-config-dir + "Retrieves configuration directory. + First tries `CLJ_CONFIG` env var, then `$XDG_CONFIG_HOME/clojure`, then ~/.clojure." + [] (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")))) -(defn calculate-deps-edn [opts] - (or (:deps-file opts) +(defn get-local-deps-edn + "Returns the path of the `deps.edn` file (as string) in the current directory or as set by `-Sdeps-file`. + Required options: + * `:cli-opts`: command line options as parsed by `parse-opts`" + [{:keys [cli-opts]}] + (or (:deps-file cli-opts) (.getPath (io/file *dir* "deps.edn")))) -(defn calculate-cache-dir [deps-edn config-dir] - ;; Determine whether to use user or project cache +(defn get-cache-dir + "Returns cache dir (`.cpcache`) from either local dir, if `deps-edn` + exists, or the user cache dir." + [{:keys [deps-edn config-dir]}] (let [user-cache-dir (or (*getenv-fn* "CLJ_CACHE") (when-let [xdg-config-home (*getenv-fn* "XDG_CACHE_HOME")] @@ -657,8 +697,13 @@ public class ClojureToolsDownloader { (.getPath (io/file *dir* ".cpcache")) user-cache-dir))) -(defn calculate-config-paths [opts deps-edn config-dir install-dir] - (if (:repro opts) +(defn get-config-paths + "Returns vec of configuration paths, i.e. deps.edn from: + - `:install-dir` as obtained thrhough `get-install-dir` + - `:config-dir` as obtained through `get-config-dir` + - `:deps-edn` as obtained through `get-local-deps-edn`" + [{:keys [cli-opts deps-edn config-dir install-dir]}] + (if (:repro cli-opts) (if install-dir [(.getPath (io/file install-dir "deps.edn")) deps-edn] [deps-edn]) @@ -686,6 +731,16 @@ public class ClojureToolsDownloader { config-paths)))] (cksum val*))) +(defn get-help + "Returns help text as string." + [] + @help-text) + +(defn print-help + "Print help text" + [] + (println @help-text)) + (defn -main "See `help-text`. @@ -704,44 +759,42 @@ public class ClojureToolsDownloader { archive by invoking a java subprocess passing the env variable value as command line options." [& command-line-args] - (let [opts (parse-args command-line-args) + (let [cli-opts (parse-cli-opts command-line-args) {:keys [ct-jar-name]} @clojure-tools-info* debug (*getenv-fn* "DEPS_CLJ_DEBUG") java-cmd [(get-java-cmd) "-XX:-OmitStackTraceInFastThrow"] - env-tools-dir (calculate-env-tools-dir) - tools-dir (calculate-tools-dir) - install-dir tools-dir + env-tools-dir (get-env-tools-dir) + install-dir (get-install-dir) libexec-dir (if env-tools-dir (let [f (io/file env-tools-dir "libexec")] (if (.exists f) (.getPath f) env-tools-dir)) - tools-dir) + install-dir) tools-jar (io/file libexec-dir ct-jar-name) exec-jar (io/file libexec-dir "exec.jar") - proxy-settings (jvm-proxy-settings (env-proxy-info)) + proxy-opts (get-proxy-info) + proxy-settings (proxy-jvm-opts proxy-opts) clj-jvm-opts (some-> (*getenv-fn* "CLJ_JVM_OPTS") (str/split #" ")) tools-cp (or (when (.exists tools-jar) (.getPath tools-jar)) (binding [*out* *err*] (warn "Clojure tools not yet in expected location:" (str tools-jar)) - (let [java-clj-jvm-opts (when clj-jvm-opts (vec (concat clj-jvm-opts - proxy-settings)))] - (clojure-tools-jar-download libexec-dir java-clj-jvm-opts {:debug debug})) + (clojure-tools-download! {:out-dir libexec-dir :debug debug :clj-jvm-opts clj-jvm-opts :proxy-opts proxy-opts}) tools-jar)) - mode (:mode opts) + mode (:mode cli-opts) exec? (= :exec mode) tool? (= :tool mode) exec-cp (when (or exec? tool?) (.getPath exec-jar)) - deps-edn (calculate-deps-edn opts) + deps-edn (get-local-deps-edn {:cli-opts cli-opts}) clj-main-cmd (vec (concat java-cmd clj-jvm-opts proxy-settings ["-classpath" tools-cp "clojure.main"])) - config-dir (calculate-config-dir) + config-dir (get-config-dir) 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)] @@ -763,21 +816,24 @@ public class ClojureToolsDownloader { ;; Determine user cache directory (let [;; Chain deps.edn in config paths. repro=skip config dir config-user - (when-not (:repro opts) + (when-not (:repro cli-opts) (.getPath (io/file config-dir "deps.edn"))) config-project deps-edn - config-paths (calculate-config-paths opts deps-edn config-dir install-dir) - cache-dir (calculate-cache-dir deps-edn config-dir) + config-paths (get-config-paths {:cli-opts cli-opts + :deps-edn deps-edn + :config-dir config-dir + :install-dir install-dir}) + cache-dir (get-cache-dir {:deps-edn deps-edn :config-dir config-dir}) ;; Construct location of cached classpath file - tool-name (:tool-name opts) - tool-aliases (:tool-aliases opts) - ck (calculate-checksum opts config-paths) + tool-name (:tool-name cli-opts) + tool-aliases (:tool-aliases cli-opts) + ck (calculate-checksum cli-opts config-paths) cp-file (.getPath (io/file cache-dir (str ck ".cp"))) jvm-file (.getPath (io/file cache-dir (str ck ".jvm"))) main-file (.getPath (io/file cache-dir (str ck ".main"))) basis-file (.getPath (io/file cache-dir (str ck ".basis"))) manifest-file (.getPath (io/file cache-dir (str ck ".manifest"))) - _ (when (:verbose opts) + _ (when (:verbose cli-opts) (println "deps.clj version =" deps-clj-version) (println "version =" @version) (when install-dir (println "install_dir =" install-dir)) @@ -786,14 +842,14 @@ public class ClojureToolsDownloader { (println "cache_dir =" cache-dir) (println "cp_file =" cp-file) (println)) - tree? (:tree opts) + tree? (:tree cli-opts) ;; Check for stale classpath file cp-file (io/file cp-file) stale - (or (:force opts) - (:trace opts) + (or (:force cli-opts) + (:trace cli-opts) tree? - (:prep opts) + (:prep cli-opts) (not (.exists cp-file)) (when tool-name (let [tool-file (io/file config-dir "tools" (str tool-name ".edn"))] @@ -821,67 +877,68 @@ public class ClojureToolsDownloader { (not (.exists (io/file entry))))) entries))) tools-args - (when (or stale (:pom opts)) + (when (or stale (:pom cli-opts)) (cond-> [] - (not (str/blank? (:deps-data opts))) - (conj "--config-data" (:deps-data opts)) - (:main-aliases opts) - (conj (str "-M" (:main-aliases opts))) - (:repl-aliases opts) - (conj (str "-A" (str/join "" (:repl-aliases opts)))) - (:exec-aliases opts) - (conj (str "-X" (:exec-aliases opts))) + (not (str/blank? (:deps-data cli-opts))) + (conj "--config-data" (:deps-data cli-opts)) + (:main-aliases cli-opts) + (conj (str "-M" (:main-aliases cli-opts))) + (:repl-aliases cli-opts) + (conj (str "-A" (str/join "" (:repl-aliases cli-opts)))) + (:exec-aliases cli-opts) + (conj (str "-X" (:exec-aliases cli-opts))) tool? (conj "--tool-mode") tool-name (conj "--tool-name" tool-name) tool-aliases (conj (str "-T" tool-aliases)) - (:force-cp opts) + (:force-cp cli-opts) (conj "--skip-cp") - (:threads opts) - (conj "--threads" (:threads opts)) - (:trace opts) + (:threads cli-opts) + (conj "--threads" (:threads cli-opts)) + (:trace cli-opts) (conj "--trace") tree? (conj "--tree"))) - classpath-not-needed? (boolean (some #(% opts) [:describe :help :version]))] + classpath-not-needed? (boolean (some #(% cli-opts) [:describe :help :version]))] ;; If stale, run make-classpath to refresh cached classpath (when (and stale (not classpath-not-needed?)) - (when (:verbose opts) + (when (:verbose cli-opts) (warn "Refreshing classpath")) - (let [res (shell-command (into clj-main-cmd - (concat - ["-m" "clojure.tools.deps.script.make-classpath2" - "--config-user" config-user - "--config-project" (relativize config-project) - "--basis-file" (relativize basis-file) - "--cp-file" (relativize cp-file) - "--jvm-file" (relativize jvm-file) - "--main-file" (relativize main-file) - "--manifest-file" (relativize manifest-file)] - tools-args)) - {:to-string? tree?})] + (let [{:keys [out]} (*aux-process-fn* {:cmd (into clj-main-cmd + (concat + ["-m" "clojure.tools.deps.script.make-classpath2" + "--config-user" config-user + "--config-project" (relativize config-project) + "--basis-file" (relativize basis-file) + "--cp-file" (relativize cp-file) + "--jvm-file" (relativize jvm-file) + "--main-file" (relativize main-file) + "--manifest-file" (relativize manifest-file)] + tools-args)) + :out (when tree? + :string)})] (when tree? - (print res) (flush)))) + (print out) (flush)))) (let [cp (cond (or classpath-not-needed? - (:prep opts)) nil - (not (str/blank? (:force-cp opts))) (:force-cp opts) + (:prep cli-opts)) nil + (not (str/blank? (:force-cp cli-opts))) (:force-cp cli-opts) :else (slurp cp-file))] - (cond (:help opts) (do (println @help-text) - (*exit-fn* 0)) - (:version opts) (do (println "Clojure CLI version (deps.clj)" @version) - (*exit-fn* 0)) - (:prep opts) (*exit-fn* 0) - (:pom opts) - (shell-command (into clj-main-cmd - ["-m" "clojure.tools.deps.script.generate-manifest2" - "--config-user" config-user - "--config-project" (relativize config-project) - "--gen=pom" (str/join " " tools-args)])) - (:print-classpath opts) + (cond (:help cli-opts) (do (print-help) + (*exit-fn* {:exit 0})) + (:version cli-opts) (do (println "Clojure CLI version (deps.clj)" @version) + (*exit-fn* {:exit 0})) + (:prep cli-opts) (*exit-fn* {:exit 0}) + (:pom cli-opts) + (*aux-process-fn* {:cmd (into clj-main-cmd + ["-m" "clojure.tools.deps.script.generate-manifest2" + "--config-user" config-user + "--config-project" (relativize config-project) + "--gen=pom" (str/join " " tools-args)])}) + (:print-classpath cli-opts) (println cp) - (:describe opts) + (:describe cli-opts) (describe [[:deps-clj-version deps-clj-version] [:version @version] [:config-files (filterv #(.exists (io/file %)) config-paths)] @@ -890,22 +947,22 @@ public class ClojureToolsDownloader { (when install-dir [:install-dir install-dir]) [:config-dir config-dir] [:cache-dir cache-dir] - [:force (boolean (:force opts))] - [:repro (boolean (:repro opts))] - [:main-aliases (str (:main-aliases opts))] - [:all-aliases (str (:all-aliases opts))]]) - tree? (*exit-fn* 0) - (:trace opts) + [:force (boolean (:force cli-opts))] + [:repro (boolean (:repro cli-opts))] + [:main-aliases (str (:main-aliases cli-opts))] + [:all-aliases (str (:all-aliases cli-opts))]]) + tree? (*exit-fn* {:exit 0}) + (:trace cli-opts) (warn "Wrote trace.edn") - (:command opts) - (let [command (str/replace (:command opts) "{{classpath}}" (str cp)) + (:command cli-opts) + (let [command (str/replace (:command cli-opts) "{{classpath}}" (str cp)) main-cache-opts (when (.exists (io/file main-file)) (-> main-file slurp str/split-lines)) main-cache-opts (str/join " " main-cache-opts) command (str/replace command "{{main-opts}}" (str main-cache-opts)) command (str/split command #"\s+") - command (into command (:args opts))] - (*process-fn* command)) + command (into command (:args cli-opts))] + (*clojure-process-fn* {:cmd command})) :else (let [jvm-cache-opts (when (.exists (io/file jvm-file)) (-> jvm-file slurp str/split-lines)) @@ -921,14 +978,14 @@ public class ClojureToolsDownloader { java-opts proxy-settings jvm-cache-opts - (:jvm-opts opts) + (:jvm-opts cli-opts) [(str "-Dclojure.basis=" (relativize basis-file)) "-classpath" cp "clojure.main"] main-opts) main-args (filterv some? main-args) - main-args (into main-args (:args opts))] + main-args (into main-args (:args cli-opts))] (when (and (= :repl mode) - (pos? (count (:args opts)))) + (pos? (count (:args cli-opts)))) (warn "WARNING: Implicit use of clojure.main with options is deprecated, use -M")) - (*process-fn* main-args))))))) + (*clojure-process-fn* {:cmd main-args}))))))) diff --git a/test/borkdude/deps_test.clj b/test/borkdude/deps_test.clj index 9f0f3c1..5c712fb 100644 --- a/test/borkdude/deps_test.clj +++ b/test/borkdude/deps_test.clj @@ -5,10 +5,9 @@ [borkdude.deps :as deps] [clojure.edn :as edn] [clojure.java.io :as io] + [clojure.set :as set] [clojure.string :as str] - [clojure.test :as t :refer [deftest is testing]] - [clojure.set :as set]) - + [clojure.test :as t :refer [deftest is testing]]) (:import [java.util.zip ZipEntry ZipOutputStream])) ;; Print out information about the java executable that will be used @@ -33,22 +32,22 @@ [& args] (case (or (System/getenv "DEPS_CLJ_TEST_ENV") "clojure") - "babashka" (let [classpath (str/join deps/path-separator ["src" "test" "resources"])] + "babashka" (let [classpath (str/join @#'deps/path-separator ["src" "test" "resources"])] (apply str "bb -cp " classpath " -m borkdude.deps " args)) "native" (apply str "./deps " args) "clojure" (cond->> - (apply str "clojure -M -m borkdude.deps " args) - deps/windows? + (apply str "clojure -M -m borkdude.deps " args) + @#'deps/windows? ;; the `exit` command is a workaround for ;; https://ask.clojure.org/index.php/12290/clojuretools-commands-windows-properly-exit-code-failure (format "powershell -NoProfile -Command %s; exit $LASTEXITCODE")))) -(deftest parse-args-test +(deftest parse-cli-opts-test (is (= {:mode :repl, :jvm-opts ["-Dfoo=bar" "-Dbaz=quuz"]} - (deps/parse-args ["-J-Dfoo=bar" "-J-Dbaz=quuz"]))) + (deps/parse-cli-opts ["-J-Dfoo=bar" "-J-Dbaz=quuz"]))) (is (= {:mode :main, :main-aliases nil, :args '("-e" "(+ 1 2 3)")} - (deps/parse-args ["-M" "-e" "(+ 1 2 3)"]))) - (is (= {:mode :main, :main-aliases ":foo", :args nil} (deps/parse-args ["-M:foo"])))) + (deps/parse-cli-opts ["-M" "-e" "(+ 1 2 3)"]))) + (is (= {:mode :main, :main-aliases ":foo", :args nil} (deps/parse-cli-opts ["-M:foo"])))) (deftest path-test (is (str/includes? (with-out-str @@ -85,11 +84,10 @@ :msg The process's error messsage (if any)." [& command-line-args] `(binding [deps/*exit-fn* - (fn - ([exit-code#] (when-not (= exit-code# 0) - (throw (ex-info (str ::deps-main-throw) {:exit-code exit-code#})))) - ([exit-code# msg#] (throw (ex-info (str ::deps-main-throw) - {:exit-code exit-code# :msg msg#}))))] + (fn [{:keys [~'exit ~'message]}] + (when ~'message + (throw (ex-info (str ::deps-main-throw) + {:exit-code ~'exit :msg ~'message}))))] (deps/-main ~@command-line-args))) (deftest whitespace-test @@ -108,7 +106,7 @@ temp-file-path (str temp-file) _ (deps-main-throw "-Sdeps" (format - (if-not deps/windows? + (if-not @#'deps/windows? "{:aliases {:space {:main-opts [\"-e\" \"(spit \\\"%s\\\" (+ 1 2 3))\"]}}}" "{:aliases {:space {:main-opts [\"-e\" \"(spit \\\\\"%s\\\\\" (+ 1 2 3))\"]}}}") (.toURI (fs/file temp-file-path))) @@ -117,29 +115,29 @@ (is (= "6" out))))) (deftest jvm-proxy-settings-test - (is (= {:host "aHost" :port "1234"} (deps/parse-proxy-info "http://aHost:1234"))) - (is (= {:host "aHost" :port "1234"} (deps/parse-proxy-info "http://user:pw@aHost:1234"))) - (is (= {:host "aHost" :port "1234"} (deps/parse-proxy-info "https://aHost:1234"))) - (is (= {:host "aHost" :port "1234"} (deps/parse-proxy-info "https://user:pw@aHost:1234"))) - (is (nil? (deps/parse-proxy-info "http://aHost:abc"))) + (is (= {:host "aHost" :port "1234"} (#'deps/parse-proxy-info "http://aHost:1234"))) + (is (= {:host "aHost" :port "1234"} (#'deps/parse-proxy-info "http://user:pw@aHost:1234"))) + (is (= {:host "aHost" :port "1234"} (#'deps/parse-proxy-info "https://aHost:1234"))) + (is (= {:host "aHost" :port "1234"} (#'deps/parse-proxy-info "https://user:pw@aHost:1234"))) + (is (nil? (#'deps/parse-proxy-info "http://aHost:abc"))) (is (= {:http-proxy {:host "aHost" :port "1234"}} (binding [deps/*getenv-fn* {"http_proxy" "http://aHost:1234"}] - (deps/env-proxy-info)))) + (deps/get-proxy-info)))) (is (= {:http-proxy {:host "aHost" :port "1234"}} (binding [deps/*getenv-fn* {"HTTP_PROXY" "http://aHost:1234"}] - (deps/env-proxy-info)))) + (deps/get-proxy-info)))) (is (= {:https-proxy {:host "aHost" :port "1234"}} (binding [deps/*getenv-fn* {"https_proxy" "http://aHost:1234"}] - (deps/env-proxy-info)))) + (deps/get-proxy-info)))) (is (= {:https-proxy {:host "aHost" :port "1234"}} (binding [deps/*getenv-fn* {"HTTPS_PROXY" "http://aHost:1234"}] - (deps/env-proxy-info)))) + (deps/get-proxy-info)))) (is (= {} (binding [deps/*getenv-fn* {}] - (deps/env-proxy-info)))) + (deps/get-proxy-info)))) (is (= {} (binding [deps/*getenv-fn* {"http_proxy" "http://aHost:abc"}] - (deps/env-proxy-info))))) + (deps/get-proxy-info))))) (deftest jvm-opts-test (let [temp-dir (fs/create-temp-dir) @@ -149,7 +147,7 @@ "-M" "-e" (format " (spit \"%s\" (pr-str [(System/getProperty \"foo\") (System/getProperty \"baz\")]))" (.toURI (fs/file temp-file-path)))) - (is (= ["bar" "quux"] (edn/read-string (slurp temp-file-path)))))) + (is (= ["bar" "quux"] (edn/read-string (slurp temp-file-path)))))) (deftest tools-dir-env-test (fs/delete-tree "tools-dir") @@ -207,8 +205,8 @@ 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 + (let [body-str (pr-str body)] + `(let [shell-command# @#'deps/internal-shell-command ret*# (promise) sh-mock# (fn mock# ([args#] @@ -218,17 +216,18 @@ (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#})))) + (binding [deps/*clojure-process-fn* (fn ~'[{:keys [cmd]}] + (sh-mock# ~'cmd)) + deps/*aux-process-fn* (fn ~'[{:keys [cmd out]}] + (sh-mock# ~'cmd {:to-string? (= :string ~'out)})) + deps/*exit-fn* (fn [{:keys [~'exit ~'message]}] + (when ~'message + (throw (ex-info "mock-shell-failed" + {:exit-code ~'exit :msg ~'message})))) 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}))))))) + ~@body + (or (deref ret*# 500 false) (ex-info "No shell-command invoked in body." {:body ~body-str})))))) (defn java-major-version-get "Returns the major version number of the java executable used to run @@ -249,7 +248,7 @@ (let [{:keys [ct-base-dir ct-aux-files-names ct-jar-name]} @@#'deps/clojure-tools-info* file (io/file out-dir "borkdude-deps-test-dummy-tools.zip")] (with-open [os (io/output-stream file) - zip (ZipOutputStream. os)] + zip (ZipOutputStream. os)] (doseq [entry (into [ct-jar-name] ct-aux-files-names)] (doto zip (.putNextEntry (ZipEntry. (str ct-base-dir "/" entry))) @@ -274,10 +273,10 @@ dest-zip-file (fs/file temp-dir ct-zip-name)] (if (< java-version 11) ;; requires java11+, fails otherwise - (do (is (= false (#'deps/clojure-tools-download-java url dest-zip-file []))) + (do (is (= false (#'deps/clojure-tools-download-java! {:url url :dest dest-zip-file}))) (is (not (fs/exists? dest-zip-file)))) - (do (is (= true (#'deps/clojure-tools-download-java url dest-zip-file []))) + (do (is (= true (#'deps/clojure-tools-download-java! {:url url :dest dest-zip-file}))) (is (fs/exists? dest-zip-file))))))) (when (>= java-version 11) @@ -285,7 +284,7 @@ (fs/with-temp-dir [temp-dir {}] (let [dest-jar-file (fs/file temp-dir ct-jar-name)] - (with-redefs [deps/clojure-tools-download-direct + (with-redefs [deps/clojure-tools-download-direct! (fn [& _] (throw (Exception. "Direct should not be called.")))] (let [xx-pclf "-XX:+PrintCommandLineFlags" xx-gc-threads "-XX:ConcGCThreads=1" @@ -303,14 +302,14 @@ [temp-dir {}] (let [url-str ct-url-str dest-zip-file (fs/file temp-dir ct-zip-name)] - (is (= true (deps/clojure-tools-download-direct url-str dest-zip-file))) + (is (= true (deps/clojure-tools-download-direct! {:url url-str :dest dest-zip-file}))) (is (fs/exists? dest-zip-file))))) (testing "direct downloader called from -main (CLJ_JVM_OPTS not set)" (fs/with-temp-dir [temp-dir {}] (let [dest-jar-file (fs/file temp-dir ct-jar-name)] - (with-redefs [deps/clojure-tools-download-java + (with-redefs [deps/clojure-tools-download-java! (fn [& _] (throw (Exception. "Java subprocess should not be called.")))] (binding [deps/*getenv-fn* #(or (get {"DEPS_CLJ_TOOLS_DIR" (str temp-dir) "CLJ_JVM_OPTS" nil} %) @@ -326,9 +325,9 @@ dest-zip-file (fs/file temp-dir ct-zip-name) dest-jar-file (fs/file temp-dir ct-jar-name)] (fs/copy tools-zip-file dest-zip-file) ;; user copies downloaded file - (with-redefs [deps/clojure-tools-download-java + (with-redefs [deps/clojure-tools-download-java! (fn [& _] (throw (Exception. "Java should not be called."))) - deps/clojure-tools-download-direct + deps/clojure-tools-download-direct! (fn [& _] (throw (Exception. "Direct should not be called.")))] (binding [deps/*getenv-fn* #(or (get {"DEPS_CLJ_TOOLS_DIR" (str temp-dir)} %) (System/getenv %))] @@ -340,30 +339,32 @@ (fs/with-temp-dir [temp-dir {}] (let [dest-jar-file (fs/file temp-dir ct-jar-name)] - (with-redefs [deps/clojure-tools-download-java + (with-redefs [deps/clojure-tools-download-java! (fn [& _] (throw (Exception. "Java should not be called."))) - deps/clojure-tools-download-direct + deps/clojure-tools-download-direct! (fn [& _] (throw (Exception. "Direct should not be called.")))] (binding [deps/*getenv-fn* #(or (get {"DEPS_CLJ_TOOLS_DIR" (str temp-dir)} %) (System/getenv %)) - deps/*custom-clojure-tool-downloader* - (fn [_url dest-path _proxy-info] + deps/*clojure-tools-download-fn* + (fn [{:keys [url dest] :as opts}] ; Simulate download by creating dummy file and copying to ; specified destination location + (is (str/starts-with? url "http")) + (is (contains? opts :clj-jvm-opts)) + (is (contains? opts :proxy-opts)) (let [tools-zip-file (clojure-tools-dummy-zip-file-create (str temp-dir)) - dest-zip-file (fs/file dest-path)] + dest-zip-file (fs/file dest)] (fs/copy tools-zip-file dest-zip-file) true))] - (deps-main-throw "--version") (is (fs/exists? dest-jar-file))))))) (testing "prompt for manual user install" (fs/with-temp-dir [temp-dir {}] - (with-redefs [deps/clojure-tools-download-java + (with-redefs [deps/clojure-tools-download-java! (fn [& _] false) - deps/clojure-tools-download-direct + deps/clojure-tools-download-direct! (fn [& _] false)] (binding [deps/*getenv-fn* #(or (get {"DEPS_CLJ_TOOLS_DIR" (str temp-dir)} %) (System/getenv %))] @@ -424,7 +425,7 @@ (let [deps-map (pr-str '{:mvn/local-repo "test/mvn" :deps {medley/medley {:mvn/version "1.4.0"} io.github.borkdude/quickblog {:git/sha "8f5898ee911101a96295f59bb5ffc7517757bc8f"}}}) delete #(do (fs/delete-tree (fs/file "test" "mvn")) - (fs/delete-tree (fs/file (or (some-> (System/getenv "GITLIBS") (fs/file )) + (fs/delete-tree (fs/file (or (some-> (System/getenv "GITLIBS") (fs/file)) (fs/file (System/getProperty "user.dir" ".gitlibs"))) "libs" "io.github.borkdude/quickblog" "8f5898ee911101a96295f59bb5ffc7517757bc8f"))) test #(deps/-main "-Sdeps" deps-map "-M" "-e" "(require '[medley.core]) (require '[quickblog.api])")]