16

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

(defproject my-web-service "1.2-SNAPSHOT"
  ; ... rest of project.clj
  )

代码库被打包为一个 JAR 文件。

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

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

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

(可能重复,虽然它不包括 Jenkins 方面:Embed version string from leiningen project in application

4

1 回答 1

19

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

  1. 将 Jenkins 内部版本号传递给 Leiningen,以合并到project.cljdefproject声明中。
  2. 指示 Leiningen 构造 aMANIFEST.MF的值为Implementation-Version
  3. 调用Package#getImplementationVersion()以获取对String包含版本号的访问权限。

1 - 获取 Jenkins 内部版本号

可以使用 Jenkins 的环境变量来访问内部版本号(很好命名BUILD_NUMBER)。这在 JVM 进程中可用,使用System.getenv("BUILD_NUMBER"). 在这种情况下,JVM 进程可以是 leiningenproject.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}

  ... )

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

使用此配置,当 Jenkins 在版本号 42 上调用 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。因此,我们确保从这个命名空间(对 的调用(:gen-class))生成一个 Java 类(然后我们可以getPackage从该类访问该方法。

该函数的结果是一个字符串,例如“1.2.42”。

注意事项

值得注意的是,您可能需要担心一些陷阱,但对于我们的用例来说是可以接受的:

  • project.clj动态设置'调用中定义的版本字符串(defproject ...)可能会导致其他一些工具无法工作,如果它们依赖于被硬编码的版本
  • 的语义getImplementationVersion被稍微滥用了。确实版本应​​该是: pkg.getSpecificationVersion() + "." + pkg.getImplementationVersion(),但由于没有其他任何东西读取这些值,我们可以只设置实现版本。请注意,正确执行此操作还需要将“规范版本”添加到清单中。

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

于 2012-09-26T10:40:36.597 回答