A small script for turning babashka scripts into native binaries
- JVM (because it depends on clj.native-image)
- babashka
- GraalVM
From the nativity directory: bb nativity.clj path/to/script
or even just ./nativity.clj path/to/script
you can specify which mode to run on by using the ./nativity.clj path/to/script -m implicit
it will default to "untouched"
Untouched will leave the file completely alone, make a copy into the src directory and create the deps.edn file for example:
#!/usr/bin/env bb
(ns which
(:require [clojure.java.io :as io])
(:gen-class))
(defn where [executable]
(let [path (System/getenv "PATH")
paths (.split path (System/getProperty "path.separator"))]
(loop [paths paths]
(when-first [p paths]
(let [f (io/file p executable)]
(if (and (.isFile f)
(.canExecute f))
(.getCanonicalPath f)
(recur (rest paths))))))))
(defn -main [& args]
(when-first [executable args]
(println (where executable))))
(when-not (System/getProperty "babashka.main")
(when (find-ns 'babashka.classpath) (apply -main *command-line-args*)))
This can compile as is because it has:
- Ns form with :gen-class and explicit requires (
(ns which (:require [clojure.java.io :as io]) (:gen-class))
) - an entry point (
defn -main [& args]
) - no in-line require forms (
(require ....)
)
This mode cuts out parts of your script and puts it into a main function for you: for example assume you have this script:
#!/usr/bin/env bb
(ns which
(:require [clojure.java.io :as io])
(:gen-class))
(defn where [executable]
(let [path (System/getenv "PATH")
paths (.split path (System/getProperty "path.separator"))]
(loop [paths paths]
(when-first [p paths]
(let [f (io/file p executable)]
(if (and (.isFile f)
(.canExecute f))
(.getCanonicalPath f)
(recur (rest paths))))))))
(when-first [executable *command-line-args*]
(println (where executable)))
and you run ./nativity.clj which.clj -m directed -w 17-18
it will wrap lines 17 to 18 in the main entry point (in this case those lines would be the last 2 at the end), making a compilable script.
This mode is for when you made a script using the implicit requires that babashka has for one-liners. Be warned that this mode is very inefficient in so many ways. for example:
#!/usr/bin/env bb
(defn where [executable]
(let [path (System/getenv "PATH")
paths (.split path (System/getProperty "path.separator"))]
(loop [paths paths]
(when-first [p paths]
(let [f (io/file p executable)]
(if (and (.isFile f)
(.canExecute f))
(.getCanonicalPath f)
(recur (rest paths))))))))
(when-let [executable (first *command-line-args*)]
(println (where executable)))
This one will require implicit mode and so it should be run ./nativity which.clj -m implicit -d io
(look below to understand the -d
flag)
Short Long Default Description
-d, --deps LIST [] Specify the dependency list for implicit mode (comma separated values, example: "io,str,json,edn")
-n, --name FILENAME Override default file name for the binary (will default to the input file name )
-c, --clean Clean up the src and deps.edn files
-m, --mode MODE untouched Choose the processing mode. Currently available: implicit, untouched
--no-compile Don't run binary compilation step
--namespace NAMESPACE-NAME If your main function is in a namespace different from your file name you can override with this. It will also use this to name the namespace in implicit mode
-w, --wrap RANGE [0 0] Determine the lines you want to want to wrap with main (Only directed mode)
You can specify which of the built in bb dependencies to require by using the ./nativity.clj path/to/script -d io,str,json
.
If you use -d all
it will include all of them.
if you want your binary to have a different name:
./nativity.clj path/to/script -n name-of-binary
With this flag you can make it automatically remove the generated src and deps.edn files:
./nativity.clj path/to/script -c