lynaghk / cljx

Write a portable codebase targeting Clojure/ClojureScript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Does cljx somehow affect my classpath?

samedhi opened this issue · comments

I have the following project.clj

(defproject samedhi.quizry "0.0.1-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/clojurescript "0.0-1878"]
                 [domina "1.0.1"]
                 [ch.qos.logback/logback-classic "1.0.7"
                  :exclusions [org.slf4j/slf4j-api]]
                 [io.pedestal/pedestal.app "0.2.1"]
                 [io.pedestal/pedestal.app-tools "0.2.1"]
                 [com.cemerick/piggieback "0.1.0"]
                 [org.clojure/test.generative "0.5.1" 
                   :exclusions [org.clojure/tools.namespace]]
                 [org.clojure/core.async "0.1.242.0-44b1e3-alpha"]]
  :repl-options
  {:port 5000
   :init-ns user
   :init (do
           (try
             (use 'io.pedestal.app-tools.dev)
             (catch Throwable t
               (println "ERROR: There was a problem loading io.pedestal.app-tools.dev")
               (clojure.stacktrace/print-stack-trace t)
               (println)))
           (start))
   :welcome (println "Welcome to pedestal-app! Run (tools-help) for help.")}

  :aliases {"test!"  ["do" "clean," "test"]
            "repl-headless"["trampoline""repl" ":headless"]}

  :repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}
  :min-lein-version "2.0.0"
  :source-paths ["app/src" "app/templates" "app/generated/cljs"]
  :test-paths   ["app/src" "test" "app/generated/clj"]
  :resource-paths ["config"]
  :target-path "out/"
  :clean-targets [:target-path "app/generated"]
  :main ^{:skip-aot true} io.pedestal.app-tools.dev
  :plugins [[com.keminglabs/cljx "0.3.1"]]
  :cljx {:builds [{:source-paths ["app/cljx"]
                   :output-path "app/generated/clj/samedhi/quizry"
                   :rules :clj}

                  {:source-paths ["app/cljx"]
                   :output-path "app/generated/cljs/samedhi/quizry"
                   :rules :cljs}

                  {:source-paths ["app/macro-cljx"]
                   :output-path "app/generated/clj/samedhi/quizry"
                   :rules
                   {:filetype "clj"
                    :features #{"clj"}
                    :transforms []}}

                  {:source-paths ["app/macro-cljx"]
                   :output-path "app/generated/cljs/samedhi/quizry"
                   :rules
                   {:filetype "clj"
                    :features #{"cljs"}
                    :transforms []}}]}
  :hooks [cljx.hooks])

And have 2 odd things to report.

Number 1:

As you can see, I separated my simple clojure and macro containing clojure into two separate areas. I compile them into "app/generated/cljs" and "app/generated/clj". When I am messing around in clojure nrepl, or when I for instance run my "lein test!" alias (both pure clojure code) then everything is fine.

However, if I try to start up the (cljs-repl) and then go to a browser to compile the app for development, I get a funny error. It is clear that the cljs files are being used, however, it appears the that the macros files for the clojure world are being used instead of the macro files for the clojurescript world. Stated differently, when I :require-macro's from within a .cljs file, the macros used are the macros that were meant for the clojure world, not for the clojurescript world. Or, to just put it directly, the .cljs files, which are under "app/generated/cljs" are using the equivalently named and namespaced files under "app/generated/clj" for their macros.

The confusing matter is that I don't seem to have included "app/generated/clj" in my :source-paths, so I don't see how it is the case that the clojurescript compiler can even see "app/generated/clj".

As you might guess, I can easily fix the issue by removing the "app/generated/clj" directory before I attempt to use the clojurescript compiler. Then it finds the macros (which are .clj files) in "app/generated/cljs" and everything works fine.

Number 2:

When I run "lein clean; lein repl-headless" I don't seem to be able to find the expected files when compiling for cljs. However, if I run "lein clean; lein cljx once; lein repl-headless" everything works fine.

This would seem to have something to do with the classpath being checked and found missing before cljx had a chance to generate "app/generated/*" ? Just a guess.

