动态 leiningen :profiles

Dynamic leiningen :profiles

我正在尝试使用函数作为 defproject 表单中 :profiles 键的值。从一个新项目 (lein new app test) 开始工作正常:

:profiles {}

(如您所愿!)。但是如果我把它改成:

:profiles (merge {})

然后当我 运行 lein repl 它爆炸了:

Caused by: java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.util.Map$Entry

我对此感到困惑,因为如果我将 :profiles 设置回空地图并询问 repl 这些东西是相等的:

test.core=> (= {} (merge {}))
true

我的误会在哪里?我错过了一些基本的东西吗?这是 defproject 宏的不幸产物吗?还有别的吗?

(clojure 1.8.0, leiningen 2.7.1, java 1.8.0_102)


编辑 - 斯科特回答的有效解决方案:

(def project-name 'myproj)
(def mains ["foo" "bar"])
...
(defn- lein-alias [main]
  { main ["with-profile" main] })

(defn- lein-profile [main]
  (let [jar      (str main ".jar")
        entry    `~(str project-name "." main)]
    {(keyword main) {:main          entry 
                     :bin           {:name main}
                     :jar-name      jar
                     :uberjar-name  jar}}))

(defproject project-name "0.1.0"
...
  :profiles ~(apply merge (concat (map lein-profile mains) {:uberjar {:aot :all}}))
  :aliases ~(apply merge (map lein-alias mains))
  ...

所以现在我可以 lein foo binlein bar bin 尽情享受了。

您没有手动合并配置文件。它们由 lein 自动(正常情况)或在您使用 with-profile 关键字(手动控制)时合并在一起。

例如,考虑这个 project.clj:

(defproject xyz "0.1.0-SNAPSHOT"

  :dependencies [ [org.clojure/clojure "1.8.0"] 
                  [tupelo "0.9.19"] ]

  :profiles {:dev     {:dependencies [ [org.clojure/test.check "0.9.0"]
                                       [criterium "0.4.4"] ] }
             :sample  {:dependencies [medley "0.8.2"] }
  ...
)

这个 project.clj 表示项目 always 需要 org.clojure/clojuretupelo。在开发过程中,:dev 的地图将合并到 root-level 中,因此 :dependencies 将更新为包括 test.checkcriterium。但是,创建 uberjar 时不包含 :dev 配置文件值,因此这些库不会包含在交付给用户的代码中。

由于 :sample 不是默认配置文件之一,只有在您使用如下命令时才会包含它:

> lein with-profile sample test

请注意,尽管我们在 project.clj 文件中使用关键字 :sample 和冒号,但前导冒号并未包含在命令行中。

完整的细节在这里:https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md#default-profiles

这里:https://github.com/technomancy/leiningen/blob/master/sample.project.clj

说了这么多,我一般不需要用:profiles。除非你有比平常更复杂的东西,否则你通常应该将所有依赖项放在根级别(即 lein new app xyz 项目中 (defproject xyz ...) 下第一级的 :dependencies 关键字给你。

另外建议:lein项目中很多地方都用到test这个词(目录名、文件名后缀等),所以把项目本身命名会很混乱test!如果您选择一个独特的名称,例如 xyzjoe 或其他任何名称,您将避免自己(和任何其他读者)的痛苦。

如果您 unquote 您的表单 Leiningen 将在 评估项目地图之前 执行表单。
所以 ~(merge {}) 应该可以。

Leiningen 中有一个名为 unquote-project 的函数 src/leiningen/core/project.clj#L176

"Inside defproject forms, unquoting (~) allows for arbitrary evaluation."

看起来 unquote 允许执行的项目。根据评论,这看起来可能会在 3.0 中消失并建议 use read-eval syntax


注意: 如果您只是想合并不同配置文件的值,您应该查看 Profiles documentation About Merging and Composite Profiles

Composite Profiles

Sometimes it is useful to define a profile as a combination of other profiles. To do this, just use a vector instead of a map as the profile value. This can be used to avoid duplication:

{:shared {:port 9229, :protocol "https"}
 :qa [:shared {:servers ["qa.mycorp.com"]}]
 :stage [:shared {:servers ["stage.mycorp.com"]}]
 :production [:shared {:servers ["prod1.mycorp.com", "prod1.mycorp.com"]}]}