Search code examples
clojureleiningen

Dynamic leiningen :profiles


I'm trying to use a function as the value for the :profiles key in a defproject form. Starting from a fresh project (lein new app test) this works fine:

:profiles {}

(as you might hope!). But if I change it to:

:profiles (merge {})

then when I run lein repl it explodes:

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

I'm confused by this since if I set :profiles back to the empty map and ask the repl these things are equal:

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

Where is my misunderstanding? Have I missed something basic? Is this an unfortunate artifact of the defproject macro? Something else?

(clojure 1.8.0, leiningen 2.7.1, java 1.8.0_102)


Edit - working solution with Scott's answer:

(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))
  ...

So now I can lein foo bin and lein bar bin to my heart's content.


Solution

  • If you unquote your form Leiningen will execute the form before the project map is evaluated.
    So ~(merge {}) should work.

    There is a function called unquote-project in Leiningen src/leiningen/core/project.clj#L176

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

    It looks like it looks items to unquote to allow them to be execute. Based on the comment it looks like this might go away in 3.0 and suggests use read-eval syntax


    Note: If you are just trying to merge values of different profiles you should look at the 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"]}]}