I have a Clojure library that has two gen-class directives. When I run lein run, there are no issues. However, when I run lein uberjar, I get errors:
$ lein uberjar
Compiling 6 source files to /Users/frank/src/user/target/uberjar/classes
Compiling user.common
Compiling user.core
java.lang.ClassNotFoundException: user.server.UserAuthenticationServer, compiling:(user/core.clj:15:30)
Exception in thread "main" java.lang.ClassNotFoundException: user.server.UserAuthenticationServer, compiling:(user/core.clj:15:30)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6926)
.....
at clojure.lang.Compiler.analyze(Compiler.java:6701)
Caused by: java.lang.ClassNotFoundException: user.server.UserAuthenticationServer
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
...
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6919)
... 86 more
In addition to the generated java files, there is the project.clj, server.clj, and core.clj.
project.clj
(defproject user "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.9.0-alpha14"]
[io.grpc/grpc-core "1.7.0"]
[io.grpc/grpc-netty "1.7.0"
:exclusions [io.grpc/grpc-core]]
[io.grpc/grpc-protobuf "1.7.0"]
[io.grpc/grpc-stub "1.7.0"]]
:main ^:skip-aot user.core
:aot [user.server]
:target-path "target/%s"
:source-paths ["src/clj"]
:java-source-paths ["src/generated/proto"
"src/generated/grpc"]
:profiles {:uberjar {:aot :all}})
core.clj
(ns user.core
(:import [io.grpc Server ServerBuilder])
(:gen-class))
(defonce start-server-atom (atom nil))
(def port 8080)
(defn start-server []
(when-not #start-server-atom
(reset! start-server-atom
(-> (ServerBuilder/forPort port)
(.addService (new user.server.UserAuthenticationServer))
.build
.start
.awaitTermination))))
(defn -main
[& args]
(start-server))
server.clj
(ns user.server
(:gen-class
:main false
:name user.server.UserAuthenticationServer
:extends xyz.skroo.user.UserAuthenticationGrpc$UserAuthenticationImplBase))
(defn -startUserAuthentication [this req res]
(.onNext res req)
(.onCompleted res))
It's weird because this was working and I think the compile-time order changed and now I cannot generate a standalone jar.
:profiles {:uberjar {:aot :all}} means that when you run uberjar it will try to compile all Namespaces. When you do lein run it only compiles the namespace in the :aot key.
Try to update the uberjar profile to only aot the server namespace and see if that works.
Related
On a fresh lein new re-frame bc +handler app, if I lein uberjar or lein jar it doesnt seem to set the main class correctly. At the end of the compillation it tells me
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, or the namespace has not been AOT-compiled.
Here is the server.clj and project.clj that is created using the re-frame +handler template:
server.clj:
(ns bc.server
(:require [bc.handler :refer [handler]]
[config.core :refer [env]]
[ring.adapter.jetty :refer [run-jetty]])
(:gen-class))
(defn -main [& args]
(let [port (Integer/parseInt (or (env :port) "3000"))]
(run-jetty handler {:port port :join? false})))
project.clj:
(defproject bc "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.1"]
[org.clojure/clojurescript "1.10.597"
:exclusions [com.google.javascript/closure-compiler-unshaded
org.clojure/google-closure-library
org.clojure/google-closure-library-third-party]]
[thheller/shadow-cljs "2.8.83"]
[reagent "0.8.1"]
[re-frame "0.10.9"]
[compojure "1.6.1"]
[yogthos/config "1.1.7"]
[ring "1.7.1"]]
:plugins [
[lein-shell "0.5.0"]]
:min-lein-version "2.5.3"
:source-paths ["src/clj" "src/cljs"]
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
:shell {:commands {"open" {:windows ["cmd" "/c" "start"]
:macosx "open"
:linux "xdg-open"}}}
:aliases {"dev" ["with-profile" "dev" "do"
["clean"]
["run" "-m" "shadow.cljs.devtools.cli" "watch" "app"]]
"prod" ["with-profile" "prod" "do"
["clean"]
["run" "-m" "shadow.cljs.devtools.cli" "release" "app"]]
"build-report" ["with-profile" "prod" "do"
["clean"]
["run" "-m" "shadow.cljs.devtools.cli" "run" "shadow.cljs.build-report" "app" "target/build-report.html"]
["shell" "open" "target/build-report.html"]]
"karma" ["with-profile" "prod" "do"
["clean"]
["run" "-m" "shadow.cljs.devtools.cli" "compile" "karma-test"]
["shell" "karma" "start" "--single-run" "--reporters" "junit,dots"]]}
:profiles
{:dev
{:dependencies [[binaryage/devtools "0.9.11"]]}
:prod { }
:uberjar {:source-paths ["env/prod/clj"]
:omit-source true
:main bc.server
:aot [bc.server]
:uberjar-name "bc.jar"
:prep-tasks ["compile" ["prod"]]}
})
It does generate a jar file when I lein uberjar but when I try to run it it errors out telling me that it does not include a main class.
What am I doing incorrectly?
Your uberjar profile calls ["compile" ["prod"]] in :prep-tasks. Your "prod" alias calls "clean" and "target" is listed in :clean-targets.
In essence, your uberjar deletes your compiled Clojure code.
You need to tell leiningen what namespace has your main function. In project.clj:
:main my.service.runner
from:
https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L222
In my project.cli I have a dependency on clj-http that is used for tests only, with clj-http.client.
When I look at the uberjar file created for that project, I see that the class fils associated with this dependency are included. That makes the jar file bigger than it need be.
So, is there a way to define a dependency in clojure such that it is only used during tests, and is not included in the uberjar?
I know that I could do this in a pom.xml, but the pom.xml is generated when using clojure, so I only have recourse to something that works in the project.clj file.
To add more colour, my project.clj looks like this
(defproject aproject "0.1.0-SNAPSHOT"
:description "A project"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/data.json "0.2.6"]
[compojure "1.5.0"]
[hiccup "1.0.5"]
[http-kit "2.1.18"]
[org.clojure/tools.logging "0.3.1"]
[ch.qos.logback/logback-classic "1.1.7"]
[ring/ring-devel "1.4.0"]]
:plugins [[lein-ring "0.9.7"]]
:ring {:handler aproject.core/app-routes}
:main ^:skip-aot aproject.core
:target-path "target/%s"
:resources-paths ["resources/"]
:profiles {:uberjar {:aot :all}
:dev {:dependencies [[peridot "0.4.3"]
[midje "1.8.3"]]
:plugins [[lein-midje "3.2.1"]]
:aliases {"test" ["midje"]}}
:test {:dependencies [[clj-http "3.5.0"]
[midje "1.8.3"]]
:plugins [[lein-midje "3.2.1"]]}
})
I am running the tests like this:
lein with-profile test midje :filters dt
What I am seeing is:
Exception in thread "main" java.io.FileNotFoundException: Could not locate midje/util/ecosystem__init.class or midje/util/ecosystem.clj on classpath., compiling:(/private/var/folders/7l/0fwd_7ls1m19q3_z1_tgg1w80000gn/T/form-init7253661442775183594.clj:1:125)
at clojure.lang.Compiler.load(Compiler.java:7391)
The filter probably does not affect this, but just in case the test looks like this:
(ns aproject.deployment.core
(:require [midje.sweet :refer :all]
[clj-http.client :as client]
[peridot.core :as p]
[clojure.data.json :as json]
[front-end.core :as fe]))
(facts "'aproject' deployed" :dt
(let [response (client/get "http://localhost:8080/ping")]
(response :status) => 200
))
I can see that the test profile is being triggered, and I seem to have the dependency for midje, and the plugin, but ...?
Thanks
Nathan
Add it to the :test profile, then run your tests with lein with-profile test midje :filters dt. Generate your uberjar as usual, lein uberjar and it shouldn't include the extra files.
I'm writing a web app which uses Overtone. When I try running the app using lein run or when I try to start the repl while in the project's directory I get the same error: java.lang.ExceptionInInitializerError at clojure.main.<clinit>(main.java:20) Caused by: java.lang.Exception: Server needs to be connected before you can perform this action.
It seems to me that both of those actions make all the files in my project compile. Is there any way to compile the namespaces which use Overtone after I run the server? Or maybe this isn't the issue, and the problem is coming from something else?
Here is my project.clj file:
(defproject comusic "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:dependencies [[buddy "1.3.0"]
[compojure "1.5.2"]
[conman "0.6.3"]
[cprop "0.1.10"]
[funcool/struct "1.0.0"]
[luminus-immutant "0.2.3"]
[luminus-migrations "0.3.0"]
[luminus-nrepl "0.1.4"]
[markdown-clj "0.9.98"]
[metosin/muuntaja "0.1.0"]
[metosin/ring-http-response "0.8.2"]
[mount "0.1.11"]
[org.clojure/clojure "1.8.0"]
[org.clojure/tools.cli "0.3.5"]
[org.clojure/tools.logging "0.3.1"]
[org.postgresql/postgresql "42.0.0"]
[org.webjars.bower/tether "1.4.0"]
[org.webjars/bootstrap "4.0.0-alpha.5"]
[org.webjars/font-awesome "4.7.0"]
[org.webjars/jquery "3.1.1"]
[org.webjars/webjars-locator-jboss-vfs "0.1.0"]
[ring-webjars "0.1.1"]
[ring/ring-core "1.6.0-RC1"]
[ring/ring-defaults "0.2.3"]
[selmer "1.10.7"]
[overtone "0.10.1"]]
:min-lein-version "2.0.0"
:jvm-opts ["-server" "-Dconf=.lein-env"]
:source-paths ["src/clj"]
:test-paths ["test/clj"]
:resource-paths ["resources"]
:target-path "target/%s/"
:main ^:skip-aot comusic.core
:migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")}
:plugins [[lein-cprop "1.0.1"]
[migratus-lein "0.4.4"]
[lein-immutant "2.1.0"]]
:profiles
{:uberjar {:omit-source true
:aot :all
:uberjar-name "comusic.jar"
:source-paths ["env/prod/clj"]
:resource-paths ["env/prod/resources"]}
:dev [:project/dev :profiles/dev]
:test [:project/dev :project/test :profiles/test]
:project/dev {:dependencies [[prone "1.1.4"]
[ring/ring-mock "0.3.0"]
[ring/ring-devel "1.5.1"]
[pjstadig/humane-test-output "0.8.1"]]
:plugins [[com.jakemccrary/lein-test-refresh "0.18.1"]]
:source-paths ["env/dev/clj"]
:resource-paths ["env/dev/resources"]
:repl-options {:init-ns user}
:injections [(require 'pjstadig.humane-test-output)
(pjstadig.humane-test-output/activate!)]}
:project/test {:resource-paths ["env/test/resources"]}
:profiles/dev {}
:profiles/test {}})
EDIT: After booting a SC server and trying to connect to it in my main function I still get the same error. File which contains main function:
(ns comusic.core
(:require [comusic.handler :as handler]
[luminus.repl-server :as repl]
[luminus.http-server :as http]
[luminus-migrations.core :as migrations]
[comusic.config :refer [env]]
[clojure.tools.cli :refer [parse-opts]]
[clojure.tools.logging :as log]
[mount.core :as mount]
[overtone.core :as overtone])
(:gen-class))
(def cli-options
[["-p" "--port PORT" "Port number"
:parse-fn #(Integer/parseInt %)]])
(mount/defstate ^{:on-reload :noop}
http-server
:start
(http/start
(-> env
(assoc :handler (handler/app))
(update :port #(or (-> env :options :port) %))))
:stop
(http/stop http-server))
(mount/defstate ^{:on-reload :noop}
repl-server
:start
(when-let [nrepl-port (env :nrepl-port)]
(repl/start {:port nrepl-port}))
:stop
(when repl-server
(repl/stop repl-server)))
(defn stop-app []
(doseq [component (:stopped (mount/stop))]
(log/info component "stopped"))
(shutdown-agents))
(defn start-app [args]
(doseq [component (-> args
(parse-opts cli-options)
mount/start-with-args
:started)]
(log/info component "started"))
(.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
(defn -main [& args]
(cond
(some #{"migrate" "rollback"} args)
(do
(mount/start #'comusic.config/env)
(migrations/migrate args (select-keys env [:database-url]))
(System/exit 0))
:else
(do
(overtone/connect-external-server 57110)
(start-app args))))
I am trying to :gen-class a Servlet
This is my code:
(ns test.test
(:import (java.io PrintWriter) (javax.servlet.http HttpServlet))
(:gen-class :name test.TestServlet :extends javax.servlet.http.HttpServlet))
(defn -doGet[request response]
(let [wrtr (.getWriter response)]
(.println wrtr "hello from clojure")))
it can't be compiled by Lein
it said Exception in thread "main" java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet (Test.clj:1)
I already modified the :library-path in Lein as ":library-path "/home/long/workspaces/spring/LongHDi/war/WEB-INF/lib" but it didn't work.
Do you have any idea why?
I am trying to work with Google App Engine. The servlet class I want to extend is already in the lib folder I specified.
Which version of lein are you using ?
I downloaded jetty from here, and lein version1 worked for me with project.clj
(defproject st2 "1.0.0-SNAPSHOT"
:description "FIXME: write description"
:library-path "/Users/Niko/Downloads/jetty-hightide-8.1.7.v20120910/lib"
:aot [st2.core]
:dependencies [[org.clojure/clojure "1.3.0"]])
with st2.core the same as your code:
(ns st2.core
(:import (java.io PrintWriter) (javax.servlet.http HttpServlet))
(:gen-class :name test.TestServlet :extends javax.servlet.http.HttpServlet))
(defn -doGet[request response]
(let [wrtr (.getWriter response)]
(.println wrtr "hello from clojure")))
If you are using lein2, :library-path is not supported so I suspect you would have to add the dependencies "a-la-maven" and add them to your project dependencies.
I'd like to have two main classes (or more) with leiningen, and then be able to choose which one at the java command line. For example I have:
(ns abc (:gen-class))
(defn -main [] (println "abc"))
(ns def (:gen-class))
(defn -main [] (println "def"))
With a project.clj having:
(defproject my-jar "0.0.1"
:description "test"
:dependencies [
]
:main abc)
Then I build with lein uberjar, and run:
java -cp my-jar-0.0.1-standalone.jar abc
java -cp my-jar-0.0.1-standalone.jar def
I get it that when I specified :main abc in the project.clj it was calling that out as the main-class in the manifest, but I couldn't get it to run without putting something. But either way when I try to run the 'def' main, I get a class not found:
Exception in thread "main" java.lang.NoClassDefFoundError: def
This works at least with leiningen 2.0+
(defproject my-jar "0.0.1"
:description "test"
:dependencies [
]
:profiles {:main-a {:main abc}
{:main-b {:main def}}
:aliases {"main-a" ["with-profile" "main-a" "run"]
"main-b" ["with-profile" "main-b" "run"]})
Then you can run each main like so:
lein main-a
lein main-b
Which expands to this:
lein with-profile main-a run
lein with-profile main-b run
I'm using this in one of my projects and it works perfectly.
I added :aot [abc def] to the project.clj to generate compiled code and it worked.
What worked for me in both lein 2.7.0's run task as well as from the resulting uberjar is as follows...
project.clj:
(defproject many-mains "0.1.0-SNAPSHOT"
:description "Project containing multiple main methods"
:dependencies [[org.clojure/clojure "1.8.0"]]
:main nil
:target-path "target/%s"
:profiles {:main-abc {:main many-mains.abc}
:main-def {:main many-mains.def}
:main-ghi {:main org.rekdev.mm.ghi}
:core {:main many-mains.core}
:uberjar {:aot :all}})
For source like...
$ cat src/many_mains/abc.clj
(ns many-mains.abc
(:gen-class))
(defn -main
""
[& args]
(println "Hello, from many-mains.abc!"))
This lets lein run work like...
$ lein with-profile main-abc run
Hello, from many-mains.abc!
From the command line the '-' in many-mains needs to become a '_' which makes it a legal Java classname.
$ java -cp target/uberjar/many-mains-0.1.0-SNAPSHOT-standalone.jar many_mains.abc
Hello, from many-mains.abc!
There seems to have been some behavior changes between Lein 2.7.0 and prior around the effect of :main nil on the MANIFEST.MF. What I've got here works like a champ in Lein 2.7.0. The full source is at https://github.com/robertkuhar/many-mains