Embedding version string from Leiningen project

January 2, 2019

Frequently, inside my Clojure applications, I would put project version string picked from Leiningen project.clj file. This way, it would solve me these problems:

  1. User can easily report what application version was affected with some issue.
  2. Version number is updated only once.
  3. Leiningen lein change version leiningen.release/bump-version (automatic version number increase) works as well.

In the past, used this code:

(defn get-version
  "Return application version, depending if run from REPL or jar.
If fails, returns nil."
  [app]
  (if-let [str (System/getProperty (format "%s.version" app))]
    str
    (let [path (format "META-INF/maven/%s/%s/pom.properties" app app)
          in   (ClassLoader/getSystemResourceAsStream path)]
      (when in
        (with-open [in in]
          (let [p (java.util.Properties.)]
            (try
              (.load p in)
              (.getProperty p "version")
              (catch Exception _))))))))

and would call it with:

(get-version <project-name>)

It worked well for my usecase: version string could be obtained from REPL, but also from uberjar as well.

In one occasion I had to build pure binary using GraalVM compiler which makes above approach useless - there is no jar to unpack and there is no pom.properties file. Also, given code isn't free - it had to load and parse properties file just for displaying version string.

Nice thing about Leiningen is that it already publish project version value in compilation phase via <project-name>.version property and thanks to Clojure macros, it can be exploited easily:

;; assume the project is called foo

(defmacro get-version
  "Get project version in compilation phase. Only applicable
  to Leiningen projects."
  []
  `~(System/getProperty "foo.version"))

That should be it. Just call it whenever version string is needed.

Because the value is static (it is evaluated in compilation phase), it will work with uberjar, GraalVM compiled binary and from REPL without any problems. Best of all, it keeps resources usage to minimum.