How to get runtime access to version number of a running Clojure application?

前端 未结 1 1382
说谎
说谎 2020-12-29 08:08

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

相关标签:
1条回答
  • 2020-12-29 08:17

    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:

    1. Passing the Jenkins build number to Leiningen, to incorporate in project.clj's defproject declaration.
    2. Instructing Leiningen to construct a MANIFEST.MF with a value for Implementation-Version.
    3. Invoking Package#getImplementationVersion() to get access to a String containing the version number.

    1 - Getting the Jenkins build 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".

    2 - Setting the version in MANIFEST.MF

    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".

    3 - Accessing version at runtime

    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".

    Caveats

    It's worth noting there's a couple of gotchas you may have to worry about, but were acceptable for our use case:

    • dynamically setting the version string defined in the project.clj's (defproject ...) call may cause some other tools not to work, if they rely on the version being hardcoded
    • the semantics of getImplementationVersion 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.

    0 讨论(0)
提交回复
热议问题