Finally, I want to note that I am using pedestal, it is possible that these issues have nothing to do with cljx at all, I will ask there as well.

You know, it was pretty obvious actually.

I was just so myopically stuck thinking about the fact that I couldn't (with cljx) change the filename that I didn't even think about the fact that I could put #+clj and #+cljs in different subdirectories. I do this now.

:cljx {:builds [{:source-paths ["app/cljx"]
                   :output-path "app/generated/clj/samedhi/quizry"
                   :rules :clj}

                  {:source-paths ["app/cljx"]
                   :output-path "app/generated/cljs/samedhi/quizry"
                   :rules :cljs}

                  {:source-paths ["app/macro-cljx"]
                   :output-path "app/generated/clj/samedhi/quizry"
                   :rules
                   {:filetype "clj"
                    :features #{"clj"}
                    :transforms []}}

                  {:source-paths ["app/macro-cljx"]
                   :output-path "app/generated/cljs/samedhi/quizry/macros"
                   :rules
                   {:filetype "clj"
                    :features #{"cljs"}
                    :transforms []}}]}

I then conditionally set the macros files to have the appropriate namespace, and also set the files that use said macros to refer to the "extended" namespace in the event that they are cljs files referencing cljs macros.

I still don't know why I am picking up the clj macros, but I at least have a solution for how to get around it.

So, cljx doesn't modify classpaths in any way, and certainly won't affect REPL classpaths. However, there are some circumstances (the details of which I don't know off the top of my head) where, if a directory a classpath doesn't exist when the corresponding classloader is initialized, that directory will effectively be blacklisted for later lookups. Why not always run lein do clean, cljx, repl, if you want all of your transformed sources to be available from the start?

FWIW, I personally don't use cljx for macro namespaces (which probably explains the lack of a :rules shortcut for producing .clj files that include #+cljs features). You can determine which environment for which your macro is being evaluated by looking at the value of &env at macro expansion time; I've sometimes used this macro-defining-macro to make those checks more clear. I don't know if that's necessarily better than either checking &env explicitly in the macro body, or better than using cljx as you are; again, just FWIW.

Are you still having an issue? I'm not entirely clear on the current status as of your last message. Is the project in question public?

Unfortunately it isn't public.

Issue 1:

Mostly I just wanted to hear that I could scratch cljx off of the list of things that might be causing the #+clj version of the code to be within the classpath (or to have been previously evaluated) even though I am in #+cljs's world.

The error that the #+clj code is loaded when compiling to clojurescript is still present.

However, I figured out a simple way to circumvent it by having #+cljs based macros placed into samedhi.quizry.macros.this-ns and #+clj macros placed in samedhi.quizry.this-ns. I then refer to the first from the generated ".cljs" files and the second from the generated ".clj" files.

That is a very interesting idea about using &env to figure out whether we are in clojure or clojurescripts world. In my case, the only difference between clojure and clojurescript based macros is that in clojurescript I am using cljs.core.async.macros/go and in clojure I am using clojure.core.async/go.

Issue 2:

It does sound like I am having the issue of the classpath being blacklisted from the point of view of the repl. I will just do as you suggest and do lein do clean, cljx, repl. Easily enough solved.

Thank you.

In case anyone is reading this, for issue number 2, I think the problem was that my project :alias had

"repl-headless" ["trampoline" "do" "clean," "cljx," "repl" ":headless"]

which causes the generated classpaths to be blacklisted for some reason, changing it to

"repl-headless" ["do" "clean," "cljx," "trampoline" "repl" ":headless"] 

seems to fix the issue.

Just now read your second-to-last comment, and now I understand why you were mucking with ns names. :-)

I have never seen trampoline be anything but a pain. It's useful if you're using Leiningen for production deployments, but otherwise can produce very strange effects. I'd try avoiding trampoline entirely to eliminate the classpath issue. Further, just see what the classpath is, i.e. (System/getProperty "java.class.path"). The ordering of entries and such may give you some clue as to where the rogue Clojure-related macros are coming from and why.

It doesn't look like there's a problem here, so closing. Do comment if you're still having trouble, etc.