Background:
This problem is specific to building jars that contain AOT (Ahead Of Time) compiled Clojure code using Maven and the maven-shade-plugin.
Clojure AOT compilation depends on timestamps of .class files vs .clj files being accurate. When both .class files and their associated .clj files exist, the AOT .class files are only used by the compiler if their last modified timestamp is strictly greater than the last modified timestamp of the associated .clj file.
Also note that the Clojure core jar itself is deployed AOTed.
I know that much of the Clojure ecosystem uses Leiningen as a build tool (and boot now too I guess). This problem doesn't apply to Leiningen (and I haven't looked at boot).
Problem:
The maven-shade-plugin is popular for building shaded/uber/standalone jars in Maven. Typically this means the jar will include some/all of its dependency jars' files. The maven-shade-plugin has an unfortunate property though. It does not preserve the timestamps on files that are added to this final shaded jar. The resulting jar actually ends up with all files inside of it having the same timestamp (when the jar was created). In particular, if you originally had AOT Clojure .class files and .clj files with different last modified timestamps, now they will have the same timestamps in the shaded jar.
I have rarely seen people bring up issues around this with maven-shade-plugin beyond this particular case. I believe I have seen a complaint or two around the timestamp loss during shading (I can't find them now), but nothing that has gained any traction (I may try to bring it up to the plugin people soon though).
When the AOTed .class file is ignored in favor of the .clj file, the namespace is JIT (Just-In-Time) compiled. There are several issues with this.
1) Performance: It makes the AOT files mostly worthless since they are not saving you on startup time costs anymore. Everything is JITed anyways.
2) Errors: The reloading of the .clj files is a forced reload of the .clj namespaces involved. This can cause classpath clashes among ClassLoaders.
- There are quite a few CLJ Jiras out there that faced trouble dealing with the mix of reloading namespaces and AOT compilation.
You may be thinking, "Just don't build your Clojure jars with Maven. Use Leiningen instead perhaps?"
This is fine when it is something you control. However, what if I want to use Clojure to develop a library that may be consumed by Java consumers who very likely will be using Maven. If my library is going to be shaded into a standalone jar with maven-shade-plugin by this Java consumer (again, likely) then this scenario can happen.
Another thought that may occur is to just avoid AOT compilation of my library application to avoid the problem (this is recommended in the community I believe). However, Clojure core is AOT compiled and it will get included in the shaded jar. That alone is enough to cause the issue.
Example:
I have a GitHub repo to show a minimum example of where this can be a problem. In particular it shows a way for the problem (2) to occur.
This repo has a Java API through shade.ShadeJava that will cause the Clojure compiler to require the shade.main namespace using JIT compilation. However, shade.main uses clojure.pprint, which is AOT compiled via Clojure core.
clojure.pprint was chosen here just because it one of the cases I've seen come up that actually fail with problem (2) from above. Even if there were no failures though, the Clojure core namespaces would be getting recompiled with problem (1).