Symbols not evaluated for versions of dependencies #49

hlship opened this Issue Jun 16, 2010 · 13 comments


None yet

3 participants

hlship commented Jun 16, 2010

I started work on using lein with an existing project of mine. I seemed obvious in the Dont Repeat Yourself sense of the word, to define symbols for repeated and related version numbers. No dice:

(def slf4j-version "1.5.2")
(def jetty-version "7.0.0.RC4")
(def selenium-version "1.0.1")

(defproject com.howardlewisship.cascade/core "1.0.0-SNAPSHOT"
  :description "Simple, fast, easy web applications in idiomatic Clojure"
  :url ""
    [[org.clojure/clojure "1.1.0"]
     [org.clojure/clojure-contrib "1.1.0"]
     [org.slf4j/slf4j-api slf4j-version]
     [org.slf4j/slf4j-log4j12 slf4j-version]
     [log4j/log4j "1.2.14"]]
    [[org.eclipse.jetty/jetty-servlet jetty-version]
     [org.easymock/easymock "2.5.1"]
     [org.seleniumhq.selenium.client-drivers/selenium-java-client-driver selenium-version]
     [org.seleniumhq.selenium.server/selenium-server selenium-version] 
     [org.seleniumhq.selenium.server/selenium-server-coreless selenium-version]]
  :warn-on-reflection true
  :source-path "src/main/clojure"
  :test-path "src/test/clojure"
  :resources-path "src/main/resources"
  :jar-dir "target/")

$ lein deps
Exception in thread "main" java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.String (NO_SOURCE_FILE:0)
    at clojure.lang.Compiler.eval(
    at clojure.core$eval__5236.invoke(core.clj:2017)
    at clojure.main$eval_opt__7411.invoke(main.clj:227)
    at clojure.main$initialize__7418.invoke(main.clj:246)
    at clojure.main$null_opt__7446.invoke(main.clj:271)
    at clojure.main$main__7466.doInvoke(main.clj:346)
    at clojure.lang.RestFn.invoke(
    at clojure.lang.Var.invoke(
    at clojure.lang.AFn.applyToHelper(
    at clojure.lang.Var.applyTo(
    at clojure.main.main(
Caused by: java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.String
    at leiningen.deps$make_dependency__704.invoke(deps.clj:20)
    at leiningen.deps$deps__716.invoke(deps.clj:57)
    at leiningen.deps$deps__716.invoke(deps.clj:66)
    at clojure.lang.Var.invoke(
    at clojure.lang.AFn.applyToHelper(
    at clojure.lang.Var.applyTo(
    at clojure.core$apply__4370.invoke(core.clj:436)
    at leiningen.core$_main__46$fn__49.invoke(core.clj:81)
    at leiningen.core$_main__46.doInvoke(core.clj:78)
    at clojure.lang.RestFn.invoke(
    at user$eval__55.invoke(NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.eval(
    ... 10 more

This seems sensible to me; the symbols should be expanded to string and used as version numbers.

hlship commented Jun 16, 2010
$ lein version
Leiningen 1.1.0 on Java 1.6.0_20 Java HotSpot(TM) 64-Bit Server VM

This is trickier than it looks since you can't eval the dependencies--the symbols used for the artifact/group-id are not bound and thus can't be evaled. So it's doable, but only in certain places--we could special-case it for version strings.

Will give this some thought and consider for Leiningen 1.3.


I wrote a fairly simple patch for defproject which makes it so that explicitly unquoted forms within defproject will be evaluated at runtime. With that in place, e.g. this becomes possible:

(def clojure-artifact-name 'clojure)
(def clojure-version "1.1.0")

(defproject why-do-you-say-its-silly "0.0.1"
  :dependencies [[~clojure-artifact-name ~clojure-version]])

See the commit here.


Ah, apparently I got the whitespace all wrong in there... Fixed in the next commit.


This is cool, but I should note that it's a one-line change to make it work without unquote if we only want to let it work for version strings; just eval the version before using it.

My intuition says that the only place this unquote trick would be used would be version strings. Can you think of a use case for it that the simpler solution doesn't address?

hlship commented Jun 23, 2010

I would be satisfied with being able to use symbols for versions.


Well, I'm not sure if this is all that useful myself. I can see using this to switch between different people's group-ids on Clojars (this would make use of unquoting for the artifact name part of a dependency). I also have a slight preference for explicit unquoting over "magic" behaviour of symbols just in the version part of deps -- but note the word "slight". ;-)

I was thinking about making an additional patch to support specifying an init file for REPLs launched via lein repl, say through an :init entry in defproject; I haven't properly looked into that yet, but with that in place, I could see a use for unquoting to pick an init file dynamically.

At any rate, the code is already written. If it's not useful now, that's fine, it'll stick around in case it's useful in the future. ;-)


Good enough for me; I'll take the unquote tactic then. Thanks.


Great! :-)

I was thinking about documenting this, but am unsure about the best way to do that... I mean, does this go in the README and/or the tutorial at all?

For sample.project.clj, I reckon something along the lines of

;; Beyond this point, all unquoted forms (forms with ~ prepended to them)
;; will be evaluated; the others are implicitly quoted and will be used verbatim.

would do?

Or maybe you have something in mind already (in that case, whew ;-)).


Just added this to sample.project.clj and applied your commits. I did add an eval in the munge function rather than passing it through literally just so anything can happen inside the unquotes, not just literal insertion.


Actually the "literal insertion" means precisely that anything can happen, because that part of defproject will be evaluated at runtime. Adding an extra eval means that things will be evaluated at macro expansion time and then again at runtime, which may break things. Just tested this with the original commit:

(def contrib-version "1.2.0-SNAPSHOT")

(defproject foo "0.0.1"
  :dependencies [[~(symbol "org.clojure" "clojure") "1.2.0-master-SNAPSHOT"]
                 [org.clojure/clojure-contrib ~contrib-version]]

Surely enough, there's clojure-1.2.0-master-20100623.220259-87.jar and clojure-contrib-1.2.0-20100615.150419-128.jar after lein deps (after a rm lib/*).

With eval in place, this produces java.lang.ClassNotFoundException: org.clojure (project.clj:5).

You could use eval and then add a quote around its output, but then there's the problem that this eval would resolve Var names in leiningen.core, whereas project.clj seems to do its stuff in the user namespace (checked that with a (println *ns*) in project.clj).


Oh, good catch. Not sure what I was thinking; I only tested it with evaling a string literal. Went back to no-extra-eval.


Cool, thanks! Incidentally, looking at your commit, I notice that your function-naming-fu is superior to mine. :-)

@robwolfe robwolfe pushed a commit to robwolfe/leiningen that referenced this issue Aug 20, 2011
@michalmarczyk @technomancy michalmarczyk + Modified defproject to emit unquoted forms verbatim.
This means they are evaluated at runtime. See #49.
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment