--uberscript option (#197)

This commit is contained in:
Michiel Borkent 2019-12-28 22:52:56 +01:00 committed by GitHub
parent c446dced0e
commit ded6dd2355
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 130 deletions

View file

@ -137,25 +137,26 @@ You may also download a binary from [Github](https://github.com/borkdude/babashk
Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose] Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ] [ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> ) ( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ] [ --uberscript <file> ] [ arg* ]
Options: Options:
--help, -h or -? Print this help text. --help, -h or -? Print this help text.
--version Print the current version of babashka. --version Print the current version of babashka.
-i Bind *input* to a lazy seq of lines from stdin. -i Bind *input* to a lazy seq of lines from stdin.
-I Bind *input* to a lazy seq of EDN values from stdin. -I Bind *input* to a lazy seq of EDN values from stdin.
-o Write lines to stdout. -o Write lines to stdout.
-O Write EDN values to stdout. -O Write EDN values to stdout.
--verbose Print entire stacktrace in case of exception. --verbose Print entire stacktrace in case of exception.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration. --stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
-e, --eval <expr> Evaluate an expression. -e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file. -f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use. -cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args. -m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL --uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666). --repl Start REPL
--time Print execution time before exiting. --socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time Print execution time before exiting.
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise. If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*. Everything after that is bound to *command-line-args*.
@ -408,10 +409,10 @@ Note that you can use the `clojure` tool to produce classpaths and download depe
``` shellsession ``` shellsession
$ cat deps.edn $ cat deps.edn
{:deps {:deps
{my_gist_script {my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42" {:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}} :sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
$ CLASSPATH=$(clojure -Spath) $ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script $ bb --classpath "$CLASSPATH" --main my-gist-script
@ -428,6 +429,36 @@ $ bb "(my-gist-script/-main)"
Hello from gist script! Hello from gist script!
``` ```
Using the [deps.clj](https://github.com/borkdude/deps.clj/) script, you can also
pass the classpath and main opts to `bb`:
``` shell
$ deps.clj -A:my-script -Scommand "bb my_script.clj {{main-opts}}"
Hello from gist script!
```
## Uberscript
The `--uberscript <file>` option collects the expressions in
`BABASHKA_PRELOADS`, the command line expression or file, the main entrypoint
and all required namespaces from the classpath into a single file. This can be
convenient for debugging purposes and deployment.
Given the `deps.edn` from above:
``` shell
$ deps.clj -A:my-script -Scommand "bb {{main-opts}} --uberscript my-script.clj"
$ cat my-script.clj
(ns my-gist-script)
(defn -main [& args]
(println "Hello from gist script!"))(require '[my-gist-script])
(ns user (:require [my-gist-script])) (apply my-gist-script/-main *command-line-args*)%
$ bb my-script.clj
Hello from gist script!
```
## Parsing command line arguments ## Parsing command line arguments
Babashka ships with `clojure.tools.cli`: Babashka ships with `clojure.tools.cli`:

View file

@ -1 +1 @@
0.0.51-SNAPSHOT 0.0.51

2
sci

@ -1 +1 @@
Subproject commit 79563a0c94daa11e56e0969c963a52af9f7ee00f Subproject commit 92f2251fcc2ca795db05e78605e1c78bb8462884

View file

@ -1,12 +0,0 @@
(ns babashka.impl.utils
{:no-doc true}
(:require
[sci.impl.vars :as vars]
[sci.core :as sci]))
(sci.impl.vars/bindRoot sci/in *in*)
(sci.impl.vars/bindRoot sci/out *out*)
(sci.impl.vars/bindRoot sci/err *err*)
(defn eval-string [expr ctx]
(sci/eval-string expr ctx))

View file

@ -13,7 +13,6 @@
[babashka.impl.repl :as repl] [babashka.impl.repl :as repl]
[babashka.impl.socket-repl :as socket-repl] [babashka.impl.socket-repl :as socket-repl]
[babashka.impl.tools.cli :refer [tools-cli-namespace]] [babashka.impl.tools.cli :refer [tools-cli-namespace]]
[babashka.impl.utils :refer [eval-string]]
[babashka.wait :as wait] [babashka.wait :as wait]
[clojure.edn :as edn] [clojure.edn :as edn]
[clojure.java.io :as io] [clojure.java.io :as io]
@ -26,6 +25,10 @@
[sci.impl.interpreter :refer [eval-string*]]) [sci.impl.interpreter :refer [eval-string*]])
(:gen-class)) (:gen-class))
(sci.impl.vars/bindRoot sci/in *in*)
(sci.impl.vars/bindRoot sci/out *out*)
(sci.impl.vars/bindRoot sci/err *err*)
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
;; To detect problems when generating the image, run: ;; To detect problems when generating the image, run:
;; echo '1' | java -agentlib:native-image-agent=config-output-dir=/tmp -jar target/babashka-xxx-standalone.jar '...' ;; echo '1' | java -agentlib:native-image-agent=config-output-dir=/tmp -jar target/babashka-xxx-standalone.jar '...'
@ -68,6 +71,15 @@
(assoc opts-map (assoc opts-map
:edn-in true :edn-in true
:edn-out true)) :edn-out true))
("--classpath", "-cp")
(let [options (next options)]
(recur (next options)
(assoc opts-map :classpath (first options))))
("--uberscript")
(let [options (next options)]
(recur (next options)
(assoc opts-map
:uberscript (first options))))
("-f" "--file") ("-f" "--file")
(let [options (next options)] (let [options (next options)]
(recur (next options) (recur (next options)
@ -87,10 +99,6 @@
(let [options (next options)] (let [options (next options)]
(recur (next options) (recur (next options)
(assoc opts-map :expression (first options)))) (assoc opts-map :expression (first options))))
("--classpath", "-cp")
(let [options (next options)]
(recur (next options)
(assoc opts-map :classpath (first options))))
("--main", "-m") ("--main", "-m")
(let [options (next options)] (let [options (next options)]
(recur (next options) (recur (next options)
@ -128,7 +136,7 @@
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose] (def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ] [ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> ) ( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ]") [ --uberscript <file> ] [ arg* ]")
(defn print-usage [] (defn print-usage []
(println usage-string)) (println usage-string))
@ -140,21 +148,22 @@
(println) (println)
(println "Options:") (println "Options:")
(println " (println "
--help, -h or -? Print this help text. --help, -h or -? Print this help text.
--version Print the current version of babashka. --version Print the current version of babashka.
-i Bind *input* to a lazy seq of lines from stdin. -i Bind *input* to a lazy seq of lines from stdin.
-I Bind *input* to a lazy seq of EDN values from stdin. -I Bind *input* to a lazy seq of EDN values from stdin.
-o Write lines to stdout. -o Write lines to stdout.
-O Write EDN values to stdout. -O Write EDN values to stdout.
--verbose Print entire stacktrace in case of exception. --verbose Print entire stacktrace in case of exception.
--stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration. --stream Stream over lines or EDN values from stdin. Combined with -i or -I *input* becomes a single value per iteration.
-e, --eval <expr> Evaluate an expression. -e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file. -f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use. -cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args. -m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL --uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
--socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666). --repl Start REPL
--time Print execution time before exiting. --socket-repl Start socket REPL. Specify port (e.g. 1666) or host and port separated by colon (e.g. 127.0.0.1:1666).
--time Print execution time before exiting.
If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise. If neither -e, -f, or --socket-repl are specified, then the first argument that is not parsed as a option is treated as a file if it exists, or as an expression otherwise.
Everything after that is bound to *command-line-args*.")) Everything after that is bound to *command-line-args*."))
@ -173,14 +182,14 @@ Everything after that is bound to *command-line-args*."))
(def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false)) (def reflection-var (sci/new-dynamic-var '*warn-on-reflection* false))
(defn load-file* [ctx f] (defn load-file* [sci-ctx f]
(let [f (io/file f) (let [f (io/file f)
s (slurp f)] s (slurp f)]
(sci/with-bindings {vars/file-var (.getCanonicalPath f)} (sci/with-bindings {vars/file-var (.getCanonicalPath f)}
(eval-string s ctx)))) (eval-string* sci-ctx s))))
(defn eval* [ctx form] (defn eval* [sci-ctx form]
(eval-string (pr-str form) ctx)) (eval-string* sci-ctx (pr-str form)))
(defn start-socket-repl! [address ctx] (defn start-socket-repl! [address ctx]
(socket-repl/start-repl! address ctx) (socket-repl/start-repl! address ctx)
@ -194,6 +203,45 @@ Everything after that is bound to *command-line-args*."))
;; (sci/set-var-root! sci/*out* *out*) ;; (sci/set-var-root! sci/*out* *out*)
;; (sci/set-var-root! sci/*err* *err*) ;; (sci/set-var-root! sci/*err* *err*)
(def aliases
'{tools.cli 'clojure.tools.cli
edn clojure.edn
wait babashka.wait
sig babashka.signal
shell clojure.java.shell
io clojure.java.io
async clojure.core.async
csv clojure.data.csv
json cheshire.core})
(def namespaces
{'clojure.tools.cli tools-cli-namespace
'clojure.edn {'read edn/read
'read-string edn/read-string}
'clojure.java.shell {'sh shell/sh}
'babashka.wait {'wait-for-port wait/wait-for-port
'wait-for-path wait/wait-for-path}
'babashka.signal {'pipe-signal-received? pipe-signal-received?}
'clojure.java.io io-namespace
'clojure.core.async async-namespace
'clojure.data.csv csv/csv-namespace
'cheshire.core cheshire-core-namespace})
(def bindings
{'java.lang.System/exit exit ;; override exit, so we have more control
'System/exit exit})
(defn error-handler* [^Exception e verbose?]
(binding [*out* *err*]
(let [d (ex-data e)
exit-code (:bb/exit-code d)]
(if exit-code [nil exit-code]
(do (if verbose?
(print-stack-trace e)
(println (.getMessage e)))
(flush)
[nil 1])))))
(defn main (defn main
[& args] [& args]
(handle-pipe!) (handle-pipe!)
@ -205,7 +253,7 @@ Everything after that is bound to *command-line-args*."))
:expression :stream? :time? :expression :stream? :time?
:repl :socket-repl :repl :socket-repl
:verbose? :classpath :verbose? :classpath
:main] :as _opts} :main :uberscript] :as _opts}
(parse-opts args) (parse-opts args)
read-next (fn [*in*] read-next (fn [*in*]
(if (pipe-signal-received?) (if (pipe-signal-received?)
@ -219,6 +267,7 @@ Everything after that is bound to *command-line-args*."))
(edn-seq *in*) (edn-seq *in*)
:else :else
(edn/read *in*)))))) (edn/read *in*))))))
uberscript-sources (atom ())
env (atom {}) env (atom {})
classpath (or classpath classpath (or classpath
(System/getenv "BABASHKA_CLASSPATH")) (System/getenv "BABASHKA_CLASSPATH"))
@ -226,35 +275,18 @@ Everything after that is bound to *command-line-args*."))
(cp/loader classpath)) (cp/loader classpath))
load-fn (when classpath load-fn (when classpath
(fn [{:keys [:namespace]}] (fn [{:keys [:namespace]}]
(cp/source-for-namespace loader namespace))) (let [res (cp/source-for-namespace loader namespace)]
(when uberscript (swap! uberscript-sources conj (:source res)))
res)))
_ (when file (vars/bindRoot vars/file-var (.getCanonicalPath (io/file file)))) _ (when file (vars/bindRoot vars/file-var (.getCanonicalPath (io/file file))))
ctx {:aliases '{tools.cli 'clojure.tools.cli ctx {:aliases aliases
edn clojure.edn :namespaces (assoc namespaces 'clojure.core
wait babashka.wait (assoc core-extras
sig babashka.signal '*command-line-args*
shell clojure.java.shell (sci/new-dynamic-var '*command-line-args* command-line-args)
io clojure.java.io '*file* vars/file-var
async clojure.core.async '*warn-on-reflection* reflection-var))
csv clojure.data.csv :bindings bindings
json cheshire.core}
:namespaces {'clojure.core (assoc core-extras
'*command-line-args*
(sci/new-dynamic-var '*command-line-args* command-line-args)
'*file* vars/file-var
'*warn-on-reflection* reflection-var)
'clojure.tools.cli tools-cli-namespace
'clojure.edn {'read edn/read
'read-string edn/read-string}
'clojure.java.shell {'sh shell/sh}
'babashka.wait {'wait-for-port wait/wait-for-port
'wait-for-path wait/wait-for-path}
'babashka.signal {'pipe-signal-received? pipe-signal-received?}
'clojure.java.io io-namespace
'clojure.core.async async-namespace
'clojure.data.csv csv/csv-namespace
'cheshire.core cheshire-core-namespace}
:bindings {'java.lang.System/exit exit ;; override exit, so we have more control
'System/exit exit}
:env env :env env
:features #{:bb} :features #{:bb}
:classes classes/class-map :classes classes/class-map
@ -271,37 +303,47 @@ Everything after that is bound to *command-line-args*."))
String java.lang.String String java.lang.String
System java.lang.System System java.lang.System
Thread java.lang.Thread} Thread java.lang.Thread}
:load-fn load-fn} :load-fn load-fn
ctx (update-in ctx [:namespaces 'clojure.core] assoc :dry-run uberscript}
'eval #(eval* ctx %)
'load-file #(load-file* ctx %))
ctx (assoc-in ctx [:namespaces 'clojure.main 'repl]
(fn [& opts]
(let [opts (apply hash-map opts)]
(repl/start-repl! ctx opts))))
ctx (addons/future ctx) ctx (addons/future ctx)
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx))
expression (if main
(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
main)
expression)
sci-ctx (sci-opts/init ctx) sci-ctx (sci-opts/init ctx)
_ (swap! (:env sci-ctx)
(fn [env]
(update-in env [:namespaces 'clojure.core] assoc
'eval #(eval* sci-ctx %)
'load-file #(load-file* sci-ctx %))))
_ (swap! (:env sci-ctx)
(fn [env]
(assoc-in env [:namespaces 'clojure.main 'repl]
(fn [& opts]
(let [opts (apply hash-map opts)]
(repl/start-repl! sci-ctx opts))))))
preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim))
[expression exit-code]
(cond expression [expression nil]
main [(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
main) nil]
file (try [(read-file file) nil]
(catch Exception e
(error-handler* e verbose?))))
expression (str (when preloads
(str preloads "\n"))
expression)
exit-code exit-code
(sci/with-bindings {reflection-var false} (or exit-code
(or (sci/with-bindings {reflection-var false}
#_(binding [*out* *err*] (or
(prn ">>" _opts)) #_(binding [*out* *err*]
(second (prn ">>" _opts))
(cond version (second
[(print-version) 0] (cond version
help? [(print-version) 0]
[(print-help) 0] help?
repl [(repl/start-repl! sci-ctx) 0] [(print-help) 0]
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0] repl [(repl/start-repl! sci-ctx) 0]
:else socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
(try expression
(let [ expr (if file (read-file file) expression)] (try
(if expr
(loop [in (read-next *in*)] (loop [in (read-next *in*)]
(let [_ (swap! env update-in [:namespaces 'user] (let [_ (swap! env update-in [:namespaces 'user]
assoc (with-meta '*input* assoc (with-meta '*input*
@ -310,7 +352,7 @@ Everything after that is bound to *command-line-args*."))
(sci/new-dynamic-var '*input* in))] (sci/new-dynamic-var '*input* in))]
(if (identical? ::EOF in) (if (identical? ::EOF in)
[nil 0] ;; done streaming [nil 0] ;; done streaming
(let [res [(let [res (eval-string* sci-ctx expr)] (let [res [(let [res (eval-string* sci-ctx expression)]
(when (some? res) (when (some? res)
(if-let [pr-f (cond shell-out println (if-let [pr-f (cond shell-out println
edn-out prn)] edn-out prn)]
@ -323,22 +365,22 @@ Everything after that is bound to *command-line-args*."))
(if stream? (if stream?
(recur (read-next *in*)) (recur (read-next *in*))
res))))) res)))))
[(repl/start-repl! sci-ctx) 0])) (catch Throwable e
(catch Throwable e (error-handler* e verbose?)))
(binding [*out* *err*] :else [(repl/start-repl! sci-ctx) 0]))
(let [d (ex-data e) 1)))
exit-code (:bb/exit-code d)]
(if exit-code [nil exit-code]
(do (if verbose?
(print-stack-trace e)
(println (.getMessage e)))
(flush)
[nil 1]))))))))
1))
t1 (System/currentTimeMillis)] t1 (System/currentTimeMillis)]
(flush)
(when uberscript
uberscript
(let [uberscript-out uberscript]
(spit uberscript-out "") ;; reset file
(doseq [s @uberscript-sources]
(spit uberscript-out s :append true))
(spit uberscript-out expression :append true)
(spit uberscript-out file :append true)))
(when time? (binding [*out* *err*] (when time? (binding [*out* *err*]
(println "bb took" (str (- t1 t0) "ms.")))) (println "bb took" (str (- t1 t0) "ms."))))
(flush)
exit-code)) exit-code))
(defn -main (defn -main

View file

@ -26,6 +26,13 @@
(is (= "(\"1\" \"2\" \"3\" \"4\")\n" (is (= "(\"1\" \"2\" \"3\" \"4\")\n"
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4")))) (tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "1" "2" "3" "4"))))
(deftest uberscript-test
(let [tmp-file (java.io.File/createTempFile "uberscript" ".clj")]
(.deleteOnExit tmp-file)
(tu/bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" "-m" "my.main" "--uberscript" (.getPath tmp-file))
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
(tu/bb nil "--file" (.getPath tmp-file) "1" "2" "3" "4")))))
(deftest error-while-loading-test (deftest error-while-loading-test
(is (true? (is (true?
(bb nil "--classpath" "test-resources/babashka/src_for_classpath_test" (bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"