如何在运行中访问运行的Clojure应用程序的版本号?

16
我是一位有用的助手,可以为您翻译文本。
我有一个用Clojure编写的Web服务,它是持续交付的。为了让我们的自动化部署工具知道已经部署了哪个版本的代码库,Web服务应该提供一种查询版本的方法。版本是在Leiningen构建工具的项目设置中声明的,如下所示:
(defproject my-web-service "1.2-SNAPSHOT"
  ; ... rest of project.clj
  )

代码库被打包为JAR文件。
我们开发人员不想在每次提交时递增版本号。相反,我们希望在每次持续集成服务器(在本例中为Jenkins)触发新构建时自动递增版本号。例如,当版本控制提交引发此代码库的第42个构建时,版本号为1.2.42
对于任何已构建和部署的特定JAR,我希望可以以某种方式查询版本号(例如使用HTTP请求,但这是实现细节)。响应应该包含字符串1.2.42
如何使该版本号对正在运行的应用程序可用?
(可能重复,尽管它不包括Jenkins方面:将Leiningen项目中的版本字符串嵌入应用程序
1个回答

21

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

  1. 向Leiningen传递Jenkins构建号以合并到project.cljdefproject声明中。
  2. 指示Leiningen使用Implementation-Version的值构造MANIFEST.MF
  3. 调用Package#getImplementationVersion()以访问包含版本号的字符串。

1 - 获取Jenkins构建号

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

2- 在MANIFEST.MF中设置版本

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

(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

通过这个配置,在第42次构建时,当Jenkins调用Leiningen时,生成的JAR文件中的清单将包含"Implementation-Version: 1.2.42"这一行。

3 - 在运行时访问版本信息

既然我们想要使用的版本字符串已经在清单文件中了,我们可以在Clojure代码中使用Java标准库来访问它。以下代码片段展示了如何实现:

(ns version-namespace
  (:gen-class))

(defn implementation-version []
  (-> (eval 'version-namespace) .getPackage .getImplementationVersion))
请注意,为了调用getImplementationVersion(),需要一个Package实例,而要获取该实例,需要一个java.lang.Class的实例。因此,我们确保从这个命名空间生成了一个Java类(调用(:gen-class)),以便我们可以从该类访问getPackage方法

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

注意事项

值得注意的是,可能会有一些需要注意的问题,但对于我们的使用情况来说是可接受的:

  • 动态设置project.clj(defproject ...)调用中定义的版本字符串可能会导致其他工具无法正常工作,如果它们依赖于版本固定不变。
  • getImplementationVersion的语义被稍微滥用了一下。实际上,版本应该是:pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion(),但由于没有其他地方读取这两个值,我们可以只设置实现版本即可。请注意,正确执行此操作还需要将“Specification-Version”添加到清单中。

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

1
不错的写作。两个注意点:在project.clj中,你应该使用or而不是if-let。另外,我相信你可以直接读取清单而不是在类上调用.getPackage。虽然如果你不能,你仍然可以获取类而不使用eval。如果你想要在没有AOT的情况下优雅地失败,你可以尝试使用resolve。 - user61051
感谢您的回复。已更新为使用or而不是if-let。如果读取清单的最简单方法是这样的:https://dev59.com/_k3Sa4cB1Zd3GeqPv4i7,我认为我更喜欢使用标准JDK获取此特定属性的方式。非常感谢! - Grundlefleck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接