Hey, I'm Adam Renklint

Execute shell commands in Boot with boot-exec

In several recent projects, I've needed to execute a shell command from within a Boot task, and sometimes use a predicate function to determine if the command should be executed or skipped, each time it's invoked.

I found one existing task, degree9/boot-exec, but this does not satisfy the requirement for conditional execution or allow me to define the command as a simple string. So I decided to write my own version, simplified and with these specific needs in mind.

You can find it on Clojars and include it your build.boot dependencies:

(set-env! :dependencies '[[adamrenklint/boot-exec "1.0.1"]])

(require '[adamrenklint.boot-exec :refer [exec]])

Let's say we have some images that need to be transformed with a fictional image transformer executable, that does not have an existing Clojure wrapper. From the command line, we'd just write:

> transform-image resources/images/my-image.png

To make this composable with the rest of our build pipeline, we simply add a new task to build.boot, using the command as a string:

(deftask transform-image []
  "Transform my-image"
  (exec :cmd "transform-image resources/images/my-image.png"))

Our fictional executable would perform the transformation and save the result to resources/images/my-image-transformed.png.

But what if executing the command is expensive, and we want to avoid unnecessary operations when including the transform-image in another task, which is called with watch and invoked every time a file in our :source-paths change?

(deftask dev []
  (comp (watch)
        (build)
        (transform-image)
        (target)))

We can solve this by providing the exec task with a predicate function.

(require '[clojure.java.io :as io])

(defn is-image-not-transformed? []
  (not (.exists (io/file "resources/images/my-image-transformed.png"))))

(deftask transform-image []
  "Transform my-image"
  (exec :cmd "transform-image resources/images/my-image.png"
        :pred-fn 'is-image-not-transformed?))

With this simple change, we skip executing the expensive command if the artifact it produces already exists.

Please note that we intentionally ignore the idea that the source image could change - the example is simplified and a bit contrived for sake of clarity. The real world use case for this task was much more complex, and explaining it here would probably have been more confusing than enlightening.

The library is published to Clojars, and the source code is available on GitHub under the MIT license.

Published on December 12, 2017.
Browse the archive or go to the homepage.