I want to serialize a clojure data structure to EDN and back. I tried this:
user=> (pr-str {:a/b 1})
"#:a{:b 1}"
but it isn't recognized by clojure -Sdeps, which treats it as a file name:
$ clojure -Sdeps '#:a{:b :1}'
Error building classpath. Extra deps file not found: #:a{:b :1}
what's the intended way to do this? can I tell pr-str to use a simpler format?#:... {...} namespaced maps aren't supported by EDN.
But you can change how they're printed:
user=> (binding [*print-namespace-maps* false] (pr-str {:a/b 1}))
"{:a/b 1}"oh beautiful, tysm
that worked!
Make sure to unbind *print-length* and *print-level* too.
I really hate that clojure.edn doesn't have a specialized serialization function and we have to rely on pr which moonlights as a devtime presentation tool.
the good news is that this only runs in CI so if it breaks i will notice very quickly
Good. I once found out a little too late that my "EDN database" gets truncated at 10 kilobytes.
oof
somehow i don't think this is how most people write CI pipelines lol
(defn clj [args]
(let [deps (binding [*print-namespace-maps* false] (pr-str deps))]
(str "bash -x $(which clojure) "
(join-shell (concat ["-Sdeps" deps] args)))))When it comes to CI pipelines, there could be no judgment. This is a safe space π«.
look at least it deal with shell escaping correctly
that's better than i can say for 99% of hand-written yaml
I once had to use 16 backslashes to escape one backslash in a very nested cross-tool invocation string. I non-ironically used Emacs' C-1 6 \ to write them out because I couldn't be bothered to count by hand.
there are actually 6 dynamic vars that influence how pr works https://ask.clojure.org/index.php/14203/how-to-persist-clojure-data-structures-to-disk?show=14205#a14205
In case you didnβt notice already, -Sdeps takes a deps.edn format so so need a {:deps β¦ } around that
i'm passing {:mvn/local-repo "./m2"}, which i don't think has a :deps prefix
correct, that's top-level
what is this warning trying to tell me? what should I use instead?
$ clojure -A:test -P
WARNING: Use of :main-opts with -A is deprecated. Use -M instead.
if I just append -M to the end, it still gives the same warning.if it helps, i have {:aliases {:test {:main-opts ["-m" "kaocha.runner"]}}} in deps.edn
oh, using -X instead of -A seems to help
I think it's telling you to do:
clojure -M:test -P
ohhhh
also, you should put the -P before -M or -X or -T
everything after -M / -X / -T is an arg passed to the program and will not be sent to the CLI
clojure -P -M:test
if the clojure CLI runs into an error, it prints the immediate message and then says "Full report at: /tmp/clojure-...edn". How can I tell it to always print the report to stdout/stderr?
Btw, why isn't it a default behaviour: to write a full stack trace to stderror? Just today I spent a lot of time investigating an issue: building an uberjar fails on CI, and it only says: see full report in /tmp/foobar123.edn.
Because itβs usually more annoying than useful during dev
I know this isn't going to happen but on CI the reverse is annoying and CI's do happen to have the CI env variable set to true... so there's maybe an opportunity to flip the default there
At this point, canβt flip the default
ah, it's --report
hm no that breaks other things, it starts treating -Sdeps as a file path
$ clojure -M --report stderr -e '(/ 1 0)'
{:clojure.main/message
"Execution error (ArithmeticException) at user/eval1 (REPL:1).\nDivide by zero\n",
:clojure.main/triage
{:clojure.error/class java.lang.ArithmeticException,
:clojure.error/line 1,
:clojure.error/cause "Divide by zero",
:clojure.error/symbol user/eval1,
:clojure.error/phase :execution},
:clojure.main/trace
{:via
[{:type java.lang.ArithmeticException,
:message "Divide by zero",
:at [clojure.lang.Numbers divide "Numbers.java" 190]}],
:trace
[[clojure.lang.Numbers divide "Numbers.java" 190]
[clojure.lang.Numbers divide "Numbers.java" 3915]
[user$eval1 invokeStatic "NO_SOURCE_FILE" 1]
[user$eval1 invoke "NO_SOURCE_FILE" 1]
[clojure.lang.Compiler eval "Compiler.java" 7757]
[clojure.lang.Compiler eval "Compiler.java" 7712]
[clojure.core$eval invokeStatic "core.clj" 3232]
[clojure.main$eval_opt invokeStatic "main.clj" 489]
[clojure.main$eval_opt invoke "main.clj" 483]
[clojure.main$initialize invokeStatic "main.clj" 509]
[clojure.main$null_opt invokeStatic "main.clj" 543]
[clojure.main$null_opt invoke "main.clj" 540]
[clojure.main$main invokeStatic "main.clj" 665]
[clojure.main$main doInvoke "main.clj" 617]
[clojure.lang.RestFn applyTo "RestFn.java" 140]
[clojure.lang.Var applyTo "Var.java" 707]
[clojure.main main "main.java" 40]],
:cause "Divide by zero"}}
Execution error (ArithmeticException) at user/eval1 (REPL:1).
Divide by zero--report stderr
but note the order:
clojure [clj-opt*] -M[aliases] [init-opt*] [main-opt] [arg*]report is part of init-opt and -Swhatever is part of clj-opt*
I never remember ordering either, I always have to do a clojure --help
now I wonder how to change the printing to stderr of -X functions. does that always barf an exception?
this is quite annoying because it doesn't compose
$ clj -X clojure.core/assoc 1 2 3 4
Execution error (ArityException) at clojure.run.exec/exec (exec.clj:88).
Wrong number of args (1) passed to: clojure.core/assoc--5502
Full report at:
/var/folders/j9/xmjlcym958b1fr0npsp9msvh0000gn/T/clojure-11315834231884795333.edni want to tell a shell script "use clojure --report stderr when running clojure" and that breaks because now i can no longer use -M:test in the script
$ clj --report -X clojure.core/assoc 1 2 3 4
WARNING: Implicit use of clojure.main with options is deprecated, use -M --report -X clojure.core/assoc 1 2 3 4
{:clojure.main/message
"Execution error (FileNotFoundException) at java.io.FileInputStream/open0 (FileInputStream.java:-2).\nclojure.c
$ clj -X --report clojure.core/assoc 1 2 3 4
Key is missing value: 4
Hmm @alexmiller - how to do this for -X ?Use the system prop version with -J
-J-Dclojure.main.report=stderr
https://clojure.org/reference/repl_and_main#_as_launcher for docs
$ clj -J-Dclojure.main.report=stderr -X clojure.core/assoc 1 2 3
Key is missing value: 3oh the -J version does compose quite well, thank you
@alexmiller With -X?
clj -J-Dclojure.main.report=stderr -X clojure.core/assoc 1 2 3That case is caught and handled as non-exceptional by clojure, I think, @borkdude.
oh right...
$ clj -J-Dclojure.main.report=stderr -X clojure.core/assoc 1 2 3 4
{:clojure.main/message
"Execution error (ArityException) at clojure.run.exec/exec (exec.clj:88).\nWrong number of args (1) passed to: clojure.core/assoc--5502\n",β
I am using clojure -Sdeps '{:mvn/local-repo "cache/m2"}' to use a local cache instead of a user-global cache. most of the time this works. but if I use -T instead of -X/A/M, it ignores the -Sdeps flag and uses the global cache anyway. What am I doing wrong?
Just checking: make sure to write -Sdeps before -X etc
This works on my machine, it starts re-downloading deps for a project I already have run before:
clojure -Sdeps '{:mvn/local-repo "cache/m2"}' -X clojure.core/assoc 1 2 3 4doesn't -T ignore all deps?
oh sorry, misread...
This also downloads stuff on my machine:
clojure -Sdeps '{:mvn/local-repo "cache/m2"}' -T clojure.core/assoc 1 2 3 4I'm testing with clojure -Sdeps '{:mvn/local-repo "cache/m2"}' -T:build native. If I run that twice in a row, nothing gets downloaded. If I delete ~/.m2 and rerun it, it downloads again.
this assumes a file named build.clj in the current directory and a deps.edn with {:aliases {:build {:ns-default build}}}
ok, let me try...
still downloads lots of stuff here!
it creates cache/m2 anew
hm
let me make a minimal repro
do you also have mvn/local-repo in your deps.edn perhaps?
i do not, no
(even though -Sdeps should overwrite)
is "clojure" aliasing something else on your machine, perhaps with the -J-D prefix?
(let me make the repro, my current setup is extremely complicated and i don't want to give unrelated details)
ok yeah -T is unrelated
hm
ohhhh this is happening inside create-basis
jeez
is there a way to tell create-basis to inherit the -Sdeps from the command line?
no
Well, maybe with some scripting yes.
user=> (System/getProperty "clojure.basis")
"/Users/borkdude/.clojure/.cpcache/582082103.basis"you can read that thing and there is probably your local/repo
hm, that will probably be the basis for the currently executing code, right? I have different :deps for my build.clj and my main executable
yeah it's in there
oh i see you mean :basis-config
yes, it's the basis of this runtime, but you invoked that one with -Sdeps, which is what you were asking
so you pull out the mvn/local-repo from that file and set it in your build
that worked!
ok passing this through a bunch of bash scripts is quite annoying and i can't seem to get access to the args to clojure itself, only the args to my script π is there some way to set :mvn/local-repo with an env variable?
maybe i will just bypass a bunch of this code in CI tbh