Skip to content
Martin Klepsch edited this page Jan 5, 2018 · 15 revisions

Snippets

  1. Access a version string at runtime, from a jar
  2. Accessing test resources
  3. Task environment conflicts
  4. Add previously resolved jar to the fileset
  5. Check dependency conflicts
  6. Re-using a built-in task name

Access a version string at runtime, from a jar

Often it is desirable to print the version of the source used to build the jar. This may be a version string or a git hash. One way to achieve this is to add the version number to a file in the classpath:

;; in build.boot

(def version "1.0")

;; or from the environment
;; (def version (or (System/getenv "CIRCLE_SHA1") "SNAPSHOT"))

(deftask add-version-txt []
  (with-pre-wrap fs
    (let [t (tmp-dir!)]
      (spit (clojure.java.io/file t "version.txt") version)
      (-> fs (add-resource t) commit!))))

(deftask dist []
  (comp (pom :project 'my-project
             :version version)
        (add-version-txt)
        (aot :namespace '#{my-project.core})
        (uber)
        (jar :main 'my-project.core)))
;; in your app code

(defn get-version []
   (some-> "version.txt" clojure.java.io/resource slurp clojure.string/trim))

Accessing test resources

In lein, you just create a dev-resources directory for your testing resources. Boot is almost as easy. Just as you need to add the source files you use for testing to the source-paths, you also need to add to the resource-paths:

(deftask testing
  "Profile setup for running tests."
  []
  (set-env! :source-paths #(conj % "test/clj"))
  (set-env! :resource-paths #(conj % "dev-resources"))
  identity)

Task environment conflicts

Source: Micha Niskin on Slack

Sometimes boot tasks have conflicts with your project. You can filter the dependencies that will be used on the pod using this:

(defmacro with-env
  [env & body]
  (let [orig (into {} (map #(vector % (gensym)) (keys env)))]
    `(let [~@(mapcat (fn [[k v]] [v `(get-env ~k)]) orig)]
       ~@(for [[k v] env] `(set-env! ~k ~v))
       (with-let [ret# (do ~@body)]
         ~@(for [[k v] orig] `(set-env! ~k ~v))))))

(defn filter-deps
  [deps]
  (->> (get-env :dependencies)
       (filterv #(some (set deps) ((juxt first (comp symbol name first)) %)))))

(defmacro with-deps
  [deps & body]
  `(with-env {:dependencies (filter-deps '~deps)} ~@body))

Usage: Project has deps that conflict with boot-jetty, so we filter all of those out when we instantiate the task--it will not create pods with the troublesome deps.

(deftask foo
  []
  (comp (watch)
        (hoplon)
        (cljs)
        (with-deps [boot-jetty] (serve))))

Add previously resolved jar to the fileset

Source: Andrea Richiardi's boot-pack-source

The standard built-in/sift :add-jar will fail when trying to resolve symbols that are not in the current env - not in (:dependencies (get-env).

But what if I want to explode a jar given its (already resolved) path? Say a transitive dependency obtained through pod/resolve-dependencies. The following task will help you with that.

(core/deftask sift-jar
  "Custom version of boot.task.built-in/sift :add-jar which accepts
  a (previously resolved) jar file in input."
  [j jar     PATH  str      "The path of the jar file."
   i include MATCH #{regex} "The set of regexes that paths must match."
   v invert        bool     "Invert the sense of matching."]

  (core/with-pre-wrap fileset
    (-> fileset 
        (core/add-cached-resource
         (digest/md5 jar)
         (partial pod/unpack-jar jar)
         :include (when-not invert include)
         :exclude (when invert include)
         :mergers pod/standard-jar-mergers)
        (core/commit!))))

Check dependency conflicts

Source: Sean Corfield on Slack

Be fussy about dependency conflicts. Throws an exception if any.

(deftask check-conflicts
  "Verify there are no dependency conflicts."
  []
  (with-pass-thru fs
    (require '[boot.pedantic :as pedant])
    (let [dep-conflicts (resolve 'pedant/dep-conflicts)]
      (if-let [conflicts (not-empty (dep-conflicts pod/env))]
        (throw (ex-info (str "Unresolved dependency conflicts. "
                             "Use :exclusions to resolve them!")
                        conflicts))
        (println "\nVerified there are no dependency conflicts.")))))

Re-using a built-in task name

The tasks in boot.task.built-in are automatically included in the boot.user namespace. As a result, re-using one of these task names in your build.boot results in an error.

For example, if you try to define a task called repl, you will see the following exception:

java.lang.IllegalStateException: repl already refers to: #'boot.task.built-in/repl in namespace: boot.user

The solution is to remove the binding from the namespace before defining the task.

(ns-unmap 'boot.user 'repl)
(require '[boot.task.built-in])

(deftask repl []
  (comp
   (with-pass-thru _
     (println "it works"))
   (boot.task.built-in/repl)))
Clone this wiki locally