如何获得运行时访问正在运行的Clojure应用程序的版本号?

Gru*_*eck 16 continuous-integration clojure leiningen continuous-deployment jenkins

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

(defproject my-web-service "1.2-SNAPSHOT"
  ; ... rest of project.clj
  )
Run Code Online (Sandbox Code Playgroud)

代码库打包为JAR文件.

我们的开发人员不希望在每次提交时增加版本号.相反,我们希望只要在我们的持续集成服务器(在本例中为Jenkins)上触发新构建,它就会自动递增.例如,当版本控制签入提示此代码库的第四十二个版本时,版本是1.2.42.

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

如何使正在运行的应用程序可以使用该版本号?

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

Gru*_*eck 19

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

  1. 路过詹金斯建立数Leiningen,以纳入project.cljdefproject声明.
  2. 指导Leiningen构建MANIFEST.MF一个有价值的Implementation-Version.
  3. 调用Package#getImplementationVersion()以访问String包含版本号的a.

1 - 获取Jenkins内部版本号

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

2 - 在MANIFEST.MF中设置版本

在构建JAR时,Leiningen MANIFEST.MF默认会包含一个文件.它还有一个配置选项,允许在该文件中设置任意键值对.因此,当我们可以在Clojure中访问Jenkins构建号时,我们可以将它与静态版本声明相结合来设置Implementation-Version清单中的内容.project.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}

  ... )
Run Code Online (Sandbox Code Playgroud)

值得注意的是这个例子中的一些细节.该(if-let ...)定义的时候build-version是为了让开发者在本地构建JAR,而无需效仿詹金斯的环境变量.的:uberjar-name配置是允许生成机构,该使用Maven /常春藤约定命名的JAR文件.此示例中的结果文件将是my-web-service-1.2.42.jar.

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

3 - 在运行时访问版本

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

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

(defn implementation-version []
  (-> (eval 'version-namespace) .getPackage .getImplementationVersion))
Run Code Online (Sandbox Code Playgroud)

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

该函数的结果是一个String,例如"1.2.42".

注意事项

值得注意的是,您可能需要担心几个问题,但我们的用例可以接受:

  • 动态设置在定义的版本字符串project.clj(defproject ...)通话可能会引起一些其他工具无法正常工作,如果他们依靠的版本被硬编码
  • 语义getImplementationVersion被轻微滥用.实际上版本应该是:pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion(),但由于没有其他任何内容可以读取这些值,我们可以通过设置实现版本来逃避.请注意,正确执行此操作还需要在清单中添加"Specification-Version".

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