Fork me on GitHub
#tools-deps
<
2024-03-26
>
yuhan13:03:45

Is this a bug or documentation error? The https://clojure.org/reference/clojure_cli#aliases says that :exec-args specified in multiple aliases are combined with deep-merge, but the actual implementation does a regular merge: https://github.com/clojure/tools.deps/blob/0774b8cc144a06f09c0ba4ec75e3d2adc55a04ba/src/main/clojure/clojure/tools/deps.clj#L169

Alex Miller (Clojure team)14:03:15

I updated that section of doc recently, so that may be a doc error

yuhan15:03:00

Is there a case to be made though for actually deep-merging or concatenating values in the exec-args map? For context, I was looking at calling nrepl's main function using -X instead of -M, which lets me specify the middlewares in an regular EDN vector (instead of the awkward command-line string via :main-opts). I was hoping that this could also compose with other aliases which add their own dependencies and nREPL middlewares, but there doesn't seem to be a clean way of doing so - eg. being able to call clojure -X:portal:nrepl and having the :portal alias somehow append the symbol portal.nrepl/wrap-portal to the list of middlewares passed to :nrepl's exec-fn.

Alex Miller (Clojure team)16:03:59

could you share the example values you are trying to combine?

yuhan17:03:07

I'm imagining something like this, where running clojure -X:portal:cider would pass a concatenated sequence of 3 elements as the :middleware arg.

{,,,

 :aliases
 {:portal {:extra-deps {djblue/portal {:mvn/version "0.53.0"}}
           :exec-args  {:middleware [portal.nrepl/wrap-portal]}}

  :cider {:extra-deps {cider/cider-nrepl             {:mvn/version "0.47.1"}
                       refactor-nrepl/refactor-nrepl {:mvn/version "3.9.1"}}
          :exec-fn    nrepl.cmdline/dispatch-commands
          :exec-args  {:port       2171
                       :middleware [cider.nrepl/cider-middleware
                                    refactor-nrepl.middleware/wrap-refactor]}}}}

yuhan17:03:19

The sample configurations out there all use -M options, where there's no way of composing arguments so one ends up with something like:

{,,,

 :aliases
 {:portal {:extra-deps {cider/cider-nrepl {:mvn/version "0.47.1"}
                        djblue/portal     {:mvn/version "0.53.0"}}
           :main-opts  ["-m" "nrepl.cmdline" "--middleware"
                        "[cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor,portal.nrepl/wrap-portal]"]}
  :cider  {:extra-deps {cider/cider-nrepl             {:mvn/version "0.47.1"}
                        refactor-nrepl/refactor-nrepl {:mvn/version "3.9.1"}}
           :main-opts  ["-m" "nrepl.cmdline" "--middleware"
                        "[cider.nrepl/cider-middleware,refactor-nrepl.middleware/wrap-refactor]"]}}}

Alex Miller (Clojure team)17:03:40

but that's not even deep merge, that's something more

Alex Miller (Clojure team)17:03:25

you're doing deep merge + vector concat

Alex Miller (Clojure team)17:03:15

where do these magic semantics end?

yuhan17:03:56

Yeah, I realise it might be out of scope for tools.deps, maybe one would supply another key on the final alias specifying how to merge exec-arg values, like { :exec-args-merge-with {:middleware clojure.core/concat}}

Alex Miller (Clojure team)17:03:20

I don't think I'm going to do that

Alex Miller (Clojure team)17:03:52

try to unroll the need more - why not maps here? order I presume

Alex Miller (Clojure team)17:03:19

this is a good example, feel free to put it on http://ask.clojure.org as a request. happy to collect votes and think about ideas

yuhan17:03:58

I'm not exactly sure if the order of nrepl middleware matters in practice, guess I was just looking for some sort of mechanism for composing aliases which 'depend' upon each other.

Alex Miller (Clojure team)17:03:36

middleware generally stack so order is relevant

Alex Miller (Clojure team)17:03:33

I am open to things that have more general semantics, there is already too much preciousness in these merge rules

Alex Miller (Clojure team)17:03:53

really don't want to make anything less general than what's there now

yuhan17:03:02

yeah, the current UX of specifying these optional dependencies+middlewares via :main-opts just results in combinatorial explosion since each one needs to be the final alias actually performing the call to clojure.main, I always found it quite unfriendly

yuhan17:03:22

Will think about it a bit more and post on ask.clojure

seancorfield18:03:01

I'll just chime in and say that I used to wish for this too -- specifically for merging/concat of the middleware argument(s) -- but in the end I wrote a little REPL-starter library and use that instead. It tests to see what is available on the classpath and dynamically builds the middleware vector based on that: https://github.com/seancorfield/dot-clojure/blob/develop/src/org/corfield/dev/repl.clj#L123-L132

seancorfield18:03:18

I do think there's a gap in the -M / -X stuff regarding "repeated" arguments -- tools.cli supports arguments that can be repeated to produce a collection of values -- but I don't know how you could provide that in a sane way. If -M concatenated arguments, you could do basic repeated arguments but then you'd also lose other aspects of positionality (e.g., you couldn't rely on having a -m argument in one alias that let you pass subsequent command-line arguments into that -main function). And -X is map-based so you can't have repeated keys there that combined their arguments because you'd lose the ability to override arguments. I don't know what the solution would look like.

1
yuhan11:03:53

I came up with an ad-hoc solution using namespaced keys to bypass the overriding merge semantics in :exec-args , then relies on the exec-fn to combine them together with appropriate semantics. https://gist.github.com/yuhan0/26702914130243382aeb133cfd778fc0 Feels like somewhat of a hack since the merge order isn't guaranteed, even though it seems to follow the order of aliases specified at the cli (something something ArrayMap vs HashMap)

yuhan11:03:25

strange how the CLI allows overriding of nested args at the command line using -X '[:nested :key :path]' val syntax but there's no way for aliases to achieve the same.