Clojure CLI - which execution option to use
Execution options (-A -M -T -X) define how aliases are used with the Clojure CLI. Aliases are included via one of these execution options and each option can affect how the alias is used.
Since the first documented released which exclusively used the -A
execution option to include aliases, the design has evolved to provide specific execution options to run code via clojure.main (-M
) and clojure.exec (-X
). In July 2021, the ability to run tools (-T
) independent from the Clojure project classpath was also introduced.
The exec-opts command line flags have evolved to enable these features, so lets explore those flags and show how each flag is typically used.
Quick summary
Use -M
for code that should be called via the -main
function in a specified namespace, passing string-based arguments.
Use -X
to run a specified function, passing arguments as key and value pairs (not hash-maps yet)
Use -T
to run a tool independently from any libraries defined in a project. Only the libraries in the alias are included in the ClassPath. The path is defined as "."
by default.
Use -P
to download libraries on the class path, including those from specified aliases
Use -A
in the specific case of running a basic terminal UI REPL with the clojure
command (or clj
wrapper).
Common aliases will be used to explain the use of these execution options in more detail.
Run a Clojure project with clojure.main
-M
execution option uses clojure.main
to run Clojure code.
Run a project with the main namespace practicalli.sudoku-solver
, without any additional aliases on the command line
clojure -M -m practicalli.sudoku-solver
-M
flag instructs Clojure CLI tools to use clojure.main
to run the Clojure code.
-m
flag is an argument to clojure.main
which specifies the namespace to search for a -main
function.
Adding a :project/run
alias to the project deps.edn
file provides a simpler way to run the project on the command line
:project/run {:main-opts ["-m" "practicalli.sudoku-solver"]}
Now the project code can be run using the simple command line form
clojure -M:project/run
How clojure.main works
clojure.main/main
function searches for a -main
function in the given namespace (-m fully-qualified.namespace
)
If no -main function is found or the namespace is not specified, then a REPL session is run. This is the approach Leiningen has always used and still uses today.
clojure.main
namespace has been the way Clojure code was run (including a REPL) for most of its history. This is now evolving with the addition of clojure.exec. clojure.main has other features, as covered in the REPL and main entrypoints article) on clojure.org.
Run a rich terminal REPL with clojure.main
Rebel readline provides a terminal UI REPL, providing auto-completion, function signatures, documentation, etc.
:repl/rebel
is an alias that includes nrepl, cider-nrepl and rebel-readline libraries, with a :main-opts
to run the rebel-readline.main/-main
function via clojure.main
.
:repl/rebel
{:extra-deps {nrepl/nrepl {:mvn/version "0.9.0"}
cider/cider-nrepl {:mvn/version "0.28.2"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
:main-opts ["-m" "nrepl.cmdline"
"--middleware" "[cider.nrepl/cider-middleware]"
"--interactive"
"-f" "rebel-readline.main/-main"]}
Use the :repl/rebel
alias with the -M
execution option
clojure -M:repl/rebel
Multiple aliases can be specified to include additional paths and libraries. Aliases chained together have their configuration merged
:env/dev
adds "dev" as an extra path, with the dev/user.clj
file automatically loading its code into the user
namespace when the REPL starts
:lib/hotload
alias adds the org.clojure/tools.deps.alpha
library to provide hotloading of dependencies into the running REPL
Start a REPL process with this alias
clojure -M:env/dev:lib/hotload:repl/rebel
The Rebel REPL UI will start, include the dev directory on the class path and the org.clojure/tools.deps.alpha
library loaded into the REPL
Chaining aliases together with clojure.main
Alises can be used together by chaining their names on the command line
clojure -M:env/dev:lib/hotload:repl/rebel
The clojure
command will merge the :extra-paths
and :extra-deps
values from each alias in the chain.
The :main-opts
values from the aliases are not merged. Only the :main-opts
value from the last alias in the chain is used with clojure.main
to run the Clojure code.
If the command line includes the -m
flag with a namespace, then that namespace is passed to clojure.main
, ignoring all :main-opts
values from the aliases. The -i
and -e
flags for clojure.main also replace :main-opts
values.
Run a specific function with clojure.exec
-X
flag provides the flexibility to call any fully qualified function, so Clojure code is no longer tied to -main
Any function on the class path can be called and is passed a hash-map as an argument. The argument hash-map is either specified in an alias using :exec-args
or assembled into a hash-map from key/value pairs on the command line. Key/values from the command line are merged into the :exec-args
map if it exists, with the command line key/values taking precedence.
Call the status
function from the namespace practicalli.service
, which is on the classpath in the practicalli.service project
clojure -X practicalli.service/status
Pass arguments to a start
function in the practicalli.service
namespace
clojure -X practicalli.service/start :port 8080 :join? false
As the arguments are key/value pairs, it does not matter in which order the pairs are used in the command line.
Built in clojure.exec functions
Clojure CLI tools has some built in tools under the special :deps
alias (not to be confused with the :deps
configuration in a deps.edn
file)
-X:deps mvn-install
- install a maven jar to the local repository cache-X:deps find-versions
- Find available versions of a library-X:deps prep
- prepare source code libraries in the dependency tree
See
clojure --help
for an overview orman clojure
for detailed descriptions
Run an independent Tool -T
-T
install, run and remove a tool, by the tool name or an alias.
The -T
execution option also uses the clojure.exec
approach, although the :deps
and :path
values from a project deps.edn
file are ignored. This isolates the tool from the dependencies in a Clojure project.
Calling Tools on the command line has the general form:
clojure -Ttool-name function-name :key "value" ,,,
A tool may provide many functions, so the specific function name is provided when calling the tool.
key/value pairs can be passed as arguments to that function (as with the -X execution option)
-Ttools
is a built-in tool to install
and remove
other tools, with the :as
directive providing a specific name for the tool.
In this example, the antq tool is installed using the name antq
clojure -Ttools install com.github.liquidz/antq '{:git/tag "1.3.1"}' :as antq
Installing a tool adds an EDN configuration file using the name of the tool in $XDG_HOME/.clojure/tools/
or $HOME/.clojure/tools/
directory.
Once a tool is installed, run by using the name of the tool.
clojure -Tantq outdated
Options to the tool are passed as key/value pairs (as the tool is called by clojure.exec)
clojure -Tantq outdated :upgrade true
-Ttools remove
will remove the configuration of the tool of the given name
clojure -Ttools remove :tool antq
Tools install or aliases
Tools can also be defined in an alias with :exec-fn
can be run via -T:alias-name
as they are both executed using clojure.exec
.
-X
execution option can emulate-T
behaviour when an alias uses:replace-paths
and:replace-deps
keys, instead of:extra-paths
and:extra-deps
, so project paths and dependencies are not included loaded by the alias.
Using an alias for a tool has the advantage allowing a use to define their preferred default arguments that are passed to the :exec-fn
, using the :exec-args
key.
Default arguments could be included in the deps.edn
of the installed tool itself, although this is controlled by the developer of that tool project.
The :search/outdated
alias defined in the practicalli/clojure-deps-edn
user level configuration is an example of a tool alias with default arguments
:search/outdated
{:replace-paths ["."]
:replace-deps {com.github.liquidz/antq {:mvn/version "1.3.1"}
org.slf4j/slf4j-nop {:mvn/version "1.7.32"}}
:main-opts ["-m" "antq.core"]
:exec-fn antq.tool/outdated
:exec-args {:directory ["."] ; default
:exclude ["com.cognitect/rebl"
"org.openjfx/javafx-base"
"org.openjfx/javafx-controls"
"org.openjfx/javafx-fxml"
"org.openjfx/javafx-swing"
"org.openjfx/javafx-web"]
;; :focus ["com.github.liquidz/antq"]
:skip ["boot" "leiningen"]
:reporter "table" ; json edn format
:verbose false
:upgrade false
:force false}}
This alias is called using clojure -T:search/outdated
and is the same as calling clojure -Tantq outdated ,,, ,,,
with a long list of key value options that represent the arguments in the alias.
As the output is a table of results, the command output is typically pushed to a file: clojure -T:search/outdated > outdated-2021-12-24.txt
Example tools include
- liquidz/antq - search dependencies for newer library versions
- seancorfield/deps-new - create new projects using templates
- clojure-nvd - check dependencies against National Vunerability Database
Prepare by downloading dependencies -P
-P
flag instructs the clojure
command to download all library dependencies to the local cache and then stop without executing a function call.
The -P
flag is often used with Continuous Integration workflows and to create pre-populated Container images, to avoid repeatedly downloading the same library jar files.
If used with just a project, then the Maven dependencies defined in the project deps.edn
file will be downloaded, if not already in the users local cache (~/.m2/repository/
).
If :git
or :local/root
dependencies are defined, the respective code will be downloaded and added to the classpath.
Prepare flag by itself download dependencies defined in the :deps
section of the deps.edn
file of the current project.
clojure -P
Including one or more aliases will preparing all the dependencies from every alias specified
clojure -P -M:env/dev:lib/hotload:repl/cider
-P
flag must be used before any subsequent arguments, i.e. before-M
,-X
,-T
As prepare is essentially a dry run, then the clojure
command does not call :main-opts
or :exec-fn
functions, even if they exist in an alias or on the command line.
-P
will warn if a project has dependencies that require building from source (i.e Java code) or resource file manipulation. If so then clojure -X:deps prep
will prepare these source based dependencies.
Alias with built-in terminal UI REPL -A
-A
is stated as the official way to include an alias when running a REPL terminal UI clojure
or clj
.
Practicalli recommends using Rebel Readline which uses -M execution option, so -A execution option is rarely used by Practicalli.
The :env/dev
alias adds "dev" directory to the class path, typically used to add a user.clj
that will automatically load code from the user
namespace defined in that file.
clojure -A:env/dev
The alias definition is :env/dev {:extra-paths ["dev"]}
Aliases can be chained together and their configuration will be merged
:lib/hotload
adds a dependency to provide hotloading of other dependencies
:lib/hotload
{:extra-deps {org.clojure/tools.deps.alpha
{:git/url "https://github.com/clojure/tools.deps.alpha"
:sha "d77476f3d5f624249462e275ae62d26da89f320b"}
org.slf4j/slf4j-nop {:mvn/version "1.7.32"}}}
Start a REPL process with this alias
clojure -A:env/dev:lib/hotload
Using an alias that contains a
:main-opts
key with-A
will fail to run a REPL and print a warning to use-M
execution option The:main-opts
configuration for-A
execution option is deprecated (although currently works in 1.10.x). To run Clojure code viaclojure.main
the-M
option should be with aliases that includes:main-opts
.
Summary
There are many options when it comes to running Clojure CLI tools that are not covered here, however, this guide gives you the most common options used so far.
Practicalli recommends using the -X
execution option where possible, as arguments follow the data approach of Clojure design.
The -J
and :jvm-opts
are useful to configure the Java Virtual machine and deserve an article to themselves as there are many possible options.
The -T
tools is an exciting and evolving approach and it will be interesting to see how the Clojure community adopt this model.
See the Deps and CLI Reference Rationale for more details and description of these options.
References
Thank you