I have the following code in the file project.clj:
(defproject pinger "0.0.1-SNAPSHOT"
:description "A website availability tester"
:dependencies [[org.clojure/clojure "1.3.0"]]
:main pinger.core)
(ns pinger.core
(:import (java.net URL HttpURLConnection))
(:gen-class))
(defn response-code [address]
(let [conn ^HttpURLConnection (.openConnection (URL. address))
code (.getResponseCode conn)]
(when (< code 400)
(-> conn .getInputStream .close))
code))
(defn available? [address]
(= 200 (response-code address)))
(defn -main []
(let [addresses '("http://google.com"
"http://amazon.com"
"http://google.com/badurl")]
(while true
(doseq [address addresses]
(println (available? address)))
(Thread/sleep (* 1000 60)))))
I create an uberjar:
C:\Documents and Settings\vreinpa\My Documents\Books\ProgrammingClojure\code\src
\pinger>lein uberjar
Cleaning up.
Copying 1 file to C:\Documents and Settings\vreinpa\My Documents\Books\Programmi
ngClojure\code\src\pinger\lib
Warning: *classpath* not declared dynamic and thus is not dynamically rebindable
, but its name suggests otherwise. Please either indicate ^:dynamic *classpath*
or change the name.
Copying 1 file to C:\Documents and Settings\vreinpa\My Documents\Books\Programmi
ngClojure\code\src\pinger\lib
Created C:\Documents and Settings\vreinpa\My Documents\Books\ProgrammingClojure\
code\src\pinger/pinger-0.0.1-SNAPSHOT.jar
Including pinger-0.0.1-SNAPSHOT.jar
Including clojure-1.3.0.jar
Created C:\Documents and Settings\vreinpa\My Documents\Books\ProgrammingClojure\
code\src\pinger/pinger-0.0.1-SNAPSHOT-standalone.jar
I then try to run that uberjar and get the following error:
C:\Documents and Settings\vreinpa\My Documents\Books\ProgrammingClojure\code\src
\pinger>java -jar pinger-0.0.1-SNAPSHOT-standalone.jar
Exception in thread "main" java.lang.NoClassDefFoundError: pinger/core
Caused by: java.lang.ClassNotFoundException: pinger.core
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
Could not find the main class: pinger.core. Program will exit.
What am I doing wrong here?
As I said in response to your other question, the project.clj file is not the place to put source code - project.clj is loaded by leiningen to set up your project configuration and putting arbitrary code there is not guaranteed to work at all, and will certainly mess up the loading of namespaces you defined in there. follow the conventions for source libs and put the files under the src directory in your project tree.
$ lein new pinger
$ cd pinger
$ lein deps
pinger/project.clj
(defproject pinger "0.0.1-SNAPSHOT"
:description "A website availability tester"
:dependencies [[org.clojure/clojure "1.3.0"]]
:main pinger.core)
pinger/src/pinger/core.clj
(ns pinger.core
(:import (java.net URL HttpURLConnection))
(:gen-class))
(defn response-code [address]
(let [conn ^HttpURLConnection (.openConnection (URL. address))
code (.getResponseCode conn)]
(when (< code 400)
(-> conn .getInputStream .close))
code))
(defn available? [address]
(= 200 (response-code address)))
(defn -main []
(let
[addresses
'("http://google.com"
"http://amazon.com"
"http://google.com/badurl")]
(while true
(doseq [address addresses]
(println (available? address)))
(Thread/sleep (* 1000 60)))))
$ cd pinger
$ lein uberjar
absolute path
$ java -jar /vagrant/MyClojure/pinger/target/pinger-0.0.1-SNAPSHOT-standalone.jar
or
relative path from project.clj
$ java -jar ./target/pinger-0.0.1-SNAPSHOT-standalone.jar
Related
EDIT: Added more information at the end. Also, got war to run with jetty-runner.jar (not with tomcat however)
I am using boot as the build tool for my clojure application.
This is the first application I have written with cljs, cljc and clj code (along with sass) all in one project.
My build.boot has the following task-options
(def db-opts (:db (clojure.edn/read-string (slurp "resources/config.edn"))))
(task-options!
pom {:project 'gcr
:version "0.1"}
cljs {:optimizations :simple}
uber {:as-jars true}
war {:file "gcr.war"}
ragtime {:database (str "jdbc:"
(:dbtype db-opts) "://"
(:user db-opts) ":"
(:password db-opts) "#"
(:host db-opts) ":"
(:port db-opts) "/"
(:dbname db-opts))}
test {:include #"gcr.test"
:junit-output-to "junit-out"})
my build task is defined as follows:
(deftask build
"Builds an uberjar of this project that can be run with java -jar"
[]
(comp
(aot :all true ;; :namespace #{'gcr.server}
)
(pom)
(cljs :optimizations :simple)
(sass)
(uber)
(jar :file "gcr.jar" :main 'gcr.server)
(sift :include #{#"gcr.jar"})
(target)))
When i run boot build the project builds fine without any errors.
But when I run java -jar target/gcr.jar
I get the following:
$ java -jar target/gcr.jar
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true
Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/Var
at gcr.server.<clinit>(Unknown Source)
Caused by: java.lang.ClassNotFoundException: clojure.lang.Var
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
I have been looking at this for about 24 hours now and still unable to figure out how to get this working. (I finally was able to build a war that works with jetty-runner but not on tomcat)
----------
Some more info:
My boot.properties is
BOOT_CLOJURE_NAME=org.clojure/clojure
BOOT_CLOJURE_VERSION=1.10.0
BOOT_VERSION=2.8.2
and I am using openjdk version "1.8.0_202"
I checked if the jar file built has the clojure jar using jar tf target/gcr.jar and the following line is listed:
7c506484bc48541ffa5dcb4d9366a8a2-clojure-1.10.0.jar
My IFDE works fine, I have the following dev task defined as follows:
(deftask dev
"Launch Immediate Feedback Development Environment"
[]
(comp
(serve :handler 'gcr.core/app ;; ring handler
:resource-root "public" ;; root classpath
:reload true) ;; reload ns
(watch)
(reload)
(cljs-repl) ;; before cljs task
(cljs)
(sass)
(target :dir #{"public"})))
set-env! part is as follows:
(set-env!
:source-paths #{"src/scss" "src/clj" "src/cljs" "src/cljc"}
:resource-paths #{"html" "resources"}
:dependencies '[[org.clojure/clojure "1.10.0"]
[org.clojure/clojurescript "1.10.520"]
;; server
[compojure "1.6.1"]
[ring/ring-core "1.7.1"]
[ring/ring-jetty-adapter "1.7.1"]
[ring/ring-json "0.4.0"]
[ring-middleware-format "0.7.4"]
;; [javax.servlet/servlet-api "2.5" :scope "test"]
;; [yogthos/config "1.1.1"]
;; java 10 upgrade
[javax.xml.bind/jaxb-api "2.4.0-b180830.0359"]
;; common
[org.clojure/core.async "0.4.490"]
[org.clojars.akiel/async-error "0.3"]
[hickory "0.7.1"]
[instaparse "1.4.10"]
;; clj
[clj-time "0.15.1"]
[clj-http "3.9.1"]
[clout "2.2.1"]
[org.clojure/data.json "0.2.6"]
[org.clojure/data.codec "0.1.1"] ;; Base64
;; cljs
[com.andrewmcveigh/cljs-time "0.5.2"]
[prismatic/dommy "1.1.0"]
[hipo "0.5.2"]
[hodgepodge "0.1.3"]
[cljs-http "0.1.46"]
[com.cemerick/url "0.1.1"]
[reagent "0.8.1"]
;; db
[ragtime "0.8.0"] ;; Migrations
[hikari-cp "2.7.1"] ;; Connection Pooling
[org.clojure/java.jdbc "0.7.9"]
[honeysql "0.9.4"]
[org.postgresql/postgresql "42.2.5"]
;; Authentication framework with JWT
[buddy/buddy-auth "2.1.0"]
[buddy/buddy-core "1.5.0"]
[buddy/buddy-sign "3.0.0"]
;; argon2 for password hashing
[de.mkammerer/argon2-jvm "2.5"]
;; logging
[com.taoensso/timbre "4.10.0"] ;; logging
[com.fzakaria/slf4j-timbre "0.3.12"]
[org.slf4j/log4j-over-slf4j "1.7.26"]
[org.slf4j/jul-to-slf4j "1.7.26"]
[org.slf4j/jcl-over-slf4j "1.7.26"]
;; spec , test & gen
[org.clojure/test.check "0.9.0"]
;; Dependencies for build process
[adzerk/boot-cljs "2.1.5"]
[adzerk/boot-test "1.2.0" :scope "test"]
[pandeiro/boot-http "0.8.3"]
[adzerk/boot-reload "0.6.0"]
[adzerk/boot-cljs-repl "0.4.0"]
[cider/piggieback "0.3.9" :scope "test"]
[nrepl "0.4.5" :scope "test"]
;; [com.cemerick/piggieback "0.2.2" :scope "test"]
[weasel "0.7.0" :scope "test"]
[org.clojure/tools.nrepl "0.2.13" :scope "test"]
[mbuczko/boot-ragtime "0.3.1"]
[deraen/boot-sass "0.3.1"]
[tolitius/boot-check "0.1.12"]])
After a lot of playing around with tasks, I was able to build a war file that works with jetty-runner with the following task:
(deftask build-war
"Build the target/app.war file"
[]
(comp
(aot :all true)
(web :serve 'gcr.core/app)
(cljs :optimizations :advanced)
(sass)
(sift :move {#"(.*)js$" "public/$1js"
#"(.*)html$" "public/$1html"
#"css/(.*)" "public/css/$1"})
(uber)
(war :file "gcr.war")
(sift :include [#"gcr.war"])
(target)))
Using the sift task to move the static resources to the public folder was key, since I had my compojure resources defined as follows in my routes.clj (not including API and other routes).
(defroutes gen-routes
(GET "/login" [] auth/auth-handler)
(files "/" {:root "public"}) ;; to serve static resources
(resources "/" {:root "public"}) ;; to serve anything else
(not-found "404 Page Not Found")) ;; page not found
This however still does not produce a war file that works on tomcat. That is, on deploying to tomcat, all the API routes works as expected, but the static html files come up as HTTP/404.
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.
Is it possible to grab the project information within the clojure repl?
For example, if there was a project defined:
(defproject blahproject "0.1.2" ....)
When running a repl in the project directory, is there a function like this?
> (project-version)
;=> 0.1.2
While you can parse project.clj yourself, this may be annoying. It's also a lot of work. Instead, you can just do:
(System/getProperty "projectname.version")
Leiningen project files are just Clojure data :)
(-> "/path/to/project.clj" slurp read-string (nth 2))
I use environ (https://github.com/weavejester/environ) which sucks in settings from a number of sources, including system properties. The project version appears as :<project-name>-version:
foobar.repl=> (require '[environ.core :refer [env]])
nil
foobar.repl=> (:foobar-version env)
"0.1.0-SNAPSHOT"
Add the below code to the end of project.clj:
(def project (assoc-in project [:repl-options :init]
`(~'def ~'project-version ~(project :version))))
Now you will have a var called project-version in the initial namespace for the repl.
As described in this discussion.
(ns myproject.example
(:require [clojure.java.io :as io])
(:import java.util.Properties))
(defn get-version [dep]
(let [path (str "META-INF/maven/" (or (namespace dep) (name dep))
"/" (name dep) "/pom.properties")
props (io/resource path)]
(when props
(with-open [stream (io/input-stream props)]
(let [props (doto (Properties.) (.load stream))]
(.getProperty props "version"))))))
(get-version 'myproject) ; => 0.1.0
(get-version 'org.clojure/clojure) ; => 1.3.0
As vemv said, Leiningen project files are just Clojure data. So, it's easy to access your project as an ordinary hash-map:
(->> "project.clj"
slurp
read-string
(drop 2)
(cons :version)
(apply hash-map)
(def project))
If you need this variable only in your repl, you can add it to repl-options to your project.clj:
(defproject yourproject "0.1.0"
:description ""
:url ""
:dependencies [ [org.clojure/clojure "1.4.0"]]
:repl-options { :init (->> "project.clj"
slurp
read-string
(drop 2)
(cons :version)
(apply hash-map)
(def project))})
Now, you have project variable in your repl. So, to access the version of your project you can simply type (:version project).
Of course, you can simply use native Leiningen code to parse you project file:
(defproject yourproject "0.1.0"
:description ""
:url ""
:dependencies [ [org.clojure/clojure "1.4.0"]
[leiningen-core "2.1.3"]]
:repl-options { :init (do (require 'leiningen.core.project)
(def project
(leiningen.core.project/read)))})
But, if you need only the version of your project and nothing more, then it's best to use Ankur's solution.
For a more fully-featured approach, you might want to take a look at the configleaf plugin for Leiningen (https://github.com/davidsantiago/configleaf). It will make the project map, with active profiles merged in, available to project code in a namespace of your choosing.
In case you need to do this from clojurescript you could create a macro (from another clj file) and call it from the cljs code :
;;ex: macro.clj
(defmacro get-project-version []
(System/getProperty "penelope.version"))
;;my_logic_code.cljs
(ns my-logic-code
(:require-macros [macros :as m]))
(def project-version (m/get-project-version))
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 have a clojure application called mr1; its project.clj is located in ./mr1/, and mr1.clj is located in ./mr1/src.
I cannot figure out how to structure the directories, namespace, and project.clj file correctly, so I can build a binary using cake bin.
Enclosed are my current project.clj and the head and tail of mr1.clj.
src/mr1.clj loads fine into cake repl and executes. cake bin produces an mr1 file that contains no -main.
(defproject mr1 "0.0.1-SNAPSHOT"
:description "TODO: add summary of your project"
:dependencies [[org.clojure/clojure "1.3.0"]
[org.clojure/tools.cli "0.1.0"]]
:main mr1)
(ns mr1
(:use [clojure.string :only [split]]
[clojure.string :only [join]]))
.
.
.
(defn -main
[& args]
(do
(reset! grid-dim (prompt-for-grid-dim))
(reset! mr1-pos (prompt-for-rover-pos 1))
(let [moves (prompt-for-rover-moves)]
(execute-each-move moves #mr1-pos))
(reset! mr2-pos (prompt-for-rover-pos 2))
(let [moves (prompt-for-rover-moves)]
(execute-each-move moves #mr2-pos))
)
)
As I mentioned in answer to your first question, you have to enable AOT-compilation of your namespace. I've copied that answer here in case if someone would have same problem.
As sample.project.clj file says, :main key should have as an assigned value a namespace which contains -main function. So you should have such function
(defn -main [& args]
(do-things-you-want-to-do-on-program-start))
in your mr1.clj. Also AFAIR if you want to use your program as a standalone jar you have to have this namespace gen-classed. By this I mean that you have to:
Include :gen-class option in your namespace definition like this:
(ns mr1
(:gen-class)
...other options...)
Make the namespace AOT-compiled (AOT stands for Ahead Of Time). To do this you need to specify your namespace in the list of AOT-compiled namespaces in project.clj:
(defproject mr1 "0.0.1-SNAPSHOT"
...other definitions...
:aot [mr1]
:main mr1)
After you've done this, you can use cake to generate executable jar for you.