How to debug static initialiser errors in jars generated by Leiningen? - clojure

in my Clojure library testlib, i've got a namespace with a :gen-class directive that looks like this:
(ns com.some_long_path.NewClass
(:import (java.util List ArrayList))
(:gen-class
:name com.some_long_path.NewClass
:methods [^{:static true} [getValues [String] java.util.List]]
)
(:require
[testlib.core :refer [var1 var2]]))
(defn getValues [^String]
(java.util.ArrayList. [3 5]))
If i try to import this class inside another namespace in the testlib project (after invoking compile), i can invoke getValues method without errors.
However, if i lein install, include testlib in another project jartest, and then use it in a test namespace below
(ns jartest.core
(:import [com.some_long_path NewClass]))
(NewClass.)
(NewClass/getValues "some string")
invoking NewClass constructor gives an exception
CompilerException java.lang.NoClassDefFoundError: Could not initialize class com.some_long_path.NewClass
and getValues as a consequence gives
CompilerException java.lang.IllegalStateException: Attempting to call unbound fn: #'com.some_long_path.NewClass/getValues
However, if i remove the requires from NewClass namespace definition above, the code works even in another library. So the problem is caused by some missing dependencies, although i've made sure that all dependencies of testlib are also included in jartest, and that testlib.core namespace is AOT-compiled.
Also, i've tried decompiling generated com.some_long_path.NewClass class file, and there is a static initializer block that looks like this:
static
{
Util.loadWithClass("/com/some_long_path/NewClass", NewClass.class);
}
Most probably the above-mentioned error is thrown from within loadWithClass. But how do i find out what exactly is wrong?
Thanks!
UPDATE: I was able to figure out what was wrong in the namespace i was requiring through a binary search for errors (commenting out code until things worked again). It turned out that some files were slurped from resources folder in testlib, but they were not present in jartest project. Changing the code to use clojure.java.io/resource fixed the problem. However, the question still stands - how to find out exactly what was the problem, without resorting to brute force methods?

Here is the obligatory answer- there isnt a better way, without understanding the dependency tree more deeply, which often can only be done by commenting stuff out and seeing what now works. That has been my experience with java classloading from clojure and using gen-class.
Heres hoping this isnt the highest voted answer.

Related

Error when sharing namespace among files using (in-ns)

