I have a web service written in Clojure which is continuously delivered. To allow our automated deployment tools to know which version of the codebase has been deployed, the web service should provide a way to query which version it is. The version is declared as part of the project setup in the Leiningen build tool, like this:
(defproject my-web-service "1.2-SNAPSHOT"
; ... rest of project.clj
)
The codebase is packaged as a JAR file.
We developers do not want to increment the version number on each commit. Instead, we want it to be incremented automatically whenever a new build is triggered on our continuous integration server (in this case Jenkins). For example, when a version control checkin prompts the forty-second build of this codebase, the version is 1.2.42
.
For any particular JAR that's been built and deployed, I want to allow querying the version number somehow (e.g. with an HTTP request, but that's an implementation detail). The response should include the string 1.2.42
.
How do I make that version number available to the running application?
(Possible duplicate, though it doesn't include the Jenkins aspect: Embed version string from leiningen project in application)
Clojure depends on Java and all Clojure code is compiled to Java 8 compatible bytecode (newer versions of Java can load this as well). Supported: LTS (long term support) releases, currently Java 8, Java 11, Java 17 Read the Changelog for detailed release information.
Builds of the very latest version of Clojure’s master branch are available at oss.sonatype.org. Specify the version of Clojure that you want in your deps.edn:
But nooooo... Microsoft has never made it easy to get proper runtime version information that's suitable for display in an application. Hell, in full framework you had to resort to checking the registry and then translating magic partial version numbers to an official release version number (like 4.7.1).
Open a Database in Access Go to File - Account Then click About Access On the resulting page at the very top, you will see your version No....
One way to access this version number is through the MANIFEST.MF
file which is stored within the JAR file. This will allow access at runtime, through Java's java.lang.Package
class. This requires the following three steps:
project.clj
's defproject
declaration.MANIFEST.MF
with a value for Implementation-Version
.Package#getImplementationVersion()
to get access to a String
containing the version number.It is possible to use Jenkins' environment variables to access the build number (nicely named BUILD_NUMBER
). This is available within a JVM process, using System.getenv("BUILD_NUMBER")
. In this case, the JVM process can be the leiningen project.clj
script, which is Clojure code which can invoke (System/getenv "BUILD_NUMBER")
. Following the above example, the String returned would be "42".
When building a JAR, Leiningen will include a MANIFEST.MF
file by default. It also has a configuration option, which allows setting arbitrary key-value pairs in that file. So when we can access the Jenkins build number in Clojure, we can combine that with the static version declaration to set the Implementation-Version
in the manifest. The relevant portions of the project.clj
look like this:
(def feature-version "1.2")
(def build-version (or (System/getenv "BUILD_NUMBER") "HANDBUILT"))
(def release-version (str feature-version "." build-version))
(def project-name "my-web-service")
(defproject project-name feature-version
:uberjar-name ~(str project-name "-" release-version ".jar")
:manifest {"Implementation-Version" ~release-version}
... )
It's worth noting a couple of the details in this example. The (if-let ...)
when defining build-version
is to allow developers to build the JAR locally, without needing to emulate Jenkins' environment variables. The :uberjar-name
configuration is to allow creating a JAR file which is named using Maven/Ivy conventions. The resulting file in this example would be my-web-service-1.2.42.jar
.
With this configuration, when Leiningen is invoked by Jenkins on build number 42, the manifest in the resulting JAR will contain the line "Implementation-Version: 1.2.42".
Now that the version String we want to use is in the manifest file, we can access it using Java standard libraries in Clojure code. The following snippet demonstrates this:
(ns version-namespace
(:gen-class))
(defn implementation-version []
(-> (eval 'version-namespace) .getPackage .getImplementationVersion))
Note here, that in order to invoke getImplementationVersion()
, we need a Package
instance, and to get that we need an instance of java.lang.Class
. Hence we ensure that a Java class is generated from this namespace (the call to (:gen-class)
) (we can then access the getPackage
method from that class.
The result of this function is a String, e.g. "1.2.42".
It's worth noting there's a couple of gotchas you may have to worry about, but were acceptable for our use case:
project.clj
's (defproject ...)
call may cause some other tools not to work, if they rely on the version being hardcodedgetImplementationVersion
have been abused slightly. Really the version should be: pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion()
, but since nothing else reads either of these values, we can get away with just setting the implementation version. Note that doing this correctly would require also adding "Specification-Version" to manifest.With the steps above, my running Clojure application can access a version number which corresponds to the Jenkins build which packaged the code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With