travisbhartwell/nix-emacs

Request: functionality to load a nix-shell environment (emacsclient)

Closed this issue · 6 comments

A typical problem using nix and emacsclient/server is that the environment from nix-shell isn't inherited. This break lots of functionality - anaconda-mode for python, etc.

The easiest solution is to start a dedicated emacs per nix-shell, but that's annoying.

Since all(?) of the environment is contained in the environment variables it seems like just replacing process-environment with the environment set up by nix-shell should work quite well?

It's possible to make process-environment buffer local so the changes are contained to the relevant
buffers.

A quick and dirty function to this is provided below:

(defun nix-shell-load-env (nix-file)
  "Loads the environment defined by NIX-FILE. Runs nix-shell to get the
environment and makes process-environment buffer local"
  (interactive "fnix-file")
  (let ((environment
         (with-temp-buffer
           (let* ((nix-file-shell-safe (shell-quote-argument nix-file))
                  (command (format "nix-shell %s --command 'printenv -0' 2>/dev/null"
                                   nix-file-shell-safe))
                  (status (call-process-shell-command command nil t)))
             (when (> status 0)
               (message "'nix-shell %s' failed with %d" nix-file-shell-safe status)
               (return nil))
             (split-string (buffer-string) "\0")))))

    (when environment
      (set (make-local-variable 'process-environment)
           environment))
    ))

A possible complication is whether the various modes react to changes in the environment. Anaconda mode seems to pick up the changes.

Another problem is to automate it. With the correct hooks (that hopefully runs before mode hooks) and nix-current-sandbox it doesn't seem to hard though. Ideally we don't want to call nix-shell each time a new buffer is opened, and it would be nice to share the process-enviroment across buffers belonging to the same project. Which start to sound like a projectile feature/plugin.

A related discussion: flycheck/flycheck#610

I can try to clean things up and create a PR, but some early feedback is always a good idea.

If I get this correctly, this implies that the same process-environment is shared between all files/buffers contained in the same nix project, whereas currently nix-shell is called for each buffer that is opened.

@olejorgenb, can you please point out in more detail the differences and implications between your suggested change and what is already implemented in nix-sandbox.el. Thanks.

If I get this correctly, this implies that the same process-environment is shared between all files/buffers contained in the same nix project, whereas currently nix-shell is called for each buffer that is opened.

Yes, ideally I think nix-shell should be called once per project (could check timestamp of shell.nix ofc) and the environment be shared (mostly to save some memory I guess) between the project buffers. (1)

Project would have to be defined somehow of course.

Unless I'm missing something, the functions in nix-sandbox.el help run stuff explicitly in a nix-shell environment. That doesn't help eg. anaconda-mode. anaconda-mode provide completion and documentation lookup for python and need to know where the dependencies are. Setting process-environment dictates which python version should be used, the dependencies (through PYTHONPATH) and possibly more. (2)

It might be naive to think that setting the environment per buffer actually emulate closely what happens when emacs is started from within nix-shell though. It all depends on how much environment-dependent setup is done only once and not per buffer. (?)

(1) The process-environment would probably have to be buffer-local unless emacs have a project local variable concept, but I assume it's possible to share the actual value.

(2) anaconda-mode might have some other way of providing this information, but it's a lot less hassle to use the environment and other modes might not have such mechanisms.

Project would have to be defined somehow of course.

My current definition of project is all files below a directory containing a (shell|default).nix file.

Unless I'm missing something, the functions in nix-sandbox.el help run stuff explicitly in a nix-shell environment. That doesn't help eg. anaconda-mode. anaconda-mode provide completion and documentation lookup for python and need to know where the dependencies are.

I wrote nix-sandbox.el originally to allow flycheck-checkers to get access to the nix environment. I guess this would allow anaconda-mode in a similar fashion to provide completions and document lookup.

(setq flycheck-command-wrapper-function
        (lambda (command) (apply 'nix-shell-command (nix-current-sandbox) command))
      flycheck-executable-find
        (lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd)))

Furthermore, I just remembered nix-sandbox.el already calls nix-shell only once per project since the execution environment is cached in the hash maps nix-sandbox-rc-map and nix-exec-path-map. When you want to update your nix configuration, simply call nix-clear-caches, which simply clears the hash maps and nix-shell is called again to retrieve the environment of the new configuration.

Please let me know if you still have a problem with the existing code in nix-sandbox.el.

Yes, that's a reasonable project definition.

I guess what I'm trying to ask is if this could be a home for something a bit more general (not having to set up manual wrappers), and if you have given any thoughts to that problem :)

I just noticed that temp buffers don't inherit buffer-local variables though (which makes sense I guess). That seems like a problem for the buffer-local process-environment approach.

(No problem with the existing code :))

(I'll reopen or make a PR if I get any further in the future)

Please do. I'm always open for improvements and discussion. Thanks @olejorgenb.