svaante / dape

Debug Adapter Protocol for Emacs
GNU General Public License v3.0
477 stars 31 forks source link

Added batteries for Java debugging with JDTLS + plugin. #34

Closed MagielBruntink closed 10 months ago

MagielBruntink commented 10 months ago

This PR consolidates our earlier work to get Java debugging working.

I am in the progress of transferring copyright of this edit. However, this may be considered a "trivial" change, alleviating the need for this process?

svaante commented 10 months ago

Great! I am happy. Thank you for your patience.

I am unsure how to move forward though. From https://www.gnu.org/software/emacs/manual/html_node/emacs/Copyright-Assignment.html

We can accept small changes (roughly, fewer than 15 lines) without an assignment. This is a cumulative limit (e.g., three separate 5 line patches) over all your contributions.

I don't want to keep you hanging.

MagielBruntink commented 10 months ago

The folks at FSF ask also for a disclaimer from my employer. I'll try to get that sorted this week so we can finally get this one merged :-)

MagielBruntink commented 10 months ago

@svaante I have submitted my copyright transfer to FSF and employer disclaimer.

MagielBruntink commented 10 months ago

FSF let me know that assignment has been completed.

sham1 commented 10 months ago

I'm sorry that I'm asking about this so abruptly, but would it also be possible to add support for attaching the debugger from jdtls into a running JVM instance (i.e. one that has a JDWP agent active and listening on a port via the -Xrunjdwp flag).

So basically a recipe that would ask for a port (or have the port be configurable, either works) instead of a main class, and have the :request be "attach" instead of "launch". Obviously, the part about launching the debug adapter via eglot could stay the same.

The usecase I'm mostly thinking of is having a Java app running on an application server such as Tomcat, which means that it can't necessarily be launched in the manner presented here, but has to be launched separately and then attached to later on.

svaante commented 10 months ago

Great work @MagielBruntink I will go ahead and merge this. As to @sham1 request I don't know what that entails are you or @sham1 interested in looking into how a configuration for that use case would look?

MagielBruntink commented 9 months ago

At the moment attaching to a running debugger is not a use case of mine so I will not work on it. I would like to look into running & debugging specific Java unit tests. If that overlaps it could result in an attach configuration.

MagielBruntink commented 9 months ago

@sham1 and @svaante I am changing my mind about the Java configs :-) It seems that running the JVM outside of the language server (JDTLS) is way more convenient. Eg. using Maven one can then request main classes and specific tests to be debugged, asking Dape to attach JDTLS to the running debugger. So @sham1 's request actually pointed me in the right direction, cheers!

Here is my current init.el config for Java:

;; ----------------------------------------
;; Java-related packages and config
;; ----------------------------------------
;; Needs JDK version 17+, installed with JAVA_HOME set and binaries on PATH.
;; Also install the following and put their executables on the PATH.
;; - JDTLS: https://github.com/eclipse-jdtls/eclipse.jdt.ls
;; - Java Debug Server: https://github.com/microsoft/java-debug

(defcustom jdtls-java-debug-plugin-jar
    "/PATH/TO/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-VERSION.jar"
    "The path to the Java Debug Server plugin jar, see https://github.com/microsoft/java-debug"
    :type '(string)
    :group 'init-java)

(use-package eglot
  :config
  (add-to-list 'eglot-server-programs
           `((java-mode java-ts-mode) .
         ("jdtls" ;; Try to add "-Dline.separator=\n"
          :initializationOptions
                  (:bundles [,jdtls-java-debug-plugin-jar])))))

;; Debug Java main classes or tests using Maven, then use Dape to attach the
;; JDT LS + Java Debug Server to the running JVM.
;;
;; Main classes:
;; https://www.mojohaus.org/exec-maven-plugin/
;; $ mvnDebug -DforkCount=0 -Dexec.mainClass="Main" exec:java
;;
;; Tests:
;; https://maven.apache.org/surefire/maven-surefire-plugin/examples/debugging.html
;; https://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html
;; $ mvnDebug -DforkCount=0 test
;;
;; TODO:
;; - Create convenience functions in Elisp to execute Maven commands that debug a
;;   main class or test class/method at point.
;; - How to handle run/debug configurations?

(use-package dape
  :if (package-installed-p 'dape)
  :after eglot
  :config
  (add-to-list 'dape-configs
           `(jdtls
         modes (java-mode java-ts-mode)
         hostname "localhost"
         port (lambda () (eglot-execute-command (eglot-current-server)
                            "vscode.java.startDebugSession" nil))
                 :request "attach"
         :hostname "localhost"
         :port 8000 ;; Default port exposed by mvnDebug
         :type "java")))
skybert commented 7 months ago

Thanks you so much, @sham1 and @MagielBruntink. Using that dape-config, I can successfully connect to an external Java process (DropWizard app started with java -agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=n) :tada:

The only thing that's lacking, is the stack view. When clicking back and forth between the "tabs": Stack, Modules, Sources, I see an error when I click back on "Stack":

jsonrpc--remove: Wrong type argument: (or eieio-object cl-structure-object oclosure), nil
MagielBruntink commented 7 months ago

Hi @skybert,

Here is my personal Dape config:

(use-package dape
  :if (package-installed-p 'dape)
  :after eglot
  :config
  (add-to-list 'dape-configs
               `(jdtls
                 modes (java-mode java-ts-mode)
                 fn (lambda (config)
                      (with-current-buffer
                          (find-file-noselect (expand-file-name (plist-get config :program)
                                                                (project-root (project-current))))
                          (thread-first
                            config
                            (plist-put 'hostname "localhost")
                            (plist-put 'port (eglot-execute-command (eglot-current-server)
                                                                    "vscode.java.startDebugSession" nil))
                            (plist-put :projectName (project-name (project-current))))))
                 :program dape-buffer-default
                 :request "attach"
                 :hostname "localhost"
                 :port 8000)))

I had the issue with the Stack window too, but @svaante kindly debugged and pointed out that was a problem with eglot-java (which I use for general Java IDE features like running and debugging tests). The issue has been fixed since. If you are also an eglot-java user (which I recommend) I think updating to the latest version will fix the problem.