How to include build number?

Hello all,
I would like to build a JAR (aot) that includes its own build time, release number, etc within compiled code. In Java we do a bit of dirty tricks with replacing strings in sources, but I was wondering if there was something smarter with Clojure - e.g. capturing a system variable when a macro runs.
Anybody does that? what are the best practices?

1 Like

Here’s how I’d do it:

The idea is to include a file called version.txt in the uberjar, which can be read from your code at runtime.

The example linked above uses boot, but a similar technique should be possible with leiningen.

1 Like

Thanks - I’m aware of this possibility, but it seems kind of… wasteful to read a file just for something that is totally static.

I also like the approach of storing it in a file but if you’re concerned about reading that file lots of times you could do something like wrapping it in a delay:

(def version
    (let [from-file (some-> "version.txt" slurp clojure.string/trim)]
      (or from-file "unknown"))))

then use as @version whereever you need it.

Why worry? Look at it this way - the time you spent typing out this question is probably more than the time consumed by the totality of your servers reading the classpath resource.

In Java it’s important because the compiler does tree-shaking optimizations based on our env variables but in Clojure maybe not that much :slight_smile:

That information should be available as metadata inside the jar you build, here’s how I do it for Chestnut

(defn load-props [file-name]
  (with-open [^ reader ( file-name)]
    (let [props (java.util.Properties.)]
      (.load props reader)
      (into {} (for [[k v] props] [(keyword k) v])))))

(defn chestnut-version []
  (let [resource (io/resource "META-INF/maven/chestnut/lein-template/")
        props (load-props resource)
        version (:version props)
        revision (:revision props)
        snapshot? (re-find #"SNAPSHOT" version)]
    (str version " (" (str/join (take 8 revision)) ")")))

I did develop a bit on the idea of capturing build environment using a macro:

(defn shellout [cmd]
  (let [p (.exec (Runtime/getRuntime) cmd)
        _ (.waitFor p)]
    (with-open [r ( (.getInputStream p))]
      (vec (line-seq r)))))

(defmacro capture-build-env-to
  "Captures build environment"
  (let [now (now-as :datetime)
        user (System/getenv "USER")
        gitbuild (first (shellout "git log --oneline -n 1 --pretty=%h.%aD.%aN"))
    `(defonce ~env-sym {:built-at ~now
                        :build-no "1"
                        :git-build ~gitbuild
                        :user ~user})))

This way I capture date and time and current git revision. I also like @plexus approach anyway :slight_smile: