Towards a declarative dependency management solution for Emacs

If you're an Emacs user, you probably have customized your editor to your liking. You may also have devised a way to make your configuration portable. Naturally, you want to be able to use Emacs on any computer where works needs to be done, and you want everywhere the same bespoke experience. The ways to achieve that portability are varied. Over time, I have experimented with different approaches that I believe reflect the natural evolution of any Emacs user, that is to say a shift from unsophisticated techniques to more refined ones. I would like to start off summarizing those approaches. 

First, there's the manual approach. Often, novice users don't know better. In this scenario, duplicating an Emacs setup involves copying your config file to the new computer, examining the extensions that you will need to install manually, or simply watching the errors popping up as a result of unmet dependencies. Then you go to the slow process of reinstalling them one by one. 

Sooner or later, you realize that there must be a better way. When you start asking around, people will likely tell you to put .emacs.d under version control. This way, both your .init.el and your extensions are available anytime at the press of a git pull command (or equivalent). This is a valid approach adopted by many advanced Emacs users. It always bothered me for two reasons. One is that I have to put shared code under version control. I'd avoid that if I could help it. In modern programming environments, after all, you put your own code under version control. The dependencies, on the other hand, are formally declared in a file, and a native facility allows you to pull those dependencies when time comes to duplicate your project elsewhere (at deploy time, for example). In Ruby, that native facility is called bundler, Java users might be familiar with maven, and the list goes on. The second thing that bothers me with this approach is that when dependencies are themselves pulled from repositories, you suddenly have to deal with submodules, which complicate matters further.

What I really wanted was to put the config file, and only that, under version control. Somehow, the dependencies would be pulled automatically. With the advent of the Emacs Lisp Package Archive (ELPA), this became a possibility. No more manual downloading of packages, copying them to the right place, adding specific initialization code in the config file. ELPA would take care of all this. You could write code in your config file that ensured the presence of packages, and have them installed automatically if they were missing: 

ELPA was a huge progress in Emacsland. Still, it wasn't perfect. See Bozhidar Batsov's post for a good discussion around ELPA. And unless you had .emacs.d under version control, ELPA still fell short of providing all the pieces needed to make your configuration truly portable. When you were uninstalling a package on one machine, you would have to replicate that action on all the other machines. What was missing is a cleanup function, elisp code that would uninstall packages as soon as they were dereferenced from the canonical list. My quest for a fully portable declarative dependency management solution for Emacs was not over.

I started with posting a question on stack overflow. I wanted to make sure I was not missing anything. Maybe somebody had already written such code. The answer, unsurprisingly, was to put .emacs.d under version control. A valid answer, but not what I was looking for. So I opened an issue on github for the el-get project. 

Why el-get

package.el (which started in a package called ELPA and was later merged in Emacs 24) understand the package format, which implies that extension authors or maintainers need to package their elisp code. This is reasonable, but the reality is that many Emacs extensions come as a single elisp file on EmacsWiki, or are hosted on privately hosted pages, or are available behind systems like bazaar, git and others. el-get is an interface that abstracts away the chaos, and plays nicely along ELPA to boot.  

Soon enough, I got a reply from the maintainer that no, he didn't have a cleanup feature, but that he was open to the idea. Would I write it? And so, with Dimitri Fontaine's invaluable assistance, I published a pull request enabling the cleanup feature. This concluded my search for a comprehensive, declarative dependency management solution for Emacs. The previous code could be replaced by a simple el-get 'sync. Now I could write:

The cleanup functionality is integrated in el-get's master branch, which is installed like so:

So, now you have the ability to declare your Emacs extensions in your .init.el, and version control only that. To make git ignore all files except the config file, you'll instruct .gitignore in the following manner:

And oh, happy Emacsing!

P.S. Follow me on Twitter.

Daniel Szmulewicz 03 October 2012
blog comments powered by Disqus