From 30f260e76fa8c007f60c9816fcca7e7d30d61b05 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 11 May 2023 17:50:32 +0100 Subject: [PATCH] Always escape clojure cli command params on MS-win even for non powershell invocations --- .github/workflows/test.yml | 7 ++ CHANGELOG.md | 4 + cider.el | 79 ++++++------ test/integration/integration-tests.el | 165 ++++++++++++++------------ 4 files changed, 142 insertions(+), 113 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb8701862..8fd31cd77 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bf84be36..cd971ccfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/cider.el b/cider.el index 13518e13b..bb39bd3b2 100644 --- a/cider.el +++ b/cider.el @@ -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)) @@ -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. @@ -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 @@ -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) " ") @@ -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. @@ -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 @@ -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) diff --git a/test/integration/integration-tests.el b/test/integration/integration-tests.el index dd4907a86..61bbfa986 100644 --- a/test/integration/integration-tests.el +++ b/test/integration/integration-tests.el @@ -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) @@ -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))) @@ -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 @@ -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. @@ -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 @@ -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