Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lein fails to add jars to uberjar

I am building a simple web app, which is dependent on a couple of pre-compiled jars and which I want to deploy on Heroku. I put the jars into a resources folder and added the following lines to my project.clj:

:resource-paths ["resources/jsesh-6.5.5.jar"
                 "resources/jseshGlyphs-6.5.5.jar"
                 "resources/java-cup-11b-runtime.jar"
                 "resources/java-cup-11b.jar"
                 "resources/qenherkhopeshefUtils-6.5.5.jar"]

Now I am able to run the project using lein run -m hieroglyphs.web; however, when I compile everything with lein uberjar and try

java -cp ./target/hieroglyphs-standalone.jar clojure.main -m hieroglyphs.web

The program starts up, but then crashes with a java.lang.NoClassDefFoundError when it tries to access one of the classes defined in those jars:

java.lang.NoClassDefFoundError: jsesh/mdcDisplayer/preferences/DrawingSpecification

Should I do something extra so that classes defined in the jars are accessible after compilation?

All the code can be found here: https://github.com/macleginn/jsesh-web

like image 786
macleginn Avatar asked Jan 28 '23 20:01

macleginn


2 Answers

Just having a resources directory doesn't help. You're going to need a proper local Maven repository. Luckily, making one isn't that hard.

Steps:

  1. Install Maven if needed
  2. Make a lib folder in your source repository
  3. Add :repositories {"local" "file:lib"} to your project.clj
  4. For each of your dependencies run something like mvn deploy:deploy-file -Dfile=resources/jsesh-6.5.5.jar -DartifactId=jsesh -Dversion=6.5.5 -DgroupId=jsesh -Dpackaging=jar -Durl=file:lib (that works for the jsesh jar). Note in particular the artifactId, groupId and version
  5. Add an appropriate :dependencies item for each jar. e.g. for the one I did in Step 4, [jsesh/jsesh "6.5.5"]
  6. Repeat steps 4 and 5 for each dependency

You'll need to commit the lib folder to source control, but can now delete the resources folder and the :resource-paths bit from project.clj

(Heavily based on notes from https://gist.github.com/stuartsierra/3062743)

like image 74
Tom Parker-Shemilt Avatar answered Feb 03 '23 17:02

Tom Parker-Shemilt


You should change your java invocation to use the -jar option like so:

~/expr/rundir > java -jar ./calc-0.1.0-SNAPSHOT-standalone.jar 
main - enter
  (ac/add2 3 5) => 8
main - leave

for code that looks like this:

calc
├── project.clj
├── resources
│   └── adder.jar
└── src
    ├── calc
    │   └── core.clj

~/expr/calc > cat src/calc/core.clj 
(ns calc.core
  "Contains the core functions for namespace `calc.core`."
  (:require [adder.core :as ac] )
  (:gen-class))

(defn -main []
  (println "main - enter")
  (println (ac/add2 3 5))
  (println "main - leave"))

The file adder.jar was created using lein jar from another project with a single function:

(ns adder.core)
(defn add2 [x y]
  (+ x y))

The resulting file was renamed "adder.jar" and plopped into the resources dir of the calc project. Looking at project.clj:

(defproject calc  "0.1.0-SNAPSHOT"
  :description    "FIXME: write description"
  :url            "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url  "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [
    [org.clojure/clojure "1.9.0"]
    [org.clojure/test.check "0.9.0"]
    [prismatic/schema "1.1.7"]
    [tupelo "0.9.71"]
  ]
  :profiles {:dev {:dependencies []
                   :plugins [
                     [com.jakemccrary/lein-test-refresh "0.22.0"] ] }
             :uberjar {:aot :all}}
  :global-vars {*warn-on-reflection* false}
  :main ^:skip-aot calc.core

  :source-paths       ["src"]
  :test-paths         ["src"]
  :resource-paths     ["resources/adder.jar"]
  :target-path        "target/%s"
  :jvm-opts           ["-Xms500m" "-Xmx2g"]
)

The trick is in the line for :resource-paths, like so:

:resource-paths     [ "resources/adder.jar" ]

You need to list each *.jar file separately as a string in the vector. Note that the following will not work:

:resource-paths     [ "resources" ]            ; does not find *.jar files
:resource-paths     [ "resources/*.jar" ]      ; wildcards do not work
:resource-paths     [  resources/adder.jar ]   ; without quotes fails

We can then create a uberjar for the calc project, which will include the stuff from adder.jar:

~/expr/calc > lein uberjar
Compiling _bootstrap
Compiling calc.core
Compiling tst.calc.core
Created /home/alan/expr/calc/target/uberjar/calc-0.1.0-SNAPSHOT.jar
Created /home/alan/expr/calc/target/uberjar/calc-0.1.0-SNAPSHOT-standalone.jar

You want to use the *-standalone.jar version for your uberjar. We copy it to an empty dir to verify it works:

~/expr/calc > mkdir -p ../rundir
~/expr/calc > cp target/uberjar/calc-0.1.0-SNAPSHOT-standalone.jar ../rundir

~/expr/calc > cd ../rundir
~/expr/rundir > ls -al
total 11744
drwxrwxr-x 2 alan alan     4096 Jan 14 19:22 .
drwxrwxr-x 5 alan alan     4096 Jan 14 19:22 ..
-rw-rw-r-- 1 alan alan 12016027 Jan 14 19:30 calc-0.1.0-SNAPSHOT-standalone.jar

~/expr/rundir > java -jar calc-0.1.0-SNAPSHOT-standalone.jar 
main - enter
  (ac/add2 3 5) => 8
main - leave
like image 21
Alan Thompson Avatar answered Feb 03 '23 19:02

Alan Thompson