ijt
ijt

Reputation: 3825

How can I run a jar from a fresh lein app without using uberjar?

I'm finding that a fresh app from lein can be run as an uberjar but not as a jar. Is it supposed to work this way, or is something wrong with my setup?

$ lein new app feedme
$ cd feedme

Lein uberjar works just fine:

$ lein uberjar
Compiling feedme.core
Created .../feedme/target/uberjar+uberjar/feedme-0.1.0-SNAPSHOT.jar
Created .../feedme/target/uberjar/feedme-0.1.0-SNAPSHOT-standalone.jar

$ java -jar target/uberjar/feedme-0.1.0-SNAPSHOT-standalone.jar
Hello, World!

Lein jar fails:

$ lein jar
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.
Created .../feedme/target/feedme-0.1.0-SNAPSHOT.jar
$ java -jar target/feedme-0.1.0-SNAPSHOT.jar 
Error: Could not find or load main class feedme.core

There's a :gen-class directive in the ns section of core.clj, so I'm not sure what the problem is:

$ cat src/feedme/core.clj 
(ns feedme.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

Upvotes: 1

Views: 925

Answers (3)

Chris Murphy
Chris Murphy

Reputation: 6509

Look inside any jar (they are just zip files) at its MANIFEST.MF file. That file will tell you the main class. So if you make sure MANIFEST.MF gets into your jar file it will be self-runnable. The jar file contains .class files and refers to other jar files - so it can make up its own classpath as long as the referred to jar files are accessible.

I've just looked inside a few jar files. MANIFEST.MF is in META-INF. Here's an example contents:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.7.0_76-b13 (Oracle Corporation)
Main-Class: com.cmts.client.applic.upgrader.UsersGUI
Date-Created: 15/07/2015 02:23 AM
Class-Path: ../lib/config-1.2.1.jar          
            ../lib/scala-library-2.11.5.jar                      
            ../lib/org.springframework.beans-3.1.0.RC2.jar       
            ../lib/org.springframework.aop-3.1.0.RC2.jar

This one was created by Ant. I don't know if lein does the same thing. But even if it does not there is no reason you can't have META-INF\MANIFEST.MF being put into the jar. Then you will have a self-runnable jar file.

later edit - lein manifest example:

:manifest {"Class-Path" "../lib/clojure-1.8.0.jar"}

Upvotes: 3

Alex Filatov
Alex Filatov

Reputation: 5052

You get Error: Could not find or load main class feedme.core because the jar doesn't contain feedme/core.class file. If you run jar -tf target/feedme-0.1.0-SNAPSHOT.jar you'll see that it doesn't contain any .class file at all. lein jar doesn't compile sources by default. This is intended behaviour: Clojure compiles code on the fly unless AOT compilation is requested. In case of uberjar AOT compilation is requested in project.clj file via {:uberjar {:aot :all}} profile (you can do a small experiment by changing uberjar profile to an empty one {:uberjar {}}, then lein uberjar && java -jar target/uberjar/feedme-0.1.0-SNAPSHOT-standalone.jar will result in exactly the same error).

You can run lein with-profile uberjar jar to compile your code. But if you run java -jar target/uberjar+uberjar/feedme-0.1.0-SNAPSHOT.jar you'll get another error: java.lang.NoClassDefFoundError: clojure/lang/IFn. As pointed by noisesmith lein jar doesn't package any dependencies including Clojure itself. So you must explicitly specify Clojure in the classpath: java -cp "$HOME/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:target/uberjar+uberjar/feedme-0.1.0-SNAPSHOT.jar" feedme.core. This will print Hello, World! (assuming you have Clojure 1.8 in your local Maven repo, you might need to fetch it first or change path/version).

Without compilation you can run jar generated with lein jar with: java -cp "$HOME/.m2/repository/org/clojure/clojure/1.8.0/clojure-1.8.0.jar:target/feedme-0.1.0-SNAPSHOT.jar" clojure.main --main feedme.core (again your path/version of Clojure's jar can be different).

Upvotes: 2

noisesmith
noisesmith

Reputation: 20194

Lein jar only wraps up your own code in a jar, such that it could be used by another project for example. It does not include any of your dependencies (which will be declared in your pom.xml), including clojure itself. Without a valid class to run (eg. clojure.main), it can't run your code. And the next problem would be your require statements that refer to definitions in artifacts that are not on your classpath (unless some program loaded your pom.xml and knew how to fulfill those dependencies).

Upvotes: 4

Related Questions