5

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?

Sam Estep
  • 12,974
  • 2
  • 37
  • 75
  • 1
    I think the problem may be that you don't have an `:aot` statement in your project.clj to tell Leiningen that `example.core` needs to be compiled beforehand--before it's used. If some other class is using this one, then that's the problem. If not, then maybe the class has to exist before `-main` can be compiled. When you make an uberjar, Leiningen may compile in a different order. I'm not sure, but I'd bet it's something along those lines. – Mars Sep 19 '15 at 04:40

1 Answers1

3

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.

tungd
  • 14,467
  • 5
  • 41
  • 45
  • 1
    OK, thanks. Could you please give a concrete working example? – Sam Estep Sep 19 '15 at 11:11
  • I'm not sure `defrecord` will work in this case, because my class needs to extend `javafx.application.Application` (unless I'm misunderstanding how `defrecord` works). – Sam Estep Sep 19 '15 at 15:07
  • 3
    Excerpt from [these notes](https://github.com/mars0i/majure/blob/master/doc/ClojureMASONinteropTips.md): "There are five ways to make classes in Clojure: `defrecord`, `deftype`, `reify`, `proxy`, `gen-class`. ... All of the five class creation macros allow implementing interfaces. but only `proxy` and `gen-class` allow you to extend a class. ... `proxy` is potentially the slowest of the five methods, because it uses an extra level of indirection to execute methods, and it’s more limited than `gen-class` in many ways." Try messing around with the `:aot` specifications in project.clj. – Mars Sep 19 '15 at 20:13
  • If `proxy` is adequate to your needs, I recommend it over `gen-class`. `gen-class` can require you to be very verbose. It's not designed for convenience--it's designed to do (almost) everything that a Java class can do, and the work that `gen-class` requires of coders (and readers of code) can be close to what you'd need to do in Java. One of the beautiful things about Clojure is that it usually allows you to avoid details that Java requires you to specify. However, there's a lot that `proxy` won't do. Sometimes `gen-class` is the only option. Or Java. – Mars Sep 19 '15 at 20:20
  • @Mars Thanks, I'll take another look at `:aot`. `proxy` is the first thing I tried; unfortunately, I can't use it because JavaFX applications need to be started through [`Application.launch`](http://docs.oracle.com/javase/8/javafx/api/javafx/application/Application.html#launch-java.lang.Class-java.lang.String...-), which requires a full class. – Sam Estep Sep 19 '15 at 20:58