gcv / julia-snail

An Emacs development environment for Julia
GNU General Public License v3.0
231 stars 21 forks source link

support for docker container #65

Closed norci closed 2 years ago

norci commented 2 years ago

I want to run julia inside a docker container, in my local machine. so I changed julia-snail--launch-command to

(defun julia-snail--launch-command ()
  "docker-compose --project-directory=/x/julia run --rm -v ~/.emacs.d/elpa/julia-snail-20210818.310/:/julia-snail -e 10011 -p 127.0.0.1:10011:10011 julia julia -L /julia-snail/JuliaSnail.jl")

the command julia-snail-send-line works. But all the other functions do not work. e.g.

(julia-snail--send-to-server :Main "1")

Debugger entered--Lisp error: (error "Buffer *julia* process has no process")
  process-send-string(#<buffer *julia* process> "(ns = [:Main], reqid = \"4a56136f\", code = \"1\")\n")
  (let* ((process-buf (get-buffer (julia-snail--process-buffer-name repl-buf))) (originating-buf (current-buffer)) (module-ns (julia-snail--construct-module-path module)) (reqid (format "%04x%04x" (random (expt 16 4)) (random (expt 16 4)))) (code-str (json-encode-string str)) (display-code-str (if julia-snail-debug code-str (s-truncate 80 code-str))) (msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid code-str)) (display-msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid display-code-str)) (res-sentinel (gensym)) (res res-sentinel)) (save-current-buffer (set-buffer process-buf) (goto-char (point-max)) (insert display-msg)) (process-send-string process-buf msg) (spinner-start 'progress-bar) (puthash reqid (record 'julia-snail--request-tracker repl-buf originating-buf #'(lambda (request-info &optional data) (if async nil (setq res (or data :nothing))) (if callback-success (progn (funcall callback-success request-info data)))) #'(lambda (request-info) (if async nil (setq res :nothing)) (if callback-failure (progn (funcall callback-failure request-info)))) display-error-buffer-on-failure\? nil nil) julia-snail--requests) (if async reqid (let ((wait-result (catch 'julia-snail--server-filter-error (let (... ... ...) (while ... ... ... ...) (< g9 g11))))) (if (eq t wait-result) res (let ((error-msg (if ... "Snail command timed out" ...))) (if callback-failure (progn (funcall callback-failure))) (save-current-buffer (set-buffer originating-buf) (spinner-stop)) (error error-msg))))))
  (progn (if repl-buf nil (user-error "No Julia REPL buffer %s found; run julia-snail" julia-snail-repl-buffer)) (let* ((process-buf (get-buffer (julia-snail--process-buffer-name repl-buf))) (originating-buf (current-buffer)) (module-ns (julia-snail--construct-module-path module)) (reqid (format "%04x%04x" (random (expt 16 4)) (random (expt 16 4)))) (code-str (json-encode-string str)) (display-code-str (if julia-snail-debug code-str (s-truncate 80 code-str))) (msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid code-str)) (display-msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid display-code-str)) (res-sentinel (gensym)) (res res-sentinel)) (save-current-buffer (set-buffer process-buf) (goto-char (point-max)) (insert display-msg)) (process-send-string process-buf msg) (spinner-start 'progress-bar) (puthash reqid (record 'julia-snail--request-tracker repl-buf originating-buf #'(lambda (request-info &optional data) (if async nil (setq res ...)) (if callback-success (progn ...))) #'(lambda (request-info) (if async nil (setq res :nothing)) (if callback-failure (progn ...))) display-error-buffer-on-failure\? nil nil) julia-snail--requests) (if async reqid (let ((wait-result (catch 'julia-snail--server-filter-error (let ... ... ...)))) (if (eq t wait-result) res (let ((error-msg ...)) (if callback-failure (progn ...)) (save-current-buffer (set-buffer originating-buf) (spinner-stop)) (error error-msg)))))))
  (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '(:repl-buf :async :async-poll-interval :async-poll-maximum :display-error-buffer-on-failure\? :callback-success :callback-failure :allow-other-keys)) (setq --cl-keys-- (cdr (cdr --cl-keys--)))) ((car (cdr (memq ... --cl-rest--))) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:repl-buf :async :..." (car --cl-keys--)))))) (progn (if repl-buf nil (user-error "No Julia REPL buffer %s found; run julia-snail" julia-snail-repl-buffer)) (let* ((process-buf (get-buffer (julia-snail--process-buffer-name repl-buf))) (originating-buf (current-buffer)) (module-ns (julia-snail--construct-module-path module)) (reqid (format "%04x%04x" (random (expt 16 4)) (random (expt 16 4)))) (code-str (json-encode-string str)) (display-code-str (if julia-snail-debug code-str (s-truncate 80 code-str))) (msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid code-str)) (display-msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid display-code-str)) (res-sentinel (gensym)) (res res-sentinel)) (save-current-buffer (set-buffer process-buf) (goto-char (point-max)) (insert display-msg)) (process-send-string process-buf msg) (spinner-start 'progress-bar) (puthash reqid (record 'julia-snail--request-tracker repl-buf originating-buf #'(lambda (request-info &optional data) (if async nil ...) (if callback-success ...)) #'(lambda (request-info) (if async nil ...) (if callback-failure ...)) display-error-buffer-on-failure\? nil nil) julia-snail--requests) (if async reqid (let ((wait-result (catch ... ...))) (if (eq t wait-result) res (let (...) (if callback-failure ...) (save-current-buffer ... ...) (error error-msg))))))))
  (let* ((repl-buf (car (cdr (or (plist-member --cl-rest-- ':repl-buf) (list nil (get-buffer julia-snail-repl-buffer)))))) (async (car (cdr (or (plist-member --cl-rest-- ':async) '(nil t))))) (async-poll-interval (car (cdr (or (plist-member --cl-rest-- ':async-poll-interval) '(nil 20))))) (async-poll-maximum (car (cdr (or (plist-member --cl-rest-- ':async-poll-maximum) (list nil julia-snail-async-timeout))))) (display-error-buffer-on-failure\? (car (cdr (or (plist-member --cl-rest-- ':display-error-buffer-on-failure\?) '(nil t))))) (callback-success (car (cdr (plist-member --cl-rest-- ':callback-success)))) (callback-failure (car (cdr (plist-member --cl-rest-- ':callback-failure))))) (progn (let ((--cl-keys-- --cl-rest--)) (while --cl-keys-- (cond ((memq (car --cl-keys--) '...) (setq --cl-keys-- (cdr ...))) ((car (cdr ...)) (setq --cl-keys-- nil)) (t (error "Keyword argument %s not one of (:repl-buf :async :..." (car --cl-keys--)))))) (progn (if repl-buf nil (user-error "No Julia REPL buffer %s found; run julia-snail" julia-snail-repl-buffer)) (let* ((process-buf (get-buffer (julia-snail--process-buffer-name repl-buf))) (originating-buf (current-buffer)) (module-ns (julia-snail--construct-module-path module)) (reqid (format "%04x%04x" (random ...) (random ...))) (code-str (json-encode-string str)) (display-code-str (if julia-snail-debug code-str (s-truncate 80 code-str))) (msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid code-str)) (display-msg (format "(ns = %s, reqid = \"%s\", code = %s)\n" module-ns reqid display-code-str)) (res-sentinel (gensym)) (res res-sentinel)) (save-current-buffer (set-buffer process-buf) (goto-char (point-max)) (insert display-msg)) (process-send-string process-buf msg) (spinner-start 'progress-bar) (puthash reqid (record 'julia-snail--request-tracker repl-buf originating-buf #'(lambda ... ... ...) #'(lambda ... ... ...) display-error-buffer-on-failure\? nil nil) julia-snail--requests) (if async reqid (let ((wait-result ...)) (if (eq t wait-result) res (let ... ... ... ...))))))))
  julia-snail--send-to-server(:Main "1")
  eval-expression((julia-snail--send-to-server :Main "1") nil nil 127)
  funcall-interactively(eval-expression (julia-snail--send-to-server :Main "1") nil nil 127)
  command-execute(eval-expression)

Could you have a look? thanks

gcv commented 2 years ago

Running Julia in-container isn't a use case Snail supports at the moment, but I applaud your attempt. :) I expect a number of things to break, including xref, because absolute file paths to Julia source code won't be the same in Emacs and in-container. Remote REPL over SSH should work, but of course you'll have to set up ssh on the container and edit code over Tramp even if it's mounted from a volume.

That said, the basic network connection seems like it should work based on your port mapping. What happens if you just try to access the socket on the Julia side? Command line:

echo '(reqid = "abcd1234", ns = [:Main], code = "println(\"hello world\")")' | nc localhost 10011

If that doesn't work, you have connectivity problems from your machine to the container.

norci commented 2 years ago

thanks for your help. The network issue has been fixed in #66.

how to update julia-snail.el, so we can customize julia-snail--launch-command and listen address?

1 file changed, 2 insertions(+), 22 deletions(-)
julia-snail.el | 24 ++----------------------

modified   julia-snail.el
@@ -399,27 +399,7 @@ Returns nil if the poll timed out, t otherwise."
     snail-remote-dir))

 (defun julia-snail--launch-command ()
+  "docker-compose --project-directory=/x run --rm -v ~/.emacs.d/julia-snail/:/julia-snail -e 10011 -p 127.0.0.1:10011:10011 julia julia -L /julia-snail/JuliaSnail.jl")

 (defun julia-snail--efn (path &optional starting-dir)
   "A variant of expand-file-name that (1) just does
@@ -492,7 +472,7 @@ returns \"/home/username/file.jl\"."
           (user-error "The vterm buffer is inactive; double-check julia-snail-executable path"))
         ;; now try to send the Snail startup command
         (julia-snail--send-to-repl
-          (format "JuliaSnail.start(%d); # please wait, time-to-first-plot..." (or julia-snail-remote-port julia-snail-port))
+          (format "JuliaSnail.start(%d; addr=\"%s\"); # please wait, time-to-first-plot..." (or julia-snail-remote-port julia-snail-port) "0.0.0.0")
           :repl-buf repl-buf
           ;; wait a while in case dependencies need to be downloaded
           :polling-timeout (* 5 60 1000)
gcv commented 2 years ago

The right approach is probably to add a new defcustom like julia-snail-container-params or something, have it be a real data structure like an alist with parameters, recognize that it's set in julia-snail--launch-command, and construct an appropriate launch command. It needs to be flexible to recognize different container use cases.

After you solved the network problem and made the Emacs and Julia sides of Snail start talking to each other, how much of Snail actually worked?

To make julia-snail-send-buffer-file work, you need to do path mapping from local to remote volume paths (hacking julia-snail--efn is probably necessary), and to make other julia-snail-send-* functions work, you'd need to do something with julia-snail--send-to-server-via-tmp-file because temporary files will be Emacs-local, not container-local. That might be fixable by rebinding Emacs's idea of the temporary directory root to be in-container, along with another volume mapping.

Proper xref support will require its own path translation changes, I think. Not sure without investigating.

How much of this do you want to take on? :) Container support is a good feature to add, but I don't have time to do much more than give suggestions and review code.

gcv commented 2 years ago

Thinking about this some more: julia-snail-container-params maybe should be julia-snail-container-conf, and can contain configuration parameters for path mapping (path-root-local, path-root-container, that sort of thing). The stuff relying on tmp will be trickier, since it’ll require a local temporary directory which isn’t necessarily the same as local $TMPDIR. Not sure at the moment how to keep that from polluting the user’s local disk.

gcv commented 2 years ago

Discussion in #66.