Howto Load Project Namespaces in Leiningen Plugins

250 views
Skip to first unread message

Timothy Washington

unread,
Aug 31, 2014, 5:03:26 PM8/31/14
to clo...@googlegroups.com
Ok, 

So I'm trying to write a leiningen plugin that takes some namespace arguments. Let's call it <myplugin>. This is a brand new plugin, so everything else is empty, and this value is present: {:eval-in-leiningen true}. My problem happens when, in the context of the project <myplugin> is acting on, the plugin code fails. I pass in some project-local namespace that I want to eval (which is definitely present). And I get this situation: 

Error

java.lang.Exception: No namespace: <mynamespace> found

Offending Code

(defn check-foreach-namespace [namespaces]

  (let [fnss (filter #(= Fubar (type (var-get %)))
                     (vals (ns-publics (symbol (first namespaces)))))]

    (println "filtered-namespace [" fnss "]")))

(defn myplugin [project & args]
  (check-foreach-namespace args))


So then, if I try to require the namespace being passed in, that too fails like so: 

Error: 

java.io.FileNotFoundException: Could not locate <mynamespace_file.clj> on classpath:

Offending Code:

(ns leiningen.chesk
  (:require [clojure.test.check :as tc]
            [clojure.test.check.generators :as gen]
            [clojure.test.check.properties :as prop]))

