With the power of boot, it’s possible to go from “never used java before” to budding Clojure-ist cranking out jars like a pickle factory in record time. This post walks you through the process, and provides some post-‘hello world’ examples, with pointers to more information.
Prerequisites
You will need the following: A JDK installed. Really, that’s it. Sun’s JDK or OpenJDK will work. Use the newest version. You’ll need a way to download things. Feel free to use your browser. The examples below use wget. If you’re on Linux or Mac OS, you’ll also need root access via sudo– this is not a hard requirement but allows you to install boot for everyone on your machine to use. There’s an expectation that you know basic Clojure, and tries not to be too clever. For a good introduction, check out Clojure For The Brave and True, specifically Do Things: a Clojure Crash Course. If you need help with specific forms used, the Clojure Community Documentation is extremely helpful, especially the Clojure Cheat Sheet. It may be helpful to give the boot readme and wiki documentation a read. If you have questions about boot, IRC is a great way to get boot and clojure rockstars to help you out. Come join us on freenode, in #hoplon.
Dales la Bota (Give ’em The Boot)
Boot is ‘installed‘ by simply downloading an executable file and putting it somewhere where you can execute it:
$ wget https://github.com/boot-clj/boot/releases/download/2.0.0-rc13/boot.sh $ mv boot.sh boot && chmod a+x boot && sudo mv boot /usr/local/bin
The real magic happens when boot is run. Boot sets everything up in a .boot
directory in your home folder. Without having any code to execute yet, you can trigger this by simply asking boot for help:
$ boot -h
Let’s Play With Clojure
Clojure utilizes a concept called a REPL (Read, Evaluate, Print, Loop). REPLs allow you to interactively run code and experiment.
$ boot repl
Boot then provides you with a prompt, where you can play around:
boot.user=> (+ 1 2 3 4 5) 15 boot.user=> (/ 10 0) java.lang.ArithmeticException: Divide by zero
Boot also works as a scripting platform– you can construct applications, specifying dependencies, and parse command-line arguments.
Here’s a simple Clojure function that prints the fibonacci sequence to a given number of digits:
(defn fib ([n] (fib [0 1] n)) ([pair, n] (print (first pair) "") (if (> n 0) (fib [(second pair) (apply + pair)] (- n 1)) (println))))
You can paste this into your REPL and try it out:
boot.user=> (defn fib #_=> ([n] #_=> (fib [0 1] n)) #_=> ([pair, n] #_=> (print (first pair) "") #_=> (if (> n 0) #_=> (fib [(second pair) (apply + pair)] (- n 1)) #_=> (println)))) #'boot.user/fib boot.user=> (fib 10) 0 1 1 2 3 5 8 13 21 34 55 nil
fib.boot
:#!/usr/bin/env boot (defn fib ([n] (fib [0 1] n)) ([pair, n] (print (first pair) "") (if (> n 0) (fib [(second pair) (apply + pair)] (- n 1)) (println)))) (defn -main [& args] (let [limit (first args)] (println "Printing fibonacci sequence up to " limit "numbers") (fib (Integer/parseInt limit))))
Make the script executable:
$ chmod u+x fib.boot
Now you can run the script:
$ ./fib.boot 10 Printing fibonacci sequence up to 10 numbers 0 1 1 2 3 5 8 13 21 34
The script can declare dependencies, which will be downloaded as needed when the script is run. Here, we’ll show the use of an external dependency: we can write a new fibonacci sequence that utilizes the fact that numbers in the sequence are related to each other by approximately the golden ratio (ca 1.62). Rounding makes it all work, but rounding isn’t “baked in” to Clojure, so we’ll use an external library to do it for us, called math.numeric-tower. Ok, actually, it’s there, you just need to use some existing Java libraries to make it work – I admit this is a bit of a strain!
#!/usr/bin/env boot (set-env! :dependencies '[[org.clojure/math.numeric-tower "0.0.4"]]) (require '[clojure.math.numeric-tower :refer [floor ceil round]]) (defn fib [n] (loop [counter 0 x 0] (if (= counter 0) (do (print 0 "" 1 "" 1 "") (recur 3 1)) (let [y (round (* x 1.62))] (print y "") (if (< counter 9) (recur (+ counter 1) y)))))) (defn -main [& args] (let [limit (first args)] (println "Printing fibonacci sequence up to" limit "numbers") (fib (Integer/parseInt limit)) (println)))
When you run this code the first time, you’ll notice boot tells you that it’s downloaded some new jars:
$ ./fib.boot Retrieving clojure-1.4.0.jar from http://clojars.org/repo/ Retrieving math.numeric-tower-0.0.4.jar from http://repo1.maven.org/maven2/ Printing fibonacci sequence up to 10 numbers 0 1 1 2 3 5 8 13 21 34
The syntax to define our -main
function and parse our command line options can be a bit tedious. Luckily, we can borrow a macro from boot.core that lets us specify CLI options using a robust syntax. For the full syntax, check out the documentation. Here, we’ll let the user choose which implementation they’d like to use, and utilize the task DSL to do some simple command line options:
#!/usr/bin/env boot (set-env! :dependencies '[[org.clojure/math.numeric-tower "0.0.4"]]) (require '[clojure.math.numeric-tower :refer [floor ceil round]]) (require '[boot.cli :as cli]) (defn fib ([n] (fib [0 1] n)) ([pair, n] (print (first pair) "") (if (> n 1) (fib [(second pair) (apply + pair)] (- n 1))))) (defn fibgolden [n] (loop [counter 0 x 0] (if (= counter 0) (do (print (str 0 " " 1 " " 1 " ")) (recur 3 1)) (let [y (round (* x 1.62))] (print y "") (if (< counter 9) (recur (+ counter 1) y)))))) (cli/defclifn -main "Print a fibonacci sequence to stdout using one of two algorithms." [g golden bool "Use the golden mean to calculate" n number NUMBER int "Quantity of numbers to generate. Defaults to 10"] (let [n (get :n *opts* 10) note (if golden "[golden]""[recursive]")] (println note "Printing fibonacci sequence up to" n "numbers:") (if golden (fibgolden n) (fib n))) (println))
Now you can see what options are available, tell the script what to do:
$ boot fib.boot -h Print a fibonacci sequence to stdout using one of two algorithms. Options: -h, --help Print this help info. -g, --golden Use the golden mean to calculate -n, --number NUMBER Set quantity of numbers to generate. Defaults to 10 to NUMBER. $ boot fib.boot [recursive] Printing fibonacci sequence up to 10 numbers: 0 1 1 2 3 5 8 13 21 34 $ boot fib.boot -g -n 20 [recursive] Printing fibonacci sequence up to 20 numbers: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
Working At The Pickle Factory (Packing Java Jars and More Complex Projects)
Now that we’ve got a basic feel for Clojure and using boot, we can build a project, that creates a library with an entry point that we can use and distribute as a jar file. This opens the doors to being able to deploy web applications, build libraries to share, and distribute standalone applications. First, we need to create a project structure. This will help us keep things organized, and fit in with the way Clojure handles namespaces and files. We’ll put our source code in src
, and create a new namespace, called fib.core
:
$ mkdir -p src/fib
In src/fib/core.clj
, we’ll declare our new namespace:
(ns fib.core (:require [clojure.math.numeric-tower :refer [floor ceil round]] [boot.cli :as cli]) (:gen-class)) (defn fib ([n] (fib [0 1] n)) ([pair, n] (print (first pair) "") (if (> n 1) (fib [(second pair) (apply + pair)] (- n 1))))) (defn fibgolden [n] (loop [counter 0 x 0] (if (= counter 0) (do (print (str 0 " " 1 " " 1 " ")) (recur 3 1)) (let [y (round (* x 1.62))] (print y "") (if (< counter 9) (recur (+ counter 1) y)))))) (cli/defclifn -main "Print a fibonacci sequence to stdout using one of two algorithms." [g golden bool "Use the golden mean to calculate" n number NUMBER int "Quantity of numbers to generate. Defaults to 10"] (let [n (if number number 10) note (if golden "[golden]""[recursive]")] (println note "Printing fibonacci sequence up to" n "numbers:") (if golden (fibgolden n) (fib n))) (println))
To build our jar, there are a handful of steps:
- Download our dependencies.
- Compile our clojure code ahead of time (aka AOT).
- Add a POM file describing our project and the version.
- Scan all of our dependencies and add them to the fileset to be put into the jar.
- Build the jar, specifying a module containing a -main function to run when the jar is invoked.
Helpfully, boot provides built-in functionality to do this for us. Each step is implemented as a boot task. Tasks act as a pipeline: the result of each can influence the next.
boot -d org.clojure/clojure:1.6.0 \ -d boot/core:2.0.0-rc8 \ -d org.clojure/math.numeric-tower:0.0.4 \ -s src/ \ aot -a \ pom -p fib -v 1.0.0 \ uber \ jar -m fib.core
A brief explanation of each task and command line options:
Line 1-3: the -d
option specifies a dependency. Here we list Clojure itself, boot.core
, and math.numeric-tower
.
Line 4:-s
specifies a source directory to look into for .clj
files.
Line 5: this is the AOT task, that compiles all of the .clj
files for us. The -a
flag tells the task to compile everything it finds.
Line 6: the POM task. This task adds project information to the jar. The -p
option specifies the project name, -v
is the version.
Line 7: the uber task collects the dependencies so they can be baked into the jar file. This makes the jar big (huge really), but it ends up being self-contained.
Line 8: finally, the jar task. This is the task that actually generates the jar file. The -m
option specifies which module has the -main
function. Running the above command, produces output something like this:
$ boot -d org.clojure/clojure:1.6.0 \ > -d boot/core:2.0.0-rc8 \ > -d org.clojure/math.numeric-tower:0.0.4 \ > -s src/ \ > aot -a \ > pom -p fib -v 1.0.0 \ > uber \ > jar -m fib.core Compiling fib.core... Writing pom.xml and pom.properties... Adding uberjar entries... Writing fib-1.0.0.jar...
At this point, there is a file named fib-1.0.0.jar
in the target
directory. We can use the java
command to run it:
$ java -jar target/fib-1.0.0.jar [recursive] Printing fibonacci sequence up to 10 numbers: 0 1 1 2 3 5 8 13 21 34
You can send this file to a friend, and they can use it too.
Introducing build.boot
At this point we have a project and can build a standalone jar file from it. This is great, but long command lines are prone to error. Boot provides a mechanism for defining your own tasks and setting the command line options in a single file, named build.boot. Here’s a build.boot
that configures boot in a manner equivalent to the command line switches above:
(set-env! :dependencies '[[org.clojure/math.numeric-tower "0.0.4"] [boot/core "2.0.0-rc8"] [org.clojure/clojure "1.6.0"]] :source-paths #{"src/"}) (task-options! pom {:project 'fib :version "1.0.0"} jar {:main 'fib.core} aot {:all true})
With build.boot
in the current directory, you can now run the tasks like this:
$ boot aot pom uber jar Compiling fib.core... Writing pom.xml and pom.properties... Adding uberjar entries... Writing fib-1.0.0.jar...
The convenience of build.boot
one step further, we can chain the tasks we want to use into our own task, using the deftask
macro:
(set-env! :dependencies '[[org.clojure/math.numeric-tower "0.0.4"] [boot/core "2.0.0-rc8"] [org.clojure/clojure "1.6.0"]] :source-paths #{"src/"}) (task-options! pom {:project 'fib :version "1.0.0"} jar {:main 'fib.core} aot {:all true}) (deftask build "Create a standalone jar file that computes fibonacci sequences." [] (comp (aot) (pom) (uber) (jar)))
Now, we can just run boot build
to make our standalone jar file. You’ll also see your task show up in the help output:
$ boot -h ... build Create a standalone jar file that computes fibonacci sequences. ... $ boot build Compiling fib.core... Writing pom.xml and pom.properties... Adding uberjar entries... Writing fib-1.0.0.jar...
Where To Go From Here
At this point we’ve touched most of the awesomeness that boot gives us. With these basic tools, there’s all sorts of interesting things we can do next. Here are some ideas:
- Use boot instead of a “typical” scripting language for systems automation.
- Distribute single .boot files containing entire applications.
- Build WAR files and use other boot tasks provided by the community to do all sorts of cool things, like compile SASS templates and deploy to Amazon Elastic Beanstalk.
- Write your own, specialized tasks to help streamline complex build processes – boot can replace (or augment) tools like ant and make.
