integrant

2020-03-16T09:29:13.051600Z

@imre: FYI I think if you have a problematic-ns as a dependency, you are also a problematic ns. Alex Miller seemed to indicate that clojure assumes all AOT’d namespaces have also AOT’d their transitive namespaces. I think this is the problem I had with class not found exceptions.

imre 2020-03-16T09:52:40.051800Z

the loop recur should take care of the transitive problematic thing, however the possibility that there are errors in my code is non-zero

2020-03-16T10:03:20.052Z

ok good to know — I didn’t read through the code; just thought I’d mention it incase it was the issue.

kszabo 2020-03-13T09:44:21.033900Z

sure

kszabo 2020-03-13T09:44:50.034100Z

So the goal is: reduce startup time by compiling namespaces which don’t change frequently

2020-03-13T09:44:58.034300Z

Yes

kszabo 2020-03-13T09:45:26.034500Z

Assuming you have an integrant config which has a distribution of 80% library keys - 20% application keys

kszabo 2020-03-13T09:45:37.034700Z

which I think is pretty common, ofc depending on application size

kszabo 2020-03-13T09:46:08.034900Z

you only want to compile the 80%, the things that are supplied by :duct, your-common-service-lib

kszabo 2020-03-13T09:47:23.035100Z

probably the easiest way it is to figure out all the ns’s from the integrant config, and remove ‘project’ ones with a set of regexes

2020-03-13T09:47:25.035300Z

However I’m not sure I agree with your point here: https://clojurians.slack.com/archives/C06MAR553/p1584092550229200?thread_ts=1584045635.219900&cid=C06MAR553 You might be right… but I’m not sure you are. I was thinking the point is to have a set of compiled namespaces for when you next start the repl. Any namespaces that are cached on disk, will only be recompiled if necessary.

2020-03-13T09:50:22.035800Z

reset after the first one is already pretty fast; and is already only recompiling when things change (in memory). I think what makes reset slow subsequent times is normally more the work done in the ig/init-key definitions; rather than any compilation caused by (re)load-namespaces. This is fixed by using (suspend|resume)-key.

kszabo 2020-03-13T09:52:57.036100Z

yup

2020-03-13T09:52:57.036300Z

Sorry I should’ve restated this: > So the goal is: reduce startup time by compiling namespaces which don’t change frequently I think the goal is to reduce startup time by compiling essentially all namespaces each time. compile already does a quick check of modified times to see if it’s necessary to rebuild.

2020-03-13T09:53:17.036500Z

So I don’t the see need to do what you suggest

kszabo 2020-03-13T09:53:21.036700Z

yeah, that’s the difference between our goals

kszabo 2020-03-13T09:53:44.037200Z

I was just taking Alex’s suggestion into account

2020-03-13T09:56:00.037400Z

Ok, I’d missed that… Not sure why that is necessary though. He doesn’t explain the reasoning.

kszabo 2020-03-13T09:58:04.037700Z

Probably there is some overhead in doing this

kszabo 2020-03-13T09:58:17.037900Z

But as always, I’m okay with a naive impl first

kszabo 2020-03-13T09:58:24.038100Z

let’s see what the drawbacks are ourselves

2020-03-13T09:58:27.038300Z

If that’s the goal though why involve integrant at all? Just look at deps.edn and compile all your dependencies.

2020-03-13T09:58:57.038500Z

Admitedly that will give you 80% of the speed

kszabo 2020-03-13T09:59:04.038700Z

because to do this efficiently, you have to compile the ns-es before they are loaded

kszabo 2020-03-13T09:59:18.038900Z

otherwise you have to compile and require :reload all of them

kszabo 2020-03-13T09:59:34.039100Z

like user.clj in the blogpost

2020-03-13T09:59:52.039300Z

(will return to this afk)

👍 1
2020-03-13T11:18:34.039600Z

