purcell / envrc

Emacs support for direnv which operates buffer-locally
379 stars 35 forks source link

dap-mode cpptools not picking up direnv environment #71

Closed LemonBreezes closed 9 months ago

LemonBreezes commented 9 months ago

Hi. I'm running a Nix Shell using

            (dap-register-debug-template
             "(gdb) cae Debug ATLAS"
             (list :name "(gdb) cae Debug ATLAS"
                   :type "cppdbg"
                   :request "launch"
                   :program "${workspaceFolder}/build/debug/src/xxs/atlas"
                   :args ["-c" "${workspaceFolder}/../flx/flx/PricingEngineServer/config/xxscfg_DTH_server_debug.xml"]
                   :dap-server-path dap-cpptools-debug-program
                   :stopAtEntry nil
                   :cwd "${workspaceFolder}"
                   :environment [(:name "LD_LIBRARY_PATH" :value "~/src/atlas/build/debug/src/soapif:~/src/atlas/build/debug/src/xxnull:~/src/atlas/build/debug/src/xxaudit:~/src/atlas/build/debug/src/xxcrypto:~/src/atlas/build/debug/src/flxatptestse:~/src/atlas-libraries:~/src/atlas/build/debug/_deps/pgpsdk-src/libraries/release/:~/src/atlas/build/debug/_deps/grpc-src/lib:~/src/atlas/build/debug/_deps/opentelemetry-src/lib")]
                   :externalConsole nil
                   :MIMode "gdb"
                   :setupCommands [(:description "Enable pretty-printing for gdb" :text "-enable-pretty-printing" :ignoreFailures t)]
                   :preLaunchTask "Build ATLAS"
                   :cleanup-function (lambda (sesh)
                                       (when (not (dap--session-running sesh))
                                         (kill-buffer (dap--debug-session-output-buffer sesh))))))

and my shell.nix is:

{ pkgs ? import <nixpkgs> {} }:

with pkgs;

mkShell rec {
  buildInputs = [
    boost
    cmake
    stdenv
    zlib
    unixODBC
    freetds
    pkg-config
    minizip
    nss
    jemalloc
    xalanc
    xercesc
    gdb
    curl
    gcc13
    libxml2
    abseil-cpp
    opentelemetry-cpp
    autoconf
  ];

  shellHook = ''
      export ATLAS_ROOT=/home/st/src/atlas
      export BUILD_DIR=''${ATLAS_ROOT}/build/debug
      export LD_LIBRARY_PATH=''${BUILD_DIR}/src/soapif:''${BUILD_DIR}/src/xxnull:''${BUILD_DIR}/src/xxaudit:''${BUILD_DIR}/src/xxcrypto:''${BUILD_DIR}/src/flxatptestse:''${ATLAS_ROOT}/../atlas-libraries:''${BUILD_DIR}/_deps/pgpsdk-src/libraries/release/:''${BUILD_DIR}/_deps/grpc-src/lib:''${BUILD_DIR}/_deps/opentelemetry-src/lib:''${BUILD_DIR}/src/xxcrash64
      export COMPILE_COMMANDS_JSON=''${BUILD_DIR}/compile_commands.json
      export PATH=''${BUILD_DIR}/src/xxs:''${ATLAS_ROOT}:''${PATH}
      export LDFLAGS="''${LDFLAGS} -Wl,--undefined-version"
      export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath buildInputs}:''$LD_LIBRARY_PATH"
      export LD_LIBRARY_PATH="${pkgs.stdenv.cc.cc.lib.outPath}/lib:''$LD_LIBRARY_PATH"
  '';
}

but dap-mode is not picking up the LD_LIBRARY_PATH.

purcell commented 9 months ago

Hi, I don't use lsp-mode or dap-mode myself so I can't help very directly, sorry. (I use eglot instead of lsp-mode & co.)

I'm assuming you've already tried M-: (getenv "LD_LIBRARY_PATH") inside the buffer from which you're launching DAP, and confirmed that the variable is present there?

If so, and dap-mode isn't picking it up, then the fault probably lies with dap-mode. When relying on buffer-local environments (set by envrc), there are things that elisp authors can do which lead to starting processes with the wrong environment. An example would be making a temp buffer, then starting a process in that buffer instead of the buffer where the environment is locally set. Authors should use code like my inheritenv package when doing so: it helps them copy the user's buffer-local environment to the new buffer so that it is used there too.

LemonBreezes commented 9 months ago

(getenv "LD_LIBRARY_PATH")

Hi. I just switched to Gentoo and made the .envrc just set environment variables directly instead of using Nix. And the problem is still occurring. (getenv "LD_LIBRARY_PATH") does return the correct result in the code file. So I'm going to open this in dap-mode.

purcell commented 9 months ago

There's some related discussion in this ticket btw: https://github.com/emacs-lsp/lsp-mode/issues/2583

There you'll see that for lsp-mode, people worked around this same problem by using lsp-deferred. Perhaps there's a similar solution for dap-mode.

purcell commented 9 months ago

(And someone there is mentioning the dap-mode issue)

LemonBreezes commented 9 months ago

There's some related discussion in this ticket btw: emacs-lsp/lsp-mode#2583

There you'll see that for lsp-mode, people worked around this same problem by using lsp-deferred. Perhaps there's a similar solution for dap-mode.

I actually found a hacky fix. So after enough investigation, I found that if you don't pass a :environment or similar variable to the debugging profile, it passes an empty environment. One hacky approach I just came up with is to hack the process-environment into the launch arguments:

(defun cae-dap-debug-pass-envrc-a (args)
  (when (length= (plist-get (car args) :environment) 0)
    (plist-put (car args) :environment
               (apply #'vector
                      (mapcar (lambda (s)
                                (let ((m (string-match "=" s)))
                                  (if m
                                      (list :name
                                            (substring-no-properties s 0 m)
                                            :value
                                            (substring-no-properties s (1+ m)
                                                                     (length s)))
                                    (list :name s
                                          :value ""))))
                              process-environment))))
  args)
(advice-add #'dap-start-debugging-noexpand :filter-args
            #'cae-dap-debug-pass-envrc-a)
purcell commented 9 months ago

It just occurred to me that in cases like this, you could also potentially just set the debugging or LSP command to be ("direnv" "exec" "myprog" ...) instead of ("myprog" ...).

LemonBreezes commented 9 months ago

It just occurred to me that in cases like this, you could also potentially just set the debugging or LSP command to be ("direnv" "exec" "myprog" ...) instead of ("myprog" ...).

That doesn't work because then GDB attaches to the direnv process, which finishes immediately and no debugging happens.

purcell commented 9 months ago

Oh no! That makes sense I guess. 😂

purcell commented 9 months ago

Is there perhaps a "debugger command" setting that you could set to "direnv gdb myprog" instead of "gdb myprog" then?

LemonBreezes commented 9 months ago

Is there perhaps a "debugger command" setting that you could set to "direnv gdb myprog" instead of "gdb myprog" then?

No. Specifying :debugServer "direnv exec /home/st/.config/emacs/.local/etc/dap-extension/vscode/cpptools/extension/debugAdapters/bin/OpenDebugAD7" causes GDB to still execute without the direnv environment. And modifying the miDebuggerArgs and miDebuggerPath so that it starts GDB with direnv exec gdb -mi causes the debug process to exit immediately.

I think that OpenDebugAD7 itself is executing GDB.

purcell commented 9 months ago

Hmmm, that's a shame! Anyway, glad you found a workaround.