Boot: Getting Started With Clojure In < 10 Minutes

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
We can transform that function into a command-line tool using the power of boot scripting. Assume this file is called 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:

  1. Download our dependencies.
  2. Compile our clojure code ahead of time (aka AOT).
  3. Add a POM file describing our project and the version.
  4. Scan all of our dependencies and add them to the fileset to be put into the jar.
  5. 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:

Advertisements
This entry was posted in clojure and tagged , . Bookmark the permalink.

11 Responses to Boot: Getting Started With Clojure In < 10 Minutes

    • jjmojojjmojo says:

      I’ve made an effort to keep the link in the example wget command fresh (it’s up to rc13 at the moment), but for anyone who may be looking for the latest, it’s best to go to the releases page, and substitute the latest shell script link in the example.

  1. Pingback: Bookmarks for January 28th | Chris's Digital Detritus

  2. Pingback: January Newsletter | syslab.com on open source

  3. This post makes for a great Boot crash-course. Thanks for putting it together!

  4. Pingback: Advanced Boot Scripting | jjmojojjmojo: In Effect

  5. Hi, so I’m a clojure newbie and I found this post most helpful, esp. using boot. But the first fib.boot script is very slow to run (I’m on OS X Macbook Pro) when I run $ ./fib.boot 10 – What might be happening to make this slow? When I run fib from inside boot repl, it seems instantaneous. Running a similar script in python is even faster. Any ideas?

  6. Great post! I have to put the ‘target’ task after the ‘jar -m fib.core’ to generate the files in disk. And I’m wondering why the name of the jar is generating as ‘project.jar’ instead of ‘fib-1.0.0.jar’ as specified in the pom task.

    • jjmojojjmojo says:

      This post needs an update, stay tuned, I will check this. Some semantics of some of the boot tasks have changed a bit since it was written. Thanks!

  7. Notes / an erratum for anyone”boot fib.boot -h” doesn’t work, but “./fib.boot -h” does

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s