I am sharing a namespace among all my ring routes in order to keep each route in its own file, so I have a file routes/core.clj that contains:
(ns test-proxy.routes.core)
;; Some utility functions
Then I start each route handler file like this:
(in-ns 'test-proxy.routes.core)
;; rest of the code
When I start the server, it works perfectly.
But when I make a change in any of the files and reload the page in the browser, I get the following error:
Caused by: java.lang.Exception: Found lib name 'test-proxy.routes.core'
containing period with prefix 'quote'. lib names inside prefix lists
must not contain periods
If I restart the server manually (lein ring server), all works as expected again, but as soon as I make another change in the code base, it fails with the same error. It definitely looks related to the way I'm using namespaces, but what gets me is that it works without a problem when I restart the server.
If I unquote the namespace to (in-ns test-proxy.routes.core) like the error seems to suggest, the server doesn't even start:
Syntax error (ClassNotFoundException) compiling at (diplomat/routes/docs.clj:1:1).
test-proxy.routes.core
in-ns is only meant to be used when at the REPL.
It is fine to divide up your functions any way you want. Just use the fully-qualified name like some.ns.of.mine/my-fn when you reference the function from any other namespace.
Of course, you can always use a namespace alias like:
(ns some.other.ns.of.mine
(:require [some.ns.of.mine :as snom])) ; define namespace alias
....
(snom/my-fn ...) ; invoke the fn
Be sure to also see How to ns

clojure.tools/namespace refresh fails with "No namespace: foo"

I'm using tools.namespace to provide smart reloading of namespaces on the REPL. However, when calling refresh or refresh-all, it throws an error.
user=> (require '[clojure.tools.namespace.repl :as tn])
user=> (tn/refresh)
:reloading (ep31.common ep31.routes ep31.config ep31.application user ep31.common-test ep31.example-test)
:error-while-loading user
java.lang.Exception: No namespace: ep31.config, compiling:(user.clj:1:1)
And it seems to end up in this weird state where (require ep31.config) works without an error, but afterwards the namespace isn't actually defined.
I kind of figured this out, this seems to be a combination of circumstances
there were AOT compiled classes left in target/classes from doing lein uberjar previously
tools.namespace doesn't function correctly when loaded namespaces are AOT compiled
target/classes is by default on the classpath
So long story short, if you did a jar/uberjar build before, then remove target/ and things should start working again.
The question I haven't been able to solve yet is why target/classes is on the classpath to begin with. I'm suspecting it's being added by Leiningen, but haven't found yet where or why it's happening.
I learned this the hard way, documentation for :target-path says (https://github.com/technomancy/leiningen/blob/master/sample.project.clj#L309-L313):
;; All generated files will be placed in :target-path. In order to avoid
;; cross-profile contamination (for instance, uberjar classes interfering
;; with development), it's recommended to include %s in in your custom
;; :target-path, which will splice in names of the currently active profiles.
:target-path "target/%s/"
I guess there has to be legacy reasons that :target-path "target/%s/" isn't the default.

Clojure REPL forgets classpath

I'm a bit of a Clojure newbie and something in the REPL is really confusing me.
I have a project called "misc" and a namespace at "misc/src/counter_window.clj". When I do enter the REPL from the project root folder I want to import counter_window's functions, but I seem to have to go through the following weird tango every time:
user=> (use 'counter-window)
FileNotFoundException Could not locate counter_window__init.class or counter_window.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name. clojure.lang.RT.load (RT.java:449)
user=> (use 'misc.counter-window)
CompilerException java.lang.Exception: namespace 'misc.counter-window' not found after loading '/misc/counter_window', compiling:(/tmp/form-init2530455467319465680.clj:1:1)
user=> (use 'counter-window)
nil ;; the import succeeds this third time.
The warning in the FileNotFound exception doesn't seem to apply because the namespace is called counter-window in the counter_window.clj file already.
So why does this happen? Why does the import not work the first time, but then succeed the third time?
I think there is a direct relation between file path and the namespace. A file src/counter_window.clj ought to have the namespace declaration (ns counter-window).
A namespace misc.counter-window should be defined in the file src/misc/counter_window.clj. ie it will have the form (ns misc.counter-window). Note that misc is a folder inside src. Your project could be named anything, I don't think it would affect the namespaces.
Assuming what you wanted was the namespace misc.counter-window and your file is correspondingly at src/misc/counter_window.clj, the following works in the repl
(use '[misc.counter-window])

Leiningen, repl, uberjar: Unable to resolve symbol, No such var

While my lein new app project runs merrily inside the Light Table, lein uberjar won't work. Curiously, it behaves exactly like a classic Pascal compiler: it can't resolve references ahead of definitions. Another curiosity: yesterday it worked. I am not aware of fooling with anything sensitive.
Google says that the subj symptoms are quite commonplace; I tried whatever helped other people in the same (?) plight, but to no avail. By the way, usually they blame it on software bugs: "get the latest version of Leiningen and Clojure". I've got 2.5.0 and 1.6.
The project (main file) is here: https://github.com/Tyrn/pcc/blob/master/src/pcc/core.clj
As it is, parsed-args can't be resolved inside build-album; if I move the -main function to the top of the file, 'No such var' happens to cli-options inside -main. No amount of fiddling with explicit use of namespaces makes any difference.
Again, inside the Light Table everything runs fine.
Using def inside of a function is not idiomatic, especially if there is no reason to have it as a global variable. Just pass it as a function parameter:
(let [parsed-args (parse-opts ...)]
...
(build-album parsed-args))
If you really need global state, you can use e.g. a promise (alternatively, an atom):
(defonce parsed-args (promise))
...
(deliver parsed-args (parse-opts ...))
However, Clojure files are read from top to bottom, and yes, functions not having access to bindings introduced later in the file is by design. You can use declare to tell the parser what to expect:
(declare ^:dynamic *parsed-args*)
(defn build-album ...)
(def ^:dynamic *parsed-args* ...)
...
(binding [*parsed-args* (parse-opts ...)]
(build-album))
TL;DR: If not necessary, avoid global state; if necessary, minimize it.

How to avoid having to manually evaluate defrecords and defprotocols while unit testing in Clojure?

In my Clojure codebase, I have defined several protocols and several defrecords. I am using clojure.test to unit test the concrete functions defined within my defrecords.
For example, let's say I have the following source files:
In src/foo/protocols.clj:
(ns foo.protocols)
(defprotocol RequestAcceptability
(desired-accepted-charset [this]))
In src/foo/types.clj:
(ns foo.types
(:use [foo.protocols :only [RequestAcceptability desired-accepted-charset]])
(defrecord RequestProcessor
[field-1 field-2]
RequestAcceptability
(desired-accepted-charset [this]
...implementation here...))
In test/foo/types_test.clj:
(ns foo.types-test
(:use [clojure.test])
(:use [foo.protocols :only [RequestAcceptability desired-accepted-charset]])
(:import [foo.types RequestProcessor]))
(deftest test-desired-accepted-charset_1
...test code here...)
I am using Clojure 1.4, Leiningen 2, nrepl within Emacs.
The annoyance I am facing is that when I go to run my unit tests (e.g., using C-c C-, sequence), I get a ClassNotFoundException: foo.types.RequestProcessor. To get around this, I'm doing the manual work of evaluating, individually, each of my protocol and defrecord forms. I.e., I'll navigate over to my protocols.clj and evaluate (C-M-x key sequence for nrepl) my defprotocol form; and then I'll navigate to my types.clj and evaluate my defrecord form; and then finally I'm able to successfully run my unit tests w/out getting the ClassNotFoundException.
Of course, in my real code base, I have to do this for all my protocols and defrecords, and so it is very tedious and time consuming. Also, if I simply drop to a shell and do lein test, I get the same ClassNotFoundException.
Is there a better way?
Thank you for your time and help.
You need to require the namespace that contains the defrecord in your test namespace.
(:import [foo.types RequestProcessor])) won't work on its own, since that only works for Java classes that already exist on the classpath. This isn't the case here, since you are using defrecord to dynamically create the class at runtime.
import is usually only needed for Java interop.
defrecord dynamically generates the class when it is run. Once you go into the namespace and run the code, then the class comes into being and the tests load normally because the class now exists. If you use the namespace from within your tests in addition to importing the class does it load properly for you?