如何在运行时访问正在运行的 Clojure 应用程序的版本号? [英] How to get runtime access to version number of a running Clojure application?

查看:29
本文介绍了如何在运行时访问正在运行的 Clojure 应用程序的版本号?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个用 Clojure 编写的网络服务,它持续交付.为了让我们的自动化部署工具知道已经部署了哪个版本的代码库,Web 服务应该提供一种查询它是哪个版本的方法.该版本在 Leiningen 构建工具中声明为项目设置的一部分,如下所示:

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
  )

代码库被打包为 JAR 文件.

The codebase is packaged as a JAR file.

我们开发人员不想在每次提交时增加版本号.相反,我们希望它在我们的持续集成服务器上触发新构建时自动递增(在这种情况下詹金斯).例如,当版本控制签入提示此代码库的 40 秒构建时,版本为 1.2.42.

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.

对于已构建和部署的任何特定 JAR,我希望允许以某种方式查询版本号(例如,使用 HTTP 请求,但这是一个实现细节).响应应包含字符串 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?

(可能重复,但不包括 Jenkins 方面:在应用程序中嵌入来自 leiningen 项目的版本字符串)

(Possible duplicate, though it doesn't include the Jenkins aspect: Embed version string from leiningen project in application)

推荐答案

访问此版本号的一种方法是通过存储在 JAR 文件中的 MANIFEST.MF 文件.这将允许在运行时访问,通过 Java 的 java.lang.Package 类.这需要以下三个步骤:

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. 将 Jenkins 内部版本号传递给 Leiningen,以合并到 project.cljdefproject 声明中.
  2. 指示 Leiningen 构建一个带有 Implementation-Version 值的 MANIFEST.MF.
  3. 调用 Package#getImplementationVersion() 以访问包含版本号的 String.
  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 - 获取 Jenkins 内部版本号

可以使用Jenkins的环境变量 来访问内部版本号(很好地命名为 BUILD_NUMBER).这在 JVM 进程中可用,使用 System.getenv("BUILD_NUMBER").在这种情况下,JVM进程可以是leiningen project.clj脚本,它是可以调用(System/getenv "BUILD_NUMBER")的Clojure代码.按照上面的示例,返回的字符串将为42".

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

在构建 JAR 时,Leiningen 将默认包含一个 MANIFEST.MF 文件.它还有一个配置选项,允许在该文件中设置任意键值对.因此,当我们可以在 Clojure 中访问 Jenkins 内部版本号时,我们可以将其与静态版本声明相结合,以在清单中设置 Implementation-Version.project.clj 的相关部分如下所示:

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}

  ... )

值得注意的是本示例中的一些细节.定义build-version时的(if-let ...)是为了让开发者可以在本地构建JAR,而无需模拟Jenkins的环境变量.:uberjar-name 配置允许创建一个使用 Maven/Ivy 约定命名的 JAR 文件.此示例中的结果文件将是 my-web-service-1.2.42.jar.

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.

使用此配置,当 Jenkins 在构建号 42 上调用 Leiningen 时,生成的 JAR 中的清单将包含Implementation-Version: 1.2.42"行.

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

既然我们要使用的版本字符串在清单文件中,我们可以在 Clojure 代码中使用 Java 标准库访问它.以下代码段演示了这一点:

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))

请注意,为了调用getImplementationVersion(),我们需要一个Package 实例,而要获得这个实例,我们需要一个java.lang 的实例.类.因此,我们确保从该命名空间生成一个 Java 类(调用 (:gen-class))(然后我们可以从该类访问 getPackage 方法.

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.

这个函数的结果是一个字符串,例如1.2.42".

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(defproject ...) 调用中定义的版本字符串可能会导致其他一些工具无法工作,如果他们依赖被硬编码的版本
  • getImplementationVersion 的语义被轻微滥用.真正的版本应该是: pkg.getSpecificationVersion() + "."+ pkg.getImplementationVersion(),但由于没有其他东西读取这些值中的任何一个,我们可以只设置实现版本.请注意,正确执行此操作还需要向清单添加规范版本".
  • 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.

通过上述步骤,我正在运行的 Clojure 应用程序可以访问与打包代码的 Jenkins 构建相对应的版本号.

With the steps above, my running Clojure application can access a version number which corresponds to the Jenkins build which packaged the code.

这篇关于如何在运行时访问正在运行的 Clojure 应用程序的版本号?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