tools-build

2021-11-18T10:50:29.220900Z

A small point on the docs and expectations for tools.build / uber. Yesterday I was helping a colleague get started with tools.build; and it’s not 100% clear that resources you want to put in the jar from your project come via :class-dir only. i.e. logically there are two potential sources for this information: :class-dir and the :basis. We both understood this, but it wasn’t immediately obvious which was used where. It seems after a little bit of digging that the basis is only used by uber for discovering what libs to put into the jar, and that you essentially need to copy additional resources into :class-dir yourself. After discovering this there is a subtle hint that this is the case in the doc string: > Create uberjar file containing contents of deps in basis and class-dir. However I wonder if it might be worthy of a more explicit mention, given that we both missed this subtlety either in the guide, doc string or somewhere else.

👍 1
Alex Miller (Clojure team) 2021-11-18T11:51:53.222200Z

Sure, which part needs elaboration? basis for deps OR put stuff in class-dir. or both?

Alex Miller (Clojure team) 2021-11-18T12:01:56.222600Z

updated at https://clojure.github.io/tools.build/clojure.tools.build.api.html#var-uber - let me know if that covers it

2021-11-18T16:29:16.226700Z

@alexmiller: Firstly sorry for the delayed reply… For me I knew that the basis would tell uber what libs to package; I didn’t know uber only used it for that. I think your expanded doc string is fantastic, and for me totally nails it! I’ll run it by my colleague and see what she thinks too. 🙇

2021-11-18T16:36:51.227100Z

Her comments: > Nice, that’s much more helpful! > How incredibly efficient

👍 1
2021-11-18T16:44:39.227700Z

(and now a big discussion about how great the clojure community is)

Alex Miller (Clojure team) 2021-11-18T16:45:38.227900Z

happy ubering :)

2021-11-18T16:50:05.228100Z

😁

2021-11-18T16:54:48.230300Z

another question; any idea what makes uber so slow? It seems slower than it should be. I know it does a lot of extracting and repackaging, and I guess it took a long time under leiningen too; but it seems surprising how slow it is.

2021-11-18T16:56:36.231600Z

Hmmm… I wonder if it’s because you’re using the old java.util.zip stuff; rather than the newer java.nio.FileSystems API.

2021-11-18T16:58:47.233400Z

I’ve written some code with the nio FileSystems API that copies large and small files in and out zips relatively recently and anecdotally it’s pretty quick

2021-11-18T16:59:04.233700Z

I don’t have any concrete numbers; but I could probably get some

2021-11-18T17:02:25.235200Z

hmm I guess JarInputStream and JarOutputStream aren’t available under nio

sashton 2021-12-30T18:11:16.383200Z

Sean, you are correct. The issue lies mostly with my corporate laptop antiviral scanner. The tools.build/uber slowdown is magnified due to the higher IO. Without the scanning active, my times are more comparable: -depstar: 12s -tools.build: 50s When running it from a personal laptop, the times are 6s and 13s, respectively. So this appears to be local issue as a result of high disk IO.

seancorfield 2021-12-30T18:49:02.383400Z

@sashton Thanks for following up with your analysis. Yeah, that makes much more sense and those times are closer to what I would expect with a decent HD and no system interference. Anti-virus software generally seems to do more harm than good 🙂

borkdude 2021-11-18T17:09:39.235900Z

@rickmoynihan have you tested it in isolation, excluding startup time and compile-clj?

2021-11-18T17:10:27.236300Z

It was at a REPL in the build env

borkdude 2021-11-18T17:10:37.236500Z

right

2021-11-18T17:10:40.236700Z

calling just uber

borkdude 2021-11-18T17:10:52.236900Z

(b/uber {}) ?

2021-11-18T17:11:15.237100Z

well we had some project aliases too in the basis, and a :class-dir etc

2021-11-18T17:13:05.237400Z

IIRC the final jar was about 150mb — I don’t have it too hand to double check the details now though

borkdude 2021-11-18T17:15:53.237700Z

quite big!

2021-11-18T17:17:32.238400Z

yeah I guess the dependencies/libs extracted are pretty big

seancorfield 2021-11-18T17:43:52.242500Z

depstar used to work like tools.build (copy everything to a temp folder, and then JAR it all up) and in 2.0 I switched to copying directly into a FileSystem JAR file and it was a massive speedup, like more than double. But uber unpacks every dependency down to the file system, and then reads all of that plus the source and classes etc, back from the file system as it builds the JAR, so it's doing a lot more work.

seancorfield 2021-11-18T17:44:44.242700Z

It's a much simpler approach, and has the benefit of leaving the working directory intact afterward if you need to debug things, but it is certainly a slow approach.

sashton 2021-12-29T22:10:07.382Z

Hey @seancorfield. I’m reading up on this old thread as I’m also observing slow uberjar times using clojure.tools.build.api. I compared uberjar-ing the same project with both tools.build and depstar and I see a significant speed difference: clojure.tools.build.api/uber:

$ time clj -T:build uber
real    5m14.577s
hf.depstar.uberjar/build-jar:
$ time clj -T:build uber
real    0m30.601s
However, I notice that you’ve deprecated depstar in favor of tools.build. Did you see a similar drop in build times when you switched off of depstar?

seancorfield 2021-12-29T22:59:07.382200Z

Yes, tools.build is going to be slower because you copy everything into a target folder and then build the uberjar from that.

seancorfield 2021-12-29T23:00:04.382400Z

I'm surprised the difference is 5 minutes compared to 30 seconds -- so I expect you are not comparing apples to apples there.

seancorfield 2021-12-29T23:01:32.382600Z

I would expect depstar to "only" be perhaps twice as fast as tools.build if you are doing identical operations since the only thing it does differently is not copying files to an intermediate folder.

seancorfield 2021-12-29T23:03:01.382800Z

We saw maybe 10-30 seconds extra time using tools.build over depstar I think (our JARs are 25-70MB).

borkdude 2021-11-18T17:18:12.239400Z

I notice that when adding :paths ["."] to the :build alias, clj -A:build gives you a REPL where you can require 'build and do stuff. Why doesn't it work without it? What's the logic that makes clojure -T:build see build.clj ? -T implicitly adds . to the classpath?

Alex Miller (Clojure team) 2021-11-18T18:02:37.243900Z

Correct, I'm not happy with where this ended up wrt the repl so prob something more will be done there eventually

borkdude 2021-11-18T18:43:46.244100Z

Imo it's a small price to pay to add :paths ["."] - it also makes it more explicit what's happening

seancorfield 2021-11-18T17:39:07.241900Z

We use clj -M:build -i build.clj -r as a way to get a REPL with the build.clj file loaded so we can run stuff interactively. Otherwise, my "main" development REPL is started with

SOCKET_REPL_PORT=5000 clojure -J-Dlog4j2.configurationFile=log4j2-sean.properties -M:rebel:portal:everything:dev:test:runner:build:dev/repl
so that adds the dependencies for build.clj but does not add . to :paths and then I can open build.clj in my editor and issue the "load file" editor command (so it doesn't need to be on the path, it just needs to get loaded into the REPL).

borkdude 2021-11-18T18:45:04.244700Z

That's also a way to do it