Using Guix Environments in Emacs using buffer-env

Monday, 21 October 2019

GNU Guix (just like Nix) can easily spawn development environments with all the necessary dependencies one may need to work on a project.

For example: In a shell session, all you would have to do is to run

$ guix shell gcc

and a new shell is launched with adjusted environmental variables, so that GCC can now be used. Likewise, you can instruct Guix to fetch the developmental dependencies, i.e. exactly what you need to build a package

$ guix shell --development gcc

As a matter of convenience, one can also specify what an environment should consist of and store that in a file. An easy way to generate such a file is using the --export-manifest option:

$ guix shell --export-manifest --development gcc > manifest.scm

On my system this generates this file:

;; What follows is a "manifest" equivalent to the command line you gave.
;; You can store it in a file that you may then pass to any 'guix' command
;; that accepts a '--manifest' (or '-m') option.
(package->development-manifest
  (specification->package "gcc"))

More details on other options can be found in the manual.


This is fine, but if you use Emacs, then the shell and the editor are “inverted”, or rather Emacs performs the function of a shell (generic user interface, that wraps the kernel). To use something like guix shell, you’d have to either

  1. Start Emacs in a guix shell session so that it inherits the environmental variables set by Guix.
  2. Prefix any command you might execute in M-x shell, using shell-command, compile, etc. with a guix shell ... -- prefix.`

Neither of these two options are that convenient, so I was unusually delighted when I found a simple solution in the recently updated buffer-env by Augusto Stoffel (author of many under-appreciated packages). Reading through the source for the first time was a real joy, and I kept thinking about it like one would after hearing a good, catchy song.

The package has as simple interface, and for the most part it can be configured in a single line:

(add-hook 'hack-local-variables-hook 'buffer-env-update)

The package was added to GNU ELPA earlier this year, and was initially just described as a pure-elisp direnv implementation. If this is all you need, you don’t need to bother yourself with anything else.

For those unfamiliar with hack-local-variables-hook, here is the docstring:

Normal hook run after processing a file’s local variables specs. Major modes can use this to examine user-specified local variables in order to initialize other data structure based on them.

So what buffer-env-update does, in the case of Guix, is check the buffer-env-commands variable and find an entry that says “if you find a manifest.scm command”, run

guix shell -D -f \"$0\" -- env -0

(where $0 is replaced with the absolute file path), parse the output and update variables such as process-environment and exec-path, that influence how processes are spawned.

The Guix documentation mentions something similar to this idea, but I find it much more complicated than what buffer-env allows me to do.

My configuration goes a bit further than just modifying hack-local-variables-hook: Using Setup I have the following in my init.el:

;;;;; Dynamic Environments
(setup (:if-package buffer-env)
  (:option buffer-env-script-name "manifest.scm")
  (add-hook 'comint-mode-hook #'hack-dir-local-variables-non-file-buffer)
  (add-hook 'hack-local-variables-hook #'buffer-env-update))

In other words, this changes two notable things:

  1. Setting buffer-env-script-name to looks for manifest.scm files, so that buffer-env automatically picks up on manifest.scm files. Could be a list as well, if I also wanted to use guix.scm files.
  2. Load directory local variables in comint buffers (REPLs, M-x shell, …) so that buffer-env also takes effect in these kinds of buffers that have no files associated with them.

What I found particularly clever is that there is no need for an ongoing guix shell session, but that by calling env -0 we extract all the environmental variables and apply them manually. This might run into issues if you use guix gc concurrently though. And after all, all there is to Guix or Nix are just a few clever symbolic links and environmental variables.