我有一个Clojure程序,我使用Maven构建为JAR文件。嵌入在JAR Manifest中的是构建版本号,包括构建时间戳。
我可以在运行时使用以下代码轻松地从JAR Manifest中读取它:
(defn set-version
"Set the version variable to the build number."
[]
(def version
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number"))))
但我被告知在def
内使用defn
是不好的业力。
在运行时设置常量的Clojure-idiomatic方法是什么?我显然没有将构建版本信息作为def
嵌入到我的代码中,但我希望它在程序启动时从main
函数设置一次(并且为所有)。然后,它应该以{{1}}的形式提供给正在运行的其余代码。
更新:顺便说一句,Clojure必须是我在很长一段时间内遇到过的最酷的语言之一。感谢Rich Hickey!
答案 0 :(得分:7)
我仍然认为最简洁的方法是在您的应用的alter-var-root
方法中使用main
。
(declare version)
(defn -main
[& args]
(alter-var-root #'version (constantly (-> ...)))
(do-stuff))
它在编译时声明Var,在运行时设置其根值一次,不需要deref并且不绑定到主线程。你在之前的问题中没有回应这个建议。你尝试过这种方法吗?
答案 1 :(得分:4)
您可以使用动态绑定。
(declare *version*)
(defn start-my-program []
(binding [*version* (read-version-from-file)]
(main))
现在main
并且它调用的每个函数都会看到*version*
的值。
答案 2 :(得分:2)
虽然kotarak的解决方案非常有效,但这里有另一种方法:将代码转换为返回版本的memoized函数。像这样:
(def get-version
(memoize
(fn []
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number")))))
答案 3 :(得分:1)
我希望这次我不会错过任何事情。
如果版本是一个常量,它将被定义一次并且不会被更改你可以简单地删除defn并保持(def版本...)单独。我想你出于某种原因不想要这个。
如果你想在fn中更改全局变量,我认为更惯用的方法是使用一些并发结构来存储数据和访问并以安全的方式更改它 例如:
(def *version* (atom ""))
(defn set-version! [] (swap! *version* ...))