Set the root directory of a project in Emacs with dir-locals.el

by Matthias Puech

Hi, and welcome to my blog. I’ll try to write here about the stuff I do, use or discover during my work as a Ph.D in Computer Science. There will hopefully be a bit of science (proof theory), some code (functional programming), some tips and tricks for developers.

For a gentle start, I’d like to share a nice little trick I put together for Emacs. I am developing in a big project (Coq) with multiple subdirectories, which compilation is managed by GNU make. When I open a file in a subdirectory and then hit M-x compile, make complains because I am now in the subdirectory and not at the root of the project anymore. So I took the habit for hitting very fast M-x cd .. every time I open a new file in a subdirectory. Annoying…

Now, emacs 23 has a very cool new feature called directory variables: put a file named .dir-locals.el on any directory D; whenever emacs opens a file in a (sub-)subdirectory of D, variables set in this file get set in your new buffer. Exactly what you were doing with the local variables, the -*- var: value -*- lines, but for a directory and all its contents. The syntax of this special file looks something like:

((ocaml-mode . ((tab-width . 4) (indent-tabs-mode . t)))

We could set the variable default-directory which is the one in which compilation will happen directly here:

((nil . ((default-directory . "/home/puech/Code/coq")))

But that wouldn’t be very portable right? We need default-directory to point exactly where this very file is. One option is to set it to (locate-dominating-file buffer-file-name ".dir-locals.el"): this evaluates to the first file named .dir-locals.el descending from the current directory. The problem is that this last expression needs to be evaluated before being set to default-directory. We’re going to trick emacs into thinking it sets a variable when in fact it is evaluating a piece of elisp by setting the eval name:

((nil . ((eval . (setq default-directory (locate-dominating-file
buffer-file-name ".dir-locals.el")

That’s it! Put this code in your .dir-locals.el at the root of your project, and you’ll never have to change directory before compiling again. This directory variable feature is a cheap way to get a notion of project into emacs! Of course you can use it for many other purposes. What can you think of?