【问题标题】:How to get runtime access to version number of a running Clojure application?如何在运行时访问正在运行的 Clojure 应用程序的版本号?
【发布时间】:2012-09-17 23:17:22
【问题描述】:

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

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

代码库被打包为 JAR 文件。

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

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

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

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

【问题讨论】:

    标签: clojure continuous-integration jenkins leiningen continuous-deployment


    【解决方案1】:

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

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

    1 - 获取 Jenkins 内部版本号

    可以使用 Jenkins 的 environment variables 访问内部版本号(很好地命名为 BUILD_NUMBER)。这在 JVM 进程中可用,使用 System.getenv("BUILD_NUMBER")。在这种情况下,JVM 进程可以是 leiningen project.clj 脚本,这是可以调用 (System/getenv "BUILD_NUMBER") 的 Clojure 代码。按照上面的例子,返回的字符串是“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

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

    3 - 在运行时访问版本

    现在我们要使用的版本字符串在清单文件中,我们可以在 Clojure 代码中使用 Java 标准库来访问它。下面的 sn-p 演示了这一点:

    (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(),但由于没有其他任何东西读取这些值,我们只需设置实现版本即可。请注意,正确执行此操作还需要将“规范版本”添加到清单中。

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

    【讨论】:

    • 不错的文章。两个注意事项:您应该在 project.clj 中使用 or 而不是 if-let。另外,我很确定您可以直接阅读清单,而不是在类上调用 .getPackage。虽然如果你不能,你仍然可以在不使用 eval 的情况下获得课程。如果你想在没有 AOT 的情况下优雅地失败,你可以尝试解决。
    • 感谢您的回复。更新为使用 or 而不是 if-let。如果阅读清单的最简单方法是:stackoverflow.com/questions/2751033/… 我想我更喜欢标准 JDK 获取此特定属性的方法。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2014-10-09
    • 2015-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    • 2018-06-27
    相关资源
    最近更新 更多