Why can't Leiningen always use my :gen-class p

2019-04-30 12:54发布

问题:

Let's say I create a new Leiningen project (lein new app example) and add some code in example/src/example/core.clj that makes use of :gen-class:

(ns example.core
  (:gen-class :extends javafx.application.Application))

(defn -start [this stage]
  (.show stage))

(defn -main [& args]
  (javafx.application.Application/launch example.core args))

If I then create a JAR (lein uberjar) and run it, everything works fine. However, if I instead try to run my app directly (lein run), I get a ClassNotFoundException. In addition, if I open a REPL (lein repl), I first get the same error as before, but after running this code:

(compile 'example.core)

the error no longer appears in lein run or lein repl.

Could someone please explain to me what exactly is going on here, and how I can run my app directly without needing to manually compile my code from a REPL?

Edit: After fooling around a bit more, I found that the solution to this problem is to add

:aot [example.core]

to project.clj. (Thanks @Mars!) I'm still confused, though, because I had previously tried simply removing ^:skip-aot, which (according to the docs) should work:

This will be AOT compiled by default; to disable this, attach ^:skip-aot metadata to the namespace symbol.

But it doesn't. Why?

Another edit (if I should split any of this into a separate question, let me know and I'll do so): I've been playing with hyphens (lein new app my-example), and weird stuff has been happening. This doesn't work:

(ns my-example.core
  (:gen-class :extends javafx.application.Application))
;; ...
(defn -main [& args]
  (javafx.application.Application/launch my-example.core args))

But this does:

(ns my-example.core
  (:gen-class
    :name my-example.Core
    :extends javafx.application.Application))
;; ...
(defn -main [& args]
  (javafx.application.Application/launch my-example.Core args))

So my class name can either start with a lowercase letter (example.core) or contain a hyphen (my-example.Core), but not both? I really don't get it.

And finally, lein uberjar fails on that final example (with the explicit :name), because

Warning: The Main-Class specified does not exist within the jar. It may not be executable as expected. A gen-class directive may be missing in the namespace which contains the main method.

As far as I can tell, the only way to fix that is to split the Application subclass into a separate namespace. Is there another way?

回答1:

Agreed with @Mars, the problem is that lein run does not AOT the example.core namespace. The default Leiningen template made the example.core non AOT:

(defproject example "0.1.0-SNAPSHOT"
  ...
  :main ^:skip-aot example.core
  ...)

My best guess is that you could define your app using defrecord and use that as a class instead of the namespace.