Create a war with boot-clj - clojure

I want to create a war that I can deploy with tomcat. Using lein uberwar did the job just fine, however doing the same with boot doesn't seem to work. I can build a jar and run it, but the war fails with
Dec 09, 2015 12:15:31 AM org.apache.catalina.loader.WebappClassLoader validateJarFile INFO:
validateJarFile(/var/lib/tomcat7/sites/geeknow.guru/DEBUG##0.1.7/WEB-INF/lib/javax.servlet-api-3.1.0.jar)
- jar not loaded. See Servlet Spec 3.0, section 10.7.2. Offending class: javax/servlet/Servlet.class
I'm using the following build.boot
(set-env!
:source-paths #{"src/clj"}
:resource-paths #{"resources" "src/clj"}
:dependencies '[[org.clojure/clojure "1.7.0"]
[clj-time "0.9.0"]
[org.clojure/java.jdbc "0.3.7"]
[org.postgresql/postgresql "9.4-1202-jdbc41"]
[yesql "0.5.1"]
[migratus "0.8.6"]
[markdown-clj "0.9.67"]
[jarohen/nomad "0.7.2"]
[com.draines/postal "1.11.3"]
[compojure "1.4.0"]
[ring/ring-core "1.4.0"]
[ring/ring-devel "1.4.0"]
[ring/ring-defaults "0.1.5"]
[ring/ring-jetty-adapter "1.4.0"]
[ring-refresh "0.1.1"]
[ring-logger-timbre "0.7.5"]
[com.taoensso/timbre "4.1.4"]
[hiccup "1.0.5"]
[garden "1.3.0"]
[danlentz/clj-uuid "0.1.6"]
[speclj "3.3.1" :scope "test"]
[pandeiro/boot-http "0.7.1-SNAPSHOT"]])
(require '[pandeiro.boot-http :as http])
(require '[ring.middleware.reload :refer [wrap-reload]])
(require '[ring.adapter.jetty :as jetty])
(require '[ring.middleware.refresh :refer [wrap-refresh]])
(require '[blog.handler])
;;;;taken from boot-http.util
(defn resolve-sym [sym]
(require (symbol (namespace sym)) :reload)
(resolve sym))
(deftask ring-server
[]
(comp (jetty/run-jetty (wrap-refresh (wrap-reload (resolve-sym 'blog.handler/app))) {:port 3000}) (wait)))
(deftask uberwar
[]
(comp (aot) (pom) (web) (uber) (war)))
(deftask uberjar
[]
(comp (aot) (pom) (uber) (jar)))
(task-options!
pom {:project 'geeknow
:version "0.1.7"}
;uber {:as-jars true}
aot {:all true}
jar {:main 'blog.core
:manifest {"Description" "blog"}}
web {:serve 'blog.handler/app}
war {:main 'blog.core
:manifest {"Description" "blog"}}
repl {:init-ns 'blog.core})

Tomcat is complaining because one of the jars included in your war file contains a class that only the container environment can provide. The offending jar in the war is WEB-INF/lib/javax.servlet-api-3.1.0.jar.
By default, the uber task adds all dependencies, direct and transitive, to the war at WEB-INF/lib when --as-jars is set to true. --as-jars is the preferred way of bundling dependencies for deployment to servlet containers.
The problem appears to be that one of your direct dependencies is bringing in javax.servlet-api and uber is packaging it, causing the Tomcat problem.
First we need to figure out which dependency is bringing in javax.servlet-api. boot show contains many options for diagnosing dependency problems like this. You can learn about them all with boot show -h. The one we want now is boot show -d, which prints the dependency tree.
Here is the fragment of relevant output:
[ring/ring-jetty-adapter "1.4.0"]
├── [org.eclipse.jetty/jetty-server "9.2.10.v20150310"]
│ ├── [javax.servlet/javax.servlet-api "3.1.0"]
│ ├── [org.eclipse.jetty/jetty-http "9.2.10.v20150310"]
│ │ └── [org.eclipse.jetty/jetty-util "9.2.10.v20150310"]
│ └── [org.eclipse.jetty/jetty-io "9.2.10.v20150310"]
└── [ring/ring-servlet "1.4.0"]
From this output, we know that our dependency on ring/ring-jetty-adapter is what's causing javax.servlet/javax.servlet-api to be brought in.
Because this dependency is necessary for local development we don't want to omit it entirely. Instead, we can add the dependency with "test" scope:
[ring/ring-jetty-adapter "1.4.0" :scope "test"]
Scopes are a Maven concept for limiting the transitivity of dependencies in scenarios such as these. Boot uses Maven for its underlying dependency resolution machinery. uber is sensitive to Maven scopes, and can be configured to include or exclude various scopes depending on your needs. See boot uber -h for more information.
By default, the uber task won't package dependencies in "test" scope. By marking ring/ring-jetty-adapter this way, we've excluded it from our uber war.
The "test" scope is also useful for dependencies you might use in tests (of course!), or for deployment, or for other tasks during which you need a dependency but don't want to distribute it with your artifact, whether it's a library jar or an uberwar web app.

Related

Leiningen wont exclude namespaces from uberjar

I have a project where I want certain parts of the code to be able to run on a local environment, and other parts which depend on libraries that only run on a remote environment. E.g.
src/app/core.clj <- can run anywhere
src/app/sandbox/remote_only.clj <- depnds on libs that only function in remote environ
where src/app/core.clj is
(ns app.core
(:gen-class))
(defn -main [] (println "Hello, World!"))
and src/app/sandbox/remote_only.clj is
(ns app.sandbox.remote-only
(:require
[uncomplicate.commons.core :refer [with-release]]
[uncomplicate.neanderthal
[native :refer [dv dge]]
[core :refer [mv mv!]]]))
I want to be able to uberjar and run the code located in core.clj on my local machine without pulling in remote_only.clj which will cause the program to fail locally. According to the docs, Leiningen should be able to accomplish this using profiles and uberjar exclusions e.g.:
(defproject app "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.1"]]
:profiles {:uberjar {:main app.core
:init-ns app.core
:aot :all}
:remote {:init-ns app.sandbox.remote_only
:dependencies [[uncomplicate/neanderthal "0.43.1"]]}} ; these deps will fail locally
:uberjar-exclusions [#".*sandbox.*"]
:repl-options {:init-ns app.core})
compiling the uberjar here will result in:
❯ lein uberjar
Compiling app.core
Compiling app.sandbox.remote-only
Syntax error macroexpanding at (remote_only.clj:1:1).
Execution error (FileNotFoundException) at app.sandbox.remote-only/loading (remote_only.clj:1).
Could not locate uncomplicate/commons/core__init.class, uncomplicate/commons/core.clj or uncomplicate/commons/core.cljc on classpath.
So, explicitly its trying to compile the class that was specifically excluded.
I thought that this kind of problem could be avoided by removing the :all tag from :aot compilation. E.g.:
(defproject app "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.10.1"]]
:profiles {:uberjar {:main app.core
:init-ns app.core
:aot [app.core]}
:remote {:init-ns app.sandbox.remote_only
:dependencies [[uncomplicate/neanderthal "0.43.1"]]}} ; these deps will fail locally
:uberjar-exclusions [#".*sandbox.*"]
:repl-options {:init-ns app.core})
which causes other problems:
❯ lein uberjar
Compiling app.core
Created /Users/warrenronsiek/Projects/app/target/app-0.1.0-SNAPSHOT.jar
Created /Users/warrenronsiek/Projects/app/target/app-0.1.0-SNAPSHOT-standalone.jar
~/Projects/app 5s 15:47:37
❯ java -jar ./target/app-0.1.0-SNAPSHOT.jar
Exception in thread "main" java.lang.NoClassDefFoundError: clojure/lang/Var
I've tried playing around with all kinds of :exclusions, :jar-exclusions and regexes. Variations of this question have been asked multiple times (1, 2) Nothing works.
How do I get Leiningen to compile uberjars and ignore the specified file(s)?
You are on the right track with changing :aot :all to :aot [app.core] but when you tried to run the JAR, you are running the library version instead of the whole application version:
java -jar ./target/app-0.1.0-SNAPSHOT.jar
That doesn't include Clojure or any dependencies. You want:
java -jar ./target/app-0.1.0-SNAPSHOT-standalone.jar
I haven't tried this particular setup with the "exclusions" features.
One workaround would be to create sub-projects for the "local" & "remote" parts of your app, which could then be used by a higher-level "total" project.
app
app-local
app-remote
where each of the 3 is a separate Lein project. app-local and app-remote could be developed in isolation. The top-level app project then uses has the 2 sub-projects as dependencies (and therefore only works in the remote/full environment).
The Lein feature checkouts allows you to develop in all 3 repos simultaneously, which is a huge help. It is much faster than the alternative of using lein install, which is slow, manual, repetitive, and error-prone.
P.S. Did you experiment with using lein jar instead of lein uberjar?

How do I create a Clojure application with lein uberjar if I have a dependency in checkouts?

I have a Clojure application that is using a library which is symlinked from inside the "checkouts" directory.
This lets me work on both the app and the library at the same time. And lein knows how to compile and run the program without any problems.
But I want to make a standalone with lein uberjar, and it's complaining
Caused by: java.io.FileNotFoundException: Could not locate mylib/core__init.class, mylib/core.clj or mylib/core.cljc on classpath.
I assume that that is because mylib isn't mentioned in my project.clj file. It isn't, precisely because I want to use the version of mylib symlinked inside "checkouts".
But the uberjar command doesn't seem to be able to see it.
How can I solve this?
You can accomplish this by installing mylib in your local repository (~/.m2/repository).
Run lein install in the dependent project to install it in your local repository.
Add the project to :dependencies: in project.clj:
[mylib "version"]
Run lein uberjar in the main project.
The project will find the jar in your local repository.
/Edit
If you want to develop two libraries at the same time you can use a checkouts folder where checkouts contains a symlink to the dependent library.
mkdir checkouts
ln -nfs full-path-other-lib-dir full-path-checkouts-dir
Now changes in other-lib are immediately available in the main project.
See [https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md#checkout-dependencies](the Leiningen checkouts documentation).
OK, it appears that the "checkouts" feature of lein works with lein test and lein run but not with lein uberjar.
I added the following to the local source code of a lib tupelo.core:
(def dummy-sample-data "Bogus!")
In the consuming project demo.core we can access the new Var:
(ns demo.core
(:use tupelo.core tupelo.test))
(defn -main [& args]
(println :foo-enter)
(spyx dummy-sample-data)
(println :foo-leave))
lein run produces:
:foo-enter
dummy-sample-data => "Bogus!"
:foo-leave
project.clj remains unchanged:
(defproject demo "0.1.0-SNAPSHOT"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [
[criterium "0.4.5"]
[org.clojure/math.combinatorics "0.1.6"]
[org.clojure/clojure "1.10.1"]
[prismatic/schema "1.1.12"]
[tupelo "0.9.201"]
<snip>
where "0.9.201" is the latest version of tupelo on Clojars. Checkouts looks like:
~/expr/demo > ls -ldF checkouts/*
lrwxrwxrwx 1 alan alan 17 May 12 13:57 checkouts/tupelo -> /home/alan/tupelo/
but uberjar fails:
~/expr/demo > lein clean ; lein uberjar
Compiling demo.core
Syntax error compiling at (demo/core.clj:6:3).
Syntax error compiling at (demo/core.clj:6:3).
Unable to resolve symbol: dummy-sample-data in this context
Full report at:
/tmp/clojure-10416346559924917196.edn
Compilation failed: Subprocess failed
Options
If you are wanting to deploy an application, you have 2 options:
If the lib exists on Clojars or Maven, you should probably deploy a release there before making a uberjar. If the lib is not ready for Clojars or Maven, maybe you aren't ready to use it in a uberjar...?
Simply copy the source tree of mylib (or use a symlink!) under the ./src dir in your project. You'll also have to copy in the dependencies into myproj/project.clj. You are effectively merging myproj and mylib, at least temporarily with this approach. If the are coupled tightly enough to require co-development, perhaps it should be a permanent merge?

cljc file not hot reloading for clojure

I recently implemented my first ".cljc" file that is supposed to bridge between clojure and clojurescript.
All went well, also figwheel is picking up the changes and nicely refreshes the new code, however in the clojure side the file is not hot-reloaded.
I'm using the usual
[ring.middleware.reload :refer [wrap-reload]]
in my development middleware.
In my project.clj, I have:
:source-paths ["src/clj" "src/cljc"]
Any ideas?
Make sure that source paths for both .clj and .cljc files are set at the top level in project.clj for the JVM compilation:
:source-paths ["src/clj" "src/cljc"]
And for the ClojureScript side, ensure that source paths are set anywhere that you have compilation directives for Figwheel e.g.:
; this might be your from your dev profile cljs config:
:cljsbuild
{:builds
{:app
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
:compiler
{:main "my-project.app"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
:source-map true
:optimizations :none
:pretty-print true}}}}
Sounds like your Figwheel config is good, though.

boot-clj watch build -> run jar - ok -> change code -> run jar - fail: could not locate class

I build jar with boot build watch command and then I am able to successfully execute it with java -jar target/project.jar command. After source code change and rebuild the execution fails with message
Caused by: java.io.FileNotFoundException: Could not locate my_dir/foo__init.class or my_dir/foo.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
I have the following files structure
.
├── build.boot
└── src
└── my_dir
├── core.clj
└── foo.clj
build.boot content:
#!/usr/bin/env boot
(set-env!
:source-paths #{"src"}
:dependencies '[[org.clojure/clojure "1.8.0"]])
(deftask build
"Builds an uberjar of that can be run with java -jar"
[]
(comp
(aot :all true)
(pom :project 'boottest
:version "0.0.1")
(uber)
(jar :main 'my-dir.core)
(target :dir #{"target"})))
foo.clj content:
(ns my-dir.foo)
(defn f []
(println "I am f"))
core.clj content:
(ns my-dir.core
(:gen-class)
(:require [my-dir.foo :as foo]))
(defn -main [& args]
(println "first run")
(foo/f))
Now I have boot build watch running and when I change the content of core.clj to e.g. (println "second run"), wait for the rebuild and run the resulting project.jar I get the could not locate class error mentioned above. Finally what works is to manually rebuild the code via boot build command and rerun but I'd like to do it with boot watch build and I quite don't understand why it is not be working that way. I am totally new to boot so I might be missing something obvious but still want to know. Any ideas?
boot version
http://boot-clj.com
Tue Nov 22 11:35:59 CET 2016
BOOT_CLOJURE_NAME=org.clojure/clojure
BOOT_CLOJURE_VERSION=1.8.0
BOOT_VERSION=2.6.0

How to launch a clojure repl with access to a jar

I'm new to clojure. I have a jar file I want to play with in a clojure repl, but I've failed to do so with leiningen 2.
I have tried placing the jar file in src/myjar.jar and also in src/org/mydomain/myjar.jar
When I run lein repl I get errors stating that leiningen can not find my artifact, and a reference to a page about repeatability which I do not understand.
Here is my project.clj (with the real name of myjar)
(defproject cljliveordead "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.3.0"]
[org.allen.temporalintervalrelationships/time "0.2" :extension "jar"]])
You can use local jars using the lein-localrepo plugin. Add this line to your project.clj
:plugins [[lein-localrepo "0.4.0"]]
Then install the jar to the local repository using
lein localrepo install <path-to-jar> org.allen.temporalintervalrelationships/time 0.2
You can check that the file is installed by running lein localrepo list and check that lein can resolve the project dependencies using lein deps. If all is well then you can start playing with the jar using lein repl.
Leiningen doesn't like local jars due to its goal of repeatable builds. If this was a real project using a third party closed source jar then the best thing to do is install it in a local Nexus repository and add a reference to that repository to your project.
However, this all seems a bit heavyweight for what you are trying to achieve. If all you want to do is play with the jar in the REPL then create a simple project like this
(defproject clojure-time "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.4.0"]
[com.cemerick/pomegranate "0.0.13"]])
and use the pomegranate library to add the jar to your classpath manually
(require '[cemerick.pomegranate :as p])
(p/add-classpath "jsr-310-ri-0.6.3.jar")
(javax.time.Instant/now)
and play away.
the hackish way is to just put it into /proiject/path/lib/ and the 'proper' way is to:
add a dependency for it to your project
run lein deps which will print the command for installing the jar to your local maven repo
run the command leiningen gives you with the path to your jar
run lein deps again
I use clojure-csv in a lot of my applications, so make sure the modules referencing clojure-csv were able to build with it, this is what I did:
0) Ran lein new bene-csv
1) Added this to project.clj (after enter lein new bene-csv). The pertinent line is [clojure-csv/clojure-csv "1.3.2"], but it makes sense to show you the whole project.clj for good example's sake.
(defproject bene-csv "1.0.4-SNAPSHOT"
:description "A csv parsing library"
:dependencies [[org.clojure/clojure "1.3.0"]
[clojure-csv/clojure-csv "1.3.2"]
[util "1.0.2-SNAPSHOT"]]
:aot [bene-csv.core]
:omit-source true)
2) Made sure my bene-csv/src/bene_csv/core.clj references clojure-csv.
(ns bene-csv.core
^{:author "Charles M. Norton",
:doc "bene-csv is a small library to parse a .csv file.
Created on March 8, 2012"}
(:require [clojure.string :as cstr])
(:require [util.core :as utl])
(:use clojure-csv.core))
Finally, I ran these commands, so that my main project could reference bene-csv's functions and defs.
lein deps
lein install