--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]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ]
[ --uberscript <file> ] [ arg* ]
Options:
--help, -h or -? Print this help text.
--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 EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--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.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL
--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.
--help, -h or -? Print this help text.
--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 EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--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.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
--repl Start REPL
--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.
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
$ cat deps.edn
{:deps
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}}
{my_gist_script
{:git/url "https://gist.github.com/borkdude/263b150607f3ce03630e114611a4ef42"
:sha "cfc761d06dfb30bb77166b45d439fe8fe54a31b8"}}
:aliases {:my-script {:main-opts ["-m" "my-gist-script"]}}}
$ CLASSPATH=$(clojure -Spath)
$ bb --classpath "$CLASSPATH" --main my-gist-script
@ -428,6 +429,36 @@ $ bb "(my-gist-script/-main)"
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
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.socket-repl :as socket-repl]
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
[babashka.impl.utils :refer [eval-string]]
[babashka.wait :as wait]
[clojure.edn :as edn]
[clojure.java.io :as io]
@ -26,6 +25,10 @@
[sci.impl.interpreter :refer [eval-string*]])
(: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)
;; 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 '...'
@ -68,6 +71,15 @@
(assoc opts-map
:edn-in 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")
(let [options (next options)]
(recur (next options)
@ -87,10 +99,6 @@
(let [options (next options)]
(recur (next options)
(assoc opts-map :expression (first options))))
("--classpath", "-cp")
(let [options (next options)]
(recur (next options)
(assoc opts-map :classpath (first options))))
("--main", "-m")
(let [options (next options)]
(recur (next options)
@ -128,7 +136,7 @@
(def usage-string "Usage: bb [ -i | -I ] [ -o | -O ] [ --stream ] [--verbose]
[ ( --classpath | -cp ) <cp> ] [ ( --main | -m ) <main-namespace> ]
( -e <expression> | -f <file> | --repl | --socket-repl [<host>:]<port> )
[ arg* ]")
[ --uberscript <file> ] [ arg* ]")
(defn print-usage []
(println usage-string))
@ -140,21 +148,22 @@
(println)
(println "Options:")
(println "
--help, -h or -? Print this help text.
--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 EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--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.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--repl Start REPL
--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.
--help, -h or -? Print this help text.
--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 EDN values from stdin.
-o Write lines to stdout.
-O Write EDN values to stdout.
--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.
-e, --eval <expr> Evaluate an expression.
-f, --file <path> Evaluate a file.
-cp, --classpath Classpath to use.
-m, --main <ns> Call the -main function from namespace with args.
--uberscript <file> Collect preloads, -e, -f and -m and all required namespaces from the classpath into a single executable file.
--repl Start REPL
--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.
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))
(defn load-file* [ctx f]
(defn load-file* [sci-ctx f]
(let [f (io/file f)
s (slurp f)]
(sci/with-bindings {vars/file-var (.getCanonicalPath f)}
(eval-string s ctx))))
(eval-string* sci-ctx s))))
(defn eval* [ctx form]
(eval-string (pr-str form) ctx))
(defn eval* [sci-ctx form]
(eval-string* sci-ctx (pr-str form)))
(defn start-socket-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/*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
[& args]
(handle-pipe!)
@ -205,7 +253,7 @@ Everything after that is bound to *command-line-args*."))
:expression :stream? :time?
:repl :socket-repl
:verbose? :classpath
:main] :as _opts}
:main :uberscript] :as _opts}
(parse-opts args)
read-next (fn [*in*]
(if (pipe-signal-received?)
@ -219,6 +267,7 @@ Everything after that is bound to *command-line-args*."))
(edn-seq *in*)
:else
(edn/read *in*))))))
uberscript-sources (atom ())
env (atom {})
classpath (or classpath
(System/getenv "BABASHKA_CLASSPATH"))
@ -226,35 +275,18 @@ Everything after that is bound to *command-line-args*."))
(cp/loader classpath))
load-fn (when classpath
(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))))
ctx {: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}
: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}
ctx {:aliases aliases
:namespaces (assoc 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))
:bindings bindings
:env env
:features #{:bb}
:classes classes/class-map
@ -271,37 +303,47 @@ Everything after that is bound to *command-line-args*."))
String java.lang.String
System java.lang.System
Thread java.lang.Thread}
:load-fn load-fn}
ctx (update-in ctx [:namespaces 'clojure.core] assoc
'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))))
:load-fn load-fn
:dry-run uberscript}
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)
_ (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
(sci/with-bindings {reflection-var false}
(or
#_(binding [*out* *err*]
(prn ">>" _opts))
(second
(cond version
[(print-version) 0]
help?
[(print-help) 0]
repl [(repl/start-repl! sci-ctx) 0]
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
:else
(try
(let [ expr (if file (read-file file) expression)]
(if expr
(or exit-code
(sci/with-bindings {reflection-var false}
(or
#_(binding [*out* *err*]
(prn ">>" _opts))
(second
(cond version
[(print-version) 0]
help?
[(print-help) 0]
repl [(repl/start-repl! sci-ctx) 0]
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
expression
(try
(loop [in (read-next *in*)]
(let [_ (swap! env update-in [:namespaces 'user]
assoc (with-meta '*input*
@ -310,7 +352,7 @@ Everything after that is bound to *command-line-args*."))
(sci/new-dynamic-var '*input* in))]
(if (identical? ::EOF in)
[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)
(if-let [pr-f (cond shell-out println
edn-out prn)]
@ -323,22 +365,22 @@ Everything after that is bound to *command-line-args*."))
(if stream?
(recur (read-next *in*))
res)))))
[(repl/start-repl! sci-ctx) 0]))
(catch Throwable e
(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]))))))))
1))
(catch Throwable e
(error-handler* e verbose?)))
:else [(repl/start-repl! sci-ctx) 0]))
1)))
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*]
(println "bb took" (str (- t1 t0) "ms."))))
(flush)
exit-code))
(defn -main

View file

@ -26,6 +26,13 @@
(is (= "(\"1\" \"2\" \"3\" \"4\")\n"
(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
(is (true?
(bb nil "--classpath" "test-resources/babashka/src_for_classpath_test"