Fork me on GitHub
#tools-deps
<
2023-03-16
>
practicalli-johnny13:03:47

I'd like to thank @alexmiller and other core maintainers who have created a very flexible set of tools (Clojure CLI, tools.deps, tools.build, etc) I appreciate the methodical approach to creating tools and libraries which parallels the approach taken by all Clojure core maintainers and the development of the Clojure language itself. This has greatly helped with the adoption of Clojure and retention of developers and projects using Clojure The communication about up coming features has been very useful in understanding when to adopt certain features and setting expectations. There is a small learning curve for these tools and I really appreciate the support provided on this and many other slack channels by @alexmiller which has been highly valuable to me and hopefully the rest of the Clojure community. Thank you Alex.

18
gratitude 8
👍 2
orestis16:03:15

Uberjar is a jar that contains everything (dependencies and code). Is there a term for two jars where one contains all the dependencies, and another that contains all the code? Think slow moving vs fast moving layers in Docker.

favila16:03:03

I’m not sure there’s a term of art for that except for (javaee) container-specific deploys (where the container server provides the deps, and WAR files are the application). I think typically when you start separating, you just make a lib directory of dependency jars instead of dependency + application uberjars, because it’s much simpler and safer.

favila16:03:29

there’s also jar-of-jars, but that’s just another uberjar-like technique to get one self-contained jar

orestis16:03:53

What I'm after is essentially creating a docker layer with all my (compiled) dependencies, that should, in principle, not changing very often, and another docker layer with all my (compiled) source. Aim is to deploy to production and get fast startup time (hence the compiled requirement).

favila16:03:01

if these dependencies you want to be slower-moving are clojure-aot compiled, I think there may be some trickiness there

favila16:03:33

clojure compilation is transitive to all required code, there is no stable clojure ABI, and I’ve heard can be hard to predict what the output class filenames can be

favila16:03:48

IOW if this is clojure code, I don’t think you can easily and safely separate the “my aot-ed code” from the “this lib’s aot-ed” code the same way you can with java lib class files

favila16:03:39

but you could separate by clojure source or java classfile depencency jars vs application clojure source jars

favila16:03:27

but if you clojure-aot, you probably shouldn’t split up the result of the compile into multiple jars or allow the same clojure source to have aot code on the classpath.

orestis16:03:37

I thought clojure AOT means just generating a .class file per .clj file?

favila16:03:58

there’s not a straight 1-1 correspondence between class files and clj files

practicalli-johnny16:03:17

I would push all the built dependencies to a repository, as part of the CI workflow of each project. Then in the project that uses the dependencies the docker image can include those dependencies. The cache would be used unless a newer version of that dependency is pushed to the repository Something like Artefactory or AWS artefact repository

orestis16:03:21

hm, 1-1 between class file and namespace?

favila16:03:36

try it 🙂

favila16:03:15

this is why clojure libs don’t distribute jars with class files

orestis16:03:08

Ah, I was thrown by the usual cannot find foo__init.class for the foo namespace.

orestis16:03:45

I still think that AOT compiling Clojure dependencies today and using them with AOT compiled Clojure source tomorrow should work, as long as we're talking about the same Clojure/JVM version.

favila16:03:59

It may work most of the time, but really you’re not meant to combine compilation output from different processes running different classpaths

favila16:03:11

or, you’re on your own, you may encounter subtle problems

favila16:03:28

it’s not impossible, but I wouldn’t trust myself to do it right

favila16:03:28

Here’s a discussion where Colin Fleming (who made Cursive) asks about using bits of aot-compiled stuff together (for incremental compilation in his case.) The thread touches on some of the challenges in more detail. https://clojurians.slack.com/archives/C06E3HYPR/p1651097379296839

favila16:03:57

This is the key:

favila16:03:00

> clojure basically behaves like some kind of whole program optimizing compiler, despite not doing any of things those do > [5:19 PM] so like taking apart the output of an optimizing compiler and linking it with something compiled separately is tricky, so is doing that with aot compiled clojure

orestis17:03:07

Well I can certainly compile everything together and split the output files. If Docker hashes them and the output is the same, problem solved

favila17:03:37

compilation output is not deterministic

favila17:03:51

probably never going to hash the same

favila17:03:55

You can put the contents of .m2 after a clojure -P into your slower-moving layer and probably get most or all of the savings from not having to download dependencies separately, but that doesn’t include the trickier AOT part

favila17:03:47

or slightly fancier, a tools-deps program that copies all basis jars into a lib directory

favila17:03:04

then put the lib in a layer

orestis17:03:44

I’m already doing the Clojure -P and Docker is smart to cache that automatically. Definitely helps to not download the universe. My quest for smaller layers is insignificant compared to that.

dominicm16:03:36

@U7PBP4UVA have you looked at pack.alpha?

dominicm16:03:30

> What I’m after is essentially creating a docker layer with all my (compiled) dependencies, that should, in principle, not changing very often, and another docker layer with all my (compiled) source. Aim is to deploy to production and get fast startup time (hence the compiled requirement). The split of dependencies & application source is exactly what pack takes care of. It doesn’t get involved with compilation though. Splitting the AOT compiled stuff is really difficult as discussed above.

orestis11:03:11

AOT compilation is a basic requirement for us, for faster startup times, so if we can't split AOT-compiled classes that's a no go.