Emacs package management
20 Mar 2015Lately, I've been spending some time to automate and publish my Emacs config. Being able to quickly reproduce your config has many advantages, the main one being that you no longer have to spend time to make your config reproducible.
Happily, most of my config is already published in many packages, I only have to figure out a nice
layer to glue them together. Below, I'll show some automation for the packages managed by
package.el
.
Step 1: get the main directory
This is an important step that many other peoples' configs miss, even the ones that are designed to
be distributed. You can't just assume that the config will be located in ~/.emacs.d
and rely on
Emacs defaults. Instead, it's nice to be able to clone the config into a random directory and launch
an Emacs from there, without messing with the currently installed Emacs.
It's also useful for having multiple repositories for different versions of Emacs. ELPA packages are byte-compiled, and the byte code can be incompatible between versions (for instance, 24.3 and 24.4). Having two independent checkouts with ELPA directory auto-generated really helps in that case.
So here is the code to get the main directory and define an ELPA directory with respect to that:
(defconst emacs-d
(file-name-directory
(file-chase-links load-file-name))
"The giant turtle on which the world rests.")
(setq package-user-dir
(expand-file-name "elpa" emacs-d))
Step 2: decide what you like
Next, I initialize the package and define some of the packages that I like, omitting the
dependencies that they bring. Note that the code of the whole post is stored in a separate file
packages.el
that is not intended to be loaded on start up, so it's fine to call
package-refresh-contents
here:
(package-initialize)
(setq package-archives
'(("melpa" . "http://melpa.milkbox.net/packages/")
("gnu" . "http://elpa.gnu.org/packages/")))
(package-refresh-contents)
(defconst ora-packages
'(auto-compile auto-yasnippet ace-link ace-window
company eclipse-theme flx-ido function-args
headlong ido-occasional ido-vertical-mode lispy
magit smex swiper use-package guide-key
powerline projectile slime cider worf
org-download make-it-so ukrainian-holidays
netherlands-holidays j-mode)
"List of packages that I like.")
Step 3: install and upgrade
The install step is pretty straightforward: install a package unless it's already installed. I tried to do something fancier for the upgrade, but in the end it was much more simple to just call the interactive interface. The last two lines are basically equivalent to pressing Uxy interactively:
;; install required
(dolist (package ora-packages)
(unless (package-installed-p package)
(package-install package)))
;; upgrade installed
(save-window-excursion
(package-list-packages t)
(package-menu-mark-upgrades)
(package-menu-execute t))
Step 4: make it callable
Finally, I just create a Makefile with the following contents:
emacs ?= emacs
upgrade:
$(emacs) -batch -l packages.el
run:
$(emacs) -Q -l init.el
up: upgrade run
Thanks to the first line, I can issue stuff like this on the shell:
emacs=emacs24 make up
This will use the emacs24
executable, instead of whatever emacs
points to. Since the up
target depends on upgrade
and run
targets, they will be executed in that order:
- the
upgrade
will install / upgrade all packages in a non-interactive Emacs - the
run
target will start an interactive Emacs with already updated packages
I really like putting stuff in Makefiles, since they are very flexible, yet so easy to call. In the
very same Makefile, I have a profile
target from
the post on profiling Emacs start up. I also
wrote two packages related to Makefiles: helm-make and
make-it-so. The latter one is actually very interesting and
deserves its own post, I should maybe just clean it up a bit.
Outro
I'll just cite Gall's law here:
A complex system that works is invariably found to have evolved from a simple system that worked. A complex system designed from scratch never works and cannot be patched up to make it work. You have to start over with a working simple system.
I think it applies from both sides w.r.t. my Emacs config: it kind of works, but I really wish it was reproducible from the start, before making it complex.