> If that’s the goal though why involve integrant at all? Just look at deps.edn and compile all your dependencies. I was talking nonsense here; you obviously need to identify the root namespaces and the require’s for your dependencies. You suggested a way to do it at the top of this thread; however I think it has a problem too, which is that it loses the structural tree of require’s; so if compile is expensive you’ll be compiling namespaces multiple times unnecessarily too, i.e. your sets are unordered so if a requires b you might compile b first, then a. What I think you want to do is first find the roots of all dependency subtrees that aren’t in your :paths; then call compile on each of those. I think to do that you might want to use rewrite-clj or perhaps the metadata emitted from clj-kondo; so you can do so purely syntactically without requiring at all.

👍 1
kszabo 2020-03-13T11:32:16.039900Z

yup, the clj-kondo way sounds good

kszabo 2020-03-13T11:32:54.040200Z

new borkdude project in 3..2.. 😂

😆 2
2020-03-13T11:33:47.040600Z

actually it’s pretty trivial to do just use clj-kondo to generate that list — and then manually paste the ns’s into compile

2020-03-13T11:37:02.040800Z

clj-kondo --lint src --config '{:output {:analysis true :format :edn}}' | jet --pretty --query ':analysis :namespace-usages' | less Gets you half way there

kszabo 2020-03-13T11:37:04.041Z

then the question is how do you integrate that into your running app without incurring the java clj-kondo dev dependency

kszabo 2020-03-13T11:37:42.041200Z

just shell out to a babashka script which outputs the ns’es

kszabo 2020-03-13T11:37:48.041400Z

or something like that

2020-03-13T11:38:32.041600Z

I agree that’s nicer in principle; but it’s not really much of a chore to run some process once to generate the class files. Would only need to re-run periodically, after deps.edn changes.

kszabo 2020-03-13T11:39:04.041800Z

right, could be just a ~/.clojure/deps.edn alias

2020-03-13T11:39:49.042Z

Yeah I suppose you don’t need to use the graal build of clj-kondo

kszabo 2020-03-13T11:39:51.042200Z

clj -A:faster-startup

kszabo 2020-03-13T11:41:02.042400Z

oh, well you probably want to compile :test/`:dev` alias ns’es as well

2020-03-13T11:41:03.042600Z

probably something more like clj -A:your:normal:aliases -M:compile-deps

2020-03-13T11:41:08.042800Z

jinx!

🙂 1
kszabo 2020-03-13T11:41:38.043100Z

alright, which one of us is writing this over the weekend? 😄

2020-03-13T11:41:53.043300Z

unlikely to be me I’m afraid

2020-03-13T11:42:01.043500Z

😞

kszabo 2020-03-13T11:42:05.043700Z

okay, I’ll take a stab at it

🎉 1
kszabo 2020-03-13T11:42:16.043900Z

can’t leave home bc of covid anyway

2020-03-13T11:43:00.044400Z

me neither — but I have a 9month old daughter

🎉 1
kszabo 2020-03-13T11:44:21.044700Z

good priorities 🙂 I’ll report back here and/or DM you

1
kszabo 2020-03-13T13:30:37.046Z

ok, so we don’t need anything integrant specific

kszabo 2020-03-13T13:31:39.046200Z

then we can just load all the aliases, load all the namespaces + dynamically require ones from the config, compile them, then reload the ns’es/restart the jvm

2020-03-13T13:33:27.046400Z

Could do; but I was thinking it was potentially in support of my first thought. i.e. that we may have read too much into Alex’s mention of not compiling project ns’s. If you don’t care about the distinction, you can use integrant to find the root keys as I was suggesting, and use each of those as a starting point for compile.

2020-03-13T13:34:11.046600Z

Look in #clojure but I tried using clj-kondo and manually stripping out my app namespaces and a few other problematic ones; and ran into classnotfoundexceptions.

👍 1
imre 2020-03-13T13:51:15.046900Z

following this

👋 2
imre 2020-03-13T14:52:46.047300Z

I looked a bit into the clojure source and it turns out compile is just this:

