Where Should My user.clj File Go?
Clojure programmers often use a user.clj
file to contain bits of
development tooling and code experiments that are not considered part
of the underlying project. This file has the “magical” property that
it is automatically loaded by Clojure as it starts up, without needing
to be referenced explicitly in the project’s code.
Clojure will happily load this file from anywhere on the
classpath1, which begs the question: where should I put my
user.clj
file?
In general, I think the answer is simple: dev/user.clj
.
You’ll then want to make sure dev/
is on your classpath.
If you use Leiningen that means adding to the :profiles
section
in your project.clj
file:
(defproject my-app "0.0.1"
,,,
:profiles {:dev {:source-paths ["dev"]}}
,,,)
Lastly, you’ll probably want to add dev/user.clj
to your
.gitignore
file.
You’re all set! Unless you’re vehemently disagreeing, in which case, read on!
Rationale (or, “why not src/user.clj
”?)
It’s tempting to just drop the user.clj
file into src/
because
things will immediately work given a freshly-generated
project.clj
2 file. Resist the urge.
Don’t put your user.clj
in the src/
directory as it will get
bundled into your production artifacts. At best, this is wasteful.
At worst, it’s a security threat (imagine if you have sensitive
information in there like API or encryption keys).
user.clj is magical but not too magical. Its magical power is that
it automatically gets loaded if found on the classpath, but it’s not
magically excluded from artifacts. When you run lein uberjar
it
will get bundled in like any other file under src/
—this is usually
not something you want!
You have some flexibility in how you arrange things, but the basic
constraints of how to handle the user.clj
file are:
- It needs to be at the root somewhere on the classpath to be autoloaded.
- It shouldn’t be somewhere where it will get bundled into build artifacts.
- You likely don’t want the file committed to version control.
- Given the problem involves construction of the classpath and generation of artifacts, you’ll need to leverage your build system for this.
You’re Probably Wondering How I Ended Up Here…
For a while I was getting an exception whenever I ran lein uberjar
.
The exception was clearly related to the fact that I had a
src/user.clj
file and the fact that it requires reloaded/repl
,
which is not a part of the production app (read: uberjar) that is
being built. Despite that, I didn’t really know how others were not
running into this issue. I felt like I had a typical setup.
$ lein uberjar
Exception in thread "main" java.lang.ExceptionInInitializerError
at clojure.main.<clinit>(main.java:20)
Caused by: Syntax error compiling at (user.clj:1:1).
at clojure.lang.Compiler.load(Compiler.java:7647)
at clojure.lang.RT.loadResourceScript(RT.java:381)
at clojure.lang.RT.loadResourceScript(RT.java:368)
at clojure.lang.RT.maybeLoadResourceScript(RT.java:364)
at clojure.lang.RT.doInit(RT.java:485)
at clojure.lang.RT.<clinit>(RT.java:338)
... 1 more
Caused by: java.io.FileNotFoundException: Could not locate reloaded/repl__init.class, reloaded/repl.clj or reloaded/repl.cljc on classpath.
...
Compilation failed: Subprocess failed
My gross workaround was to move user.clj
out of the src/
directory
whenever I built an uberjar. Which sounds horrible, but my uberjars
are typically built by our CI server, so I didn’t have to
do this too often (typically only if I was working on the automated
build process itself).
I didn’t really understand what was going on here until I started learning about Boot and thinking more deeply about what tools like Boot and Leiningen actually do. Until you do so, it’s easy to gloss over the boundaries between Clojure itself and the build tools. Moreover, classpaths aren’t a thing one thinks about much when everything is going swell.
As it turned out user.clj
was getting picked up by lein uberjar
.
Which was throwing exceptions because my user.clj
relies on
reloaded.repl
, which is brought in via a dependency in my
~/lein/profiles.clj
file which is (appropriately) not available on
the classpath when building for production.
Upon reflection, it’s fairly logical that src/user.clj
would get
pulled into the classpath and stuffed into the deployment uberjar, but
I think one could be forgiven for thinking user.clj
is a magical
development convenience and the “right thing” (exclusion from
production build artifacts) would just happen.
Given that that isn’t the case, I’d suggest either taking the time to learn Clojure’s build tools fairly well, or following the simple formula presented at the beginning of this post.
-
The classpath is an important concept Clojure inherits from Java—it’s essentially a list of places the JVM looks to find things (esp. classes). These “places” are searched sequentially, giving the logical appearance of a single, unified tree containing all of the things you provide as well as all of the things provided by your dependencies. One of the principal duties of a tool like Leiningen is to construct this classpath, typically from a combination of file directories and dependency information. The classpath tends to be constructed differently depending on the circumstances (e.g. interactive development, test runs, generating production artifacts); Leiningen’s primary mechanism for representing these circumstances / variants is “profiles”, which are conditionally activated to collectively achieve the desired behavior. ↩︎
-
The rest of this post assumes you’re using Leiningen. It’s hard to address this topic without making some assumptions about tooling because classpaths, file organization, and build tooling are all intertwined (dare I say “complected”?), but I think
dev/user.clj
is likely to be a fine place to store that file regardless of your build tooling. ↩︎