Liking cljdoc? Tell your friends :D

arhimedes lever

Boot

Boot is a shell interpreter for scripts written in Clojure and a Clojure build environment.

  • It can be used with the “shebang” style of shell scripts to provide a simple means to have single file, self-contained scripts in Clojure that can have dependencies on Maven artifacts but aren't part of a project or uberjar.

  • The boot build environment provides facilities to manage any Clojure build process a programmer can imagine.

    1. FIXME

Install

FIXME: How to doing?

Scripting Clojure

To get started let's make a Hello World script (of course). Create a file named hello.boot (boot scripts must have the .boot extension):

#!/usr/bin/env boot

(defn -main [& args]
  (println "hello, world!"))

Make the script executable:

$ chmod a+x hello.boot

Now you can run it:

$ ./hello.boot
hello, world

Good job dude!

Script Dependencies

Scripts can add Maven dependencies and/or directories to the class path at runtime using set-env!, like this:

#!/usr/bin/env boot

(set-env!
  :repositories #{"http://me.com/maven-repo"}
  :dependencies '[[com.hello/foo "0.1.0"]])

(require '[com.hello.foo :as foo])

(defn -main [& args]
  (println (foo/do-stuff args))
  (System/exit 0))

Boot Build Tool

In addition to interpreting scripts, boot also provides some facilities to help build Clojure projects. Omitting the -main function definition puts boot into build tool mode.

A Minimal Build Script

Create a minimal build.boot file containing only the shebang and core version:

$ boot :strap > build.boot

The resulting file should contain something like this:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

Then run it. You should see version and usage info and a list of available tasks:

$ boot
tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot

Usage: boot OPTS task ...
       boot OPTS [task arg arg] ...
       boot OPTS [help task]

OPTS:  :v       Verbose exceptions (full cause trace).
       [:v n]   Cause trace limited to `n` elements each.

Tasks: debug      Print the value of a boot environment key.
       help       Print help and usage info for a task.
       lein       Run a leiningen task with a generated `project.clj`.
       repl       Launch nrepl in the project.
       syncdir    Copy/sync files between directories.
       watch      Watch `:src-paths` and call its continuation when files change.

Create a minimal boot script: `boot :strap > build.boot`
                                          

The tasks listed in the output are defined in the core tasks namespace, which is referred into the script namespace automatically. Any tasks defined or referred into the script namespace will be displayed in the list of available tasks printed by the default help task.

Notice that when the boot script file is named build.boot and located is in the current directory you can call boot directly instead of executing the boot script file itself. This is more familiar to users of Leiningen or GNU Make, for example, and reinforces build repeatability by standardizing the build script filename and location in the project directory.

A Simple Task

Let's create a task to print a friendly greeting to the terminal. Modify the build.boot file to contain the following:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

(deftask hello
  "Print a friendly greeting."
  [& [name]]
  (fn [continue]
    (fn [event]
      (printf "hello, %s!\n" (or name "world"))
      (continue event))))

Run it again to see the new task listed among the other available tasks:

$ boot
tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot

Usage: boot OPTS task ...
       boot OPTS [task arg arg] ...
       boot OPTS [help task]

OPTS:  :v       Verbose exceptions (full cause trace).
       [:v n]   Cause trace limited to `n` elements each.

Tasks: debug      Print the value of a boot environment key.
       hello      Print a friendly greeting.
       help       Print help and usage info for a task.
       lein       Run a leiningen task with a generated `project.clj`.
       repl       Launch nrepl in the project.
       syncdir    Copy/sync files between directories.
       watch      Watch `:src-paths` and call its continuation when files change.
                                          
Create a minimal boot script: `boot :strap > build.boot`
                                          

Now we can run the hello task:

$ boot hello
hello, world!

Command Line Arguments To Tasks

An argument can be passed to the hello task like this:

$ boot \(hello :foo\)
hello, :foo!

The command line is read as Clojure forms, but task expressions can be enclosed in square brackets (optionally) to avoid having to escape parens in the shell, like this:

$ boot [hello :foo]
hello, :foo!

Command Line Composition Of Tasks

Tasks can be composed on the command line by specifying them one after the other, like this:

$ boot [hello :foo] [hello :bar]
hello, :foo!
hello, :bar!

Because tasks return middleware functions they can be composed uniformly, and the product of the composition of two task middleware functions is itself a task middleware function. The two instances of the hello task above are being combined by boot something like this:

;; [& args] command line argument list
("[hello" ":foo]" "[hello" ":bar]")
  ;; string/join with " " and read-string
  => ([hello :foo] [hello :bar])
  ;; convert top-level vectors to lists
  => ((hello :foo) (hello :bar))
  ;; compose with comp when more than one
  => (comp (hello :foo) (hello :bar))

This yields a middleware function that is called by boot to actually perform the build process. The composition of middleware sets up the pipeline of tasks that will participate in the build. The actual handler at the bottom of the middleware stack is provided by boot–it syncs artifacts between temporary staging directories (more on these later) and output/target directories.

Create New Task By Composition

Here we create a new named task in the project boot script by composing other tasks. This is a quick way to fix options and simplify documenting the build procedures. Tasks are functions that return middleware, and middleware are functions that can be composed uniformly, so a task can compose other tasks the same way as on the command line: with the comp function.

Modify the build.boot file such that it contains the following:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

(deftask hello
  "Print a friendly greeting."
  [& [name]]
  (fn [continue]
    (fn [event]
      (printf "hello, %s!\n" (or name "world"))
      (continue event))))

(deftask hellos
  "Print two friendly greetings."
  []
  (comp (hello :foo) (hello :bar)))

Now run the new hellos task, which composes two instances of the hello task with different arguments to the constructor:

$ boot hellos
hello, :foo!
hello, :bar!

The Build Environment

The global build environment contains the project metadata. This includes things like the project group and artifact ID, version string, dependencies, etc. The environment is accessible throughout the build process via the get-env and set-env! functions.

For example:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

(set-env!
  :project      'com.me/my-project
  :version      "0.1.0-SNAPSHOT"
  :description  "My Clojure project."
  :url          "http://me.com/projects/my-project"
  :license      {:name  "Eclipse Public License"
                 :url   "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies '[[tailrecursion/boot.task "2.0.0"]
                  [tailrecursion/hoplon    "5.0.0"]]
  :src-paths    #{"src"})

(deftask env-value
  "Print the value associated with `key` in the build environment."
  [key]
  (fn [continue]
    (fn [event]
      (prn (get-env key))
      (continue event))))

In the example above the environment is configured using set-env! and a task is defined to print the environment value associated with a given key using get-env. (This task is similar to the core debug task that is included in boot already.) We can run the task like this:

$ boot [env-value :src-paths]
#{"src"}

Tasks That Modify The Environment

Tasks defined in the build.boot script can dynamically modify the build environment at runtime. That is, they can use set-env! to add dependencies or directories to the classpath or otherwise update values in the build environment. This makes it possible to define "profile" tasks that can be used to modify the behavior of other tasks. These profile-type tasks can either create a middleware function or simply return Clojure's identity to pass control directly to the next task.

For example:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

(set-env!
  :project      'com.me/my-project
  :version      "0.1.0-SNAPSHOT"
  :description  "My Clojure project."
  :url          "http://me.com/projects/my-project"
  :license      {:name  "Eclipse Public License"
                 :url   "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies '[[tailrecursion/boot.task "2.0.0"]
                  [tailrecursion/hoplon    "5.0.0"]]
  :src-paths    #{"src"})

(deftask env-mod
  "Example profile-type task."
  []
  (set-env! :description "My TEST Clojure project.")
  identity)

(deftask env-value
  "Print the value associated with `key` in the build environment."
  [key]
  (fn [continue]
    (fn [event]
      (prn (get-env key))
      (continue event))))

Now, running this build.boot script produces the following:

$ boot [env-value :description]
"My Clojure project."
$ boot env-mod [env-value :description]
"My TEST Clojure project."

In the build script the deftask macro defines a function whose body is compiled lazily at runtime when the function is called. This means that inside a deftask you can add dependencies and require namespaces which will then be available for use in the build script.

For example:

#!/usr/bin/env boot

#tailrecursion.boot.core/version "2.0.0"

(set-env!
  :project      'com.me/my-project
  :version      "0.1.0-SNAPSHOT"
  :description  "My Clojure project."
  :url          "http://me.com/projects/my-project"
  :license      {:name  "Eclipse Public License"
                 :url   "http://www.eclipse.org/legal/epl-v10.html"}
  :src-paths    #{"src"})

(deftask load-hoplon 
  "Example profile-type task."
  []
  (set-env!
    :dependencies '[[tailrecursion/boot.task "2.0.0"]
                    [tailrecursion/hoplon    "5.0.0"]])
  (require '[tailrecursion.hoplon.boot :as h])
  identity)

The load-hoplon task adds the dependencies needed for building a Hoplon application and requires the hoplon boot task namespace, aliasing it to h locally. To see the effect run the build.boot script with and without this task and see how the list of available tasks changes.

First without the load-hoplon profile:

$ boot help
tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot

Usage: boot OPTS task ...
       boot OPTS [task arg arg] ...
       boot OPTS [help task]

OPTS:  :v       Verbose exceptions (full cause trace).
       [:v n]   Cause trace limited to `n` elements each.

Tasks: debug         Print the value of a boot environment key.
       help          Print help and usage info for a task.
       lein          Run a leiningen task with a generated `project.clj`.
       load-hoplon   Example profile-type task.
       repl          Launch nrepl in the project.
       syncdir       Copy/sync files between directories.
       watch         Watch `:src-paths` and call its continuation when files change.

Create a minimal boot script: `boot :strap > build.boot`
                                          

Then with the load-hoplon profile:

$ boot load-hoplon help
tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot

Usage: boot OPTS task ...
       boot OPTS [task arg arg] ...
       boot OPTS [help task]

OPTS:  :v       Verbose exceptions (full cause trace).
       [:v n]   Cause trace limited to `n` elements each.

Tasks: debug         Print the value of a boot environment key.
       help          Print help and usage info for a task.
       lein          Run a leiningen task with a generated `project.clj`.
       load-hoplon   Example profile-type task.
       repl          Launch nrepl in the project.
       syncdir       Copy/sync files between directories.
       watch         Watch `:src-paths` and call its continuation when files change.
       h/hoplon      Build Hoplon web application.
       h/html2cljs   Convert file from html syntax to cljs syntax.

Create a minimal boot script: `boot :strap > build.boot`
                                          

Notice how the second list includes h/hoplon and h/html2cljs, the two tasks defined using deftask in the Hoplon boot task namespace. You could run the hoplon task, for example, by doing

$ boot load-hoplon h/hoplon

Staging Directories And Temporary Files

The Java/Clojure build process is pretty much wedded to files in the filesystem. This adds incidental complexity to the build process and causes undesired coupling between tasks and between tasks and the project environment. Boot provides facilities to mitigate the issues with managing the files created during the build process. This allows tasks to be more general and easily composed, and eliminates configuration boilerplate in the project environment.

  • Tasks produce files which may be further processed by other tasks or emitted into the final output directory as artifacts. Using boot's file management facilities eliminates the need for the task itself to know which is the case during a particular build.

  • Boot's file management facilities eliminate the coupling between tasks and the filesystem, improving the ability to compose these tasks.

  • Boot manages these files in such a way as to never accumulate stale or garbage files, so there is no need for a "clean" task. This greatly simplifies the state model for the build process, making it easier to understand what's going on during the build and the interactions between tasks.

The boot build process deals with six types of directories–two of which are specified in the project's boot environment (in the build.boot file) and the other four are created by tasks during the build process and managed by boot.

Project Directories

These directories contain files that are part of the project itself and are read-only as far as boot tasks are concerned.

  • Project source directories. These are specified in the :src-paths key of the boot environment for the project, and boot adds them to the project's class path automatically.

  • Resource directories. These are specified using the add-sync! function in the build.boot file. The contents of these directories are overlayed on some other directory (usually the :out-path dir, but it could be any directory) after each build cycle. These directories contain things like CSS stylesheets, image files, etc. Boot does not automatically add resource directories to the project's class path.

Boot Managed Directories

These directories contain intermediate files created by boot tasks and are managed by boot. Boot deletes managed directories created by previous builds each time it starts.

  • Project output directory. This is specified in the :out-path key of the project boot environment. This is where the final artifacts produced by the entire build process are placed. This directory is kept up to date and free of stale artifacts by boot, automatically. Tasks should not add files directly to this directory or manipulate the files it contains. Instead, tasks emit artifacts to staging directories (see below) and boot takes care of syncing them to the output directory at the end of each build cycle.

  • Generated source directories. These directories are created by tasks via the mksrcdir! function. Generated source dirs are similar to the project source dirs, except that tasks can write to them and they're managed by boot. Tasks can use these directories as a place to put intermediate source files that are generated from sources in JAR dependencies (i.e. once created these files won't change from one build cycle to the next).

  • Temporary directories. Temp directories are created by tasks via the mktmp! function. Tasks can use these directories for storing intermediate files that will not be used as input for other tasks or as final compiled artifacts (intermediate JavaScript namespaces created by the Google Closure compiler, for instance). These directories are not automatically added to the project's class path.

  • Staging directories. These directories are created by tasks via the mkoutdir! function. Tasks emit artifacts into these staging directories which are cleaned automatically by boot at the start of each build cycle. Staging directories are automatically added to the project's class path so the files emitted there can be used as input for other tasks (or not) as required. Files in staging directories at the end of the build cycle which have not been consumed by another task (see below) will be synced to the output directory after all tasks in the cycle have been run.

The image above illustrates the flow of files through the boot build process. On the left and right sides of the image are the various directories involved in the build process. The build process depicted consists of two tasks, "Task 1" and "Task 2", colored orange and red, respectively, displayed in the center of the image.

Tasks participate in the three phases of the build cycle: init, build, and filter. The initialization phase occurs once per boot invocation for each task, when the tasks are constructed. Tasks return middleware functions which handle the build phase of the process. Tasks may "consume" source files (see the next section). These files are removed from the staging directories of all tasks by boot during the filter phase of the build cycle.

After the final phase of the build cycle stale artifacts are removed from the project output directory and any artifacts that remain in staging directories are synced over to it.

Source Files Consumed By Tasks

FIXME

Dependency

Artifacts are published on Clojars.

latest version

License

Copyright © 2013 Alan Dipert and Micha Niskin

Distributed under the Eclipse Public License, the same as Clojure.

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close