(defn compile
  "Compiles the namespace named by the symbol lib into a set of
  classfiles. The source for the lib must be in a proper
  classpath-relative directory. The output files will go into the
  directory specified by *compile-path*, and that directory too must
  be in the classpath."
  {:added "1.0"}
  [lib]
  (binding [*compile-files* true]
    (load-one lib true true))
  lib)
So I took what Alex posted and included it in a mount-using project like this:
(defn reset []
  (stop)
  (binding [*compile-files* true]
    (clojure.tools.namespace.repl/refresh-all :after `start)))
Calling reset did populate the classes folder however the next call to reset died with a
:type java.lang.NoClassDefFoundError,
              :message "mount/core/DerefableState",

imre 2020-03-13T14:54:34.047500Z

I haven't investigated any further but it appears there are certain namespaces where compile might be problematic to use

2020-03-13T14:57:38.047700Z

yeah there certainly are namespaces where compile is problematic — ones where a side effect is done on load, can cause problems… though I’d argue they’re usually problematic anyway. Mount might well fall into that bracket; depends on the nature of the side effect — but IIRC when I last looked at mount (years ago) I disliked it for these kind of reasons (and others).

imre 2020-03-13T15:03:21.047900Z

I know, I had similar reservations about mount, but I have to use it in a few projects where startup/reload time improvements would be welcome

2020-03-13T15:05:33.048100Z

Yeah we have a few legacy projects using it; though I certainly wouldn’t encourage its use in greenfield work when there is integrant.

imre 2020-03-13T15:07:11.048400Z

agreed

imre 2020-03-13T15:57:06.048600Z

this seems to be able to get a good coverage of all the namespaces a project uses:

(->> {:platform @(requiring-resolve 'clojure.tools.namespace.find/clj)
      :add-all? true}
     (@(requiring-resolve 'clojure.tools.namespace.dir/scan-dirs)
      {} @(requiring-resolve 'clojure.tools.namespace.repl/refresh-dirs))
     (:clojure.tools.namespace.track/deps)
     ((juxt #(->> % :dependents keys)
            #(->> % :dependents vals (apply concat))
            #(->> % :dependencies keys)
            #(->> % :dependencies vals (apply concat))))
     (into #{} cat)
     (sort))

imre 2020-03-13T15:57:47.048800Z

you could apply a filter to that and compile what remains

imre 2020-03-13T16:21:39.049Z

nah, that will still try to transitively compile stuff that's potentially removed

2020-03-13T16:24:11.049200Z

not 100% sure about how good an idea a tools.namespace approach is for this — haven’t really thought it about. But you could possibly call clear to handle removals.

2020-03-13T16:27:49.049400Z

actually using tools.ns is probably a bad idea — Though I guess it depends on whether you run it in a different process to gen classes before the dev repl process. If so it’s probably ok, otherwise I doubt clear will unload compiled classes for example.

imre 2020-03-13T16:27:58.049600Z

I think that bigass juxt is causing the problem,

imre 2020-03-13T16:36:28.049800Z

why I used tools.ns is that I'm trying to retrofit this into the reset fn of a mount/tools.ns project

imre 2020-03-13T16:37:32.050Z

startup time is the smaller thing in this case as there aren't a lot of things that the user ns loads. most stuff is only loaded when I call reset the first time

2020-03-13T16:38:01.050200Z

of course — sorry slack collapsed the thread; and I forgot you’d mentioned that earlier!

imre 2020-03-13T16:38:13.050400Z

and I'm not too keen on keeping a list of namespaces to compile somewhere when that could be parsed out

imre 2020-03-13T21:50:31.050800Z

hmmmm

imre 2020-03-13T21:51:17.051Z

it appears namespaces that s/def specs could also be problematic

imre 2020-03-13T21:53:33.051200Z

on a second thought, the issue I'm having now could be something else

imre 2020-03-13T22:12:46.051400Z

All right, I'm abandoning this for now. Unfortunately I keep running into errors when I'm attempting a tools.namespace refresh-all after having started the system with compiled classes. Here's where I'm at if someone wants to pick it up: https://gist.github.com/imrekoszo/b4ee78d69e71c520b0392886a9d511b7