diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e82a8456..833c96c3d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b29db70..a7c9189d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### New features +- [#3839](https://github.com/clojure-emacs/cider/pull/3839): Add jack-in support for ClojureCLR. + ### Changes ### Bugs fixed diff --git a/cider.el b/cider.el index 87c4c283c..d808e8b52 100644 --- a/cider.el +++ b/cider.el @@ -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 + "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" + "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") @@ -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")) @@ -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")) @@ -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. @@ -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 @@ -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) @@ -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) @@ -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)))) @@ -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'." @@ -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)))) @@ -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)) diff --git a/doc/modules/ROOT/pages/basics/up_and_running.adoc b/doc/modules/ROOT/pages/basics/up_and_running.adoc index bae36f36c..1d3bc90e5 100644 --- a/doc/modules/ROOT/pages/basics/up_and_running.adoc +++ b/doc/modules/ROOT/pages/basics/up_and_running.adoc @@ -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: diff --git a/doc/modules/ROOT/pages/caveats.adoc b/doc/modules/ROOT/pages/caveats.adoc index 132ea33e6..b2494ecc1 100644 --- a/doc/modules/ROOT/pages/caveats.adoc +++ b/doc/modules/ROOT/pages/caveats.adoc @@ -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. diff --git a/doc/modules/ROOT/pages/platforms/clojureclr.adoc b/doc/modules/ROOT/pages/platforms/clojureclr.adoc index e858f3f31..661bd7119 100644 --- a/doc/modules/ROOT/pages/platforms/clojureclr.adoc +++ b/doc/modules/ROOT/pages/platforms/clojureclr.adoc @@ -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) diff --git a/test/integration/integration-tests.el b/test/integration/integration-tests.el index c929949e2..68d2d49df 100644 --- a/test/integration/integration-tests.el +++ b/test/integration/integration-tests.el @@ -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