A GNU Emacs library which uses direnv to set
environment variables on a per-buffer basis. This means that when you
work across multiple projects which have .envrc files, all processes
launched from the buffers "in" those projects will be executed with
the environment variables specified in those files. This allows
different versions of linters and other tools to be used in each
project if desired.
This library is like the direnv.el
package, but sets all
environment variables buffer-locally, while direnv.el changes
the global set of environment variables after each command.
Installable packages are available via MELPA: do
M-x package-install RET envrc RET.
Alternatively, download
the latest release or clone the repository, and install
envrc.el with M-x package-install-file.
Add the following to your init.el (after calling package-initialize):
(envrc-global-mode)It's probably wise to do this late in your startup sequence: you
normally want envrc-mode to be initialized in each buffer before
other minor modes like flycheck-mode which might look for
executables. Counter-intuitively, this means that envrc-global-mode
should be enabled after other global minor modes, since each
prepends itself to various hooks.
You should only enable the mode if direnv is installed and available
in the default Emacs exec-path. (There is a local minor mode
envrc-mode, but you should not try to enable this granularly,
e.g. for certain modes or projects, because compilation and other
buffers might not get set up with the right environment.)
Regarding interaction with the mode, see envrc-mode-map, and the
commands envrc-reload, envrc-allow and envrc-deny. (There's also
envrc-reload-all as a "nuclear" reset, for now!)
In particular, you can enable keybindings for the above commands by
binding your preferred prefix to envrc-command-map in
envrc-mode-map, e.g.
(with-eval-after-load 'envrc
(define-key envrc-mode-map (kbd "C-c e") 'envrc-command-map))By default, Emacs has a single global set of environment variables
used for all subprocesses, stored in the process-environment
variable. direnv.el switches that global environment using values
from direnv when the user performs certain actions, such as
switching between buffers in different projects.
In practice, this is simple and mostly works very well. But some trade-offs of this simple approach are:
-
When switching to a buffer that is not "inside" a project with an
.envrcfile, the buffer will see the last project's environment. I would prefer it to see the default Emacs environment. -
When
direnvfails to execute in the course of switching to a buffer in a new project with an.envrcfile (e.g. because that.envrcfile is disallowed), buffers in the new project will see the environment variables from the previous project. -
Background buffers from a previous project will start seeing the new project's environment, so any processes they launch asynchronously after the switch will use the wrong environment. (This is probably quite rare in practice.)
Now, it is also possible to set process-environment locally in a
buffer. If this value could be correctly maintained in all buffers
based on their various respective .envrc files, then buffers across
multiple projects could simultaneously be "connected" to the
environments of their corresponding project directories. I wrote
envrc.el to explore this approach.
envrc.el uses a global minor mode (envrc-global-mode) to hook into
practically every buffer created by Emacs, including hidden and
temporary ones. When a buffer is found to be "inside" an
.envrc-managed project, process-environment is set buffer-locally
by running direnv, the results of which are also cached indefinitely
so that this is not too costly overall. Each buffer has a local minor
mode (envrc-mode) with an indicator which displays whether or not a
direnv is in effect in that buffer. (Hooking into every buffer is
important, rather than just those with certain major modes, since
separate temporary, compilation and repl buffers are routinely used
for executing processes.)
This approach also has some trade-offs:
-
Buffers like
*Help*will haveenvrc-modeenabled based on the directory of the buffer which caused them to be created initially, and then those buffers often live for a long time. If you launch programs from such buffers while working on a different project, the results might not be what you expect. I might exclude certain modes to minimise confusion, but users will always have to be aware of the fact that environments are buffer-specific. -
There's a (very small) overhead every time a buffer is created, and that happens quite a lot.
-
direnvupdates are not automatic.direnv.elre-executesdirenvwhen switching between buffers that visit files in different directories, whereasenvrc-modecaches the environment until the user refreshes it explicitly withenvrc-reload.
I hope that these will be worthwhile, since my ultimate goal is to
integrate with lorri, a daemon which re-builds Nix projects
automatically and presents their environments to direnv: if we can
hook into lorri's rebuilds, then envrc.el will allow Emacs to
programmatically refresh the environment of specific subsets of
buffers, which is not currently possible with direnv.el.
It's also possible that there's a way to call direnv more
aggressively by allowing it to see values of DIRENV_* obtained
previously such that it becomes a no-op.
💝 Support this project and my other Open Source work via Patreon