-
-
Notifications
You must be signed in to change notification settings - Fork 180
Boot for Leiningen Users
All of the different abstractions used to control how Leiningen works (plugins, profiles, middleware, injections, etc.) can be implemented as boot tasks. This document shows some of the common patterns Leiningen users will be accustomed to and their boot equivalents.
The equivalent of Leiningen profiles and middleware in boot are tasks that modify the environment and return clojure.core/identity
, the no-op task middleware.
For example, a task to add the test
directory to the :source-paths
when running tests, similar to the test
profile in Leiningen:
(deftask testing
"Profile setup for running tests."
[]
(set-env! :source-paths #(conj % "test"))
identity)
Profiles are "activated" simply by adding them to the pipeline, for example:
$ boot testing run-tests
This is equivalent to Lein's
$ lein with-profile testing run-tests
In lein
in order to pass a profile specific environment variable, it can be specified as a -D
param in :jvm-opts
:
:profiles {:dev {:jvm-opts ["-Dparam=value"]}}
While boot
has several ways to set JVM Options, if all that is needed is -D
, it can be easily done with a good old System/setProperty
, since boot
build configuration is just Clojure:
(deftask dev []
(System/setProperty "param" "value")
;; ...)
It does not have to be a deftask
, it can just be a defn
that will be available in REPL. Or, if needed, (System/setProperty "param" "value")
can be set globally (i.e. not task specific) per build.boot
.
In boot dependencies are added to the class path via the set-env!
or merge-env!
functions in the boot.core
namespace. These functions are normally called in the build.boot
script.
There is no task specifically for fetching dependencies. Running boot
with no options will accomplish this as the build.boot
file is evaluated. If you want to have a deps
task that will fetch dependencies you can define one:
(deftask deps [])
The repl
task is different from most other tasks because the nREPL server can't be run in a pod (because you want to have a REPL in the project context, not a fresh pod context). So the repl
task calls set-env!
itself to add its nREPL dependencies at runtime when it is actually used, and the project has no nREPL dependencies when the repl
task isn't used.
This means that the method described above will not fetch dependencies needed to start the REPL. This could be an issue when building Docker images, for example. The solution is to do something like this:
boot repl -s
or
(deftask deps [] (repl :server true))
In boot the show
task can print the dependency graph:
boot show -d
You can also see dependency conflicts:
boot show -p
The equivalent of lein pprint
in boot is:
boot show -e
The most trivial equivalent of lein run is a very simple task.
(require 'my.namespace)
(deftask run []
(with-pass-thru _
(my.namespace/-main)))
Obviously, you're not limited here to calling the -main
function,
and you can pick an arbitrary namespace. If you want to pass arguments
to your main function, boot actually provides a nice benefit over lein
by providing a very nice DSL for declaring task options.
Thus you can now move your argument parsing code out of your library
code and into the build.boot
file. Of course that doesn't help you
if you want to distribute a standalone jar that takes command-line
arguments.
At first glance, it seems like lein trampoline
and boot's pods
mechanism serve a similar purpose. But really they serve very
distinct purposes. lein trampoline
is an optimization lein uses to
cache your project's classpath so that the lein program can do less
work before starting the project JVM.
Boot's pods mechanism on the other hand are intended to provide the classpath isolation that lein achieves by launching the project code in a separate JVM from the one lein itself is running in.
The difference is that boot pods are first-class and in-process. Since they're in the same process, and because they're first class you can have anonymous pods, you can put a pod in a future, etc. You can make functions that take pods as arguments and functions that return pods.
boot itself is a small java program distributed as an uberjar:
boot.sh
. This uberjar can be executed directly because we exploit a nice property of jar files - you can put random garbage at the front of a jar file and the jvm will still load it without problems. So we inject a shebang and a call to java -jar there.
The java program
boot.sh
provides a single class - boot.App. boot.App has static methods that provide amain()
entry point for the jar, and methods to create new pods. Boot runs all clojure code in pods so your build.boot script is actually running in a pod but your pod can access boot.App and use it to create new pods that it can evaluate expressions in. All pods communicate with each other via boot.App.
AAAA: So far I've ported `lein uberjar` to a boot.build file. But I still don't know how to port `lein run` and `lein run -m my.non-main.namespace` and `lein speclj`. That's what's keeping me from switching from Leiningen to Boot.
AAAA: I understand that `lein spec` needs a third party plugin to be created.
AAAA: But it would be nice if the Boot website or FAQ or wiki had some instructions on porting `lein uberjar`, `lein run`, and `lein run -m some.custom.namespace` to Boot from Leiningen.
...
AAAA: As it is, these are blockers so that I can't switch to Boot yet, even though I see that it would be more helpful for me than Leiningen.
...
CCCC: AAAA: does 'boot uber jar' produce an uberjar for you similar to lein uberjar?
AAAA: CCCC: I've gotten the `lein uberjar` part figured out (the exact details vary depending on project requirements); it's the rest I still don't have yet
CCCC: i haven't used lein in a while, can you describe what lein run is supposed to do?
BBBB: AAAA: boot does not have any dependencies of its own
BBBB: AAAA: re: your question about boot's deps vs your project deps
BBBB: AAAA: that's the point of having pods, it serves a similar purpose to lein's second jvm
BBBB: but more granular and first-class
AAAA: BBBB: At the very least it depends on clojure.core
BBBB: AAAA: not really, it doesn't bring in a clojure dependency in your project
AAAA: BBBB: but only if you're using Pods, right? otherwise it still does, right?
BBBB: clojure.core is *provided* at runtime by boot, but that doesn't have any restriction on your project
BBBB: AAAA: just do `boot pom -p foo -v 1.0` and inspect the pom.xml it generates
BBBB: you'll see no dependencies at all
BBBB: so if your project needs a clojure dependency you need to add it
BBBB: AAAA: your question about uberjar and lein run is here: https://github.com/adzerk-oss/boot-uberjar-example
BBBB: it addresses both of them
BBBB: boot has an "uber" task that can be composed with any packaging task, like jar, war, zip, whatever
BBBB: we've decoupled those
BBBB: and lein run is just making your own task that runs something
BBBB: AAAA: for more info about what gets packaged in your uberjar i recommend looking at `boot uber -h`
AAAA: Great thanks.
BBBB: AAAA: it would be very awesome if you would update the wiki with any information you think is helpful
BBBB: AAAA: https://github.com/boot-clj/boot/wiki/Boot-for-Leiningen-Users
BBBB: also, re: trampoline vs. pods
BBBB: the lein "trampoline" is not the same thing as pods at all
noprompt joined the chat room.
BBBB: trampoline is an optimization lein uses to cache classpath stuff
BBBB: running multiple jvms
BBBB: pods are first-class and in-process
BBBB: boot itself is a small java program
BBBB: distributed as an uberjar (boot.sh)
BBBB: this uberjar can be executed directly because we exploit a nice property of jar files---you can put random garbage at the front of a jar file and the jvm will still load it without problems
BBBB: so we inject a shebang and a call to java -jar there
BBBB: boot.sh, the java program, provides a single class, boot.App
BBBB: boot.App has static methods that provide a main() entry point for the jar, and methods to create new pods
BBBB: boot runs all clojure code in pods
BBBB: so your build.boot script is actually running in a po
BBBB: pod
BBBB: but your pod can access boot.App and use it to create new pods that it can evaluate expressions in
BBBB: all pods communicate with each other via boot.App
BBBB: this is very different from lein trampoline
BBBB: since they're in the same process, and becuase they're first class you can have anonymous pods, you can put a pod in a future, etc
BBBB: you can make functions that take pods as arguments and functions that return pods
You can find other developers and users in the #hoplon
channel on freenode IRC or the boot slack channel.
If you have questions or need help, please visit the Discourse site.
- Environments
- Boot environment
- Java environment
- Tasks
- Built-ins
- Third-party
- Tasks Options
- Filesets
- Target Directory
- Pods
- Boot Exceptions
- Configuring Boot
- Updating Boot
- Setting Clojure version
- JVM Options
- S3 Repositories
- Scripts
- Task Writer's Guide
- Require inside Tasks
- Boot for Leiningen Users
- Boot in Leiningen Projects
- Repl reloading
- Repository Credentials and Deploying
- Snippets
- Troubleshooting
- FAQ
- API docs
- Core
- Pod
- Util