How are `user.clj` files loaded?


#1

I’m trying to understand how user.clj files are loaded by clojure.

For example, using clojure CLI, if I have a user.clj file in one of my dependencies, that user.clj file is loaded by clojure, but if add a user.clj in my paths, looks like my user.clj file is loaded instead, and the one in the dependencies it’s not loaded anymore. Not sure if my assumptions are correct, but it looks like the last user.clj wins and the other are discarded.

If it is possible to load multiple user.clj files, I think it would be nice to have a global user.clj file (used only by me), where I can define, for example, a custom pretty-print function, but also to have a user.clj file in every project, where I can define some specific helpers for the project. I guess with leiningen injections it’s possible to accomplish something similar, but I’m trying to do it using only clojure CLI.

BTW, to load a global user.clj I found this project: https://github.com/gfredericks/user.clj


#2

Clojure looks for the first user.clj file on the classpath during runtime initialization. So if there are multiple ones, which one it finds first is entirely dependent on the order of your classpath. With clj, you can see your classpath with clj -Spath. The local project paths are always placed first (followed by your dependencies). Generally, I think it would be bad for a library to publish with a user.clj, so you should only find them locally.

If you always want to find some special one first, you should ensure it’s in the first path in your source :paths.


#3

Thanks for the explanation, Alex. Knowing that, I found a way to load multiple user.clj files:

  • ~/.clojure/deps.edn:
{:aliases {:user {:extra-paths ["/path/to/.clojure"]}}}
  • ~/.clojure/user.clj:
(defn- static-classpath-dirs
  []
  (mapv #(.getCanonicalPath  %) (classpath/classpath-directories)))

(defn user-clj-paths
  []
  (->> (static-classpath-dirs)
    (map #(io/file % "user.clj"))
    (filter #(.exists %))))

(defn load-user!
  [f]
  (try
    (prn (str "Loading " f))
    (load-file (str f))
    (catch Exception e
      (binding [*out* *err*]
        (printf "WARNING: Exception while loading %s\n" f)
        (prn e)))))

(defn load-all-user!
  []
  (let [paths (user-clj-paths)]
    (prn (str "Load " (first paths)))
    (doall
      (map load-user! (rest paths)))))

(load-all-user!)

Now I can start clojure from the command line with clj -A:user:other-alias. My ~/.clojure/user.clj is the first in the classpath, and will take care of loading all the other user.clj on the classpath