Fork me on GitHub
#clojure
<
2022-04-20
>
Nazral13:04:25

Hi! Is there an equivalent to C's IFDEF preprocessor ? My goal is to have some chunks of code that I can turn on or off with a simple flag when I build the uberjar (or npm when using shadow-cljs) ?

Joshua Suskalo13:04:37

Not in core, but you can very easily write one like this

(defmacro compile-when
  [test & body]
  (when (eval test)
    (cons 'do body)))

Joshua Suskalo13:04:12

(defmacro compile-if
  [test then else]
  (if (eval test)
    then
    else)))

Joshua Suskalo13:04:53

for reference, both of these can be accomplished with just putting a regular if form at the top level of your program.

Joshua Suskalo13:04:29

Unlike many other languages, Clojure allows you to put anything as a top level form, it doesn't have to just be ns, def, defn, and declare.

Nazral13:04:21

Thanks for the idea! But how do you pass the test from the command line ?

Joshua Suskalo13:04:38

by making that test fetch an environment variable or something. The test is just code.

Nazral13:04:16

right but I don't want to use an environment variable 😅 but thanks anyway, that can be useful in other situations

Joshua Suskalo13:04:56

How do you mean then? You want to pass commandline arguments to what? To the clojure compiler? And have them make it all the way to your code?

Joshua Suskalo13:04:39

there is the dynamic var *command-line-args* that allows you to inspect the command line arguments, but the problem with that is the command line arguments are first going to be processed by the clojure compiler, which will probably detect them as invalid.

Nazral13:04:37

To the clojure compiler, yes (hence my reference to IFDEF where you can just do gcc -D ... ). *command-line-args* this might be a solution though, because one could pass some edn, and then use this in the macro, right?

Joshua Suskalo13:04:47

Yes, but the challenge will be determining where that edn was, and having it not be interpreted by the compiler as invalid arguments. I think it's a bad idea to attempt that.

Joshua Suskalo13:04:10

One thing you could do is use tools.build to allow you to pass some edn at the command line, and then that process will later start the clojure compiler in a different JVM and can set its environment variables based on your commandline arguments.

Nazral13:04:20

I'll look into that, thank you !

noisesmith17:04:04

@U5NCUG8NR I'm highly sceptical of that eval - anything you return from the macro is going to run at macro expansion time, which is when the IFDEF would operate

noisesmith17:04:46

the equivalent of IFDEF using an env var would be using System/getenv or System/getProperty to check java startup conditions (environment variables or flags to the vm, respectively)

Joshua Suskalo17:04:56

Yes, this is why I say the same thing could be done by just using if at the top level

noisesmith17:04:25

what I'm saying is the macro looks just plain wrong

Joshua Suskalo17:04:48

how's that? It's not intended to be used inside a def form or anything, it's meant to wrap def forms etc.

noisesmith17:04:06

right, but that eval is weird, at best it's a no-op

Joshua Suskalo17:04:33

It depends entirely on if you want both the then and else to be compiled into the bytecode of the result.

Joshua Suskalo17:04:56

the point here is that the eval happens at macroexpand time

noisesmith18:04:38

the deeper issue here is the initial problem statement isn't addressed yet: > My goal is to have some chunks of code that I can turn on or off with a simple flag when I build the uberjar there's nothing here so far that conditionally puts contents in the jar, so at best we have things that only work when AOT is on, I don't see any insurance the decision is 100% compile time

Joshua Suskalo18:04:04

it absolutely does conditionally include code into an uberjar if aot compilation is turned on

Joshua Suskalo18:04:26

and if you're not doing an uberjar then there's no difference between build time and runtime

noisesmith18:04:29

@U5NCUG8NR sure, clojure already compiles whatever you return from you return from your macro, and if it isn't in a defn, runs it immediately, I still don't see how eval helps

Joshua Suskalo18:04:36

so at that stage you can only use classpath flags

Joshua Suskalo18:04:49

nothing in your code can control what gets put into the jar

Joshua Suskalo18:04:54

unless there's aot

noisesmith18:04:03

I didn't say "not doing an uberjar" - AOT and uberjar are not the same

Joshua Suskalo18:04:21

Yes, I am aware, but most people building an uberjar will also aot

Joshua Suskalo18:04:57

Hence the whole point of the macro, it ensures that code is only compiled if it's needed, and you can only tell that at build time, and if build time and runtime are indistinguishable then it does nothing.

Joshua Suskalo18:04:24

just acts like regular (toplevel) if

Joshua Suskalo18:04:02

ofc if you use it inside the body of a function then it'll act much closer to ifdef

Joshua Suskalo18:04:20

because the test will not have access to function local stuff because the function won't have been run yet, and the two bodies will be used based on that compilation.

Joshua Suskalo18:04:42

So this macro remains useful even if you're not doing AOT

noisesmith18:04:44

OK - that's a good rationale for eval, I was forgetting that it didn't see locals

Joshua Suskalo18:04:11

well yeah, the macro body is evaluated only when the code is compiled, not when it's run, local values don't exist yet

Joshua Suskalo18:04:25

except in &env, but that's just for tracking what will be bound, not what is bound.

noisesmith18:04:37

but it's also weird, because it means your calls are treated differently if quoted, and if not quoted the eval is a no-op

(ins)user=> (defmacro show-and-test [t] (doto (eval t) prn))
#'user/show-and-test
(ins)user=> (show-and-test (System/getenv "HOME"))
"/home/justin"
"/home/justin"
(ins)user=> (show-and-test '(System/getenv "HOME"))
(System/getenv "HOME")
"/home/justin"

Joshua Suskalo18:04:21

sure, but be aware the result of the test is not being included in code anywhere

Joshua Suskalo18:04:24

I'm not quoting the whole form

Joshua Suskalo18:04:39

I'm just directly returning either then or else as the code to replace the macro body

noisesmith18:04:40

but why quote anything? it's a macro

noisesmith18:04:42

it's just weird

Joshua Suskalo18:04:50

nothing needs to be quoted

Joshua Suskalo18:04:52

that's my point

noisesmith18:04:56

then eval is a no-op

noisesmith18:04:03

aside from ignoring locals, right

Joshua Suskalo18:04:28

the eval isn't a no-op, it's used to branch inside the macro!

Joshua Suskalo18:04:33

I really don't get what you're trying to say

noisesmith18:04:36

I'm saying that for all inputs the code does the same thing without the eval, except for two cases I can think of: • you used a local (error with eval) • you created a function literal (error with eval)

Joshua Suskalo18:04:40

usage looks like this:

(compile-if (System/getenv "HOME")
  (something with home)
  (something with default behavior))
This system/getenv is done once, at compile time, saving runtime checks if this code is called repeatedly, and when you AOT results in only one of those something forms being in the compiled bytecode.

noisesmith18:04:05

but eval has nothing to do with that compile-time operation

Joshua Suskalo18:04:22

it absolutely does, there's no way to check the system/getenv at compile time without eval

Joshua Suskalo18:04:35

that's the distinction between macroexpand time and runtime

noisesmith18:04:50

maybe you mean inside def or defn? because otherwise, at the top level, there really is

Joshua Suskalo18:04:08

you literally can't use the result of code passed as an argument to a macro to branch in the macro at macroexpand time without calling eval

Joshua Suskalo18:04:37

Yeah, but #ifdef is used inside the bodies of functions (correctly!) all the time

Joshua Suskalo18:04:46

the original question was to allow usage of something like #ifdef

noisesmith18:04:53

OK clearly I'm not thinking straight today, I appologise for wasting your time

Joshua Suskalo18:04:18

all good, sorry if I came off as aggressive or anything, I was just really confused by what you were saying.

noisesmith17:04:09

@U5NCUG8NR did I do something wrong here? this test makes it look like the eval does nothing as I first suspected

(cmd)justin@abjection:~/clojure-experiments/ifdef$ cat src/ifdef/core.clj
(ns ifdef.core
  (:gen-class))

(defmacro ifdef-1
  [v & code]
  (when (eval (System/getenv v))
     `(do ~@code)))

(defmacro ifdef-2
  [v & code]
  (when (System/getenv v)
    `(do ~@code)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (prn :ifdef-1 (ifdef-1 "FEATURE" "feature is activated"))
  (prn :ifdef-2 (ifdef-2 "FEATURE" "feature is activated")))
(cmd)justin@abjection:~/clojure-experiments/ifdef$ lein do clean, uberjar
Compiling ifdef.core
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT.jar
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar
(cmd)justin@abjection:~/clojure-experiments/ifdef$ java -jar target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar 
:ifdef-1 nil
:ifdef-2 nil
(ins)justin@abjection:~/clojure-experiments/ifdef$ FEATURE=true java -jar target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar 
:ifdef-1 nil
:ifdef-2 nil
(ins)justin@abjection:~/clojure-experiments/ifdef$ FEATURE=true lein do clean, uberjar
Compiling ifdef.core
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT.jar
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar
(cmd)justin@abjection:~/clojure-experiments/ifdef$ java -jar target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar 
:ifdef-1 "feature is activated"
:ifdef-2 "feature is activated"

noisesmith17:04:20

both with and without the eval, the condition happens at macro expansion time, which is during compilation, which happens before the jar is made if you use AOT

noisesmith17:04:09

so as I said above, the only thing eval does here is prevent using locals

Joshua Suskalo17:04:46

yeah your eval did nothing there

Joshua Suskalo17:04:53

because eval isn't a macro, it's a function

noisesmith17:04:21

so my code is wrong? I was copying what the original example was doing

Joshua Suskalo17:04:36

That (System/getenv ...) is evaluated eagerly, returns a string, evaling a string returns the string, strings are truthy, it always includes the code.

noisesmith17:04:48

no - see the original nil

noisesmith17:04:55

because it returned nil when the var wasn't set

Joshua Suskalo17:04:10

sure, eval of nil also returns nil

noisesmith17:04:25

so how is my example different from what you suggested

Joshua Suskalo17:04:30

the problem is that you're not evaluating code, you're evaluating the result of running some code

noisesmith17:04:40

right, just like your macro above

Joshua Suskalo17:04:01

no, my example above passed a value that was a macro argument, which is the same as if it were quoted code

Joshua Suskalo17:04:26

that would appear to act identically at top level

Joshua Suskalo17:04:02

and in this specific case of getenv it would appear to act correctly everywhere, just because of what getenv does

Joshua Suskalo17:04:24

and the eval is pointless because all it's doing is evaluating constants (either the returned string or nil)

Joshua Suskalo17:04:40

doing my original example allows you to have tests that are more complex than getenv, like reading a file, or whatever else, and have it function correctly.

Joshua Suskalo17:04:58

Your version here works identically both ways because you've mandated that the only thing that argument is used for is checking an environment variable.

noisesmith17:04:45

OK - this version lets me see what eval is doing here

(cmd)justin@abjection:~/clojure-experiments/ifdef$ cat src/ifdef/core.clj
(ns ifdef.core
  (:gen-class))

(defmacro compile-when
  [test & body]
  (when (eval test)
    (cons 'do body)))

(defmacro compile-when-2
  [test & body]
   (when test
     (cons 'do body)))

(defn file-exists?
  [f]
  (.exists (java.io.File. f)))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (prn :compile-when (compile-when (file-exists? "some-file") "some-file exists"))
  (prn :compile-when-2 (compile-when-2 (file-exists? "some-file") "some-file exists")))
(cmd)justin@abjection:~/clojure-experiments/ifdef$ lein do clean, uberjar
Compiling ifdef.core
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT.jar
Created /home/justin/clojure-experiments/ifdef/target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar
(cmd)justin@abjection:~/clojure-experiments/ifdef$ java -jar target/uberjar/ifdef-0.1.0-SNAPSHOT-standalone.jar 
:compile-when nil
:compile-when-2 "some-file exists"

Joshua Suskalo17:04:54

For example:

(defmacro compile-when [test & body]
  (when (eval test)
    `(do ~@body)))

(def debug? (System/getenv "DEBUG"))

(defn compute-stuff [& args]
  (do stuff)
  (compile-when debug?
    (println "some logging"))
  (more stuff))

(defn other-stuff [& args]
  (compile-when (System/getenv "FEATURE")
    (do stuff)))

Joshua Suskalo17:04:28

the eval allows you to actually have code written in the macro arguments that gets run at macroexpand time, rather than at runtime, and instead of having some constant data that's interpreted by the macro like you had with your System/getenv example.

noisesmith17:04:13

right, thanks for your patience

Joshua Suskalo17:04:48

no problem! Sorry if I explained this in a way that was confusing.

borkdude13:04:14

@archibald.pontier_clo In Clojure you can do a lot of this stuff using macros which look at something during macro-expansion

p-himik13:04:51

It can also be a simple if - depends on what you want to put in the condition. Or you can change the classpath. So e.g. build script A has some a.clj in classpath and build script B has some other a.clj with same vars but different implementation.

Nazral13:04:35

oh that's a good idea, thank you !

Alex Miller (Clojure team)13:04:34

(btw, the best place to ask this is #tools-build)

Alex Miller (Clojure team)13:04:32

the question makes me think you maybe don't have an interactive dev environment set up with a repl. really, you don't want your build process to be in your feedback loop (and then you don't care)

Alex Miller (Clojure team)13:04:30

the build mostly takes a while because it forks a jvm for compilation and compiles a lot of code, so you could make it faster by not compiling or by compiling less, but it's hard to tell what you're getting on those dimensions without knowing more about your code or your deps

Serafeim Papastefanos14:04:14

Hey Alex thank you for replying.... The compile is out of my feedback loop and is very fast anyway

Serafeim Papastefanos14:04:34

However it bugs me that creating the uberjar is so slow

Alex Miller (Clojure team)14:04:32

compilation is comparatively slow - that's why to do it "ahead of time" :)

Serafeim Papastefanos14:04:23

Hmmm I'm not sure i still think something's fishy. Is there a way to debug that so i can understand what's taking so long?

potetm14:04:44

You can read the code and see what it does 😄 It’s copying all of your project files and your dependencies files into a temp dir, and then it’s re-compressing those into a single jar file. That’s a lot of IO.

Alex Miller (Clojure team)14:04:59

that's generally super fast

Alex Miller (Clojure team)14:04:04

the slow part is compiling the Clojure code

potetm14:04:03

My experience has been that it takes multiple seconds (up to 10-20 for a relatively small project). Doesn’t really account for the 1m+ I suppose.

potetm14:04:31

Very Unscientific measurements of course. I wouldn’t read too much into it.

Serafeim Papastefanos14:04:47

No it takes a lot of time every time... I'll try to measure it

Serafeim Papastefanos14:04:10

Also everything else is super fast

Alex Miller (Clojure team)14:04:10

all the compile step is doing is essentially - starting a jvm with the classpath from the basis, then running compile on all of the namespaces in your project

Alex Miller (Clojure team)14:04:26

that will transitively compile all code in all deps used by your project

Alex Miller (Clojure team)14:04:23

but perhaps you don't need to do that much work

Alex Miller (Clojure team)14:04:45

OR something in your top level code is doing work (perhaps unnecessarily) at load time (which happens as part of compile)

Alex Miller (Clojure team)14:04:33

OR you use a future or agent in your top level def and have background agent threads (which wait 1 minute to shut down)

Alex Miller (Clojure team)14:04:25

I don't have a great suggestion about how to determine which one of those things it is, other than by doing what I described above - start a repl with your project classpath, compile each namespace, look for something slow

Serafeim Papastefanos14:04:32

Hmmm that a good suggestion Alex

Serafeim Papastefanos14:04:54

I'm starting a ring jetty server on my top level context

Serafeim Papastefanos14:04:53

Hmmm actually no i don't, sorry

Serafeim Papastefanos14:04:05

(def app
(logger/wrap-log-response (reitit.ring/ring-handler cljcrd.routes/router (reitit.ring/create-default-handler)))) (def reloadable-app (if (cfg/get-conf :debug) (wrap-reload #'app) app)) (def port (cfg/get-conf :cljcrd-port 3000)) (defn serve ([] (serve {})) ([opts] (ring.adapter.jetty/run-jetty reloadable-app (merge {:port port} opts))))

Alex Miller (Clojure team)14:04:00

well, all that code gets loaded and run during compilation, so you are

Serafeim Papastefanos14:04:52

Yes but is something slow?

Alex Miller (Clojure team)14:04:05

you're the one that can answer that :)

Serafeim Papastefanos14:04:22

No it ain't it strts immediately

Alex Miller (Clojure team)14:04:24

start your repl and compile that namespace

Alex Miller (Clojure team)14:04:18

but also, I think it's useful to know if anything there uses a future, which starts an agent thread, which takes 1 minute to time out and shut down

Serafeim Papastefanos14:04:51

Hmmm i don't think so, i don't even know that a future is :thinking_face:

Alex Miller (Clojure team)14:04:59

it runs code in a background thread

Alex Miller (Clojure team)14:04:29

that thread comes from a pool, which caches threads for up to 1 minute in case they can be reused

Serafeim Papastefanos14:04:13

Hm no i don't think so

Alex Miller (Clojure team)14:04:42

if you load that namespace, you can take a thread dump (ctrl-\) and look for agent threads named "clojure-agent-..."

Ben Sless14:04:34

There's a chance compilation will be faster if you change defs to defns, but I noticed requiring some reitit namespaces can be slow

didibus15:04:14

Just in general, I wouldn't have defs top level that aren't constant. Make it all defn or wrap them in a delay or promise would be my recommendation.

Serafeim Papastefanos15:04:00

Thank you didibud I'll change it

Serafeim Papastefanos08:04:57

Hello friends continuing with this thingie, I added some (time ) commands to calculate each of the commands of the uber building: Here's my output

Building uber file...
Time for copy-dir:
"Elapsed time: 118.6574 msecs"
Time for compile-clj:
"Elapsed time: 14141.1312 msecs"
Time for uber:
"Elapsed time: 65064.87 msecs"
Notice that the b/uber command takes like 65 seconds (!)

Ben Sless09:04:35

You can attach visualvm or JFR to profile the process and figure out exactly where you're spending all this time

Ben Sless09:04:25

clojure -J-XX:+FlightRecorder -J-XX:StartFlightRecording=duration=20s,filename=myrecording.jfr -Sdeps '{:deps {org.clojure/core.async      {:mvn/version "1.3.618"}}}' -M -e "(require '[clojure.core.async])"
Example

didibus15:04:16

That seems unreasonably too much. How large is the resulting Uberjars? So you have really big resource files you package with it? That step should be equivalent to just zipping things, which suis generally be relatively fast for just code.

Serafeim Papastefanos16:04:08

The resulting uberjar is like 10 mb it's a simple ring webapp

Alex Miller (Clojure team)16:04:53

I agree that that does not match what I've seen in other similarly sized setups I've seen

Alex Miller (Clojure team)16:04:00

all the jars should be local by the time you have the basis - this should be effectively just unzipping all the jars and rezipping into the uber. any reason disk ops might be slow? network fs, container, etc

Serafeim Papastefanos16:04:01

No reason. these run on the filesystem,. Notice I use windows... Also notice I tried on two different pcs (both with ssd). On this one it's a little better

Time for copy-dir:
"Elapsed time: 261.3086 msecs"
Time for compile-clj:
"Elapsed time: 23923.3097 msecs"
Time for uber:
"Elapsed time: 31093.3577 msecs"

Serafeim Papastefanos16:04:15

next try is to try on wsl i guess

Serafeim Papastefanos17:04:19

uh oh. wsl is even worse

serafeim@DESKTOP-UEUH3G7:~/progr/clojure/cljcrd$ clj -T:build uber
Building uber file...
Time for copy-dir:
"Elapsed time: 502.2897 msecs"
Time for compile-clj:
"Elapsed time: 91868.1961 msecs"
Time for uber:
"Elapsed time: 161638.7026 msecs"

Serafeim Papastefanos17:04:09

5 minutes for a 10 mb jar:

serafeim@DESKTOP-UEUH3G7:~/progr/clojure/cljcrd$ ls -lh target/cljcrd-0.0.1.jar
-rw-r--r-- 1 serafeim serafeim 9.8M Apr 21 20:21 target/cljcrd-0.0.1.jar

Serafeim Papastefanos17:04:44

I also tried the flight recorder jfr mentioned before; it created a file but how to understand what's happening ?

Serafeim Papastefanos17:04:27

ok I used the jmc.exe

Alex Miller (Clojure team)17:04:48

just as a point of comparison, the Clojure CLI uses tools.build to make its uber jar and it takes 10s to run compile-clj and 26s to create the uberjar, which is about 20 MB

Serafeim Papastefanos17:04:58

I am seeing this in the first page of jmc:

The program generated an average of 243 errors per minute during 2022-04-21 20:37:06.000 – 20:38:06.

243 errors were thrown in total. The most common error was ''clojure.lang.LockingTransaction$RetryEx'', which was thrown 223 times. Investigate the thrown errors to see if they can be avoided. Errors indicate that something went wrong with the code execution and should never be used for flow control.
is this relative ?

Alex Miller (Clojure team)17:04:23

I don't know anything about jfr, so not sure I can help you there

Ben Sless17:04:02

After you have created the recording, you have a log of all events that happened on the JVM. You can open it with visualvm's gui and go to the CPU profiling tab. There you can see where you spent CPU

Serafeim Papastefanos17:04:27

i'm not familair with visualvm; i used the java mission control program

Ben Sless17:04:27

If you want, you can send me the recording

Ben Sless17:04:46

Visualvm is slightly simpler than jmc

Serafeim Papastefanos17:04:58

thank you ben I upload it here

👍 1
Ben Sless17:04:00

The CPU sampler is easy to read

Ben Sless17:04:13

I'll give it a look in a few minutes

🙌 1
Ben Sless17:04:08

okay that's odd

Ben Sless17:04:26

copying the files took ~6-5 seconds

Ben Sless18:04:15

Alex can probably explain this part - waiting for another JVM to finish?

Alex Miller (Clojure team)18:04:42

yeah, that's the outer jvm waiting for the compile

Ben Sless18:04:50

it's waiting on tools.deps worker-N threads?

Alex Miller (Clojure team)18:04:34

no, compile-clj forks a jvm that does the compile - this is the build waiting for that

Ben Sless18:04:44

Also, maven/read-descriptor takes about 17 seconds there

Serafeim Papastefanos18:04:09

I also opened it with visual vm... but this recording is lying 😞

Serafeim Papastefanos18:04:29

it says that the uptime was 20 seconds but that ain't true ! the uptime was 5 minutes. maybe the recording stops abruptly ? i run it like this clj -J-XX:+FlightRecorder -J-XX:StartFlightRecording=duration=20s,filename=myrecording.jfr -T:build uber

Serafeim Papastefanos18:04:47

the duration's there 🙂

Serafeim Papastefanos18:04:05

i'll change it to 300s to see what happens

Serafeim Papastefanos18:04:30

here's the correct recording; duration is 3 min 23 second

Serafeim Papastefanos18:04:12

and the timing output I got from running the build in case it helps:

Building uber file...
Time for copy-dir:
"Elapsed time: 308.6562 msecs"
Time for compile-clj:
"Elapsed time: 62769.2298 msecs"
Time for uber:
"Elapsed time: 135928.1001 msecs"

Ben Sless18:04:30

52 seconds on copying? are you on wsl?

Serafeim Papastefanos18:04:29

it is slow on windows also

Serafeim Papastefanos18:04:56

but not so slow 😐

Serafeim Papastefanos18:04:53

i'll try again on windows

Serafeim Papastefanos18:04:47

hmmm it's much faster now 😐

Building uber file...
Time for copy-dir:
"Elapsed time: 175.3734 msecs"
Time for compile-clj:
"Elapsed time: 11570.7838 msecs"
Time for uber:
"Elapsed time: 17938.6844 msecs"

Serafeim Papastefanos18:04:02

how is this possible ?

Ben Sless18:04:32

WSL filesystem notoriously slow

Serafeim Papastefanos18:04:02

yes i know that's why i avoid it. i tried wsl because it was slow when i was using cmd.exe

Serafeim Papastefanos18:04:12

but now it's much faster it takes less than 30 seconds to build the jar

Serafeim Papastefanos18:04:02

which is not that bad

Serafeim Papastefanos18:04:47

so let's pretend this issue never happend... thank you for the help

Ben Sless18:04:41

happy to help

didibus23:04:03

If you use WSL and keep things on the WSL filesystem, I think it should still be fast. But maybe not, it's still virtualized after all.

Serafeim Papastefanos08:04:21

i used wsl with a wsl filesystem. however it was wsl 1 so this could be the problem

didibus16:04:07

Ah yes, I've only ever used WSL2

Nom Nom Mousse15:04:34

I have a local app (clj) with a front-end (cljs) displayed in the browser. I am using Sente (websockets) to send data to the front-end. What do you think would be the best way to send pngs/svgs to the front-end? The front-end can't read my local disk.

Nom Nom Mousse15:04:08

I basically want to show images stored locally in the browser.

p-himik15:04:37

Just embed an <img> tag with the right src, that's it. No need to make things more complicated than they need to be.

p-himik15:04:53

Of course, your server will have to serve the images at the relevant URLs.

Nom Nom Mousse15:04:53

But if the server is the local computer?

Nom Nom Mousse15:04:33

Is that when you use the file:// url? Should have tried before asking XD

p-himik15:04:30

How do you serve your whole frontend? Probably via or something like that - just use that. file:// should not work.

Nom Nom Mousse15:04:26

Thanks. This was a dumb q I now realize XD

Joshua Suskalo15:04:04

Yeah, I would recommend what p-himik said. If you are working in a system which eventually is hosting images across a network rather than only localhost, then you will likely want to host the images in a cdn to reduce network traffic to your business logic server.

🙏 1
Nom Nom Mousse11:04:28

Oh, another complication: the images are created by the server and have an absolute path. I guess the easiest thing to do is to create a symlink to the images in my resources/public/img/ folder.

Nom Nom Mousse11:04:15

Hmm, symlinking just resulted in a missing image symbol, but actually copying the files to the folder did the trick.

Nom Nom Mousse11:04:26

Perhaps the issue is permissions so the webserver is not allowed to read the original folders? I'll investigate. Edit: no, it seems like the original folders are ok. Example: -rwxr-xr-x

dpsutton19:04:31

is there a way to un-extend a protocol? Wanting something to fall back to the Object implementation and wondering if we have to restart the repl or if there is something along the lines of remove-method for protocols

p-himik19:04:58

Heh, I stumbled upon it just yesterday. Here's how Cheshire does it: https://github.com/dakrone/cheshire/blob/master/src/cheshire/generate.clj#L239-L257

potetm20:04:13

@alexmiller I recall you offering the advice “Don’t do work in :or clauses.” I was wondering if the following would be considered incorrect:

:or {bd (Duration/ofDays 5)
         od Duration/ZERO
         t (Instant/now)}

potetm20:04:53

2 of those could basically be compile-time constants, but the (Instant/now) is the bit I wasn’t sure about

p-himik21:04:58

FWIW my guess is that the advice was given against using something that's not supposed to be run when t is present in the map. Because whatever you pass to :or will be run unconditionally.

potetm21:04:41

It was to do with dependencies between elements in the :or block

potetm21:04:31

Alex gave the more general advice of “don’t do work,” but I failed to ask what exactly that meant 😂

potetm21:04:15

But what you’re saying makes sense as well.

p-himik21:04:19

Ah, then it's "don't depend on the order of map destructuring". :) So you must not do :or {a 1, b (inc a)}.

p-himik21:04:01

Or even :or {a 1 b a}. There's no computation, but there's a dependency. This should not be done.

p-himik21:04:02

So I'd say your case with (Instant/now) is perfectly fine, as long as you don't mind a few extra cycles and aren't willing to make the code a couple of lines longer to remove those cycles.

potetm21:04:58

that makes sense

dpsutton21:04:58

I always just destructure from a (normalize x) which can merge all of that in

Nundrum21:04:04

I'm looking for something that'll do some of what find does. Recurse directories without crossing filesystem boundaries, specifically. And not follow symlinks. I couldn't find something quite like that, so thought I'd ask before building it.

dorab21:04:44

Depending on what you are specifically looking for, take a look at glob in babashka.fs

Nundrum21:04:50

It doesn't look like that knows about filesystem boundaries. But it does understand symlinks.

p-himik21:04:27

I'd try to search for a Java library that does that. And if you can't find any, you can use File.list, File.getCanonicalPath, and Files.getFileStorage to fit all the requirements. Just as a small example:

jshell> Path p = Path.of("/")
p ==> /

jshell> Path p2 = Path.of("/home")
p2 ==> /home

jshell> Files.getFileStore(p)
$15 ==> / (/dev/nvme0n1p3)

jshell> Files.getFileStore(p2)
$16 ==> /home (/dev/nvme0n1p2)

Nundrum21:04:18

The closest I could find was an Apache project that looks dead: https://commons.apache.org/sandbox/commons-finder/ Guess I'll have to build it hard-hat

dorab22:04:36

If all the glob is missing is the filesystem boundaries, you might ask borkdude to add that to glob.

👍 1
potetm01:04:00

(Files/walk (.toPath (io/file "/tmp"))
            (into-array FileVisitOption
                        []))
?

potetm01:04:48

It doesn’t filter based on filesystem, but you can do (iterator-seq (.iterator …)) and do a filter with a check on Files/getFileStore like p-himik said.

Nundrum19:04:16

I don't want to descend into a directory that's not valid - that could be a time-consuming diversion. And I need to get the oldest/newest file timestamp found there.