Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Symbols not evaluated for versions of dependencies #49

Closed
hlship opened this Issue · 13 comments

3 participants

@hlship

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 "http://github.com/hlship/cascade/"
  :dependencies 
    [[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"]]
  :dev-dependencies
    [[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(Compiler.java:4658)
    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(RestFn.java:426)
    at clojure.lang.Var.invoke(Var.java:363)
    at clojure.lang.AFn.applyToHelper(AFn.java:175)
    at clojure.lang.Var.applyTo(Var.java:476)
    at clojure.main.main(main.java:37)
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(Var.java:359)
    at clojure.lang.AFn.applyToHelper(AFn.java:173)
    at clojure.lang.Var.applyTo(Var.java:476)
    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(RestFn.java:413)
    at user$eval__55.invoke(NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.eval(Compiler.java:4642)
    ... 10 more
~/clojure-workspace/cascade
$

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

@hlship
$ lein version
Leiningen 1.1.0 on Java 1.6.0_20 Java HotSpot(TM) 64-Bit Server VM
@technomancy
Owner

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.

@michalmarczyk
Collaborator

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.

@michalmarczyk
Collaborator

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

@technomancy
Owner

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

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

@michalmarczyk
Collaborator

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

@technomancy
Owner

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

@michalmarczyk
Collaborator

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

@technomancy
Owner

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.

@michalmarczyk
Collaborator

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

@technomancy
Owner

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

@michalmarczyk
Collaborator

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

@robwolfe robwolfe referenced this issue from a commit in robwolfe/leiningen
@michalmarczyk michalmarczyk Modified defproject to emit unquoted forms verbatim.
This means they are evaluated at runtime. See #49.
b18c417
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.