Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ jobs:
# emacs_version: ['27.2', '28.2', '29.3', '30.1', 'snapshot']
emacs_version: ['27.2', '28.2', '29.3', '30.1']
java_version: ['21']
dotnet_version: ['8.0.413', '9.0.304']
include:
# For other OSes, test only the latest stable Emacs version.
- os: macos-latest # aarch64
emacs_version: '30.1'
java_version: '21'
dotnet_version: '8.0.413'
- os: macos-13 # x64
emacs_version: '30.1'
java_version: '21'
dotnet_version: '8.0.413'
- os: windows-latest
emacs_version: '30.1'
java_version: '21'
dotnet_version: '8.0.413'

steps:
- name: Set up Emacs
Expand Down Expand Up @@ -99,6 +103,16 @@ jobs:
- run: |
pip install basilisp==0.1.0b2

- name: Prepare dotnet
uses: xt0rted/setup-dotnet@v1.5.0
with:
dotnet-version: ${{matrix.dotnet_version}}

- name: Prepare ClojureCLR
run: |
dotnet tool install --global Clojure.Main --version 1.12.2
dotnet tool install --global Clojure.Cljr --version 0.1.0-alpha11

- name: Test integration
run: |
# The tests occasionally fail on macos&win in what is seems to
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### New features

- [#3839](https://github.com/clojure-emacs/cider/pull/3839): Add jack-in support for ClojureCLR.

### Changes

### Bugs fixed
Expand Down
62 changes: 59 additions & 3 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,26 @@ By default we favor the project-specific shadow-cljs over the system-wide."
:safe #'stringp
:package-version '(cider . "1.14.0"))

(defcustom cider-clr-command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should used the more descriptive naming "clojure-clr-cli" (or the shorter "clr-cli") instead of just "clr" here, in case at some point we want to add a different way to jack in for Clojure CLR projects. Although, I guess that's somewhat unlikely.

Copy link
Author

@rene-descartes2021 rene-descartes2021 Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should used the more descriptive naming "clojure-clr-cli" (or the shorter "clr-cli") instead of just "clr" here,

So cider-clojure-clr-cli-command in full? Anywhere else? I wasn't really sure how to name things I just tried to follow any patterns I could make out.

in case at some point we want to add a different way to jack in for Clojure CLR projects.

A different way being for Arcadia? That makes sense I think. I personally don't use Unity. Looking around in their issues/PRs I think they've made do just connecting with CIDER manually.

From what I see the commercial support for Arcadia collapsed and they suggested someone else fork it and maintain it.

Looking at the 3 public forks active on GitHub after Arcadia's last commit, they didn't last more than a year, and all had some nREPL changes/fixes [1][2][3].

Well that's a tangent just exploring ideas.

Maybe instead the features within the Arcadia fork of ClojureCLR will get de-fragmented back in/around ClojureCLR by a motivated person(s), I think that would be the best path rather than forking Arcadia... in their videos they described their rationale for the fork and I think it makes sense now to de-fragment. But I wouldn't worry about it till a motivated person shows up and comment.

So I won't worry about supporting a different way to jack into ClojureCLR or a variant of it. Upcoming Clojure LLVM/C++ dialect Jank might better suit their Unity and performance use-case more anyway... or maybe not, I'm not sure.

"cljr"
"The command used to execute ClojureCLR."
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(defcustom cider-clr-parameters
"-X clojure.tools.nrepl/start-server!"
"Params passed to ClojureCLR to start an nREPL server via `cider-jack-in'."
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(defcustom cider-clr-nrepl-sha "a58009f03b489b51194834466a2ee7040dad5861"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't it have any tagged releases?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it does that's what I tried at first but Clojure isn't designed that way for git dependencies. For maven just specifying :mvn/version works, but for a git dep if only :git/tag is specified it errors and still requires :git/sha anyway. So rather than have end user having to specify two things to version it I just elected for the full SHA.

Dunno why it was designed that way, at a glance I'd expect the git tag should be sufficient, but I guess maybe the lack of trust is from the possibility of a bad actor in a supply chain attack changing the tag in the repo? Always specifying the SHA at least in part should mitigate that:
https://clojure.org/reference/deps_edn#deps_git_tag

"The version of clr.tools.nrepl injected on jack-in with ClojureCLR."
:type 'string
:safe #'stringp
:package-version '(cider . "1.20.0"))

(make-obsolete-variable 'cider-lein-global-options 'cider-lein-parameters "1.8.0")
(make-obsolete-variable 'cider-boot-command nil "1.8.0")
(make-obsolete-variable 'cider-boot-parameters nil "1.8.0")
Expand Down Expand Up @@ -325,7 +345,8 @@ to Leiningen."
(const gradle)
(const babashka)
(const nbb)
(const basilisp))
(const basilisp)
(const clr))
:safe #'symbolp
:package-version '(cider . "0.9.0"))

Expand All @@ -346,6 +367,7 @@ command when there is no ambiguity."
(const babashka)
(const nbb)
(const basilisp)
(const clr)
(const :tag "Always ask" nil))
:safe #'symbolp
:package-version '(cider . "0.13.0"))
Expand Down Expand Up @@ -419,7 +441,8 @@ Sub-match 1 must be the project path.")
(lein (:prefix-arg 2 :cmd (:jack-in-type clj :project-type lein :edit-project-dir t)))
(babashka (:prefix-arg 3 :cmd (:jack-in-type clj :project-type babashka :edit-project-dir t)))
(nbb (:prefix-arg 4 :cmd (:jack-in-type cljs :project-type nbb :cljs-repl-type nbb :edit-project-dir t)))
(basilisp (:prefix-arg 5 :cmd (:jack-in-type clj :project-type basilisp :edit-project-dir t))))
(basilisp (:prefix-arg 5 :cmd (:jack-in-type clj :project-type basilisp :edit-project-dir t)))
(clr (:prefix-arg 6 :cmd (:jack-in-type clj :project-type clr :edit-project-dir t))))
"The list of project tools that are supported by the universal jack in command.
Each item in the list consists of the tool name and its plist options.
Expand Down Expand Up @@ -451,6 +474,7 @@ The plist supports the following keys
('gradle cider-gradle-command)
('nbb cider-nbb-command)
('basilisp cider-basilisp-command)
('clr cider-clr-command)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defcustom cider-enrich-classpath nil
Expand Down Expand Up @@ -482,6 +506,7 @@ Throws an error if PROJECT-TYPE is unknown."
;; the exec-path
('gradle (cider--resolve-project-command cider-gradle-command))
('basilisp (cider--resolve-command cider-basilisp-command))
('clr (cider--resolve-command cider-clr-command))
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-global-options (project-type)
Expand All @@ -494,6 +519,7 @@ Throws an error if PROJECT-TYPE is unknown."
('gradle cider-gradle-global-options)
('nbb cider-nbb-global-options)
('basilisp nil)
('clr nil)
(_ (user-error "Unsupported project type `%S'" project-type))))

(defun cider-jack-in-params (project-type)
Expand All @@ -510,6 +536,7 @@ Throws an error if PROJECT-TYPE is unknown."
('gradle cider-gradle-parameters)
('nbb cider-nbb-parameters)
('basilisp cider-basilisp-parameters)
('clr cider-clr-parameters)
(_ (user-error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -856,6 +883,30 @@ Does so by concatenating GLOBAL-OPTS, DEPENDENCIES finally PARAMS."
" "
params)))

(defun cider-clr-jack-in-dependencies (params dependencies &optional command)
"Create ClojureCLR clr.core.cli jack-in dependencies.
Does so by concatenating DEPENDENCIES, and PARAMS into a
suitable `cljr` invocation and quoting, also accounting for COMMAND if
provided."
(let* ((all-deps (thread-last dependencies
(cider--dedupe-deps)
(seq-map (lambda (dep)
(if (listp (cadr dep))
(format "%s {%s}"
(car dep)
(seq-reduce
(lambda (acc v)
(concat acc (format " :%s \"%s\" " (car v) (cdr v))))
(cadr dep)
""))
(format "%s {:git/sha \"%s\"}" (car dep) (cadr dep)))))))
(deps (format "{:deps {%s}}"
(string-join all-deps " ")))
(deps-quoted (cider--shell-quote-argument deps command)))
(format "-Sdeps %s %s"
deps-quoted
(if params (format " %s" params) ""))))

(defun cider-add-clojure-dependencies-maybe (dependencies)
"Return DEPENDENCIES with an added Clojure dependency if requested.
See also `cider-jack-in-auto-inject-clojure'."
Expand Down Expand Up @@ -915,6 +966,10 @@ dependencies."
(unless (seq-empty-p global-opts) " ")
params))
('basilisp params)
('clr (cider-clr-jack-in-dependencies
params
`(("io.github.clojure/clr.tools.nrepl" ,cider-clr-nrepl-sha))
command))
(_ (error "Unsupported project type `%S'" project-type))))


Expand Down Expand Up @@ -2031,7 +2086,8 @@ PROJECT-DIR defaults to current project."
(gradle . "build.gradle")
(gradle . "build.gradle.kts")
(nbb . "nbb.edn")
(basilisp . "basilisp.edn"))))
(basilisp . "basilisp.edn")
(clr . "deps-clr.edn"))))
(delq nil
(mapcar (lambda (candidate)
(when (file-exists-p (cdr candidate))
Expand Down
1 change: 1 addition & 0 deletions doc/modules/ROOT/pages/basics/up_and_running.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ The following Clojure build tools are supported so far
- kbd:[M-3 C-c C-x j u] jack-in using babashka.
- kbd:[M-4 C-c C-x j u] jack-in using nbb.
- kbd:[M-5 C-c C-x j u] jack-in using basilisp.
- kbd:[M-6 C-c C-x j u] jack-in using ClojureCLR.

Here is an example of how to bind kbd:[F12] for quickly bringing up a
babashka REPL:
Expand Down
11 changes: 5 additions & 6 deletions doc/modules/ROOT/pages/caveats.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,12 @@ from your Emacs config.

== ClojureCLR Support

CIDER currently has very basic support ClojureCLR (via Arcadia's nREPL server). The reasons for this are the following:
CIDER currently has very basic support ClojureCLR (via either Arcadia's nREPL server or
a port of Babashka's nREPL server). The reasons for this are the following:

* nREPL itself runs only on the JVM (because it leverages Java APIs
internally). There's an
https://github.com/clojure/clr.tools.nrepl[nREPL port for ClojureCLR], but
it's not actively maintained and it doesn't behave like the Clojure nREPL.
* `cider-nrepl` uses a lot of Java code internally itself.
* The https://github.com/clojure/clr.tools.nrepl/tree/master/partial-nrepl-nrepl-port[nrepl/nrepl port to ClojureCLR] is not yet working
* `cider-nrepl` uses a lot of Java code internally itself and would need to be adapted/ported like any
other clojure library adapted/ported to ClojureCLR.

Those issues are not insurmountable, but are beyond the scope of our current roadmap.
If someone would like to tackle them, we'd be happy to provide assistance.
Expand Down
16 changes: 8 additions & 8 deletions doc/modules/ROOT/pages/platforms/clojureclr.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@

== Current Status

ClojureCLR on CIDER is not great due to the lack of a fully-functional nREPL
server for ClojureCLR. There are currently two options:
You will get basic CIDER functionality with ClojureCLR. Three nREPL server options:

- https://github.com/clojure/clr.tools.nrepl[clr.tools.nrepl]: A direct (but incomplete) port of the reference Clojure nREPL server.
- https://github.com/clojure/clr.tools.nrepl[clr.tools.nrepl]: At present port of babashka's nREPL server (https://github.com/babashka/babashka.nrepl[babashka.nrepl]).
- https://github.com/clojure/clr.tools.nrepl/tree/master/partial-nrepl-nrepl-port[port of nrepl/nrepl]: A non-working, work-in-progress port of nrepl/nrepl, which may
ultimately better integrate with CIDER once CIDER's middleware (cider-nrepl) is also adapted/ported.
- https://github.com/arcadia-unity/Arcadia/blob/master/Editor/NRepl.cs[Arcadia's nREPL]: A basic, but working nREPL implementation in C#.

If you need to use CIDER with ClojureCLR today Arcadia's nREPL is your only usable option. That being said - `clr.tools.nrepl` is a much
more sophisticated project and ideally we should get it over to the finish line.
An alternative to CIDER & a nREPL server is inf-clojure with ClojureCLR's stock socket REPL server.

== Usage

NOTE: Contributions welcome!

As `cider-jack-in` doesn't support ClojureCLR projects out-of-the-box currently, you'll need to start an nREPL server externally and
connect to it with `cider-connect`.
`cider-jack-in-universal` will jack into a clr.tools.nrepl server as long as a `deps-clr.edn` file
exists in the project directory, otherwise you may call `cider-jack-in-universal` with prefix
argument 6, by either `M-6` or `C-u 6` followed by `M-x cider-jack-in-universal`.

== Plans

In an ideal world we'll achieve the following objectives:

- out-of-the-box ClojureCLR support with `cider-jack-in`
- feature parity between Clojure's nREPL implementation and `clr.tools.nrepl` (the project can use some help)
- adapting `cider-nrepl` for ClojureCLR (some of its codebase is JVM-specific)

Expand Down
62 changes: 62 additions & 0 deletions test/integration/integration-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,68 @@ If CLI-COMMAND is nil, then use the default."
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

(it "to clr"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(clr-edn (expand-file-name "deps-clr.edn" project-dir)))
(write-region "{}" nil clr-edn)

(with-temp-buffer
;; set default directory to temp project
(setq-local default-directory project-dir)

(let* (;; Get a gv reference so as to poll if the client has
;; connected to the nREPL server.
(client-is-connected* (cider-itu-nrepl-client-connected-ref-make!))

;; jack in and get repl buffer
;;
;; The numerical prefix arg for `clr` in
;; `cider-jack-in-universal-options' is 6.
(nrepl-proc (cider-jack-in-universal 6))
(nrepl-buf (process-buffer nrepl-proc)))

;; wait until the client has successfully connected to the
;; nREPL server.
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 20)

;; give it some time to setup the clj REPL
(cider-itu-poll-until (cider-repls 'clj nil) 60)

;; send command to the REPL, and push stdout/stderr to
;; corresponding eval-xxx variables.
(let ((repl-buffer (cider-current-repl))
(eval-err '())
(eval-out '()))
(expect repl-buffer :not :to-be nil)

;; send command to the REPL
(cider-interactive-eval
;; ask REPL to return a string that uniquely identifies it.
"(print :clr? (some? (. RuntimeInformation FrameworkDescription)))"
(lambda (return)
(nrepl-dbind-response
return
(out err)
(when err (push err eval-err))
(when out (push out eval-out)))) )

;; wait for a response to come back.
(cider-itu-poll-until (or eval-err eval-out) 20)

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
(expect eval-out :to-equal '(":clr? true"))

;; exit the REPL.
(cider-quit repl-buffer)

;; wait for the REPL to exit
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

(it "to Basilisp"
(with-cider-test-sandbox
(with-temp-dir temp-dir
Expand Down
Loading