Running Clojure code from a non-classpath directory

Hi.

Given a clojure application which is packaged as a .jar and running in production. And a separate directory on the filesystem where the application has access to, say /etc/myapp/clj-plugins/. This directory is not on the classpath of the application, because it is configured at runtime. In this directory a clojure file plugin1/core.clj with a function (defn foo [bar] ... ).

I would like the application to be able to invoke plugin1.core/foo. But when I change code in plugin1/core.clj, when the application calls plugin1.core/foo again, the new code should run. Also when there is a whole namespaces/files structure in the directory and any file is changed, the changes have to be effective when the application makes another call.

I have implemented this in Java using ClassLoaders and calling clojure.core/require + clojure.tools.namespace.repl/refresh before invoking plugin1.core/foo. But I suspect there is a more elegant way to do this.

Is there a library which I could use?

Where should I look into? GitHub - lambdaisland/classpath: Classpath/classloader/deps.edn related utilities perhaps?

1 Like

Hi Witek,

This isn’t a generally-accepted workflow. However, I enjoy tilting at windmills too and done have some similar work. Here’s what I can offer:

Minimally, what you need is a main method written in Java that sets Thread.currentThread()#contextClassLoader to be some kind of URLClassLoader whose parent is the current contextClassLoader. A good choice for that URLClassLoader is clojure.lang.DynamicClassLoader, Clojure’s own implementation. After this, your Java main can delegate to Clojure’s main. This makes sure Clojure’s runtime is fully initialized properly.

My own dynamic loader is a fork of Pomegranite and it supports adding file:// URLs as well as Maven dependencies to the classpath at runtime. I forked Pomegranite with the intent to support Eclipse P2 repositories via a Maven Resolver plugin but haven’t gotten that to work yet.

The dot.emacs repository above is my monorepo/scratchpad for new ideas. This particular work is currently graduating into its own thing–that I’m calling FuseCode / InsideOut. The first goal is to make the Dynamo library referenced above into a general-purpose application launcher where all dependencies are dynamically resolved at [load|run] time. Initially I’ll support Maven and Git dependencies; I’d like to revisit P2 dependencies at some point too.

With that in hand I want to build out the vision outlined above in the InsideOut readme: basically, each app should be its own IDE.

(That, to me, is one logical conclusion of “On Lisp” combined somewhat with a Smalltalker’s “everything needs an interactive interface” sensibility. I’ve got an itch and I’m scratching it.)

I owe a huge debt to Boot, the first larger-scale Clojure tool to make dynamic classloading a first-class part of the development experience as opposed to an add-on one could enable later. I stole the idea of bootstrapping from Java from Boot. I keep threatening myself to port Boot’s POD system over sometime in order to have classloader-isolated dependencies and pre-loaded instantly available Clojure JVM runtimes. More generally, though, Boot’s source code is a pretty good roadmap to the minefield that is dynamic class loading in Clojure.

Dave / coconutpalm

3 Likes