wip [skip ci]

This commit is contained in:
Michiel Borkent 2021-03-17 12:25:26 +01:00
parent 90443eed80
commit 44f80f2a28
4 changed files with 250 additions and 213 deletions

View file

@ -28,17 +28,18 @@
resource-paths))) resource-paths)))
(defn path-from-jar (defn path-from-jar
[^java.io.File jar-file resource-paths {:keys [:url?]}] [^java.io.File jar-file resource-paths opts]
(with-open [jar (JarFile. jar-file)] (let [url? (:url? opts)]
(some (fn [path] (with-open [jar (JarFile. jar-file)]
(when-let [entry (.getEntry jar path)] (some (fn [path]
(if url? (when-let [entry (.getEntry jar path)]
;; manual conversion, faster than going through .toURI (if url?
(java.net.URL. "jar" nil ;; manual conversion, faster than going through .toURI
(str "file:" (.getAbsolutePath jar-file) "!/" path)) (java.net.URL. "jar" nil
{:file path (str "file:" (.getAbsolutePath jar-file) "!/" path))
:source (slurp (.getInputStream jar entry))}))) {:file path
resource-paths))) :source (slurp (.getInputStream jar entry))})))
resource-paths))))
(deftype JarFileResolver [jar-file] (deftype JarFileResolver [jar-file]
IResourceResolver IResourceResolver
@ -57,8 +58,10 @@
(getResources [_ resource-paths opts] (getResources [_ resource-paths opts]
(keep #(getResource % resource-paths opts) entries))) (keep #(getResource % resource-paths opts) entries)))
(def path-sep (System/getProperty "path.separator"))
(defn loader [^String classpath] (defn loader [^String classpath]
(let [parts (.split classpath (System/getProperty "path.separator")) (let [parts (.split classpath path-sep)
entries (map part->entry parts)] entries (map part->entry parts)]
(Loader. entries))) (Loader. entries)))
@ -88,7 +91,7 @@
(fn [{:keys [:cp]}] (fn [{:keys [:cp]}]
(let [new-cp (let [new-cp
(if-not cp extra-classpath (if-not cp extra-classpath
(str cp (System/getProperty "path.separator") extra-classpath))] (str cp path-sep extra-classpath))]
{:loader (loader new-cp) {:loader (loader new-cp)
:cp new-cp}))) :cp new-cp})))
nil) nil)
@ -96,7 +99,7 @@
(defn split-classpath (defn split-classpath
"Returns the classpath as a seq of strings, split by the platform "Returns the classpath as a seq of strings, split by the platform
specific path separator." specific path separator."
([^String cp] (vec (.split cp (System/getProperty "path.separator"))))) ([^String cp] (vec (.split cp path-sep))))
(defn get-classpath (defn get-classpath
"Returns the current classpath as set by --classpath, BABASHKA_CLASSPATH and add-classpath." "Returns the current classpath as set by --classpath, BABASHKA_CLASSPATH and add-classpath."

View file

@ -362,7 +362,7 @@ Use -- to separate script command line args from bb command line args.
(defn error [msg exit] (defn error [msg exit]
(binding [*out* *err*] (binding [*out* *err*]
(println msg) (println msg)
{:exit-code exit})) {:exec (fn [] exit)}))
(defn parse-opts [options] (defn parse-opts [options]
(let [opts (loop [options options (let [opts (loop [options options
@ -498,208 +498,229 @@ Use -- to separate script command line args from bb command line args.
opts-map))] opts-map))]
opts)) opts))
(def bb-edn
(delay
(let [bb-edn-file (or (System/getenv "BABASHKA_EDN")
"bb.edn")]
(when (fs/exists? bb-edn-file)
(edn/read-string (slurp bb-edn-file))))))
(defn resolve-task [task {:keys [:command-line-args]}] (defn resolve-task [task {:keys [:command-line-args]}]
(let [bb-edn-file (or (System/getenv "BABASHKA_EDN") (if @bb-edn
"bb.edn")] (if-let [task (get-in @bb-edn [:tasks (keyword (subs task 1))])]
(if (fs/exists? bb-edn-file) (case (:task/type task)
(let [bb-edn (edn/read-string (slurp bb-edn-file))] :babashka
(if-let [task (get-in bb-edn [:tasks (keyword (subs task 1))])] (let [cmd-line-args (get task :args)]
(let [cmd-line-args (get task :babashka/args) (parse-opts (seq (map str (concat cmd-line-args command-line-args)))))
proc (get task :babashka/process)] :shell
;; this is for invoking babashka itself with command-line-args (let [args (get task :args)]
(cond cmd-line-args {:exec (fn []
(parse-opts (seq (map str (concat cmd-line-args command-line-args)))) (p/process args {:inherit true}) p/check
proc {:exec (fn [] 0)}))
(-> (:args proc) (p/process {:inherit true}) p/check) (error (str "No such task: " task) 1))
0)})) (error (str "File does not exist: " task) 1)))
(error (str "No such task: " task) 1)))
(error (str "File does not exist: " task) 1)))) (def should-load-inits?
"if true, then we should still load preloads and user.clj"
(volatile! true))
(defn exec [opts] (defn exec [opts]
(binding [*unrestricted* true] (binding [*unrestricted* true]
(sci/binding [reflection-var false (sci/binding [reflection-var false
core/data-readers @core/data-readers core/data-readers @core/data-readers
sci/ns @sci/ns] sci/ns @sci/ns]
(if-let [f (:exec opts)] (let [{version-opt :version
(f) :keys [:shell-in :edn-in :shell-out :edn-out
(if (:clojure opts) :help? :file :command-line-args
(if-let [proc (deps/clojure (:opts opts))] :expressions :stream?
(-> @proc :exit) :repl :socket-repl :nrepl
0) :verbose? :classpath
(let [{version-opt :version :main :uberscript :describe?
:keys [:shell-in :edn-in :shell-out :edn-out :jar :uberjar :clojure]
:help? :file :command-line-args exec-fn :exec}
:expressions :stream? opts
:repl :socket-repl :nrepl _ (when verbose? (vreset! common/verbose? true))
:verbose? :classpath _ (do ;; set properties
:main :uberscript :describe? (when main (System/setProperty "babashka.main" main))
:jar :uberjar]} (System/setProperty "babashka.version" version))
opts read-next (fn [*in*]
_ (when verbose? (vreset! common/verbose? true)) (if (pipe-signal-received?)
_ (do ;; set properties ::EOF
(when main (System/setProperty "babashka.main" main)) (if stream?
(System/setProperty "babashka.version" version)) (if shell-in (or (read-line) ::EOF)
read-next (fn [*in*] (edn/read {:readers edn-readers
(if (pipe-signal-received?) :eof ::EOF} *in*))
::EOF (delay (cond shell-in
(if stream? (shell-seq *in*)
(if shell-in (or (read-line) ::EOF) edn-in
(edn/read {:readers edn-readers (edn-seq *in*)
:eof ::EOF} *in*)) :else
(delay (cond shell-in (edn/read {:readers edn-readers} *in*))))))
(shell-seq *in*) uberscript-sources (atom ())
edn-in env (atom {})
(edn-seq *in*) classpath (or classpath
:else (System/getenv "BABASHKA_CLASSPATH"))
(edn/read {:readers edn-readers} *in*)))))) _ (when classpath
uberscript-sources (atom ()) (cp/add-classpath classpath))
env (atom {}) abs-path (when file
classpath (or classpath (let [abs-path (.getAbsolutePath (io/file file))]
(System/getenv "BABASHKA_CLASSPATH")) (vars/bindRoot sci/file abs-path)
_ (when classpath (System/setProperty "babashka.file" abs-path)
(cp/add-classpath classpath)) abs-path))
abs-path (when file _ (when jar
(let [abs-path (.getAbsolutePath (io/file file))] (cp/add-classpath jar))
(vars/bindRoot sci/file abs-path) load-fn (fn [{:keys [:namespace :reload]}]
(System/setProperty "babashka.file" abs-path) (when-let [{:keys [:loader]}
abs-path)) @cp/cp-state]
_ (when jar (if ;; ignore built-in namespaces when uberscripting, unless with :reload
(cp/add-classpath jar)) (and uberscript
load-fn (fn [{:keys [:namespace :reload]}] (not reload)
(when-let [{:keys [:loader]} (or (contains? namespaces namespace)
@cp/cp-state] (contains? sci-namespaces/namespaces namespace)))
(if ;; ignore built-in namespaces when uberscripting, unless with :reload ""
(and uberscript (let [res (cp/source-for-namespace loader namespace nil)]
(not reload) (when uberscript (swap! uberscript-sources conj (:source res)))
(or (contains? namespaces namespace) res))))
(contains? sci-namespaces/namespaces namespace))) main (if (and jar (not main))
"" (when-let [res (cp/getResource
(let [res (cp/source-for-namespace loader namespace nil)] (cp/loader jar)
(when uberscript (swap! uberscript-sources conj (:source res))) ["META-INF/MANIFEST.MF"] {:url? true})]
res)))) (cp/main-ns res))
main (if (and jar (not main)) main)
(when-let [res (cp/getResource
(cp/loader jar)
["META-INF/MANIFEST.MF"] {:url? true})]
(cp/main-ns res))
main)
;; TODO: pull more of these values to compile time ;; TODO: pull more of these values to compile time
opts {:aliases aliases opts {:aliases aliases
:namespaces (-> namespaces :namespaces (-> namespaces
(assoc 'clojure.core (assoc 'clojure.core
(assoc core-extras (assoc core-extras
'*command-line-args* '*command-line-args*
(sci/new-dynamic-var '*command-line-args* command-line-args) (sci/new-dynamic-var '*command-line-args* command-line-args)
'*warn-on-reflection* reflection-var '*warn-on-reflection* reflection-var
'load-file load-file*)) 'load-file load-file*))
(assoc-in ['clojure.java.io 'resource] (assoc-in ['clojure.java.io 'resource]
(fn [path] (fn [path]
(when-let [{:keys [:loader]} @cp/cp-state] (when-let [{:keys [:loader]} @cp/cp-state]
(if (str/starts-with? path "/") nil ;; non-relative paths always return nil (if (str/starts-with? path "/") nil ;; non-relative paths always return nil
(cp/getResource loader [path] {:url? true}))))) (cp/getResource loader [path] {:url? true})))))
(assoc-in ['user (with-meta '*input* (assoc-in ['user (with-meta '*input*
(when-not stream? (when-not stream?
{:sci.impl/deref! true}))] input-var)) {:sci.impl/deref! true}))] input-var))
:env env :env env
:features #{:bb :clj} :features #{:bb :clj}
:classes classes/class-map :classes classes/class-map
:imports imports :imports imports
:load-fn load-fn :load-fn load-fn
:uberscript uberscript :uberscript uberscript
:readers core/data-readers :readers core/data-readers
:reify-fn reify-fn :reify-fn reify-fn
:proxy-fn proxy-fn} :proxy-fn proxy-fn}
opts (addons/future opts) opts (addons/future opts)
sci-ctx (sci/init opts) sci-ctx (sci/init opts)
_ (vreset! common/ctx sci-ctx) _ (vreset! common/ctx sci-ctx)
preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim)) preloads (when @should-load-inits? (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim)))
[expressions exit-code] [expressions exit-code]
(cond expressions [expressions nil] (cond expressions [expressions nil]
main [[(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)" main [[(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
main)] nil] main)] nil]
file (try [[(read-file file)] nil] file (try [[(read-file file)] nil]
(catch Exception e (catch Exception e
(error-handler e {:expression expressions (error-handler e {:expression expressions
:verbose? verbose? :verbose? verbose?
:preloads preloads :preloads preloads
:loader (:loader @cp/cp-state)})))) :loader (:loader @cp/cp-state)}))))
expression (str/join " " expressions) ;; this might mess with the locations... expression (str/join " " expressions) ;; this might mess with the locations...
exit-code exit-code
;; handle preloads ;; handle preloads
(if exit-code exit-code (if exit-code exit-code
(do (when preloads (do (when @should-load-inits?
(sci/binding [sci/file "<preloads>"] (when preloads
(sci/binding [sci/file "<preloads>"]
(try
(sci/eval-string* sci-ctx preloads)
(catch Throwable e
(error-handler e {:expression expression
:verbose? verbose?
:preloads preloads
:loader (:loader @cp/cp-state)})))))
(when @cp/cp-state
(when-let [{:keys [:file :source]}
(cp/source-for-namespace (:loader @cp/cp-state) "user" nil)]
(sci/binding [sci/file file]
(try (try
(sci/eval-string* sci-ctx preloads) (sci/eval-string* sci-ctx source)
(catch Throwable e (catch Throwable e
(error-handler e {:expression expression (error-handler e {:expression expression
:verbose? verbose? :verbose? verbose?
:preloads preloads :preloads preloads
:loader (:loader @cp/cp-state)}))))) :loader (:loader @cp/cp-state)}))))))
nil)) (vreset! should-load-inits? false))
;; socket REPL is start asynchronously. when no other args are nil))
;; provided, a normal REPL will be started as well, which causes the ;; socket REPL is start asynchronously. when no other args are
;; process to wait until SIGINT ;; provided, a normal REPL will be started as well, which causes the
_ (when socket-repl (start-socket-repl! socket-repl sci-ctx)) ;; process to wait until SIGINT
exit-code _ (when socket-repl (start-socket-repl! socket-repl sci-ctx))
(or exit-code exit-code
(second (or exit-code
(cond version-opt (second
[(print-version) 0] (cond version-opt
help? [(print-version) 0]
[(print-help) 0] help?
describe? [(print-help) 0]
[(print-describe) 0] describe?
repl [(repl/start-repl! sci-ctx) 0] [(print-describe) 0]
nrepl [(start-nrepl! nrepl sci-ctx) 0] repl [(repl/start-repl! sci-ctx) 0]
uberjar [nil 0] nrepl [(start-nrepl! nrepl sci-ctx) 0]
expressions uberjar [nil 0]
(sci/binding [sci/file abs-path] expressions
(try (sci/binding [sci/file abs-path]
(loop [] (try
(let [in (read-next *in*)] (loop []
(if (identical? ::EOF in) (let [in (read-next *in*)]
[nil 0] ;; done streaming (if (identical? ::EOF in)
(let [res [(let [res [nil 0] ;; done streaming
(sci/binding [sci/file (or @sci/file "<expr>") (let [res [(let [res
input-var in] (sci/binding [sci/file (or @sci/file "<expr>")
(sci/eval-string* sci-ctx expression))] input-var in]
(when (some? res) (sci/eval-string* sci-ctx expression))]
(if-let [pr-f (cond shell-out println (when (some? res)
edn-out prn)] (if-let [pr-f (cond shell-out println
(if (coll? res) edn-out prn)]
(doseq [l res (if (coll? res)
:while (not (pipe-signal-received?))] (doseq [l res
(pr-f l)) :while (not (pipe-signal-received?))]
(pr-f res)) (pr-f l))
(prn res)))) 0]] (pr-f res))
(if stream? (prn res)))) 0]]
(recur) (if stream?
res))))) (recur)
(catch Throwable e res)))))
(error-handler e {:expression expression (catch Throwable e
:verbose? verbose? (error-handler e {:expression expression
:preloads preloads :verbose? verbose?
:loader (:loader @cp/cp-state)})))) :preloads preloads
uberscript [nil 0] :loader (:loader @cp/cp-state)}))))
:else [(repl/start-repl! sci-ctx) 0])) exec-fn [nil (exec-fn)]
1)] clojure (if-let [proc (deps/clojure (:opts opts))]
(flush) (-> @proc :exit)
(when uberscript 0)
(let [uberscript-out uberscript] uberscript [nil 0]
(spit uberscript-out "") ;; reset file :else [(repl/start-repl! sci-ctx) 0]))
(doseq [s (distinct @uberscript-sources)] 1)]
(spit uberscript-out s :append true)) (flush)
(spit uberscript-out preloads :append true) (when uberscript
(spit uberscript-out expression :append true))) (let [uberscript-out uberscript]
(when uberjar (spit uberscript-out "") ;; reset file
(uberjar/run {:dest uberjar (doseq [s (distinct @uberscript-sources)]
:jar :uber (spit uberscript-out s :append true))
:classpath classpath (spit uberscript-out preloads :append true)
:main-class main (spit uberscript-out expression :append true)))
:verbose verbose?})) (when uberjar
exit-code)))))) (uberjar/run {:dest uberjar
:jar :uber
:classpath classpath
:main-class main
:verbose verbose?}))
exit-code))))
(defn main [& args] (defn main [& args]
(let [opts (parse-opts args)] (let [opts (parse-opts args)]
@ -718,6 +739,9 @@ Use -- to separate script command line args from bb command line args.
[& args] [& args]
(handle-pipe!) (handle-pipe!)
(handle-sigint!) (handle-sigint!)
(when-let [bb-edn @bb-edn]
(when-let [paths (:paths bb-edn)]
(cp/add-classpath (str/join cp/path-sep paths))))
(if-let [dev-opts (System/getenv "BABASHKA_DEV")] (if-let [dev-opts (System/getenv "BABASHKA_DEV")]
(let [{:keys [:n]} (if (= "true" dev-opts) {:n 1} (let [{:keys [:n]} (if (= "true" dev-opts) {:n 1}
(edn/read-string dev-opts)) (edn/read-string dev-opts))

View file

@ -0,0 +1,4 @@
(ns user)
(defn bash [& args]
(prn :args args))

View file

@ -1,9 +1,15 @@
{:paths ["script"] {:paths ["test-resources/bb-edn"]
:tasks {:bash {:bb/args [:invoke cool-script/bash]} :tasks {:count-files {:task/type :shell
:eval-plus {:bb/args [-e (apply + (map (fn [i] :args ["bash" "-c" "ls | wc -l"]}
(Integer/parseInt i)) :bash {:task/type :babashka
*command-line-args*))]} :args [:invoke user/bash]}
:tree {:bb/args [:clojure -Stree]} :eval-plus {:type :babashka
:all {:bb/args [:do :eval-plus 1 2 3 :args [-e (apply + (map (fn [i]
:__ :tree (Integer/parseInt i))
:__ :bash "ls | wc -l"]}}} *command-line-args*))]}
:tree {:task/type :babashka
:args [:clojure -Stree]}
:all {:task/type :babashka
:args [:do :eval-plus 1 2 3
:__ :tree
:__ :bash "ls | wc -l"]}}}