(defn check-foreach-namespace [namespaces]
  
  (let [nss (map #(require (symbol %)) namespaces)
         fnss (filter #(= clojure.test.check.generators.Generator (type (var-get %)))
                     (vals (ns-publics (symbol (first nss)))))]

    (println "filtered-namespace [" fnss "]")))

(defn myplugin [project & args]
  (check-foreach-namespace args))

Looking around, I thought I had found some relevant instruction on how to handle this gilardi scenario. So I tried to use eval-in-project with and without namespace prefix, and with several permutations of quoting. This is the error that occurs. 

Error: 

clojure.lang.Compiler$CompilerException: java.lang.ClassNotFoundException: leiningen.core.eval

Offending Code: 

(ns leiningen.chesk
  (:require [clojure.test.check :as tc]
            [clojure.test.check.generators :as gen]
            [clojure.test.check.properties :as prop]))

(defn check-foreach-namespace [namespaces]

  (let [fnss (filter #(= clojure.test.check.generators.Generator (type (var-get %)))
                     (vals (ns-publics (symbol (first namespaces)))))]

    (println "filtered-namespace [" fnss "]")))

(defn myplugin [project & args]
  (leiningen.core.eval/eval-in-project project
                                       (check-foreach-namespace args)
                                       (map #(require (symbol %)) args)))



So something that looks like it should be straightforward, is not working out as planned. I also can't quite see how other plugins are doing this. lein-midje seems a bit cryptic (see here and here). Are there any clearer examples?


Tim Washington 

Nelson Morris

unread,
Sep 1, 2014, 10:27:21 AM9/1/14
to Clojure
When writing lein plugins you have to think about if they should do work in lein's vm or the project's vm. In this case, since you want to load namespaces from the project, the work will need to be done in the project's vm.  This is where `leiningen.core.eval/eval-in-project` is used, to spin up the project's vm and run forms in it.

The arguments to `l.c.e/eval-in-project` will be the project map, a clojure form representing what to run, and a clojure form designed to allow avoidance of the gilardi scenario. The big question here becomes how to create the first form representing what to run.

Option 1: short syntax quote form + injecting dependency into project
Disadvantages:

1. Requires injecting a dependency into the project (could also be ok if the working code already exists in a seperate dep, marg vs lein-marg, ring-server vs lein-ring, etc).
2. Needs a require for the dependency's helper namespace as part of gilardi avoidance form.

Option 2: long syntax quote form
Disadvantages:

1. Have to think in syntax quote vs normal evaluation.
2. Needs a require for each namespace the form uses as part of the gilardi avoidance form (could be ok if only 1 or 2 ns used).


I generally recommend option 1 as easier to think about, and I believe it to be more common.

As for why `l.c.e/eval-in-project` was not found, I would hazard a guess based on limited info that it was not `require`d first.

I'll plug https://github.com/technomancy/leiningen/blob/master/doc/PLUGINS.md#code-evaluation and https://www.youtube.com/watch?v=uXebQ7RkhKs as containing similar information said in different ways, in case it comes across better there.

-
Nelson Morris


--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Timothy Washington

unread,
Sep 1, 2014, 10:59:30 PM9/1/14
to clo...@googlegroups.com
Awesome, thanks for the feedback. That all makes sense. And yes, the work will be done in the project's VM. 

I guess what I'm missing is something more basic. If you look at lein-midje or lein-spec, they're doing straightforward requires of eval-in-project. Yet, I'll get an error, even for a stripped down version. So take the below example. I'm locally installing the lein plugin, and running it from a separate project with "lein chesk fubar".  Still an error loading eval-in-project. I looked through some other leiningen projects. None of them are bundling some leiningen core library. I'm sure it'll be a simple switch that'll make sense, once I figure it out. 

error 

$ lein chesk bkell.domain.user-check 
clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to resolve symbol: eval-in-project in this context, compiling:(leiningen/chesk.clj:16:3)
 at clojure.lang.Compiler.analyze (Compiler.java:6464)
    clojure.lang.Compiler.analyze (Compiler.java:6406)
    clojure.lang.Compiler$InvokeExpr.parse (Compiler.java:3665)
    ...

project.clj 

(defproject chesk "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :license {:name "Eclipse Public License"
  :eval-in-leiningen true)

chesk.clj 

(ns leiningen.chesk
  (:require [leiningen.core.eval :refer [eval-in-project]]))

(defn check-foreach-namespace []
  (println "sanity check"))

(defn chesk [project & args]
  (eval-in-project project
                          `(check-foreach-namespace)
                          '()))


Hmm 

Tim Washington 

Timothy Washington

unread,
Sep 1, 2014, 11:18:13 PM9/1/14
to clo...@googlegroups.com
Or (ignoring the previous) getting even more basic...

$ tree
.
|-- LICENSE
|-- pom.xml
|-- project.clj
|-- README.md
`-- src
    `-- leiningen
        `-- zzz.clj

        
$ cat src/leiningen/zzz.clj 

(ns leiningen.zzz
  (:require [leiningen.core.eval :refer [eval-in-project]]))

(defn zz [] (println "Hi!"))
(defn zzz [project & args]
  (eval-in-project project
                   `(zz)
                   '(require 'leiningen.zzz)))


$ lein zzz

Exception in thread "main" java.io.FileNotFoundException: Could not locate leiningen/zzz__init.class or leiningen/zzz.clj on classpath: , compiling:(/tmp/form-init8179496390342821964.clj:1:4)
        at clojure.lang.Compiler.load(Compiler.java:7142)
        ...
        at clojure.main.main(main.java:37)
Caused by: java.io.FileNotFoundException: Could not locate leiningen/zzz__init.class or leiningen/zzz.clj on classpath: 
        at clojure.lang.RT.load(RT.java:443)
        ...
        at clojure.lang.Compiler.load(Compiler.java:7130)
        ... 11 more
Subprocess failed


Tim Washington 

Timothy Washington

unread,
Sep 1, 2014, 11:36:58 PM9/1/14
to clo...@googlegroups.com
Ok, I figured it out... :) Something in my "~/.lein/profiles.clj" was screwing things up. It was one of the repl-options, because when I turned those off, things just started to work. Hope this helps someone in future. 

#_:repl-options #_{:nrepl-middleware
                 [ritz.nrepl.middleware.javadoc/wrap-javadoc
                  ritz.nrepl.middleware.simple-complete/wrap-simple-complete
                  inspector.middleware/wrap-inspect]}}


Tim Washington 
Reply all
Reply to author
Forward
0 new messages