Skip to content

Commit

Permalink
[Fix #3733] Remove support for sideloading
Browse files Browse the repository at this point in the history
Sideloading was removed in nREPL 1.3, as it was not particularly useful
and never gain any meaningful adoption in the broader Clojure community.
  • Loading branch information
bbatsov committed Aug 15, 2024
1 parent 3c8af8b commit cd4c118
Show file tree
Hide file tree
Showing 5 changed files with 1 addition and 207 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

- Bump the injected nREPL version to 1.3.
- Bump the injected `cider-nrepl` to [0.49.3](https://github.com/clojure-emacs/cider-nrepl/blob/master/CHANGELOG.md#0493-2024-08-13).
- [#3733](https://github.com/clojure-emacs/cider/issues/3733): Remove support for sideloading. (this experimental feature was removed from nREPL 1.3)

### Bugs fixed

Expand Down
1 change: 0 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ There's a bit of info on the subject [here](https://github.com/clojure-emacs/cid

## Implement new nREPL features

* sideloading (there's some experimental support for this)
* dynamic middleware loading
* ~~completion~~
* ~~lookup~~
Expand Down
184 changes: 0 additions & 184 deletions cider-eval.el
Original file line number Diff line number Diff line change
Expand Up @@ -204,190 +204,6 @@ When invoked with a prefix ARG the command doesn't prompt for confirmation."
(save-excursion
(quit-window nil error-win))))


;;; Sideloader
;;
;; nREPL includes sideloader middleware which provides a Java classloader that
;; is able to dynamically load classes and resources at runtime by interacting
;; with the nREPL client (as opposed to using the classpath of the JVM hosting
;; nREPL server).
;;
;; This performs a similar functionality as the load-file
;; operation, where we can load Clojure namespaces (as source files) or Java
;; classes (as bytecode) by simply requiring or importing them.
;;
;; See https://nrepl.org/nrepl/design/middleware.html#sideloading

(defcustom cider-sideloader-path nil
"List of directories and jar files to scan for sideloader resources.
When not set the cider-nrepl jar will be added automatically when upgrading
an nREPL connection."
:type 'list
:group 'cider
:package-version '(cider . "1.2.0"))

(defcustom cider-dynload-cider-nrepl-version nil
"Version of the cider-nrepl jar used for dynamically upgrading a connection.
Defaults to `cider-required-middleware-version'."
:type 'string
:group 'cider
:package-version '(cider . "1.2.0"))

(defun cider-read-bytes (path)
"Read binary data from PATH.
Return the binary data as unibyte string."
;; based on f-read-bytes
(with-temp-buffer
(set-buffer-multibyte nil)
(setq buffer-file-coding-system 'binary)
(insert-file-contents-literally path nil)
(buffer-substring-no-properties (point-min) (point-max))))

(defun cider-retrieve-resource (dirs name)
"Find a resource NAME in a list DIRS of directories or jar files.
Similar to a classpath lookup. Returns the file contents as a string."
(seq-some
(lambda (path)
(cond
((file-directory-p path)
(let ((expanded (expand-file-name name path)))
(when (file-exists-p expanded)
(cider-read-bytes expanded))))
((and (file-exists-p path) (string-suffix-p ".jar" path))
(cider-jar-retrieve-resource path name))))
dirs))

(defun cider-provide-file (file)
"Provide FILE in a format suitable for sideloading."
(let ((contents (cider-retrieve-resource cider-sideloader-path file)))
(if contents
(base64-encode-string contents 'no-line-breaks)
;; if we can't find the file we should return an empty string
(base64-encode-string ""))))

(defun cider-sideloader-lookup-handler ()
"Make a sideloader-lookup handler."
(lambda (response)
(nrepl-dbind-response response (id status type name)
(if status
(when (member "sideloader-lookup" status)
(cider-request:sideloader-provide id type name))))))

(defun cider-add-middleware-handler (continue)
"Make a add-middleware handler.
CONTINUE is an optional continuation function."
(lambda (response)
(nrepl-dbind-response response (status unresolved-middleware) ;; id middleware
(when unresolved-middleware
(seq-do
(lambda (mw)
(cider-repl-emit-interactive-stderr
(concat "WARNING: middleware " mw " was not found or failed to load.\n")))
unresolved-middleware))
(when (and status (member "done" status) continue)
(funcall continue)))))

(defun cider-request:sideloader-start (&optional connection tooling)
"Perform the nREPL \"sideloader-start\" op.
If CONNECTION is nil, use `cider-current-repl'.
If TOOLING is truthy then the operation is performed over the tooling
session, rather than the regular session."
(cider-ensure-op-supported "sideloader-start")
(cider-nrepl-send-request `("op" "sideloader-start")
(cider-sideloader-lookup-handler)
connection
tooling))

(defun cider-request:sideloader-provide (id type file &optional connection)
"Perform the nREPL \"sideloader-provide\" op for ID, TYPE and FILE.
If CONNECTION is nil, use `cider-current-repl'."
(cider-nrepl-send-request `("id" ,id
"op" "sideloader-provide"
"type" ,type
"name" ,file
"content" ,(cider-provide-file file))
(cider-sideloader-lookup-handler)
connection))

(defun cider-sideloader-start (&optional connection)
"Start nREPL's sideloader.
If CONNECTION is nil, use `cider-current-repl'."
(interactive)
(message "Starting nREPL's sideloader")
(cider-request:sideloader-start connection)
(cider-request:sideloader-start connection 'tooling))

(defvar cider-nrepl-middlewares
'("cider.nrepl/wrap-apropos"
"cider.nrepl/wrap-classpath"
"cider.nrepl/wrap-clojuredocs"
"cider.nrepl/wrap-complete"
"cider.nrepl/wrap-content-type"
"cider.nrepl/wrap-debug"
"cider.nrepl/wrap-enlighten"
"cider.nrepl/wrap-format"
"cider.nrepl/wrap-info"
"cider.nrepl/wrap-inspect"
"cider.nrepl/wrap-log"
"cider.nrepl/wrap-macroexpand"
"cider.nrepl/wrap-ns"
"cider.nrepl/wrap-out"
"cider.nrepl/wrap-slurp"
"cider.nrepl/wrap-profile"
"cider.nrepl/wrap-refresh"
"cider.nrepl/wrap-resource"
"cider.nrepl/wrap-spec"
"cider.nrepl/wrap-stacktrace"
"cider.nrepl/wrap-test"
"cider.nrepl/wrap-trace"
"cider.nrepl/wrap-tracker"
"cider.nrepl/wrap-undef"
"cider.nrepl/wrap-version"
"cider.nrepl/wrap-xref"))

(defun cider-request:add-middleware (middlewares
&optional connection tooling continue)
"Use the nREPL dynamic loader to add MIDDLEWARES to the nREPL session.
- If CONNECTION is nil, use `cider-current-repl'.
- If TOOLING it truthy, use the tooling session instead of the main session.
- CONTINUE is an optional continuation function, which will be called when the
add-middleware op has finished successfully."
(cider-nrepl-send-request `("op" "add-middleware"
"middleware" ,middlewares)
(cider-add-middleware-handler continue)
connection
tooling))

(defun cider-add-cider-nrepl-middlewares (&optional connection)
"Use dynamic loading to add the cider-nrepl middlewares to nREPL.
If CONNECTION is nil, use `cider-current-repl'."
(cider-request:add-middleware
cider-nrepl-middlewares connection nil
(lambda ()
;; When the main session is done adding middleware, then do the tooling
;; session. At this point all the namespaces have been sideloaded so this
;; is faster, we don't want these to race to sideload resources.
(cider-request:add-middleware
cider-nrepl-middlewares connection 'tooling
(lambda ()
;; Ask nREPL again what its capabilities are, so we know which new
;; operations are supported.
(nrepl--init-capabilities (or connection (cider-current-repl))))))))

(defvar cider-required-middleware-version)
(defun cider-upgrade-nrepl-connection (&optional connection)
"Sideload cider-nrepl middleware.
If CONNECTION is nil, use `cider-current-repl'."
(interactive)
(when (not cider-sideloader-path)
(setq cider-sideloader-path (list (cider-jar-find-or-fetch
"cider" "cider-nrepl"
(or cider-dynload-cider-nrepl-version
cider-required-middleware-version)))))
(cider-sideloader-start connection)
(cider-add-cider-nrepl-middlewares connection))


;;; Dealing with compilation (evaluation) errors and warnings
(defun cider-find-property (property &optional backward)
Expand Down
2 changes: 0 additions & 2 deletions cider-repl.el
Original file line number Diff line number Diff line change
Expand Up @@ -1692,7 +1692,6 @@ constructs."
(declare-function cider-version "cider")
(declare-function cider-test-run-loaded-tests "cider-test")
(declare-function cider-test-run-project-tests "cider-test")
(declare-function cider-sideloader-start "cider-eval")
(cider-repl-add-shortcut "clear-output" #'cider-repl-clear-output)
(cider-repl-add-shortcut "clear" #'cider-repl-clear-buffer)
(cider-repl-add-shortcut "clear-banners" #'cider-repl-clear-banners)
Expand All @@ -1706,7 +1705,6 @@ constructs."
(cider-repl-add-shortcut "classpath" #'cider-classpath)
(cider-repl-add-shortcut "history" #'cider-repl-history)
(cider-repl-add-shortcut "trace-ns" #'cider-toggle-trace-ns)
(cider-repl-add-shortcut "sideloader-start" #'cider-sideloader-start)
(cider-repl-add-shortcut "undef" #'cider-undef)
(cider-repl-add-shortcut "refresh" #'cider-ns-refresh)
(cider-repl-add-shortcut "reload" #'cider-ns-reload)
Expand Down
20 changes: 0 additions & 20 deletions test/cider-eval-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,6 @@

;; Please, for each `describe', ensure there's an `it' block, so that its execution is visible in CI.

(describe "cider-provide-file"
(let ((tmp-dir (temporary-file-directory)))
(it "returns an empty string when the file is not found"
(expect (cider-provide-file "abc.clj") :to-equal ""))
(it "base64 encodes without newlines"
(let ((cider-sideloader-path (list tmp-dir))
(default-directory tmp-dir)
(filename (make-temp-file "abc.clj")))
(with-temp-file filename
(dotimes (_ 60) (insert "x")))
(expect (cider-provide-file filename) :not :to-match "\n")))
(it "can handle multibyte characters"
(let ((cider-sideloader-path (list tmp-dir))
(default-directory tmp-dir)
(filename (make-temp-file "abc.clj"))
(coding-system-for-write 'utf-8-unix))
(with-temp-file filename
(insert "🍻"))
(expect (cider-provide-file filename) :to-equal "8J+Nuw==")))))

(describe "cider-extract-error-info"
(it "Matches Clojure compilation exceptions"
(expect (cider-extract-error-info cider-compilation-regexp "Syntax error compiling clojure.core/let at (src/haystack/analyzer.clj:18:1).\n[1] - failed: even-number-of-forms? at: [:bindings] spec: :clojure.core.specs.alpha/bindings\n")
Expand Down

0 comments on commit cd4c118

Please sign in to comment.