Problems with manual installation of `jdtls`
This is not really a bug, more of a question.
I am trying to use lsp-java with a manual installation of jdtls. I use NixOS, and provide the language server, which is called jdt-language-server in Nixpkgs, in the environment. I also set lsp-java-server-install-dir to point to the correct directory. However, lsp-java fails to run because the installation directory of jdt-language-server is in the Nix Store, and therefore read-only, which leads to several errors. For example,
!STACK
java.nio.file.FileSystemException: /nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/config_linux: Read-only file system
at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:100)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106)
at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)
at java.base/sun.nio.fs.UnixFileSystemProvider.createDirectory(UnixFileSystemProvider.java:397)
at java.base/java.nio.file.Files.createDirectory(Files.java:700)
at java.base/java.nio.file.Files.createAndCheckIsDirectory(Files.java:807)
at java.base/java.nio.file.Files.createDirectories(Files.java:793)
at org.eclipse.equinox.launcher.Main.extractFromJAR(Main.java:2313)
at org.eclipse.equinox.launcher.Main.getLibraryFromFragment(Main.java:535)
at org.eclipse.equinox.launcher.Main.getLibraryPath(Main.java:495)
at org.eclipse.equinox.launcher.Main.setupJNI(Main.java:448)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:579)
at org.eclipse.equinox.launcher.Main.run(Main.java:1467)
at org.eclipse.equinox.launcher.Main.main(Main.java:1440)
Further, I read somehwere that the language server executable name jdtls is hardcoded into lsp-java. Is this so? (NixOS uses jdt-language-server).
I should note that everything works fine when using the jdtls installation method provided by lsp-java.
It is actually the server not being able to start - if you run the server in the terminal it will end up with the same result. You may use M-: (lsp-java--ls-command) to get the command that we are starting and try it in the terminal.
Thanks for this tip! The command is
("java" "-Declipse.application=org.eclipse.jdt.ls.core.id1" "-Dosgi.bundles.defaultStartLevel=4" "-Declipse.product=org.eclipse.jdt.ls.core.product" "-Dlog.protocol=true" "-Dlog.level=ALL" "-XX:+UseParallelGC" "-XX:GCTimeRatio=4" "-XX:AdaptiveSizePolicyWeight=90" "-Dsun.zip.disableMemoryMapping=true" "-Xmx1G" "-Xms100m" "-jar" "/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar" "-configuration" "/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/config_linux" "-data" "/home/dominik/.emacs.d/.local/etc/java-workspace" "--add-modules=ALL-SYSTEM" "--add-opens java.base/java.util=ALL-UNNAMED" "--add-opens java.base/java.lang=ALL-UNNAMED")
When running the actual command in the terminal, I get
<title>Invalid Configuration Location</title>The configuration area at '/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/config_linux' could not be created. Please choose a writable location using the '-configuration' command line option.
Note the -configuration option is pointing into the Nix store. I saw that this option is determined by lsp-java--locate-server-config which does not allow for a configuration option outside the lsp-java-server-install-dir.
Willing to provide a pr to add a configurable variable for that?
Haha, sure, I am working on it. I dug a bit deeper, and only setting the config dir is not enough. I had a look at the wrapper script that comes with jdt-language-server, and they use a shared, read-only configuration directory. The following command works:
("java" "-Declipse.application=org.eclipse.jdt.ls.core.id1" "-Dosgi.bundles.defaultStartLevel=4" "-Declipse.product=org.eclipse.jdt.ls.core.product" "-Dosgi.sharedConfiguration.area=/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/config" "-Dosgi.sharedConfiguration.area.readOnly=true" "-Dosgi.checkConfiguration=true" "-Dosgi.configuration.cascaded=true" "-Dlog.protocol=true" "-Dlog.level=ALL" "-XX:+UseParallelGC" "-XX:GCTimeRatio=4" "-XX:AdaptiveSizePolicyWeight=90" "-Dsun.zip.disableMemoryMapping=true" "-Xmx1G" "-Xms100m" "-jar" "/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar" "-configuration" "./config_linux" "-data" "./data" "--add-modules=ALL-SYSTEM" "--add-opens java.base/java.util=ALL-UNNAMED" "--add-opens java.base/java.lang=ALL-UNNAMED")
I basically added the options
-
"-Dosgi.sharedConfiguration.area=/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/config" -
"-Dosgi.sharedConfiguration.area.readOnly=true" -
"-Dosgi.checkConfiguration=true" -
"-Dosgi.configuration.cascaded=true"
And I changed options
-
"-configuration" "./config_linux"(we could just add alsp-java-configuration-dirvariable that is used, if set) -
"-data" "./local_java_workspace"(this can be configured usinglsp-java-workspace-dir)
That's a bit of a nightmare... Not sure how to proceed. I think it is maybe advantageous if I just set the command manually here.
EDIT: The lsp-java-configuration-dir option could be added in any case. Let me know if you want a PR for that.
I just wanted to share that I fair pretty well using the wrapper script which is provided with the language server. Maybe it is easier to use this wrapper in general? I have the following setup:
(setq lsp-java-server-install-dir "/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/share/java/")
(defun lsp-java--ls-command ()
(list "/nix/store/4qvyvcbn6h1sm8dvx9wjrv3y5az1znp2-jdt-language-server-1.19.0/bin/jdt-language-server"
"-configuration" "../config_linux"
"-data" "../java-workspace")
)
Hi all!
I recently stumbled on this exact issue while configuring Emacs to support Java in order to work on some exercises for a University course and the snippet provided in @dschrempf's last message works like a charm.
The problem with it, however, is that it defeats the purpose of Nix Shell by hardcoding a specific Nix Store path.
In other languages (like Rust, that I use for personal and work projects) I can specify a different version of the LSP server for every single project in my flake.nix file and then Emacs just finds it from the PATH.
Is there a way to archive something similar with Java too? I already managed to dynamically build the lsp-java--ls-command by exporting an ENV variable on a per-project basis, but this operation is obviously impossible for lsp-java-server-install-dir, as it has to be defined at Emacs startup (or, at least, is what I'm observing).
Thank you very much!
EDIT: In case someone else needs this, today I reworked my Emacs config and came up with this:
;; Configure lsp-java for Java projects
(use-package lsp-java
:hook ((envrc-mode . (lambda ()
(when (equal major-mode 'java-ts-mode)
(setq lsp-java-server-install-dir (concat (getenv "JDTLS_PATH") "/share/java/")))))
(java-ts-mode . lsp-deferred))
:config
(defun lsp-java--ls-command ()
(message (concat (getenv "JDTLS_PATH") "/share/java/"))
(list (concat (getenv "JDTLS_PATH") "/bin/jdt-language-server")
"-configuration" "/home/rsacchetto/.jdtls/config_linux"
"-data" "/home/rsacchetto/.jdtls/java-workspace")))
I still haven't tested this thoroughly, so I can not guarantee that it will not explode when you work simultaneously on multiple projects with different JDTL versions, but at least picks up the path exported from the Flake (export JDTLS_PATH=${pkgs.jdt-language-server}) instead of forcing you to hardcode one in your global config.