Skip to content

Commit

Permalink
Always escape clojure cli command params on MS-win
Browse files Browse the repository at this point in the history
even for non powershell invocations
  • Loading branch information
ikappaki committed May 11, 2023
1 parent 3b8e539 commit 30f260e
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 113 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ jobs:
curl.exe -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev.bat | cmd /Q
- name: Install deps.clj on MS-Windows
if: startsWith (matrix.os, 'windows')
run: |
iwr -Uri https://github.com/raw/borkdude/deps.clj/master/install.ps1 -outfile install_clojure.ps1
.\install_clojure.ps1
get-command deps.exe | split-path -parent | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Check out the source code
uses: actions/checkout@v2

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### Bugs

- [#3341](https://github.com/clojure-emacs/cider/issues/3341): Escape clojure-cli args on MS-Windows on non powershell invocations.

## 1.7.0 (2023-03-23)

### New features
Expand Down
79 changes: 43 additions & 36 deletions cider.el
Original file line number Diff line number Diff line change
Expand Up @@ -744,12 +744,19 @@ removed, LEIN-PLUGINS, LEIN-MIDDLEWARES and finally PARAMS."
"Removes the duplicates in DEPS."
(cl-delete-duplicates deps :test 'equal))

(defun cider-clojure-cli-jack-in-dependencies (global-options params dependencies)
(defun cider--powershell-encode-command (cmd-params)
"Base64 encode the powershell command and jack-in CMD-PARAMS for clojure-cli."
(let* ((quoted-params (replace-regexp-in-string "\"" "\"\"" cmd-params))
(command (format "clojure %s" quoted-params))
(utf-16le-command (encode-coding-string command 'utf-16le)))
(format "-encodedCommand %s" (base64-encode-string utf-16le-command t))))

(defun cider-clojure-cli-jack-in-dependencies (global-options params dependencies command)
"Create Clojure tools.deps jack-in dependencies.
Does so by concatenating DEPENDENCIES, PARAMS and GLOBAL-OPTIONS into a suitable
`clojure` invocation. The main is placed in an inline alias :cider/nrepl
so that if your aliases contain any mains, the cider/nrepl one will be the
one used."
Does so by concatenating DEPENDENCIES, PARAMS and GLOBAL-OPTIONS into a
suitable `clojure` invocation and quoting it based on COMMAND if necessary.
The main is placed in an inline alias :cider/nrepl so that if your aliases
contain any mains, the cider/nrepl one will be the one used."
(let* ((all-deps (thread-last
dependencies
(append (cider--jack-in-required-dependencies))
Expand All @@ -770,18 +777,26 @@ one used."
(apply-partially #'format "%s")
(cider-jack-in-normalized-nrepl-middlewares)
","))
(main-opts (format "\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[%s]\"" middleware)))
(format "%s-Sdeps '{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}' -M%s:cider/nrepl%s"
;; TODO: global-options are deprecated and should be removed in CIDER 2.0
(if global-options (format "%s " global-options) "")
(string-join all-deps " ")
main-opts
(if cider-clojure-cli-aliases
;; remove exec-opts flags -A -M -T or -X from cider-clojure-cli-aliases
;; concatenated with :cider/nrepl to ensure :cider/nrepl comes last
(format "%s" (replace-regexp-in-string "^-\\(A\\|M\\|T\\|X\\)" "" cider-clojure-cli-aliases))
"")
(if params (format " %s" params) ""))))
(main-opts (format "\"-m\" \"nrepl.cmdline\" \"--middleware\" \"[%s]\"" middleware))
(deps (format "%s-Sdeps '{:deps {%s} :aliases {:cider/nrepl {:main-opts [%s]}}}' -M%s:cider/nrepl%s"
;; TODO: global-options are deprecated and should be removed in CIDER 2.0
(if global-options (format "%s " global-options) "")
(string-join all-deps " ")
main-opts
(if cider-clojure-cli-aliases
;; remove exec-opts flags -A -M -T or -X from cider-clojure-cli-aliases
;; concatenated with :cider/nrepl to ensure :cider/nrepl comes last
(format "%s" (replace-regexp-in-string "^-\\(A\\|M\\|T\\|X\\)" "" cider-clojure-cli-aliases))
"")
(if params (format " %s" params) "")))
(quoted (if (eq system-type 'windows-nt)
(if (or (string-equal command "powershell")
(string-equal command "pwsh"))
(cider--powershell-encode-command deps)
(thread-last (replace-regexp-in-string "\"" "\"\"" deps)
(replace-regexp-in-string "'" "\"")))
deps)))
quoted))

(defun cider-shadow-cljs-jack-in-dependencies (global-opts params dependencies)
"Create shadow-cljs jack-in deps.
Expand Down Expand Up @@ -813,13 +828,13 @@ See also `cider-jack-in-auto-inject-clojure'."
dependencies))
dependencies))

(defun cider-inject-jack-in-dependencies (global-opts params project-type)
(defun cider-inject-jack-in-dependencies (global-opts params project-type command)
"Return GLOBAL-OPTS and PARAMS with injected REPL dependencies.
These are set in `cider-jack-in-dependencies', `cider-jack-in-lein-plugins' and
`cider-jack-in-nrepl-middlewares' are injected from the CLI according to
the used PROJECT-TYPE. Eliminates the need for hacking profiles.clj or the
boot script for supporting CIDER with its nREPL middleware and
dependencies."
These are set in `cider-jack-in-dependencies', `cider-jack-in-lein-plugins'
and `cider-jack-in-nrepl-middlewares' are injected from the CLI according
to the used PROJECT-TYPE and COMMAND. Eliminates the need for hacking
profiles.clj or the boot script for supporting CIDER with its nREPL
middleware and dependencies."
(pcase project-type
('lein (cider-lein-jack-in-dependencies
global-opts
Expand All @@ -842,7 +857,8 @@ dependencies."
global-opts
params
(cider-add-clojure-dependencies-maybe
cider-jack-in-dependencies)))
cider-jack-in-dependencies)
command))
('babashka (concat
global-opts
(unless (seq-empty-p global-opts) " ")
Expand Down Expand Up @@ -1510,13 +1526,6 @@ Params is a plist with the following keys (non-exhaustive)
(defvar cider--jack-in-cmd-history nil
"History list for user-specified jack-in commands.")

(defun cider--powershell-encode-command (cmd-params)
"Base64 encode the powershell command and jack-in CMD-PARAMS for clojure-cli."
(let* ((quoted-params (replace-regexp-in-string "\"" "\"\"" cmd-params))
(command (format "clojure %s" quoted-params))
(utf-16le-command (encode-coding-string command 'utf-16le)))
(format "-encodedCommand %s" (base64-encode-string utf-16le-command t))))

(defun cider--update-jack-in-cmd (params)
"Update :jack-in-cmd key in PARAMS.
Expand Down Expand Up @@ -1546,7 +1555,8 @@ PARAMS is a plist with the following keys (non-exhaustive list)
'cider--jack-in-nrepl-params-history)
command-params))
(cmd-params (if cider-inject-dependencies-at-jack-in
(cider-inject-jack-in-dependencies command-global-opts command-params project-type)
(cider-inject-jack-in-dependencies command-global-opts command-params
project-type command)
command-params)))
(if (or project-dir cider-allow-jack-in-without-project)
(when (or project-dir
Expand All @@ -1555,10 +1565,7 @@ PARAMS is a plist with the following keys (non-exhaustive list)
(eq cider-allow-jack-in-without-project 'warn)
(or params-project-type
(y-or-n-p "Are you sure you want to run `cider-jack-in' without a Clojure project? "))))
(let ((cmd (format "%s %s" command-resolved (if (or (string-equal command "powershell")
(string-equal command "pwsh"))
(cider--powershell-encode-command cmd-params)
cmd-params))))
(let ((cmd (format "%s %s" command-resolved cmd-params)))
(plist-put params :jack-in-cmd (if (or cider-edit-jack-in-command
(plist-get params :edit-jack-in-command))
(read-string "jack-in command: " cmd 'cider--jack-in-cmd-history)
Expand Down
165 changes: 88 additions & 77 deletions test/integration/integration-tests.el
Original file line number Diff line number Diff line change
Expand Up @@ -31,72 +31,11 @@
(require 'nrepl-tests-utils "test/utils/nrepl-tests-utils")
(require 'integration-test-utils)

(describe "jack in"
;; For each project tool, create a project in a temp directory,
;; jack-in to it, send an eval command to the REPL server specific to the
;; project to ensure it works, and finally exit the REPL.
(defun jack-in-clojure-cli-test (cli-command)
"Run clojure cli jack in test using given CLI-COMMAND.
(it "to babashka"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(bb-edn (expand-file-name "bb.edn" project-dir)))
(write-region "{}" nil bb-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
(nrepl-proc (cider-jack-in-clj '()))
(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) 5)

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

;; 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 :bb? (some? (System/getProperty \"babashka.version\")))"
(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) 5)

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
(expect eval-out :to-equal '(":bb? 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 clojure tools cli"
(with-cider-test-sandbox
If CLI-COMMAND is nil, then use the default."
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
Expand All @@ -109,6 +48,7 @@
;; than the default timeout period to complete.
(nrepl-sync-request-timeout 30)

(cider-clojure-cli-command (or cli-command cider-clojure-cli-command))
(cider-jack-in-nrepl-middlewares
(append '("ikappaki.nrepl-mdlw-log/middleware") cider-jack-in-nrepl-middlewares)))

Expand Down Expand Up @@ -177,15 +117,18 @@
(message ":ikappaki/nrepl-mdlw-log-dump\n%s\n" (buffer-string)))
(message ":!nrepl-mdlw-log-found")))))))))

(it "to leiningen"
(describe "jack in"
;; For each project tool, create a project in a temp directory,
;; jack-in to it, send an eval command to the REPL server specific to the
;; project to ensure it works, and finally exit the REPL.

(it "to babashka"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(project-clj (expand-file-name "project.clj" project-dir)))
(write-region "(defproject cider/integration \"test\"
:dependencies [[org.clojure/clojure \"1.10.3\"]])"
nil project-clj)
(bb-edn (expand-file-name "bb.edn" project-dir)))
(write-region "{}" nil bb-edn)

(with-temp-buffer
;; set default directory to temp project
Expand All @@ -196,15 +139,15 @@
(client-is-connected* (cider-itu-nrepl-client-connected-ref-make!))

;; jack in and get repl buffer
(nrepl-proc (cider-jack-in-clj `()))
(nrepl-proc (cider-jack-in-clj '()))
(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) 90)
(cider-itu-poll-until (eq (gv-deref client-is-connected*) 'connected) 5)

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

;; send command to the REPL, and push stdout/stderr to
;; corresponding eval-xxx variables.
Expand All @@ -216,7 +159,7 @@
;; send command to the REPL
(cider-interactive-eval
;; ask REPL to return a string that uniquely identifies it.
"(print :clojure? (some? (clojure-version)))"
"(print :bb? (some? (System/getProperty \"babashka.version\")))"
(lambda (return)
(nrepl-dbind-response
return
Expand All @@ -225,19 +168,87 @@
(when out (push out eval-out)))) )

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

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
(expect eval-out :to-equal '(":clojure? true"))
(expect eval-out :to-equal '(":bb? 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)) 15)
(cider-itu-poll-until (not (eq (process-status nrepl-proc) 'run)) 5)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

(it "to clojure tools cli"
(jack-in-clojure-cli-test cli-command))

(when (eq system-type 'windows-nt)
(it "to clojure tools cli (alternative deps.exe)"
(jack-in-clojure-cli-test "deps.exe")))

(it "to leiningen"
(with-cider-test-sandbox
(with-temp-dir temp-dir
;; Create a project in temp dir
(let* ((project-dir temp-dir)
(project-clj (expand-file-name "project.clj" project-dir)))
(write-region "(defproject cider/integration \"test\"
:dependencies [[org.clojure/clojure \"1.10.3\"]])"
nil project-clj)

(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
(nrepl-proc (cider-jack-in-clj `()))
(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) 90)

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

;; 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 :clojure? (some? (clojure-version)))"
(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) 10)

;; ensure there are no errors and response is as expected.
(expect eval-err :to-equal '())
(expect eval-out :to-equal '(":clojure? 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)) 15)
(expect (member (process-status nrepl-proc) '(exit signal))))))))))

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

0 comments on commit 30f260e

Please sign in to comment.